[Chore] (MG_Backend, MG_Backend/DirectGLES, MG_Backend/DirectVulkan, MG_Backend/MGPipe, MG_Backend/Record, MG_Benchmark/Buffer, MG_Benchmark/Container, MG_Benchmark/Driver, MG_Benchmark/Program, MG_Benchmark/ShaderCache, MG_Benchmark/Transpile, MG_Gbm, MG_Impl, MG_Impl/CGLImpl, MG_Impl/DyldInterpose, MG_Impl/EGLImpl, MG_Impl/GLImpl, MG_Impl/GLXImpl, MG_Impl/NSOpenGLImpl, MG_Impl/Pipe, MG_Impl/WGLImpl, MG_IntegrationTest, MG_IntegrationTest/Harness, MG_IntegrationTest/Scenarios, MG_Pipe, MG_Remote, MG_Remote/Client, MG_Remote/Protocol, MG_Remote/Server, MG_Remote/Transport, MG_Remote/Wire, MG_State/EGLState, MG_State/GLState, MG_Test, MG_Test/Backend, MG_Test/BackendLoader, MG_Test/Buffer, MG_Test/EGLState, MG_Test/Framebuffer, MG_Test/Gbm, MG_Test/Pipe, MG_Test/Pipeline, MG_Test/Program, MG_Test/Query, MG_Test/SelfTest, MG_Test/ShaderTranspiler, MG_Test/State, MG_Test/Swapchain, MG_Test/Texture, MG_Test/Util, MG_Test/VertexArray, MG_Test/Wire, MG_Util/Async, MG_Util/BackendLoaders, MG_Util/Converters, MG_Util/Damage, MG_Util/Debug, MG_Util/Math, MG_Util/Metrics, MG_Util/SelfTest, MG_Util/ShaderTranspiler, MG_Util/Texture, MG_Util/X11, MobileGL, scripts/data, tools/cts, tools/device_bench, tools/spikes, tools/trace_replay, tools/wgl-smoke): clang-format every C and C++ source

- Layout only: token streams match up to split literals, continuations and using order
- The size totals fall, so the structure baseline is lowered
This commit is contained in:
2026-10-16 06:30:38 -04:00
parent 76b10cc35b
commit f4994ee36e
747 changed files with 35842 additions and 36261 deletions
+2 -2
View File
@@ -395,7 +395,6 @@ namespace MobileGL {
Int MaxFragmentShadingRateAttachmentTexelAspectRatio = 0;
Int MaxFragmentShadingRateAttachmentLayers = 0;
static constexpr Bool IsSamplePositionCount(Int samples) {
return samples >= 2 && samples <= kMaxSamplePositionCount && (samples & (samples - 1)) == 0;
}
@@ -405,7 +404,8 @@ namespace MobileGL {
// `xy` holds `samples` (x, y) pairs.
void SetSamplePositions(Int samples, const Float* xy) {
if (!IsSamplePositionCount(samples)) return;
for (Int i = 0; i < 2 * samples; ++i) SamplePositions[2 * (samples - 2) + i] = xy[i];
for (Int i = 0; i < 2 * samples; ++i)
SamplePositions[2 * (samples - 2) + i] = xy[i];
SamplePositionCountMask |= SamplePositionCountBit(samples);
}
Bool GetSamplePosition(Int samples, Int index, Float* outXY) const {
+11 -7
View File
@@ -520,11 +520,11 @@ namespace MobileGL::MG_Config {
// MG_Remote` must be non-empty in build-split (CONTRACT-P5.md table 3, and the CI job
// P5 adds beside build-linux-verify).
enum class TransportMode : Uint8 {
Monolith = 0, // today's in-library backend; no MG_Remote object is constructed
InProcess = 1, // P5: a real apply thread in this process, over the same G3 codec
Spawn = 2, // P6: fork/exec MobileGLServer, socketpair
UnixSocket = 3,// P6: connect to an existing AF_UNIX endpoint (Endpoint = <path>)
NamedPipe = 4, // P6: Windows named pipe (Endpoint = <name>)
Monolith = 0, // today's in-library backend; no MG_Remote object is constructed
InProcess = 1, // P5: a real apply thread in this process, over the same G3 codec
Spawn = 2, // P6: fork/exec MobileGLServer, socketpair
UnixSocket = 3, // P6: connect to an existing AF_UNIX endpoint (Endpoint = <path>)
NamedPipe = 4, // P6: Windows named pipe (Endpoint = <name>)
};
// P13 W5: THE RECORD ARM'S OWN KNOBS. They kept their MOBILEGL_IPC_* names (logs, CI lanes and
@@ -799,8 +799,12 @@ namespace MobileGL::MG_Config {
// wire and on monolith alike ("monolith is the push variant"). W4 switched monolith family by
// family behind MOBILEGL_PIPE_DATA_ARM; W5 built the record arm into every library; W6 deleted
// the frontend arm and the knob. The predicate stays as a name for the sites that read it.
inline constexpr Bool DataArmIsRecord() { return true; }
inline constexpr Bool DataArmIsRecord() {
return true;
}
// ...and the record arm runs in this process over stores that ALIAS the frontend's shadows
// (monolith) instead of copying them (every wire).
inline Bool RecordArmAliasesFrontend() { return Transport == TransportMode::Monolith; }
inline Bool RecordArmAliasesFrontend() {
return Transport == TransportMode::Monolith;
}
} // namespace MobileGL::MG_Config
+21 -25
View File
@@ -115,7 +115,8 @@ namespace MobileGL::MG_ConfigLoader {
continue;
}
String value = Trimmed(line.substr(equals + 1));
if (value.size() >= 2 && (value.front() == '"' || value.front() == '\'') && value.back() == value.front()) {
if (value.size() >= 2 && (value.front() == '"' || value.front() == '\'') &&
value.back() == value.front()) {
value = value.substr(1, value.size() - 2);
}
out.emplace(key, value);
@@ -262,8 +263,7 @@ namespace MobileGL::MG_ConfigLoader {
if (lowered.empty() || lowered == "auto") return MG_Config::QuirkOverride::Auto;
if (lowered == "forceon" || lowered == "on") return MG_Config::QuirkOverride::ForceOn;
if (lowered == "forceoff" || lowered == "off" || lowered == "no") return MG_Config::QuirkOverride::ForceOff;
return IsTruthyValue(it->second) ? MG_Config::QuirkOverride::ForceOn
: MG_Config::QuirkOverride::ForceOff;
return IsTruthyValue(it->second) ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff;
}
// Multi-draw mode is a named-value preference: unset keeps Auto (best supported tier),
@@ -394,8 +394,7 @@ namespace MobileGL::MG_ConfigLoader {
features.MagmaMaxDrawsPerCommandBuffer =
QueryEnvUint32("MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER", 16384, 0, 1u << 24);
features.MagmaDescriptorTrimFrames = QueryEnvUint32("MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES", 120, 0, 1u << 20);
features.EsprytAvoidSamplerMipmapMinFilter =
QueryEnvFlag("MOBILEGL_ESPRYT_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
features.EsprytAvoidSamplerMipmapMinFilter = QueryEnvFlag("MOBILEGL_ESPRYT_AVOID_SAMPLER_MIPMAP_MIN_FILTER");
features.EsprytAvoidExplicitLodBias = QueryEnvFlag("MOBILEGL_ESPRYT_AVOID_EXPLICIT_LOD_BIAS");
features.EsprytUnlocatedIoBlocks = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS");
features.EsprytDeferGeometryEmits = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_DEFER_GEOMETRY_EMITS");
@@ -409,8 +408,7 @@ namespace MobileGL::MG_ConfigLoader {
features.EsprytDisableUploadRing = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_UPLOAD_RING");
features.EsprytDisableInvalidateFlush = QueryEnvFlag("MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH");
features.DisableLargeBufferAdoption = QueryEnvFlag("MOBILEGL_DISABLE_LARGE_BUFFER_ADOPTION");
features.EsprytForceDepthStencilReadbackEmulation =
QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
features.EsprytForceDepthStencilReadbackEmulation = QueryEnvFlag("MOBILEGL_ESPRYT_FORCE_DS_READBACK_EMULATION");
features.RelaxedSemantics = QueryEnvFlag("MOBILEGL_RELAXED_SEMANTICS");
features.MagmaDisableBlendedDepthWriteQuirk =
QueryEnvQuirkOverride("MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE");
@@ -419,13 +417,10 @@ namespace MobileGL::MG_ConfigLoader {
features.EsprytMultiDrawMode = QueryEnvGLESMultiDrawMode("MOBILEGL_ESPRYT_MULTIDRAW_MODE");
features.AsyncShaderCompile = QueryEnvQuirkOverride("MOBILEGL_ASYNC_SHADER_COMPILE");
features.AsyncShaderCompileThreads = QueryEnvUint32("MOBILEGL_ASYNC_SHADER_COMPILE_THREADS", 0, 0, 64);
features.AsyncOptimisticShaderStatus =
QueryEnvQuirkOverride("MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS");
features.AsyncOptimisticShaderStatus = QueryEnvQuirkOverride("MOBILEGL_ASYNC_OPTIMISTIC_SHADER_STATUS");
features.ShaderTranslationCache = QueryEnvQuirkOverride("MOBILEGL_SHADER_CACHE");
features.EsprytViewportArrayEmulation =
QueryEnvQuirkOverride("MOBILEGL_ESPRYT_FORCE_VIEWPORT_ARRAY_EMULATION");
features.EsprytWidenPacked16Storage =
QueryEnvQuirkOverride("MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE");
features.EsprytViewportArrayEmulation = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_FORCE_VIEWPORT_ARRAY_EMULATION");
features.EsprytWidenPacked16Storage = QueryEnvQuirkOverride("MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE");
features.MagmaPrimGenQueryReroute = QueryEnvQuirkOverride("MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE");
features.MagmaE5RawReads = QueryEnvQuirkOverride("MOBILEGL_MAGMA_E5_RAW_READS");
features.MagmaSplitSliceCopies = QueryEnvQuirkOverride("MOBILEGL_MAGMA_SPLIT_SLICE_COPIES");
@@ -444,14 +439,12 @@ namespace MobileGL::MG_ConfigLoader {
// nothing to run; it is refused here, by name, before any backend is brought up, rather
// than discovered as an armless subsystem at its first draw. Bit 63 (CSO content
// addressing off) stays a switch.
if ((features.PipePush & MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) !=
MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) {
if ((features.PipePush & MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) != MG_Pipe::kMGPipeSubsystemsMigratedAtP5e) {
MGLOG_F("MGPipe: Fatal{PipeSubsystemsFixedOn, \"MOBILEGL_PIPE_PUSH=0x%llx clears subsystem "
"bit(s) 0x%llx; bits 0-13 are fixed on since P13 - only bit 63 may be set or "
"cleared\"}",
static_cast<unsigned long long>(features.PipePush),
static_cast<unsigned long long>(MG_Pipe::kMGPipeSubsystemsMigratedAtP5e &
~features.PipePush));
static_cast<unsigned long long>(MG_Pipe::kMGPipeSubsystemsMigratedAtP5e & ~features.PipePush));
std::abort();
}
features.PipeVerify = QueryEnvFlag("MOBILEGL_PIPE_VERIFY");
@@ -629,9 +622,9 @@ namespace MobileGL::MG_ConfigLoader {
// P15: derived from MOBILEGL_FRAMES_IN_FLIGHT (Config.h states the mapping); the explicit
// knob stays as an override for experiments.
{
const Uint32 derived = std::max<Uint32>(1u, std::min<Uint32>(8u, MG_Config::Features.FramesInFlight > 2
? MG_Config::Features.FramesInFlight - 2
: 1u));
const Uint32 derived = std::max<Uint32>(
1u, std::min<Uint32>(8u, MG_Config::Features.FramesInFlight > 2 ? MG_Config::Features.FramesInFlight - 2
: 1u));
ipc.PresentCredit = QueryEnvUint32("MOBILEGL_IPC_PRESENT_CREDIT", derived, 1, 8);
}
// CONTRACT-P6 D5b's reply bound and P7's cold-start budget (Config.h has the semantics).
@@ -650,7 +643,8 @@ namespace MobileGL::MG_ConfigLoader {
// and the dual-block census is empty (no apply reads a client-filled field), so the
// shipped arm now matches what is tested - and with two blocks the client's fill is its
// own and needs no applier quiesce. 0 still turns it off (the negative control does).
ipc.RoleSplitState = QueryEnvQuirkOverride("MOBILEGL_IPC_ROLE_SPLIT_STATE") != MG_Config::QuirkOverride::ForceOff;
ipc.RoleSplitState =
QueryEnvQuirkOverride("MOBILEGL_IPC_ROLE_SPLIT_STATE") != MG_Config::QuirkOverride::ForceOff;
if (MG_Config::Features.PipeVerify && ipc.RoleSplitState) {
MGLOG_W("Config: MOBILEGL_IPC_ROLE_SPLIT_STATE=1 is incompatible with "
"MOBILEGL_PIPE_VERIFY (the comparator owns the single fill block); "
@@ -725,10 +719,12 @@ namespace MobileGL::MG_ConfigLoader {
"cold-start=%ums strict=%d audit=%d role-split-state=%d affinity='%s' surface=%s",
ipc.RingMb, ipc.StageMb, MG_Config::RecordArm.WireDeferredMb, ipc.SpinUs, ipc.EventWaitMs,
MG_Config::RecordArm.PersistentBlockKb, ipc.CreateWindow, ipc.PboReadbackSync, ipc.PollEscalate,
ipc.AdoptTier == 0 ? "0" : ipc.AdoptTier == 1 ? "1" : ipc.AdoptTier == 2 ? "2" : "unset(T0)",
ipc.AllowAdoptT0,
ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs, ipc.ColdStartMs,
static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
ipc.AdoptTier == 0 ? "0"
: ipc.AdoptTier == 1 ? "1"
: ipc.AdoptTier == 2 ? "2"
: "unset(T0)",
ipc.AllowAdoptT0, ipc.VerbBarrier, ipc.RunAhead, ipc.PresentCredit, ipc.ControlTimeoutMs,
ipc.ColdStartMs, static_cast<int>(ipc.StrictErrors), static_cast<int>(ipc.Audit),
static_cast<int>(ipc.RoleSplitState), ipc.ServerAffinity.c_str(),
MG_Config::ServerOwnedWindowSurfaces() ? "server" : "offscreen");
if (ipc.VerbBarrier == 0) {
+9 -9
View File
@@ -107,14 +107,14 @@
// 2026-08-13 renumbering unchanged; the contract is and stays
// "INFO builds: asserts OFF; DEBUG builds: asserts ON".
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
#define MOBILEGL_ASSERT(condition, ...) \
do { \
if (!(condition)) { \
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
TRAP; \
} \
} while (0)
#define MOBILEGL_ASSERT(condition, ...) \
do { \
if (!(condition)) { \
MGLOG_F("Assertion failed" __VA_OPT__(": ") __VA_ARGS__); \
MGLOG_F(" at %s:%d (%s)", __FILE__, __LINE__, __func__); \
TRAP; \
} \
} while (0)
#else
#define MOBILEGL_ASSERT(condition, ...)
#define MOBILEGL_ASSERT(condition, ...)
#endif
+1 -1
View File
@@ -124,7 +124,7 @@ namespace MobileGL {
// TODO: add and use Destroy functions for other subsystems
}
}
} // namespace
void Initialize() {
if (g_isInitialized) {
+10 -11
View File
@@ -24,10 +24,8 @@ namespace MobileGL::MG_Backend {
return std::this_thread::get_id();
}
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache,
SizeT targetIndex,
SizeT formatIndex,
FormatCapability capability) {
const char* GetFormatCapabilitySupportString(const FormatCapabilityCache& cache, SizeT targetIndex,
SizeT formatIndex, FormatCapability capability) {
if (HasFormatCapability(cache.FullCaps[targetIndex][formatIndex], capability)) return "Full";
if (HasFormatCapability(cache.CaveatCaps[targetIndex][formatIndex], capability)) return "Caveat";
return "None";
@@ -58,9 +56,7 @@ namespace MobileGL::MG_Backend {
return line.str();
}
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache,
SizeT targetIndex,
SizeT formatIndex,
String BuildFormatCapabilityRow(const FormatCapabilityCache& cache, SizeT targetIndex, SizeT formatIndex,
SizeT formatNameWidth) {
const auto format = static_cast<TextureInternalFormat>(formatIndex);
std::ostringstream line;
@@ -397,8 +393,12 @@ namespace MobileGL::MG_Backend {
}
} // namespace
const MG_Util::Damage::Region& CurrentPresentDamage() { return PresentDamageSlot(); }
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage) { PresentDamageSlot() = damage; }
const MG_Util::Damage::Region& CurrentPresentDamage() {
return PresentDamageSlot();
}
void SetCurrentPresentDamage(const MG_Util::Damage::Region& damage) {
PresentDamageSlot() = damage;
}
Bool BackendObject::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
@@ -458,8 +458,7 @@ namespace MobileGL::MG_Backend {
void BackendObject::DestroyPendingEGLSurfaceIfUnused(EGLSurface surface) {
auto surfaceIt = m_eglSurfaces.find(surface);
if (surfaceIt == m_eglSurfaces.end() || !surfaceIt->second.DestroyPending ||
IsEGLSurfaceCurrent(surface)) {
if (surfaceIt == m_eglSurfaces.end() || !surfaceIt->second.DestroyPending || IsEGLSurfaceCurrent(surface)) {
return;
}
+11 -12
View File
@@ -18,7 +18,7 @@ namespace MobileGL {
class FramebufferObject;
class ITextureObject;
class RenderbufferObject;
}
} // namespace MG_State::GLState
namespace MG_Backend {
// One endpoint of a glCopyImageSubData. GL 4.6 core 18.3.2 accepts GL_RENDERBUFFER
@@ -66,8 +66,8 @@ namespace MobileGL {
void (*MultiDrawElementsIndirect)(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
GLsizei stride);
void (*MultiDrawArraysIndirect)(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect,
GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride);
void (*MultiDrawElementsIndirectCount)(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void (*MultiDrawArraysIndirectCount)(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void (*DrawRangeElementsBaseVertex)(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
@@ -105,18 +105,16 @@ namespace MobileGL {
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
void (*BlitNamedFramebuffer)(const SharedPtr<MG_State::GLState::FramebufferObject>& readFramebuffer,
const SharedPtr<MG_State::GLState::FramebufferObject>& drawFramebuffer,
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter);
void (*CopyTexImage2D)(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width,
GLsizei height, GLint border);
void (*CopyTexSubImage2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
void (*CopyImageSubData)(const CopyImageEndpoint& src,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dst,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void (*CopyImageSubData)(const CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX,
GLint srcY, GLint srcZ, const CopyImageEndpoint& dst, GLenum dstTarget,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth,
GLsizei srcHeight, GLsizei srcDepth);
void (*GenerateMipmap)(GLenum target);
void (*ReadPixels)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void* pixels);
@@ -463,7 +461,8 @@ namespace MobileGL {
virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw);
// eglSwapBuffersWithDamage: `damage` (GL window coordinates) is a hint for the native
// present. A backend with nothing to hand it to swaps as SwapEGLBuffers does.
virtual Bool SwapEGLBuffersWithDamage(EGLDisplay dpy, EGLSurface draw, const MG_Util::Damage::Region& damage) {
virtual Bool SwapEGLBuffersWithDamage(EGLDisplay dpy, EGLSurface draw,
const MG_Util::Damage::Region& damage) {
(void)damage;
return SwapEGLBuffers(dpy, draw);
}
+129 -70
View File
@@ -26,28 +26,64 @@ namespace MobileGL::MG_Backend {
Bool GetReadbackChannelMapping(GLenum format, ReadbackChannelMapping& outMapping) {
switch (format) {
case GL_RED: outMapping = {{0, 0, 0, 0}, 1, false}; return true;
case GL_RED_INTEGER: outMapping = {{0, 0, 0, 0}, 1, true}; return true;
case GL_RED:
outMapping = {{0, 0, 0, 0}, 1, false};
return true;
case GL_RED_INTEGER:
outMapping = {{0, 0, 0, 0}, 1, true};
return true;
// Desktop-GL single-channel client formats (GL CTS packed_pixels rgba8_format_green/blue):
// the destination holds one component sourced from the named channel of the wide RGBA read.
// GL_ALPHA is mapped here from the raw enum because the state layer folds it into Red for the
// legacy alpha-texture upload hack.
case GL_GREEN: outMapping = {{1, 0, 0, 0}, 1, false}; return true;
case GL_GREEN_INTEGER: outMapping = {{1, 0, 0, 0}, 1, true}; return true;
case GL_BLUE: outMapping = {{2, 0, 0, 0}, 1, false}; return true;
case GL_BLUE_INTEGER: outMapping = {{2, 0, 0, 0}, 1, true}; return true;
case GL_ALPHA: outMapping = {{3, 0, 0, 0}, 1, false}; return true;
case GL_ALPHA_INTEGER: outMapping = {{3, 0, 0, 0}, 1, true}; return true;
case GL_RG: outMapping = {{0, 1, 0, 0}, 2, false}; return true;
case GL_RG_INTEGER: outMapping = {{0, 1, 0, 0}, 2, true}; return true;
case GL_RGB: outMapping = {{0, 1, 2, 0}, 3, false}; return true;
case GL_RGB_INTEGER: outMapping = {{0, 1, 2, 0}, 3, true}; return true;
case GL_BGR: outMapping = {{2, 1, 0, 0}, 3, false}; return true;
case GL_BGR_INTEGER: outMapping = {{2, 1, 0, 0}, 3, true}; return true;
case GL_RGBA: outMapping = {{0, 1, 2, 3}, 4, false}; return true;
case GL_RGBA_INTEGER: outMapping = {{0, 1, 2, 3}, 4, true}; return true;
case GL_BGRA: outMapping = {{2, 1, 0, 3}, 4, false}; return true;
case GL_BGRA_INTEGER: outMapping = {{2, 1, 0, 3}, 4, true}; return true;
case GL_GREEN:
outMapping = {{1, 0, 0, 0}, 1, false};
return true;
case GL_GREEN_INTEGER:
outMapping = {{1, 0, 0, 0}, 1, true};
return true;
case GL_BLUE:
outMapping = {{2, 0, 0, 0}, 1, false};
return true;
case GL_BLUE_INTEGER:
outMapping = {{2, 0, 0, 0}, 1, true};
return true;
case GL_ALPHA:
outMapping = {{3, 0, 0, 0}, 1, false};
return true;
case GL_ALPHA_INTEGER:
outMapping = {{3, 0, 0, 0}, 1, true};
return true;
case GL_RG:
outMapping = {{0, 1, 0, 0}, 2, false};
return true;
case GL_RG_INTEGER:
outMapping = {{0, 1, 0, 0}, 2, true};
return true;
case GL_RGB:
outMapping = {{0, 1, 2, 0}, 3, false};
return true;
case GL_RGB_INTEGER:
outMapping = {{0, 1, 2, 0}, 3, true};
return true;
case GL_BGR:
outMapping = {{2, 1, 0, 0}, 3, false};
return true;
case GL_BGR_INTEGER:
outMapping = {{2, 1, 0, 0}, 3, true};
return true;
case GL_RGBA:
outMapping = {{0, 1, 2, 3}, 4, false};
return true;
case GL_RGBA_INTEGER:
outMapping = {{0, 1, 2, 3}, 4, true};
return true;
case GL_BGRA:
outMapping = {{2, 1, 0, 3}, 4, false};
return true;
case GL_BGRA_INTEGER:
outMapping = {{2, 1, 0, 3}, 4, true};
return true;
default:
return false;
}
@@ -58,22 +94,50 @@ namespace MobileGL::MG_Backend {
// Non-REV types pack the first format component starting at the most significant bit,
// *_REV types starting at the least significant bit (GL CTS pack_UNSIGNED_SHORT_5_6_5:
// R bits 15-11; pack_UNSIGNED_SHORT_1_5_5_5_REV: R bits 4-0, A bit 15).
case GL_UNSIGNED_BYTE_3_3_2: out = {3, {3, 3, 2, 0}, {5, 2, 0, 0}, 1, false}; return true;
case GL_UNSIGNED_BYTE_2_3_3_REV: out = {3, {3, 3, 2, 0}, {0, 3, 6, 0}, 1, false}; return true;
case GL_UNSIGNED_SHORT_5_6_5: out = {3, {5, 6, 5, 0}, {11, 5, 0, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_5_6_5_REV: out = {3, {5, 6, 5, 0}, {0, 5, 11, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_4_4_4_4: out = {4, {4, 4, 4, 4}, {12, 8, 4, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_4_4_4_4_REV: out = {4, {4, 4, 4, 4}, {0, 4, 8, 12}, 2, false}; return true;
case GL_UNSIGNED_SHORT_5_5_5_1: out = {4, {5, 5, 5, 1}, {11, 6, 1, 0}, 2, false}; return true;
case GL_UNSIGNED_SHORT_1_5_5_5_REV: out = {4, {5, 5, 5, 1}, {0, 5, 10, 15}, 2, false}; return true;
case GL_UNSIGNED_INT_8_8_8_8: out = {4, {8, 8, 8, 8}, {24, 16, 8, 0}, 4, false}; return true;
case GL_UNSIGNED_INT_8_8_8_8_REV: out = {4, {8, 8, 8, 8}, {0, 8, 16, 24}, 4, false}; return true;
case GL_UNSIGNED_INT_10_10_10_2: out = {4, {10, 10, 10, 2}, {22, 12, 2, 0}, 4, false}; return true;
case GL_UNSIGNED_INT_2_10_10_10_REV: out = {4, {10, 10, 10, 2}, {0, 10, 20, 30}, 4, false}; return true;
case GL_UNSIGNED_BYTE_3_3_2:
out = {3, {3, 3, 2, 0}, {5, 2, 0, 0}, 1, false};
return true;
case GL_UNSIGNED_BYTE_2_3_3_REV:
out = {3, {3, 3, 2, 0}, {0, 3, 6, 0}, 1, false};
return true;
case GL_UNSIGNED_SHORT_5_6_5:
out = {3, {5, 6, 5, 0}, {11, 5, 0, 0}, 2, false};
return true;
case GL_UNSIGNED_SHORT_5_6_5_REV:
out = {3, {5, 6, 5, 0}, {0, 5, 11, 0}, 2, false};
return true;
case GL_UNSIGNED_SHORT_4_4_4_4:
out = {4, {4, 4, 4, 4}, {12, 8, 4, 0}, 2, false};
return true;
case GL_UNSIGNED_SHORT_4_4_4_4_REV:
out = {4, {4, 4, 4, 4}, {0, 4, 8, 12}, 2, false};
return true;
case GL_UNSIGNED_SHORT_5_5_5_1:
out = {4, {5, 5, 5, 1}, {11, 6, 1, 0}, 2, false};
return true;
case GL_UNSIGNED_SHORT_1_5_5_5_REV:
out = {4, {5, 5, 5, 1}, {0, 5, 10, 15}, 2, false};
return true;
case GL_UNSIGNED_INT_8_8_8_8:
out = {4, {8, 8, 8, 8}, {24, 16, 8, 0}, 4, false};
return true;
case GL_UNSIGNED_INT_8_8_8_8_REV:
out = {4, {8, 8, 8, 8}, {0, 8, 16, 24}, 4, false};
return true;
case GL_UNSIGNED_INT_10_10_10_2:
out = {4, {10, 10, 10, 2}, {22, 12, 2, 0}, 4, false};
return true;
case GL_UNSIGNED_INT_2_10_10_10_REV:
out = {4, {10, 10, 10, 2}, {0, 10, 20, 30}, 4, false};
return true;
// Packed-float RGB types: fields hold unsigned small floats; 5_9_9_9_REV's shared 5-bit
// exponent (bits 31-27) is emitted by EncodeSharedExponentRGB9E5, not a component field.
case GL_UNSIGNED_INT_10F_11F_11F_REV: out = {3, {11, 11, 10, 0}, {0, 11, 22, 0}, 4, true}; return true;
case GL_UNSIGNED_INT_5_9_9_9_REV: out = {3, {9, 9, 9, 0}, {0, 9, 18, 0}, 4, true}; return true;
case GL_UNSIGNED_INT_10F_11F_11F_REV:
out = {3, {11, 11, 10, 0}, {0, 11, 22, 0}, 4, true};
return true;
case GL_UNSIGNED_INT_5_9_9_9_REV:
out = {3, {9, 9, 9, 0}, {0, 9, 18, 0}, 4, true};
return true;
default:
return false;
}
@@ -141,9 +205,15 @@ namespace MobileGL::MG_Backend {
// Shared encoders live in MG_Util/Math/SmallFloat.h so the upload conversion
// (PixelStoreProcessor) uses byte-identical packing; kept exported here for unit tests.
Uint32 EncodeFloatToUnsignedF11(Float value) { return MG_Util::EncodeFloatToUnsignedF11(value); }
Uint32 EncodeFloatToUnsignedF10(Float value) { return MG_Util::EncodeFloatToUnsignedF10(value); }
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) { return MG_Util::EncodeSharedExponentRGB9E5(rgb); }
Uint32 EncodeFloatToUnsignedF11(Float value) {
return MG_Util::EncodeFloatToUnsignedF11(value);
}
Uint32 EncodeFloatToUnsignedF10(Float value) {
return MG_Util::EncodeFloatToUnsignedF10(value);
}
Uint32 EncodeSharedExponentRGB9E5(const Float rgb[3]) {
return MG_Util::EncodeSharedExponentRGB9E5(rgb);
}
void ConvertWideReadbackRow(const Uint8* src, Uint8* dst, SizeT width, GLenum wideType,
const ReadbackChannelMapping& mapping, GLenum type) {
@@ -203,8 +273,7 @@ namespace MobileGL::MG_Backend {
break;
}
case GL_INT: {
const auto out =
static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
const auto out = static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
@@ -229,8 +298,7 @@ namespace MobileGL::MG_Backend {
break;
case GL_UNSIGNED_SHORT:
for (Int c = 0; c < 4; ++c) {
srcValues[c] =
static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
srcValues[c] = static_cast<Float>(reinterpret_cast<const Uint16*>(srcPixel)[c]) / 65535.0f;
}
break;
case GL_SHORT:
@@ -277,24 +345,20 @@ namespace MobileGL::MG_Backend {
Uint8* dstComponent = dstPixel + static_cast<SizeT>(ch) * dstComponentSize;
switch (type) {
case GL_UNSIGNED_BYTE:
*dstComponent =
static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
*dstComponent = static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
break;
case GL_BYTE: {
const auto out =
static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
const auto out = static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_UNSIGNED_SHORT: {
const auto out =
static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
const auto out = static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
case GL_SHORT: {
const auto out =
static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
const auto out = static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
Memcpy(dstComponent, &out, sizeof(out));
break;
}
@@ -350,10 +414,9 @@ namespace MobileGL::MG_Backend {
const auto packParams = MG_Pipe::gPipeInputs->GetPixelStoreParameters(false);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT dstRowStride = AlignReadbackRow(rowPixels * dstPixelBytes, packParams.Alignment);
const SizeT imageRows =
applyPackImageParams && packParams.ImageHeight > 0
? static_cast<SizeT>(packParams.ImageHeight)
: static_cast<SizeT>(sliceHeight);
const SizeT imageRows = applyPackImageParams && packParams.ImageHeight > 0
? static_cast<SizeT>(packParams.ImageHeight)
: static_cast<SizeT>(sliceHeight);
const SizeT dstImageStride = imageRows * dstRowStride;
const SizeT skipImages =
applyPackImageParams ? static_cast<SizeT>(std::max(packParams.SkipImages, 0)) : SizeT{0};
@@ -375,7 +438,7 @@ namespace MobileGL::MG_Backend {
}
const SizeT dstOffset = dstSkipOffset + static_cast<SizeT>(slice) * dstImageStride +
static_cast<SizeT>(row) * dstRowStride;
static_cast<SizeT>(row) * dstRowStride;
Memcpy(static_cast<Uint8*>(pixels) + dstOffset, convertedRow.data(), dstRowBytes);
}
}
@@ -387,8 +450,8 @@ namespace MobileGL::MG_Backend {
// `sliceHeight * sliceCount` rows of `width` texels (slice-major, tightly stacked),
// 4 components x GetReadbackComponentSize(wideType) bytes each.
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
void* pixels, Bool applyPackImageParams) {
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type, void* pixels,
Bool applyPackImageParams) {
const SizeT dstPixelBytes = GetReadbackDstPixelSize(mapping, type);
if (dstPixelBytes == 0) {
return false;
@@ -398,17 +461,14 @@ namespace MobileGL::MG_Backend {
const SizeT swapGroupSize = isPackedType ? packedLayout.byteSize : GetReadbackComponentSize(type);
const SizeT srcPixelBytes = 4 * GetReadbackComponentSize(wideType);
return StoreClientRows(dstPixelBytes, swapGroupSize, width, sliceHeight, sliceCount, pixels,
applyPackImageParams,
[&](GLsizei slice, GLsizei row, Uint8* dstRow) {
const SizeT flatRow = static_cast<SizeT>(slice) *
static_cast<SizeT>(sliceHeight) +
static_cast<SizeT>(row);
const Uint8* srcRow =
wide + flatRow * static_cast<SizeT>(width) * srcPixelBytes;
ConvertWideReadbackRow(srcRow, dstRow, static_cast<SizeT>(width), wideType,
mapping, type);
});
return StoreClientRows(
dstPixelBytes, swapGroupSize, width, sliceHeight, sliceCount, pixels, applyPackImageParams,
[&](GLsizei slice, GLsizei row, Uint8* dstRow) {
const SizeT flatRow =
static_cast<SizeT>(slice) * static_cast<SizeT>(sliceHeight) + static_cast<SizeT>(row);
const Uint8* srcRow = wide + flatRow * static_cast<SizeT>(width) * srcPixelBytes;
ConvertWideReadbackRow(srcRow, dstRow, static_cast<SizeT>(width), wideType, mapping, type);
});
}
Bool StorePackedWordsToClient(const Uint8* srcWords, GLsizei width, GLsizei sliceHeight, GLsizei sliceCount,
@@ -420,11 +480,10 @@ namespace MobileGL::MG_Backend {
const SizeT srcRowBytes = static_cast<SizeT>(width) * 4;
return StoreClientRows(4, packedLayout.byteSize, width, sliceHeight, sliceCount, pixels,
applyPackImageParams,
[&](GLsizei slice, GLsizei row, Uint8* dstRow) {
const SizeT flatRow = static_cast<SizeT>(slice) *
static_cast<SizeT>(sliceHeight) +
static_cast<SizeT>(row);
applyPackImageParams, [&](GLsizei slice, GLsizei row, Uint8* dstRow) {
const SizeT flatRow =
static_cast<SizeT>(slice) * static_cast<SizeT>(sliceHeight) +
static_cast<SizeT>(row);
Memcpy(dstRow, srcWords + flatRow * srcRowBytes, srcRowBytes);
});
}
+2 -2
View File
@@ -59,8 +59,8 @@ namespace MobileGL::MG_Backend {
// applyPackImageParams - image height/skip images). Shared by the DirectGLES and DirectVulkan
// readback conversion paths.
Bool StoreWideRowsToClient(const Uint8* wide, GLenum wideType, GLsizei width, GLsizei sliceHeight,
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type,
void* pixels, Bool applyPackImageParams);
GLsizei sliceCount, const ReadbackChannelMapping& mapping, GLenum type, void* pixels,
Bool applyPackImageParams);
// Stores packed 32-bit source words verbatim, with the same destination addressing and PACK
// parameters as StoreWideRowsToClient. For the sources whose storage word already IS the
@@ -50,14 +50,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the recorded tuple; only that exact tuple may skip. Process-wide like the native state it
// mirrors; under split exactly one DirectGLES object exists (the server's), and only the
// apply thread reaches these.
enum class NativeBindState : Uint8 { NotBound, FreshFromSurfaceCreation, BoundForTuple };
enum class NativeBindState : Uint8 {
NotBound,
FreshFromSurfaceCreation,
BoundForTuple
};
NativeBindState g_nativeBindState = NativeBindState::NotBound;
EGLDisplay g_nativeBoundDpy = EGL_NO_DISPLAY;
EGLSurface g_nativeBoundDraw = EGL_NO_SURFACE;
EGLSurface g_nativeBoundRead = EGL_NO_SURFACE;
EGLContext g_nativeBoundCtx = EGL_NO_CONTEXT;
void NoteNativeContextGone() { g_nativeBindState = NativeBindState::NotBound; }
void NoteNativeContextGone() {
g_nativeBindState = NativeBindState::NotBound;
}
void NoteNativeContextFreshFromSurfaceCreation() {
g_nativeBindState = NativeBindState::FreshFromSurfaceCreation;
}
@@ -70,8 +76,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool NativeBindCanBeSkippedFor(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
switch (g_nativeBindState) {
case NativeBindState::NotBound: return false;
case NativeBindState::FreshFromSurfaceCreation: return true;
case NativeBindState::NotBound:
return false;
case NativeBindState::FreshFromSurfaceCreation:
return true;
case NativeBindState::BoundForTuple:
return g_nativeBoundDpy == dpy && g_nativeBoundDraw == draw && g_nativeBoundRead == read &&
g_nativeBoundCtx == ctx;
@@ -364,8 +372,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// for EVERY colour format while GL_MAX_SAMPLES - ES 3.0 core, so genuinely answered -
// reports 4. Once the frontend validates against this list, that would reject every
// multisample renderbuffer on such a context.
Int GetGLESRenderbufferFormatMaxSamples(const MG_External::GLESCapabilities& capabilities,
GLenum imageFormat) {
Int GetGLESRenderbufferFormatMaxSamples(const MG_External::GLESCapabilities& capabilities, GLenum imageFormat) {
const Bool isInteger = imageFormat == GL_RED_INTEGER || imageFormat == GL_RG_INTEGER ||
imageFormat == GL_RGB_INTEGER || imageFormat == GL_RGBA_INTEGER;
return isInteger ? capabilities.MaxIntegerSamples : capabilities.MaxSamples;
@@ -474,8 +481,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint prevFramebuffer = 0, prevRenderbuffer = 0;
gl.glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prevFramebuffer);
gl.glGetIntegerv(GL_RENDERBUFFER_BINDING, &prevRenderbuffer);
for (Int samples = 2; samples <= DynamicBackendParameters::kMaxSamplePositionCount &&
samples <= capabilities.MaxSamples;
for (Int samples = 2;
samples <= DynamicBackendParameters::kMaxSamplePositionCount && samples <= capabilities.MaxSamples;
samples *= 2) {
GLuint framebuffer = 0, renderbuffer = 0;
gl.glGenFramebuffers(1, &framebuffer);
@@ -502,8 +509,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// A count the driver refused leaves its error behind; it must not reach the application.
if (gl.glGetError) {
for (Int drained = 0; drained < 16 && gl.glGetError() != GL_NO_ERROR; ++drained) {
}
for (Int drained = 0; drained < 16 && gl.glGetError() != GL_NO_ERROR; ++drained) {}
}
}
@@ -728,12 +734,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Flags<PixelFormatNormalizeOptionBit> forcedProbeOptions =
(outerHasForcedFallback || widenUnconditionally) ? forcedOptions | renderTargetOptions
: forcedOptions;
hasForcedFallback =
BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedProbeOptions, true,
fallbackInfo);
hasForcedFallback = BuildFallbackProbeFormatInfo(requestedInternalFormat, forcedProbeOptions,
true, fallbackInfo);
if (!hasForcedFallback) {
BuildFallbackProbeFormatInfo(requestedInternalFormat,
driverOptions | renderTargetOptions, false, fallbackInfo);
BuildFallbackProbeFormatInfo(requestedInternalFormat, driverOptions | renderTargetOptions,
false, fallbackInfo);
}
// HONEST STATUS OF THE FORCED PATH. A forced fallback is only ever built
// for ANGLE (GetForcedPixelFormatNormalizeOptions returns nothing for any
@@ -1162,7 +1167,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
const auto state = m_eglSurfaces.find(surface);
const auto native = m_nativeSurfaces.find(surface);
if (state == m_eglSurfaces.end() || state->second.Kind != SurfaceKind::Pbuffer || native == m_nativeSurfaces.end()) {
if (state == m_eglSurfaces.end() || state->second.Kind != SurfaceKind::Pbuffer ||
native == m_nativeSurfaces.end()) {
return BackendObject::ResizeEGLWindowSurface(surface, width, height);
}
// A pbuffer has no resize: the client surface gets a new native pbuffer at the new size,
@@ -1398,9 +1404,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// driver dequeues); only the client has to be told.
const auto native = m_nativeSurfaces.find(client);
if (native == m_nativeSurfaces.end()) continue;
published = DirectGLES::RepublishWindowSurfaceShape(native->second,
reinterpret_cast<NativeWindowType>(window)) ||
published;
published =
DirectGLES::RepublishWindowSurfaceShape(native->second, reinterpret_cast<NativeWindowType>(window)) ||
published;
}
return published;
}
@@ -1443,8 +1449,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool drawIndirectSupported, Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features) {
Vector<GLExtension> extensions = {
@@ -1452,23 +1457,21 @@ namespace MobileGL::MG_Backend::DirectGLES {
// TargetGLVersion is {4,6,0}, and a list that stopped at OpenGL40 told an
// application feature-detecting off these tokens the opposite of what
// GL_MAJOR_VERSION / GL_MINOR_VERSION told it.
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, V_OpenGL41, V_OpenGL42, V_OpenGL43,
V_OpenGL44, V_OpenGL45, V_OpenGL46,
E_GL_ARB_draw_buffers_blend,
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, V_OpenGL41, V_OpenGL42, V_OpenGL43, V_OpenGL44,
V_OpenGL45, V_OpenGL46, E_GL_ARB_draw_buffers_blend,
// The GLSL 1.00/1.10-era ARB trio plus NPOT textures: all core since GL 2.0 and
// trivially true for every context this backend claims (4.6). KWin 6's
// EglContext::checkSupported() gates desktop-GL compositing on exactly these four
// strings, so withholding them fails its renderer bring-up on an otherwise capable
// implementation.
E_GL_ARB_shader_objects, E_GL_ARB_vertex_shader, E_GL_ARB_fragment_shader,
E_GL_ARB_texture_non_power_of_two,
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object,
E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage, E_GL_ARB_texture_storage,
E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access,
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters, E_GL_ARB_shader_draw_parameters,
E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind, E_GL_ARB_shading_language_420pack,
E_GL_ARB_vertex_attrib_binding,
E_GL_ARB_texture_non_power_of_two, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object,
E_GL_ARB_shader_image_load_store, E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query,
E_GL_ARB_framebuffer_object, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_clear_texture,
E_GL_ARB_direct_state_access, E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding,
// Both are core from GL 3.2/3.3 on and implemented here for
// every advertised version, but an app targeting 3.0/3.1
// only reaches them through the extension string - the CTS
@@ -1808,8 +1811,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendObject_DirectGLES::UpdateDynamicBackendParameters() {
m_dynamicParameters.UniformBufferOffsetAlignment = m_GLESCapabilities.UniformBufferOffsetAlignment;
m_dynamicParameters.ShaderStorageBufferOffsetAlignment =
m_GLESCapabilities.ShaderStorageBufferOffsetAlignment;
m_dynamicParameters.ShaderStorageBufferOffsetAlignment = m_GLESCapabilities.ShaderStorageBufferOffsetAlignment;
m_dynamicParameters.MaxTextureMaxAnisotropy = m_GLESCapabilities.MaxTextureMaxAnisotropy;
m_dynamicParameters.AliasedLineWidthRangeMin = m_GLESCapabilities.AliasedLineWidthRangeMin;
m_dynamicParameters.AliasedLineWidthRangeMax = m_GLESCapabilities.AliasedLineWidthRangeMax;
@@ -1962,8 +1964,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_dynamicParameters.SubgroupSupportedFeatures = subgroups.features;
m_dynamicParameters.SubgroupQuadOperationsInAllStages = subgroups.quadInAllStages;
MGLOG_I("DirectGLES: GL_KHR_shader_subgroup %s (size %u, stages 0x%x, features 0x%x)",
SubgroupImplementationName(ResolveEsprytSubgroupImplementation(m_GLESCapabilities)),
subgroups.size, subgroups.stages, subgroups.features);
SubgroupImplementationName(ResolveEsprytSubgroupImplementation(m_GLESCapabilities)), subgroups.size,
subgroups.stages, subgroups.features);
}
// Follows the line above, and must: OpenGL ES has no double-precision vertex format and no
// fp64 type to consume one with, so a 64-bit vertex attribute has nowhere to land here.
@@ -1977,11 +1979,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// exercised on a healthy driver (the pinned integration lane); =0 restores the
// detected answer's declines.
m_dynamicParameters.SupportsTessellationPointSize =
m_GLESCapabilities.TessellationPointSizeSupport !=
MG_External::GLESCapabilities::PointSizeTier::None;
m_GLESCapabilities.TessellationPointSizeSupport != MG_External::GLESCapabilities::PointSizeTier::None;
m_dynamicParameters.SupportsGeometryPointSize =
m_GLESCapabilities.GeometryPointSizeSupport !=
MG_External::GLESCapabilities::PointSizeTier::None;
m_GLESCapabilities.GeometryPointSizeSupport != MG_External::GLESCapabilities::PointSizeTier::None;
switch (MG_Config::Features.PointSizeDemotion) {
case MG_Config::QuirkOverride::ForceOn:
MGLOG_I("DirectGLES: MOBILEGL_POINT_SIZE_DEMOTION=1 - treating tessellation/geometry "
@@ -15,8 +15,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Populates the same format-capability cache used by backend startup. The caller
// must keep the supplied GLES context current for the duration of this call.
void PopulateFormatCapabilities(const MG_External::GLESFunctionsTable& gl,
const MG_External::GLESCapabilities& capabilities,
FormatCapabilityCache& cache);
const MG_External::GLESCapabilities& capabilities, FormatCapabilityCache& cache);
// Clamps a requested sample count down to what the ES driver can really deliver for this
// format on this format-capability target: the probed per-format list when there is one, the
@@ -118,8 +117,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// plus the feature set in `features`. The MOBILEGL_DISABLE_TIMERQUERY escape hatch is applied
// inside.
Vector<GLExtension> BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported,
Bool drawIndirectSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool drawIndirectSupported, Bool nonZeroIndirectBaseInstanceSupported,
Bool textureViewSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features = {});
@@ -27,5 +27,5 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool server = MG_Config::Transport != MG_Config::TransportMode::Monolith;
return {g_backendContextGeneration, server ? MG_Pipe::MGPipeApplierContextSerial() : 0, server};
}
}
} // namespace MobileGL::MG_Backend::DirectGLES
#endif
@@ -57,8 +57,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// substitution itself on the server; this is only where its inputs come from, and the CSO
// record carries all three (MGPipeValueTypes.h). The SharedPtr overload below is the same
// three lines with one dereference in front, so the two arms cannot part.
Bool NeedsRawDepthFetchSampler(const SamplerParameters& samplerParams,
TextureInternalFormat textureFormat) {
Bool NeedsRawDepthFetchSampler(const SamplerParameters& samplerParams, TextureInternalFormat textureFormat) {
if (!MG_Util::IsDepthFormatInternalFormat(textureFormat)) {
return false;
}
@@ -115,13 +114,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
void ErrorLopper::Loop(const std::function<void(GLenum)>& func) {
static_cast<void>(func);
for (Int i = 0; i < kMaxDrainedESErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
}
for (Int i = 0; i < kMaxDrainedESErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {}
}
void ErrorLopper::Clear() {
for (Int i = 0; i < kMaxDrainedESErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
}
for (Int i = 0; i < kMaxDrainedESErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {}
}
ErrorLopper::ErrorLopper() {
+10 -9
View File
@@ -75,11 +75,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLsizei height, GLint border);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height);
void CopyImageSubData(const CopyImageEndpoint& src,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dst,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void CopyImageSubData(const CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY,
GLint srcZ, const CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX,
GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void GenerateMipmap(GLenum target);
const GLubyte* GetString(GLenum name);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
@@ -291,8 +289,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT operator()(const TwinKey& key) const {
// The 64-bit mix SlotAllocator and the applier's registry already use for their own
// pair keys, so every keyed table in this tree hashes a session the same way.
Uint64 mixed = key.SessionKey ^ (key.ShareGroupKey + 0x9E3779B97F4A7C15ull +
(key.SessionKey << 6) + (key.SessionKey >> 2));
Uint64 mixed = key.SessionKey ^
(key.ShareGroupKey + 0x9E3779B97F4A7C15ull + (key.SessionKey << 6) + (key.SessionKey >> 2));
mixed ^= mixed >> 33;
mixed *= 0xFF51AFD7ED558CCDull;
mixed ^= mixed >> 33;
@@ -344,7 +342,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
CacheEntry Cache[kCacheEntries]{};
};
inline constinit thread_local NativeContextThreadState t_nativeContextThread;
inline Uint64 CurrentNativeContextSerial() { return t_nativeContextThread.BoundSerial; }
inline Uint64 CurrentNativeContextSerial() {
return t_nativeContextThread.BoundSerial;
}
class PerNativeContextBase {
public:
@@ -391,7 +391,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
template <typename F>
void ForEach(F&& visit) {
const std::lock_guard<std::mutex> lock(m_mutex);
for (auto& entry : m_bySerial) visit(entry.second);
for (auto& entry : m_bySerial)
visit(entry.second);
}
void Clear() {
const std::lock_guard<std::mutex> lock(m_mutex);
+97 -142
View File
@@ -34,8 +34,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// The IBO half lives in SyncNeccessaryBuffers beside the legacy one, for the reason it
// always did: the index slot is not part of the configuration (D5).
void SyncVaoAttributeBuffersByRecord(
VertexArrayImpl::BackendVertexArrayObject::ResolvedDrawBuffers* memo, Uint64 bufferEpoch) {
void SyncVaoAttributeBuffersByRecord(VertexArrayImpl::BackendVertexArrayObject::ResolvedDrawBuffers* memo,
Uint64 bufferEpoch) {
const auto& st = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle elements = st.BoundVertexElements;
Uint64 elementsSerial = 0;
@@ -164,8 +164,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// absent `possibleIBO` is below: SyncToBackendFromApplier is what binds
// GL_ELEMENT_ARRAY_BUFFER back to 0 for such a draw, and it keys on the same
// serial.
const DrawIndexBufferRequest request =
ResolveDrawIndexBufferFromRecord(MG_Pipe::MGPipeApplier());
const DrawIndexBufferRequest request = ResolveDrawIndexBufferFromRecord(MG_Pipe::MGPipeApplier());
const MG_Pipe::MGPipeHandle iboHandle = request.Res;
const Uint64 iboSerial = request.Serial;
if (!MG_Pipe::MGPipeHandleIsNull(iboHandle)) {
@@ -173,8 +172,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
memo != nullptr && memo->iboHandle == iboHandle && memo->iboSerial == iboSerial;
if (identityHolds && memo->iboCleanEpoch == bufferEpoch) {
// probed fully clean at this epoch; nothing can have dirtied it
} else if (identityHolds &&
IsBufferDrawCleanByHandle(iboHandle, memo->iboResource, nullptr)) {
} else if (identityHolds && IsBufferDrawCleanByHandle(iboHandle, memo->iboResource, nullptr)) {
memo->iboCleanEpoch = bufferEpoch;
} else {
auto* resource = EnsureBufferResourceForHandle(nullptr, iboHandle);
@@ -217,8 +215,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// binder (e.g. Flywheel's indirect vertex shaders pull instance data from storage buffers).
// P5e (sb, §5.6): the record arm, selected by transport + bit 13. Same `if (…) else`
// shape the indirect buffer above already uses.
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassShaderStorage,
GL_SHADER_STORAGE_BUFFER);
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassShaderStorage, GL_SHADER_STORAGE_BUFFER);
}
} // namespace BufferImpl
@@ -306,12 +303,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!resource || resource->id == 0) continue;
xfb.targets.push_back({nullptr, resource->id, static_cast<SizeT>(range.Offset),
static_cast<SizeT>(range.Offset + range.Size), i, range.Res});
BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(i),
resource->id, static_cast<GLintptr>(range.Offset), static_cast<GLsizeiptr>(range.Size));
BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast<GLuint>(i), resource->id,
static_cast<GLintptr>(range.Offset),
static_cast<GLsizeiptr>(range.Size));
continue;
}
// A layout with holes or several interleaved buffers is not expressible on ES:
// capture gap-free into scratch storage and place the records at End instead.
xfb.scattered = false;
@@ -386,9 +383,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"program %u, %zu capture buffer(s), %zu target(s), mode=%s.",
MG_Util::ConvertGLEnumToString(xfb.primitiveMode).c_str(),
static_cast<unsigned>(xfb.primitiveMode),
MG_Util::ConvertGLEnumToString(beginError).c_str(),
PrgramImpl::g_lastUsedBackendProgramId, bufferCount,
xfb.targets.size(),
MG_Util::ConvertGLEnumToString(beginError).c_str(), PrgramImpl::g_lastUsedBackendProgramId,
bufferCount, xfb.targets.size(),
MG_Util::ConvertGLEnumToString(program->GetTransformFeedbackBufferMode()).c_str());
}
xfb.started = true;
@@ -442,8 +438,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 elementsSerial = 0;
const auto& st = MG_Pipe::MGPipeApplier();
elementsHandle = st.BoundVertexElements;
if (!MG_Pipe::MGPipeHandleIsNull(elementsHandle) &&
elementsHandle.Slot < st.VertexElementsCsos.size()) {
if (!MG_Pipe::MGPipeHandleIsNull(elementsHandle) && elementsHandle.Slot < st.VertexElementsCsos.size()) {
const auto& record = st.VertexElementsCsos[elementsHandle.Slot];
if (record.Live && record.Gen == elementsHandle.Gen) {
elementsRecord = &record;
@@ -475,8 +470,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// client re-emits create_vertex_elements on the same handle for it, and the
// applier ++s ContentSerial - so this key opens exactly when the old one did
// and never wraps (Managers.h states the strictly-stronger argument).
if (!memo.valid || !(memo.elementsHandle == elementsHandle) ||
memo.elementsSerial != elementsSerial || activeAttribMask != memo.activeMask) {
if (!memo.valid || !(memo.elementsHandle == elementsHandle) || memo.elementsSerial != elementsSerial ||
activeAttribMask != memo.activeMask) {
Uint32 pending = 0;
for (Uint32 remaining = activeAttribMask; remaining != 0; remaining &= remaining - 1) {
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
@@ -485,8 +480,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (MGPipeApplyCreateVertexElements), so a location past AttributeCount
// reads Enabled = 0, which is the same answer the frontend's cold
// attribute slot gave.
if (location >= MG_Pipe::kMGPipeMaxVertexAttribs ||
!elementsRecord->Attributes[location].Enabled) {
if (location >= MG_Pipe::kMGPipeMaxVertexAttribs || !elementsRecord->Attributes[location].Enabled) {
pending |= (1u << location);
}
}
@@ -516,9 +510,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glVertexAttribI4uiv(location, currentValue.uintValue.data());
break;
case MG_State::GLState::VertexAttribBaseType::Unsupported:
MGLOG_E_ONCE("SyncCurrentVertexAttributeValues: program=%u location=%u has no enabled array and its "
"shader input type 0x%x is not supported as a current generic vertex attribute",
program->GetExternalIndex(), location, program->GetAttribType(location));
MGLOG_E_ONCE(
"SyncCurrentVertexAttributeValues: program=%u location=%u has no enabled array and its "
"shader input type 0x%x is not supported as a current generic vertex attribute",
program->GetExternalIndex(), location, program->GetAttribType(location));
break;
}
}
@@ -591,8 +586,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 elementsSerial = 0;
const auto& st = MG_Pipe::MGPipeApplier();
elementsHandle = st.BoundVertexElements;
if (!MG_Pipe::MGPipeHandleIsNull(elementsHandle) &&
elementsHandle.Slot < st.VertexElementsCsos.size()) {
if (!MG_Pipe::MGPipeHandleIsNull(elementsHandle) && elementsHandle.Slot < st.VertexElementsCsos.size()) {
const auto& elements = st.VertexElementsCsos[elementsHandle.Slot];
if (elements.Live && elements.Gen == elementsHandle.Gen) {
elementsRecord = &elements;
@@ -618,13 +612,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (activeAttribMask == 0) return;
auto& memo = vaoTwin->GetPendingAttribValueMaskMemo();
if (!memo.valid || !(memo.elementsHandle == elementsHandle) ||
memo.elementsSerial != elementsSerial || activeAttribMask != memo.activeMask) {
if (!memo.valid || !(memo.elementsHandle == elementsHandle) || memo.elementsSerial != elementsSerial ||
activeAttribMask != memo.activeMask) {
Uint32 pending = 0;
for (Uint32 remaining = activeAttribMask; remaining != 0; remaining &= remaining - 1) {
const Uint32 location = static_cast<Uint32>(std::countr_zero(remaining));
if (location >= MG_Pipe::kMGPipeMaxVertexAttribs ||
!elementsRecord->Attributes[location].Enabled) {
if (location >= MG_Pipe::kMGPipeMaxVertexAttribs || !elementsRecord->Attributes[location].Enabled) {
pending |= (1u << location);
}
}
@@ -646,8 +639,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// artefacts instance, and here the two vectors arrived over a wire. A short tail
// is an archive whose halves disagree; 0 classifies as Unsupported and says so
// below rather than reading past the end.
const GLenum attribType =
location < link.attribTypes.size() ? link.attribTypes[location] : 0;
const GLenum attribType = location < link.attribTypes.size() ? link.attribTypes[location] : 0;
const auto& currentValue = MG_Pipe::gPipeInputs->GetCurrentVertexAttribute(location);
const auto typeInfo = MG_State::GLState::ClassifyVertexAttribType(attribType);
switch (typeInfo.baseType) {
@@ -665,9 +657,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// names and must not learn them (ProgramArchiveSource::Identity states the
// same rule for the twin's log lines).
MGLOG_E_ONCE("SyncCurrentVertexAttributeValues: ShaderCso {%u, %u} location=%u has no "
"enabled array and its shader input type 0x%x is not supported as a current "
"generic vertex attribute",
cso.Slot, cso.Gen, location, attribType);
"enabled array and its shader input type 0x%x is not supported as a current "
"generic vertex attribute",
cso.Slot, cso.Gen, location, attribType);
break;
}
}
@@ -784,8 +776,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (g_imageSweepValid && g_imageSweepShaderImagesSerial == st.ShaderImagesSerial &&
g_imageSweepTextureShutterSerial == st.TextureShutterSerial &&
g_imageSweepContextSerial == st.ContextSerial &&
g_imageSweepBackendContextGeneration == g_backendContextGeneration &&
ImageSweepContentUnchanged(st)) {
g_imageSweepBackendContextGeneration == g_backendContextGeneration && ImageSweepContentUnchanged(st)) {
return;
}
SyncImageTextureBindings();
@@ -842,10 +833,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (twinEntry && *twinEntry) {
(*twinEntry)->Bind(target);
} else {
MGLOG_E_ONCE(
"No backend FBO found (maybe not synced) for the current %s FBO record, "
"cannot bind FBO.",
(target == FramebufferTarget::Read ? "READ" : "DRAW"));
MGLOG_E_ONCE("No backend FBO found (maybe not synced) for the current %s FBO record, "
"cannot bind FBO.",
(target == FramebufferTarget::Read ? "READ" : "DRAW"));
}
} else {
MGLOG_D("Binding default framebuffer as %s FBO",
@@ -914,13 +904,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// say so or the phase's own "accessor calls per draw" number would keep counting
// reads that no longer happen. Two independent bits, so it is a sum and not a
// three-way pick.
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::AccessorCalls,
(0) + (0));
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::AccessorCalls, (0) + (0));
}
const TextureImpl::DrawTextureSyncKeys textureKeys = TextureImpl::CaptureDrawTextureSyncKeys();
BufferImpl::SyncNeccessaryBuffers(currentVAO, vaoTwin, vaoConfigVersion,
syncBit & DrawSyncBit::IndexBuffer,
BufferImpl::SyncNeccessaryBuffers(currentVAO, vaoTwin, vaoConfigVersion, syncBit & DrawSyncBit::IndexBuffer,
syncBit & DrawSyncBit::IndirectBuffer);
VertexArrayImpl::SyncCurrentVAOFromRecords(vaoTwin);
TextureImpl::SyncNeccessaryTextures(textureKeys);
@@ -962,9 +950,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
VertexArrayImpl::SyncCurrentVertexAttributeValues(vaoTwin, MG_Pipe::MGPipeApplier().DrawProgram);
BindCurrentTextures(textureKeys, currentProgram);
BindCurrentProgramWithResources(currentProgram, textureKeys,
MG_Pipe::MGPipeApplier().DrawProgram
);
BindCurrentProgramWithResources(currentProgram, textureKeys, MG_Pipe::MGPipeApplier().DrawProgram);
// Last: opening the capture span needs the program current and the capture
// buffers bound, and ES rejects most binding changes once it is open.
@@ -1014,15 +1000,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// resolved this unit against - not a second derivation from the texture's
// descriptor, which would be a second authority for one value.
const auto* viewRecord = PipeSamplerViewRecordForHandle(view.View);
const auto target = viewRecord != nullptr
? static_cast<TextureTarget>(viewRecord->View.Target)
: TextureTarget::Unknown;
const auto target = viewRecord != nullptr ? static_cast<TextureTarget>(viewRecord->View.Target)
: TextureTarget::Unknown;
if (viewRecord == nullptr) {
MGLOG_E_ONCE("MGPipe: unit %d's sampler view {%u, %u} has no applier record, "
"so the target to bind texture {%u, %u} at is unknown; the unit "
"keeps what it holds",
static_cast<int>(unit), view.View.Slot, view.View.Gen,
view.Texture.Slot, view.Texture.Gen);
static_cast<int>(unit), view.View.Slot, view.View.Gen, view.Texture.Slot,
view.Texture.Gen);
fullyResolved = false;
} else if (!TextureImpl::IsSupportedTextureTarget(target)) {
MGLOG_D(" Texture target %s is not supported, skipping.",
@@ -1030,8 +1015,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} else if (auto* twin = TextureImpl::ResolveTextureTwin(view.Texture)) {
const GLenum targetGL = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
twin->Bind(targetGL, unit);
boundBackendTargets[static_cast<SizeT>(
TextureImpl::MapToBackendTextureTarget(target))] = true;
boundBackendTargets[static_cast<SizeT>(TextureImpl::MapToBackendTextureTarget(target))] = true;
} else {
// No twin yet: the sync pass that would have built it declined and
// named why. Not memoisable - a later draw would bind it without any
@@ -1046,8 +1030,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT t = 0; t < (SizeT)TextureTarget::TextureTargetCount; ++t) {
const auto target = static_cast<TextureTarget>(t);
if (!TextureImpl::IsSupportedTextureTarget(target)) continue;
const auto backendTargetIndex =
static_cast<SizeT>(TextureImpl::MapToBackendTextureTarget(target));
const auto backendTargetIndex = static_cast<SizeT>(TextureImpl::MapToBackendTextureTarget(target));
if (visitedBackendTargets[backendTargetIndex]) continue;
visitedBackendTargets[backendTargetIndex] = true;
if (boundBackendTargets[backendTargetIndex]) continue;
@@ -1105,8 +1088,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BindCurrentUnitSamplers(const TextureImpl::DrawTextureSyncKeys& keys) {
const Int maxTouchedUnit = keys.maxTouchedUnit;
const SizeT rowBytes =
static_cast<SizeT>(maxTouchedUnit + 1) * sizeof(SamplerImpl::g_boundSamplersCache[0]);
const SizeT rowBytes = static_cast<SizeT>(maxTouchedUnit + 1) * sizeof(SamplerImpl::g_boundSamplersCache[0]);
if (g_unitSamplerWalkValid && g_unitSamplerWalkContextId == keys.contextId &&
g_unitSamplerWalkEpoch == keys.unitBindingsEpoch && g_unitSamplerWalkMaxUnit == maxTouchedUnit &&
g_unitSamplerWalkContextGeneration == g_backendContextGeneration &&
@@ -1216,7 +1198,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P13: no bind_sampler_states window applied, i.e. no unit holds a sampler object -
// the record arm emits one for every unit that does. Unbind, so a sampler object
// left on a unit by an earlier draw is not applied to this one.
for (Int unit = 0; unit <= maxTouchedUnit; ++unit) SamplerImpl::UnbindSampler(unit);
for (Int unit = 0; unit <= maxTouchedUnit; ++unit)
SamplerImpl::UnbindSampler(unit);
}
g_unitSamplerWalkContextId = keys.contextId;
g_unitSamplerWalkEpoch = keys.unitBindingsEpoch;
@@ -1284,8 +1267,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and keys on {DrawProgram, ShaderCso.Serial, BindingsSerial} (ruling ID-86) and never
// looks at this value, but it was still being loaded out of the caller's SharedPtr every
// draw - and once the caller stops holding one there is nothing there to load.
const void* programKey =
nullptr;
const void* programKey = nullptr;
ResolvedTextureBindingMemo* memoSlot = nullptr;
for (auto& candidate : g_resolvedTextureBindingMemos) {
if (candidate.valid && candidate.drawProgram.Slot == drawProgram.Slot &&
@@ -1301,8 +1283,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
memoSlot = &g_resolvedTextureBindingMemos[g_resolvedTextureBindingMemoCursor];
}
auto& memo = *memoSlot;
const SizeT shadowBytes =
static_cast<SizeT>(maxTouchedUnit + 1) * sizeof(TextureImpl::g_boundTexturesCache[0]);
const SizeT shadowBytes = static_cast<SizeT>(maxTouchedUnit + 1) * sizeof(TextureImpl::g_boundTexturesCache[0]);
const Uint64 unitBindingsEpoch = keys.unitBindingsEpoch;
// P5e (ID-124): the legacy key is a LAZY LAMBDA, not an eagerly-computed Bool. It was
// the else-arm of a ternary whose declaration sat inside `#if MOBILEGL_BUILD_DISAGGREGATED`
@@ -1312,27 +1293,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
// arm, so the laziness the ternary gave for free is load-bearing (ID-81 / ID-110 -
// the arm decides, and the other arm's reads must not happen at all).
const auto legacyKeysMatch = [&]() -> Bool {
return (memo.valid && memo.glContextId == keys.contextId &&
memo.maxTouchedUnit == maxTouchedUnit &&
memo.unitBindingsEpoch == unitBindingsEpoch &&
memo.samplingResolutionGeneration == keys.samplingGeneration &&
memo.program == programKey &&
memo.programLifetimeId == (currentProgram ? currentProgram->GetLifetimeId() : 0) &&
memo.programBackendStateVersion ==
(currentProgram ? currentProgram->GetBackendStateVersion() : 0) &&
memo.programLinked == (currentProgram && currentProgram->GetLinkStatus()) &&
memo.contextGeneration == g_backendContextGeneration);
return (memo.valid && memo.glContextId == keys.contextId && memo.maxTouchedUnit == maxTouchedUnit &&
memo.unitBindingsEpoch == unitBindingsEpoch &&
memo.samplingResolutionGeneration == keys.samplingGeneration && memo.program == programKey &&
memo.programLifetimeId == (currentProgram ? currentProgram->GetLifetimeId() : 0) &&
memo.programBackendStateVersion ==
(currentProgram ? currentProgram->GetBackendStateVersion() : 0) &&
memo.programLinked == (currentProgram && currentProgram->GetLinkStatus()) &&
memo.contextGeneration == g_backendContextGeneration);
};
const Bool keysMatch =
(memo.valid && memo.maxTouchedUnit == maxTouchedUnit &&
memo.contextSerial == contextSerial && memo.samplerViewsSerial == samplerViewsSerial &&
memo.drawProgram.Slot == drawProgram.Slot && memo.drawProgram.Gen == drawProgram.Gen &&
memo.shaderCsoSerial == shaderCsoSerial && memo.bindingsSerial == bindingsSerial &&
memo.contextGeneration == g_backendContextGeneration);
(memo.valid && memo.maxTouchedUnit == maxTouchedUnit && memo.contextSerial == contextSerial &&
memo.samplerViewsSerial == samplerViewsSerial && memo.drawProgram.Slot == drawProgram.Slot &&
memo.drawProgram.Gen == drawProgram.Gen && memo.shaderCsoSerial == shaderCsoSerial &&
memo.bindingsSerial == bindingsSerial && memo.contextGeneration == g_backendContextGeneration);
// Short-circuited: the shadow compare is only meaningful once the key (and with it the
// snapshotted row count) matches.
if (!keysMatch || std::memcmp(memo.boundTextures.data(), TextureImpl::g_boundTexturesCache.data(),
shadowBytes) != 0) {
if (!keysMatch ||
std::memcmp(memo.boundTextures.data(), TextureImpl::g_boundTexturesCache.data(), shadowBytes) != 0) {
memo.valid = false;
if (ResolveAndBindUnitTextures(currentProgram, maxTouchedUnit)) {
memo.glContextId = keys.contextId;
@@ -1368,8 +1346,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// overload next. Today that is SanityTest's unit cases, which run under monolith
// transport and therefore take the frontend arm below unchanged.
const SharedPtr<MG_State::GLState::ProgramObject> noFrontendProgram;
BindCurrentTextures(TextureImpl::CaptureDrawTextureSyncKeys(),
noFrontendProgram);
BindCurrentTextures(TextureImpl::CaptureDrawTextureSyncKeys(), noFrontendProgram);
}
// Binds the current program's backend object and re-establishes its per-program
@@ -1394,12 +1371,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (Desc.LinkStatus is ID-88's, Desc.SpirvStatus P4a's).
const MG_Pipe::MGPipeShaderCsoRecord* programRecord = nullptr;
programRecord = PrgramImpl::FindShaderCsoRecord(programCso);
if (programRecord != nullptr &&
(programRecord->Desc.LinkStatus == 0 || programRecord->Desc.SpirvStatus == 0)) {
if (programRecord != nullptr && (programRecord->Desc.LinkStatus == 0 || programRecord->Desc.SpirvStatus == 0)) {
programRecord = nullptr;
}
const Bool programUsable =
programRecord != nullptr;
const Bool programUsable = programRecord != nullptr;
if (programUsable) {
#ifdef TRACY_ENABLE
ZoneScopedNC("BindCurrentProgram", TRACY_ZONECOLOR_BACKEND);
@@ -1409,9 +1384,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// wrote it a few lines ago in the same Prepare; the fallback is the by-handle
// resolver, which is record-first and touches no frontend identity - so unlike
// the frontend fallback below it is not a registry probe at all.
twin = PrgramImpl::g_currentDrawProgramHandle == programCso
? PrgramImpl::g_currentDrawBackendProgram
: nullptr;
twin = PrgramImpl::g_currentDrawProgramHandle == programCso ? PrgramImpl::g_currentDrawBackendProgram
: nullptr;
if (!twin) twin = PrgramImpl::ResolveProgramTwin(programCso);
if (twin) {
auto& backendProgram = *twin;
@@ -1423,8 +1397,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (pg): the block's SIZE is Desc.GlobalUboSize on the handle arm. It is the
// same number GetUBOSize() answers - the emitter takes it from there - but it is
// the record's copy, which is the point.
const Uint globalUboSize =
programRecord->Desc.GlobalUboSize;
const Uint globalUboSize = programRecord->Desc.GlobalUboSize;
if (globalUboSize > 0 && backendProgram.HasGlobalUboBlock()) {
#ifdef TRACY_ENABLE
ZoneScopedNC("UpdateGlobalUBO", TRACY_ZONECOLOR_BACKEND);
@@ -1441,8 +1414,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// block right after this one and they are P4b's (dirty bits 15-17), which
// is why nothing here touches them.
const MG_Pipe::MGPipeShaderCsoRecord* const globalConstants =
PrgramImpl::ResolveGlobalConstantsRecordForHandle(programCso,
globalUboSize);
PrgramImpl::ResolveGlobalConstantsRecordForHandle(programCso, globalUboSize);
// P5e (pg), gap G-C: `MapUBO()` HAS NO RUN-AHEAD ANSWER. It returns
// ProgramObject::globalUboScratch - the live CPU array every glUniform*
// writes into - so under a transport it is a torn read of client memory by
@@ -1469,15 +1441,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
programCso.Slot, programCso.Gen, globalUboSize);
std::abort();
}
const Uint32 uboContentVersion =
globalConstants ? globalConstants->GlobalConstantsVersion :
currentProgram->GetUBOContentVersion();
const Uint32 uboContentVersion = globalConstants ? globalConstants->GlobalConstantsVersion
: currentProgram->GetUBOContentVersion();
const SizeT uboSize = static_cast<SizeT>(globalUboSize);
// Substituted at the two upload sites rather than hoisted into a local:
// MapUBO() is called there today and only there, and hoisting it would
// call it on paths that skip the upload entirely.
#define MGB_UBO_BYTES \
(globalConstants ? static_cast<const void*>(globalConstants->GlobalConstants.data()) \
#define MGB_UBO_BYTES \
(globalConstants ? static_cast<const void*>(globalConstants->GlobalConstants.data()) \
: static_cast<const void*>(currentProgram->MapUBO()))
// Preferred path: write changed contents into a fresh slot of the
// shared persistent-mapped ring and bind it as a range. The GPU
@@ -1492,9 +1463,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& ringSlot = backendProgram.GetGlobalUboRingAllocation();
void* write = nullptr;
Uint ringId = 0;
const auto state = BufferImpl::UboRingPrepareSlot(ringSlot, uboContentVersion,
CurrentFrameSerial(), bindSize, write,
ringId);
const auto state = BufferImpl::UboRingPrepareSlot(
ringSlot, uboContentVersion, CurrentFrameSerial(), bindSize, write, ringId);
if (state == BufferImpl::UboRingSlotState::Allocated) {
std::memcpy(write, MGB_UBO_BYTES, uboSize);
if (MG_Util::PipeStats::Enabled()) {
@@ -1515,18 +1485,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// failed): the original in-place upload.
if (backendProgram.GetLastUploadedGlobalUboVersion() != uboContentVersion) {
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, backendProgram.GetBackendGlobalUBOId());
g_GLESFuncs.glBufferSubData(GL_UNIFORM_BUFFER, 0, globalUboSize,
MGB_UBO_BYTES);
g_GLESFuncs.glBufferSubData(GL_UNIFORM_BUFFER, 0, globalUboSize, MGB_UBO_BYTES);
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(
MG_Util::PipeStats::ByteClass::StageUboGlobal,
static_cast<Uint64>(globalUboSize));
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageUboGlobal,
static_cast<Uint64>(globalUboSize));
}
g_GLESFuncs.glBindBuffer(GL_UNIFORM_BUFFER, 0);
backendProgram.SetLastUploadedGlobalUboVersion(uboContentVersion);
}
BufferImpl::BindBufferBaseCached(GL_UNIFORM_BUFFER, 0,
backendProgram.GetBackendGlobalUBOId());
BufferImpl::BindBufferBaseCached(GL_UNIFORM_BUFFER, 0, backendProgram.GetBackendGlobalUBOId());
}
#undef MGB_UBO_BYTES
}
@@ -1543,8 +1510,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Pre-loop epoch read; the per-resource drawCleanEpoch stamps below
// follow the same stamp-the-pre-read-value rule as the VAO memo
// (Managers.h documents the contract and the mutation-site list).
const Uint64 bufferEpoch =
uboCount > 0 ? BufferImpl::CurrentBufferMutationEpoch() : 0;
const Uint64 bufferEpoch = uboCount > 0 ? BufferImpl::CurrentBufferMutationEpoch() : 0;
Uint lastUBOBinding = 0; // binding 0 is reserved for the global UBO
for (Int i = 0; i < uboCount; ++i) {
++lastUBOBinding;
@@ -1559,8 +1525,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// cannot answer - set_program_bindings' first tail carries it, dense in
// this very index space. The BINDING POINT it names is sb's row (the
// GetBufferBindingPoint below); this line is the boundary S4 R5 named.
auto binding =
PrgramImpl::ProgramBlockBindingFromRecord(*programRecord, i);
auto binding = PrgramImpl::ProgramBlockBindingFromRecord(*programRecord, i);
// P5e (sb, §5.6): THE POINT HALF, by record. `binding` above is the
// PROGRAM's block binding and stays exactly where it is (it is package
// pg's row); what moves is the point it indexes. The index space is the
@@ -1568,13 +1533,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// applier's window directly, and a binding at or above Count means
// "nothing bound", which is what the frontend array's default said too.
const MG_Pipe::MGPipeApplierState& st = MG_Pipe::MGPipeApplier();
const Uint32 windowStart =
st.ShaderBufferStart[MG_Pipe::kMGPipeShaderBufferClassUniform];
const SizeT windowEnd =
static_cast<SizeT>(windowStart) +
st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassUniform];
if (static_cast<SizeT>(binding) < windowStart ||
static_cast<SizeT>(binding) >= windowEnd) {
const Uint32 windowStart = st.ShaderBufferStart[MG_Pipe::kMGPipeShaderBufferClassUniform];
const SizeT windowEnd = static_cast<SizeT>(windowStart) +
st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassUniform];
if (static_cast<SizeT>(binding) < windowStart || static_cast<SizeT>(binding) >= windowEnd) {
continue; // nothing bound there - the frontend arm's `if (bufferObj)`
}
const MG_Pipe::MGPBufferRange& entry =
@@ -1588,12 +1550,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// short-circuit is unchanged; it was never the point read.
auto* backendResource = BufferImpl::FindBufferResourceForHandle(entry.Res);
if (!backendResource || backendResource->drawCleanEpoch != bufferEpoch) {
if (backendResource && BufferImpl::IsBufferDrawCleanByHandle(
entry.Res, backendResource, nullptr)) {
if (backendResource &&
BufferImpl::IsBufferDrawCleanByHandle(entry.Res, backendResource, nullptr)) {
backendResource->drawCleanEpoch = bufferEpoch;
} else {
backendResource =
BufferImpl::EnsureBufferResourceForHandle(nullptr, entry.Res);
backendResource = BufferImpl::EnsureBufferResourceForHandle(nullptr, entry.Res);
}
}
if (backendResource && backendResource->id != 0) {
@@ -1606,14 +1567,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
backendResource->id);
} else {
const SizeT storage = backendResource->storageSize;
const SizeT rangeStart =
std::min<SizeT>(static_cast<SizeT>(entry.Offset), storage);
const SizeT rangeEnd = std::min<SizeT>(
static_cast<SizeT>(entry.Offset + entry.Size), storage);
const SizeT rangeStart = std::min<SizeT>(static_cast<SizeT>(entry.Offset), storage);
const SizeT rangeEnd =
std::min<SizeT>(static_cast<SizeT>(entry.Offset + entry.Size), storage);
BufferImpl::BindBufferRangeCached(
GL_UNIFORM_BUFFER, lastUBOBinding, backendResource->id,
static_cast<GLintptr>(rangeStart),
static_cast<GLsizeiptr>(rangeEnd - rangeStart));
static_cast<GLintptr>(rangeStart), static_cast<GLsizeiptr>(rangeEnd - rangeStart));
}
} else {
MGLOG_E_ONCE("No backend buffer found for UBO binding, cannot bind UBO.");
@@ -1652,14 +1611,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// set_program_bindings means. Truncated to the memo's Uint32 field, which is
// safe for a memo key - it is a change detector, not an ordering - and the
// record's serial would need four billion binding records to wrap.
const Uint32 programBackendStateVersion =
static_cast<Uint32>(programRecord->BindingsSerial);
Bool samplerPassClean =
samplerPassMemo.valid && samplerPassMemo.contextId == keys.contextId &&
samplerPassMemo.unitBindingsEpoch == keys.unitBindingsEpoch &&
samplerPassMemo.samplingGeneration == keys.samplingGeneration &&
samplerPassMemo.backendStateVersion == programBackendStateVersion &&
samplerPassMemo.textureContextGeneration == g_backendContextGeneration;
const Uint32 programBackendStateVersion = static_cast<Uint32>(programRecord->BindingsSerial);
Bool samplerPassClean = samplerPassMemo.valid && samplerPassMemo.contextId == keys.contextId &&
samplerPassMemo.unitBindingsEpoch == keys.unitBindingsEpoch &&
samplerPassMemo.samplingGeneration == keys.samplingGeneration &&
samplerPassMemo.backendStateVersion == programBackendStateVersion &&
samplerPassMemo.textureContextGeneration == g_backendContextGeneration;
if (samplerPassClean) {
for (Uint i = 0; i < samplerPassMemo.count; ++i) {
if (SamplerImpl::g_boundSamplersCache[samplerPassMemo.units[i]] !=
@@ -1679,9 +1636,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (pg): the unit glUniform1i put on this sampler - the second
// of the three post-link mutable fields, carried sparse and ascending
// by location in set_program_bindings' second tail.
const auto unit =
PrgramImpl::ProgramSamplerUnitFromRecord(
*programRecord, samplerBinding.frontendLocation);
const auto unit = PrgramImpl::ProgramSamplerUnitFromRecord(*programRecord,
samplerBinding.frontendLocation);
if (unit == -1) continue;
// Record the touched unit for the memo; a pass touching more
// units than the memo can carry (or an out-of-range unit)
@@ -1874,8 +1830,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// IS STILL `true` - a flattened batch that turns out to need per-sub-draw values is
// unrecoverable, so "not known yet" has to count as "may need them", and a missing
// record is the most not-known-yet a program can be.
const auto* const record =
PrgramImpl::FindShaderCsoRecord(MG_Pipe::MGPipeApplier().DrawProgram);
const auto* const record = PrgramImpl::FindShaderCsoRecord(MG_Pipe::MGPipeApplier().DrawProgram);
if (record == nullptr || program->GetSyncedShaderCsoSerial() != record->Serial) {
return true;
}
+84 -100
View File
@@ -30,8 +30,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// never from a client binding slot. A twin with no noted object was never synced through
// any binding - the DSA-on-a-never-bound-framebuffer case, which the split path already
// refuses for the named clears - and is loud rather than silently unconfigured.
void SyncAndBindFramebufferByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget target,
Bool forceSync = false) {
void SyncAndBindFramebufferByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget target, Bool forceSync = false) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -91,11 +90,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// trio, so the honest thing is to leave it invalid and pay one extra sync. Under a
// transport the legacy arm is never taken anyway (SyncCurrentFBO refuses first), so the
// memo it feeds has no reader.
SyncAndBindFramebufferByHandle(
MG_Pipe::MGPipeApplier().BoundFramebuffer[static_cast<SizeT>(
target == FramebufferTarget::Read ? MG_Pipe::MGPipeFramebufferTarget::Read
: MG_Pipe::MGPipeFramebufferTarget::Draw)],
target);
SyncAndBindFramebufferByHandle(MG_Pipe::MGPipeApplier().BoundFramebuffer[static_cast<SizeT>(
target == FramebufferTarget::Read ? MG_Pipe::MGPipeFramebufferTarget::Read
: MG_Pipe::MGPipeFramebufferTarget::Draw)],
target);
// P4a decline-site F9 / MINOR-3: THE HANDLE ARM'S MEMO IS INVALIDATED HERE, NOT
// STAMPED, and it is done per target for the reason the memo is kept per target at
// all - this entry point syncs ONE binding and must not say anything about the other.
@@ -137,8 +135,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint previous = 0;
g_GLESFuncs.glGetIntegerv(GL_RENDERBUFFER_BINDING, &previous);
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(attachmentName));
g_GLESFuncs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT,
&internalFormat);
g_GLESFuncs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &internalFormat);
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(previous));
} else if (attachmentType == GL_TEXTURE) {
if (!g_GLESFuncs.glGetTexLevelParameteriv) return 0;
@@ -170,8 +167,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static GLsizei s_resolveHeight = 0;
static Uint s_resolveContextGeneration = ~0u;
if (!g_GLESFuncs.glGenFramebuffers || !g_GLESFuncs.glGenRenderbuffers ||
!g_GLESFuncs.glRenderbufferStorage || !g_GLESFuncs.glFramebufferRenderbuffer) {
if (!g_GLESFuncs.glGenFramebuffers || !g_GLESFuncs.glGenRenderbuffers || !g_GLESFuncs.glRenderbufferStorage ||
!g_GLESFuncs.glFramebufferRenderbuffer) {
return false;
}
const GLenum sourceFormat = QueryReadColorAttachmentInternalFormat();
@@ -292,26 +289,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
" vUv = p * uUvTransform.xy + uUvTransform.zw;\n"
"}\n";
static const char* const kDepthFragmentSource =
"#version 300 es\n"
"precision highp float;\n"
"precision highp sampler2D;\n"
"uniform sampler2D uSource;\n"
"in vec2 vUv;\n"
"void main() {\n"
" gl_FragDepth = texture(uSource, vUv).r;\n"
"}\n";
static const char* const kDepthFragmentSource = "#version 300 es\n"
"precision highp float;\n"
"precision highp sampler2D;\n"
"uniform sampler2D uSource;\n"
"in vec2 vUv;\n"
"void main() {\n"
" gl_FragDepth = texture(uSource, vUv).r;\n"
"}\n";
static const char* const kStencilFragmentSource =
"#version 300 es\n"
"precision highp float;\n"
"precision highp usampler2D;\n"
"uniform usampler2D uSource;\n"
"uniform uint uBit;\n"
"in vec2 vUv;\n"
"void main() {\n"
" if ((texture(uSource, vUv).r & uBit) == 0u) discard;\n"
"}\n";
static const char* const kStencilFragmentSource = "#version 300 es\n"
"precision highp float;\n"
"precision highp usampler2D;\n"
"uniform usampler2D uSource;\n"
"uniform uint uBit;\n"
"in vec2 vUv;\n"
"void main() {\n"
" if ((texture(uSource, vUv).r & uBit) == 0u) discard;\n"
"}\n";
GLuint BuildProgram(const char* fragmentSource) {
const GLuint vertexShader = g_GLESFuncs.glCreateShader(GL_VERTEX_SHADER);
@@ -424,8 +419,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, static_cast<GLuint>(objectName));
const Bool bound = g_GLESFuncs.glGetError() == GL_NO_ERROR;
if (bound) {
g_GLESFuncs.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
&internalFormat);
g_GLESFuncs.glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
internalFormat = 0;
}
@@ -482,9 +476,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Sampling the stencil half of a packed texture goes through
// GL_DEPTH_STENCIL_TEXTURE_MODE, which is ES 3.1 state; on an older driver the pname
// would just raise GL_INVALID_ENUM and the shader would read depth bits as stencil.
const Bool supportsStencilTextureMode = g_GLESCapabilities.GLESVersion.Major > 3 ||
(g_GLESCapabilities.GLESVersion.Major == 3 &&
g_GLESCapabilities.GLESVersion.Minor >= 1);
const Bool supportsStencilTextureMode =
g_GLESCapabilities.GLESVersion.Major > 3 ||
(g_GLESCapabilities.GLESVersion.Major == 3 && g_GLESCapabilities.GLESVersion.Minor >= 1);
if (wantStencil && !supportsStencilTextureMode) {
return false;
}
@@ -582,8 +576,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_STENCIL_TEXTURE_MODE, GL_DEPTH_COMPONENT);
}
g_GLESFuncs.glUseProgram(s_depthProgram);
g_GLESFuncs.glUniform4f(s_depthUvTransform, uvTransform[0], uvTransform[1], uvTransform[2],
uvTransform[3]);
g_GLESFuncs.glUniform4f(s_depthUvTransform, uvTransform[0], uvTransform[1], uvTransform[2], uvTransform[3]);
g_GLESFuncs.glEnable(GL_DEPTH_TEST);
g_GLESFuncs.glDepthFunc(GL_ALWAYS);
g_GLESFuncs.glDepthMask(GL_TRUE);
@@ -698,8 +691,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint drawBuffer = GL_NONE;
g_GLESFuncs.glGetIntegerv(GL_DRAW_BUFFER0 + i, &drawBuffer);
if (drawBuffer < GL_COLOR_ATTACHMENT0 || drawBuffer > GL_COLOR_ATTACHMENT31) continue;
if (IsSameDriverImage(source, QueryDriverAttachmentImage(GL_DRAW_FRAMEBUFFER,
static_cast<GLenum>(drawBuffer)))) {
if (IsSameDriverImage(
source, QueryDriverAttachmentImage(GL_DRAW_FRAMEBUFFER, static_cast<GLenum>(drawBuffer)))) {
shared |= GL_COLOR_BUFFER_BIT;
break;
}
@@ -733,8 +726,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint previous = 0;
g_GLESFuncs.glGetIntegerv(GL_RENDERBUFFER_BINDING, &previous);
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(image.Name));
g_GLESFuncs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT,
&internalFormat);
g_GLESFuncs.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &internalFormat);
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(previous));
} else {
if (!g_GLESFuncs.glGetTexLevelParameteriv) return 0;
@@ -767,11 +759,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
// A texture specified with an unsized format reports it back; renderbuffer storage
// takes only sized ones.
switch (internalFormat) {
case GL_RGBA: return GL_RGBA8;
case GL_RGB: return GL_RGB8;
case GL_DEPTH_STENCIL: return GL_DEPTH24_STENCIL8;
case GL_DEPTH_COMPONENT: return GL_DEPTH_COMPONENT24;
default: return static_cast<GLenum>(internalFormat);
case GL_RGBA:
return GL_RGBA8;
case GL_RGB:
return GL_RGB8;
case GL_DEPTH_STENCIL:
return GL_DEPTH24_STENCIL8;
case GL_DEPTH_COMPONENT:
return GL_DEPTH_COMPONENT24;
default:
return static_cast<GLenum>(internalFormat);
}
}
@@ -793,8 +790,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static GLsizei s_depthStencilWidth = 0, s_depthStencilHeight = 0;
static Uint s_scratchContextGeneration = ~0u;
if (!g_GLESFuncs.glGenFramebuffers || !g_GLESFuncs.glGenRenderbuffers ||
!g_GLESFuncs.glRenderbufferStorage || !g_GLESFuncs.glFramebufferRenderbuffer) {
if (!g_GLESFuncs.glGenFramebuffers || !g_GLESFuncs.glGenRenderbuffers || !g_GLESFuncs.glRenderbufferStorage ||
!g_GLESFuncs.glFramebufferRenderbuffer) {
return false;
}
const GLint left = std::min(srcX0, srcX1);
@@ -818,9 +815,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLenum depthStencilFormat = 0;
GLenum depthStencilAttachment = GL_NONE;
if ((mask & (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)) != 0) {
const GLenum depthFormat = (mask & GL_DEPTH_BUFFER_BIT) != 0
? QueryDriverReadAttachmentInternalFormat(GL_DEPTH_ATTACHMENT)
: 0;
const GLenum depthFormat =
(mask & GL_DEPTH_BUFFER_BIT) != 0 ? QueryDriverReadAttachmentInternalFormat(GL_DEPTH_ATTACHMENT) : 0;
const GLenum stencilFormat = (mask & GL_STENCIL_BUFFER_BIT) != 0
? QueryDriverReadAttachmentInternalFormat(GL_STENCIL_ATTACHMENT)
: 0;
@@ -869,12 +865,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
};
if (colorFormat != 0) {
ensureStorage(s_colorRenderbuffer, s_colorFormat, s_colorWidth, s_colorHeight, colorFormat, width,
height);
ensureStorage(s_colorRenderbuffer, s_colorFormat, s_colorWidth, s_colorHeight, colorFormat, width, height);
}
if (depthStencilFormat != 0) {
ensureStorage(s_depthStencilRenderbuffer, s_depthStencilFormat, s_depthStencilWidth,
s_depthStencilHeight, depthStencilFormat, width, height);
ensureStorage(s_depthStencilRenderbuffer, s_depthStencilFormat, s_depthStencilWidth, s_depthStencilHeight,
depthStencilFormat, width, height);
}
g_GLESFuncs.glBindRenderbuffer(GL_RENDERBUFFER, static_cast<GLuint>(previousRenderbuffer));
@@ -896,8 +891,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const GLbitfield stagedMask =
(colorFormat != 0 ? static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT) : 0) |
(depthStencilFormat != 0 ? mask & static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT)
: 0);
(depthStencilFormat != 0 ? mask & static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) : 0);
Bool copied = g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
if (copied) {
// The staging copy is not the application's blit: its scissor box would clip the
@@ -973,7 +967,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// outright, desktop GL replicates the source sample into every destination one.
if (ReplicateBlitIntoMultisampleDraw(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask)) {
if ((mask & GL_COLOR_BUFFER_BIT) != 0) {
MGLOG_E_ONCE("BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated");
MGLOG_E_ONCE(
"BlitFramebuffer: colour replicate into a multisample draw framebuffer is not emulated");
}
}
return;
@@ -995,9 +990,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
DrainBlitErrors();
g_GLESFuncs.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, colourBit, filter);
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
const Bool emulated =
multisampleResolve &&
ResolveThenBlit(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, filter);
const Bool emulated = multisampleResolve &&
ResolveThenBlit(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, filter);
if (!emulated) {
MGLOG_E_ONCE("BlitFramebuffer: the colour aspect was dropped - the driver rejected it on its "
"own and no emulation applies");
@@ -1051,10 +1045,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& color = record.Color[i];
if (color.Res == record.ReadSurface.Res && color.Kind == record.ReadSurface.Kind &&
color.Layered == record.ReadSurface.Layered && color.Level == record.ReadSurface.Level &&
color.Layer == record.ReadSurface.Layer &&
color.UploadTarget == record.ReadSurface.UploadTarget) {
return static_cast<FramebufferAttachmentType>(
static_cast<Int>(FramebufferAttachmentType::Color0) + static_cast<Int>(i));
color.Layer == record.ReadSurface.Layer && color.UploadTarget == record.ReadSurface.UploadTarget) {
return static_cast<FramebufferAttachmentType>(static_cast<Int>(FramebufferAttachmentType::Color0) +
static_cast<Int>(i));
}
}
return FramebufferAttachmentType::None;
@@ -1062,8 +1055,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static GLbitfield BlitLayeredDestinationAspects(const MG_Pipe::MGPFramebufferState& readRecord,
const MG_Pipe::MGPFramebufferState& drawRecord, GLint srcX0,
GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0,
GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask) {
GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask) {
if (mask == 0) return 0;
if (!g_GLESFuncs.glCopyImageSubData) return 0;
if (!MG_Util::SelfTest::BlitIgnoresDestinationArrayLayer(g_GLESFuncs)) return 0;
@@ -1109,8 +1102,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if ((mask & plan.bit) == 0) continue;
if (plan.source == FramebufferAttachmentType::Unknown ||
plan.destination == FramebufferAttachmentType::Unknown ||
plan.source == FramebufferAttachmentType::None ||
plan.destination == FramebufferAttachmentType::None) {
plan.source == FramebufferAttachmentType::None || plan.destination == FramebufferAttachmentType::None) {
continue;
}
const MG_Pipe::MGPSurface* const sourceSurface =
@@ -1141,8 +1133,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Copying an image region onto itself is undefined for glCopyImageSubData, and a blit
// whose source and destination overlap is undefined for GL too - so this is not a
// shape to substitute FOR, it is one to leave exactly as the application wrote it.
if (sourceSurface->Res == destinationSurface->Res &&
sourceSurface->Level == destinationSurface->Level &&
if (sourceSurface->Res == destinationSurface->Res && sourceSurface->Level == destinationSurface->Level &&
sourceSurface->Layer == destinationSurface->Layer) {
continue;
}
@@ -1158,25 +1149,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (hasStencil && (mask & GL_STENCIL_BUFFER_BIT) == 0) continue;
// ... and having carried both, it must be credited with both, or the caller hands the
// stencil half to the driver and it lands on layer 0 after all.
const GLbitfield aspectBits =
hasDepth || hasStencil
? static_cast<GLbitfield>((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) |
(hasStencil ? GL_STENCIL_BUFFER_BIT : 0))
: static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT);
const GLbitfield aspectBits = hasDepth || hasStencil
? static_cast<GLbitfield>((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) |
(hasStencil ? GL_STENCIL_BUFFER_BIT : 0))
: static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT);
if ((handled & aspectBits) == aspectBits) continue;
if (!oneToOne || scissorEnabled || (plan.bit == GL_COLOR_BUFFER_BIT && enabledDrawBuffers != 1)) {
MGLOG_E_ONCE("BlitFramebuffer: this driver ignores a non-zero destination array layer and this "
"blit cannot be expressed as a copy (%s), so it will land on layer 0",
!oneToOne ? "it scales or flips"
: scissorEnabled ? "the scissor test is enabled"
: "the destination has more than one draw buffer");
!oneToOne ? "it scales or flips"
: scissorEnabled ? "the scissor test is enabled"
: "the destination has more than one draw buffer");
continue;
}
auto& backendSource =
TextureImpl::SyncTextureToBackendByHandle(sourceSurface->Res,
/*imageBindableStorageRequired=*/false);
auto& backendSource = TextureImpl::SyncTextureToBackendByHandle(sourceSurface->Res,
/*imageBindableStorageRequired=*/false);
if (!backendSource) continue;
const GLuint sourceName = backendSource->GetBackendTextureId();
// BY VALUE past this point, for the reason the copy-image endpoint builder gives:
@@ -1193,11 +1182,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<TextureTarget>(destinationSurface->TextureTarget));
ClearGLErrors();
g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, static_cast<GLint>(sourceSurface->Level),
srcX0, srcY0, static_cast<GLint>(sourceSurface->Layer),
destinationName, destinationTarget,
static_cast<GLint>(destinationSurface->Level), dstX0, dstY0,
static_cast<GLint>(destinationSurface->Layer), width, height, 1);
g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, static_cast<GLint>(sourceSurface->Level), srcX0,
srcY0, static_cast<GLint>(sourceSurface->Layer), destinationName,
destinationTarget, static_cast<GLint>(destinationSurface->Level), dstX0,
dstY0, static_cast<GLint>(destinationSurface->Layer), width, height, 1);
if (const GLenum error = g_GLESFuncs.glGetError(); error != GL_NO_ERROR) {
// The driver blit still runs for this aspect - onto the wrong layer, but the
// substitute has to leave the call no worse off than it found it.
@@ -1247,9 +1235,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
SyncAndBindFramebufferByHandle(drawFbo, FramebufferTarget::Draw, /*forceSync=*/true);
MGLOG_D("ES BlitNamedFramebuffer({%u,%u} -> {%u,%u}, %d, %d, %d, %d, %d, %d, %d, %d, 0x%x, %s)",
readFbo.Slot, readFbo.Gen, drawFbo.Slot, drawFbo.Gen, srcX0, srcY0, srcX1,
srcY1, dstX0, dstY0, dstX1, dstY1, mask,
MG_Util::ConvertGLEnumToString(filter).c_str());
readFbo.Slot, readFbo.Gen, drawFbo.Slot, drawFbo.Gen, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
dstX1, dstY1, mask, MG_Util::ConvertGLEnumToString(filter).c_str());
// P5e (fb, §5.4): only the probed defect makes this do anything, and it now
// asks the two RECORDS. The state-note detour this used to take - the twin
// table handing back the frontend object it was synced from, plus
@@ -1260,12 +1247,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto* readRecord = BlitEndpointRecord(readFbo, FramebufferTarget::Read);
const auto* drawRecord = BlitEndpointRecord(drawFbo, FramebufferTarget::Draw);
if (readRecord != nullptr && drawRecord != nullptr) {
mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1,
srcY1, dstX0, dstY0, dstX1, dstY1, mask);
mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1, srcY1, dstX0,
dstY0, dstX1, dstY1, mask);
}
if (mask != 0) {
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1,
mask, filter);
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
}
// The driver's bindings are now the two NAMED framebuffers; put the bound ones
// back exactly as the monolith named entry point does.
@@ -1305,13 +1291,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// case). SyncCurrentFBO ran a few lines up and refuses if either is missing, so a null
// here means the blit is against the default framebuffer's record, which the overload
// declines on IsDefault.
const auto* readRecord =
BlitEndpointRecord(MG_Pipe::kMGPipeNullHandle, FramebufferTarget::Read);
const auto* drawRecord =
BlitEndpointRecord(MG_Pipe::kMGPipeNullHandle, FramebufferTarget::Draw);
const auto* readRecord = BlitEndpointRecord(MG_Pipe::kMGPipeNullHandle, FramebufferTarget::Read);
const auto* drawRecord = BlitEndpointRecord(MG_Pipe::kMGPipeNullHandle, FramebufferTarget::Draw);
if (readRecord != nullptr && drawRecord != nullptr) {
mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1,
srcY1, dstX0, dstY0, dstX1, dstY1, mask);
mask &= ~BlitLayeredDestinationAspects(*readRecord, *drawRecord, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0,
dstX1, dstY1, mask);
}
if (mask != 0) {
IssueBlitWithResolveFallback(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
@@ -12,8 +12,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace BufferImpl {
// See Managers.h for why these two are named functions over the applier state and
// nothing else.
Uint ResolveDrawVertexBuffersFromRecord(const MG_Pipe::MGPipeApplierState& st,
DrawVertexBufferRequest* out, Uint capacity) {
Uint ResolveDrawVertexBuffersFromRecord(const MG_Pipe::MGPipeApplierState& st, DrawVertexBufferRequest* out,
Uint capacity) {
Uint count = 0;
const Uint32 begin = st.VertexBufferStart;
const Uint32 end = begin + st.VertexBufferCount;
@@ -33,7 +33,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (alreadySeen) continue;
if (count >= capacity) {
MGLOG_E_ONCE("MGPipe: the vertex-buffer window named more distinct buffers than "
"a draw can hold (%u); the tail is dropped", capacity);
"a draw can hold (%u); the tail is dropped",
capacity);
break;
}
out[count].Res = binding.Res;
@@ -97,8 +98,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
end = std::min(end, static_cast<SizeT>(g_GLESCapabilities.MaxUniformBufferBindings));
}
for (SizeT i = start; i < end; ++i) {
const MG_Pipe::MGPBufferRange& entry =
st.BoundShaderBuffers[shaderBufferClass][i];
const MG_Pipe::MGPBufferRange& entry = st.BoundShaderBuffers[shaderBufferClass][i];
if (MG_Pipe::MGPipeHandleIsNull(entry.Res)) {
BindBufferBaseCached(glTarget, static_cast<GLuint>(i), 0);
continue;
@@ -106,8 +106,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto* backendResource = EnsureBufferResourceForHandle(nullptr, entry.Res);
if (!backendResource || backendResource->id == 0) {
MGLOG_E_ONCE("No backend buffer found for %s binding point %zu (handle {%u, %u}).",
MG_Util::ConvertGLEnumToString(glTarget).c_str(), i, entry.Res.Slot,
entry.Res.Gen);
MG_Util::ConvertGLEnumToString(glTarget).c_str(), i, entry.Res.Slot, entry.Res.Gen);
continue;
}
// P8-C: the SERVER's half of the mark MarkShaderStorageBuffersGpuWritten no longer
@@ -129,8 +128,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// made legally against a buffer that has since shrunk.
const SizeT storage = backendResource->storageSize;
const SizeT rangeStart = std::min<SizeT>(static_cast<SizeT>(entry.Offset), storage);
const SizeT rangeEnd =
std::min<SizeT>(static_cast<SizeT>(entry.Offset + entry.Size), storage);
const SizeT rangeEnd = std::min<SizeT>(static_cast<SizeT>(entry.Offset + entry.Size), storage);
BindBufferRangeCached(glTarget, static_cast<GLuint>(i), backendBufferId,
static_cast<GLintptr>(rangeStart),
static_cast<GLsizeiptr>(rangeEnd - rangeStart));
@@ -149,8 +147,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the row this site was the Espryt reader of.
const MG_Pipe::MGPipeApplierState& st = MG_Pipe::MGPipeApplier();
const Uint32 windowStart = st.ShaderBufferStart[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter];
const SizeT windowEnd = static_cast<SizeT>(windowStart) +
st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter];
const SizeT windowEnd =
static_cast<SizeT>(windowStart) + st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter];
for (const Int glBinding : glBindings) {
if (glBinding < 0) continue;
const Int esslBinding = esslBindingTop - glBinding;
@@ -163,8 +161,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// which is the hole the frontend walk's `continue` never had because its
// bound was the whole 84-point array.
const MG_Pipe::MGPBufferRange* entry = nullptr;
if (static_cast<SizeT>(glBinding) >= windowStart &&
static_cast<SizeT>(glBinding) < windowEnd) {
if (static_cast<SizeT>(glBinding) >= windowStart && static_cast<SizeT>(glBinding) < windowEnd) {
entry = &st.BoundShaderBuffers[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter]
[static_cast<SizeT>(glBinding)];
}
@@ -180,15 +177,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
if (entry->Offset == 0 && entry->Size == MG_Pipe::kMGPipeWholeBuffer) {
BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<Uint>(esslBinding),
backendResource->id);
BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<Uint>(esslBinding), backendResource->id);
} else {
const SizeT storage = backendResource->storageSize;
const SizeT rangeStart = std::min<SizeT>(static_cast<SizeT>(entry->Offset), storage);
const SizeT rangeEnd =
std::min<SizeT>(static_cast<SizeT>(entry->Offset + entry->Size), storage);
BindBufferRangeCached(GL_SHADER_STORAGE_BUFFER, static_cast<Uint>(esslBinding),
backendResource->id, static_cast<GLintptr>(rangeStart),
const SizeT rangeEnd = std::min<SizeT>(static_cast<SizeT>(entry->Offset + entry->Size), storage);
BindBufferRangeCached(GL_SHADER_STORAGE_BUFFER, static_cast<Uint>(esslBinding), backendResource->id,
static_cast<GLintptr>(rangeStart),
static_cast<GLsizeiptr>(rangeEnd - rangeStart));
}
// AND NO MarkBufferGpuWritten HERE. It was the second of the three backend
@@ -239,19 +234,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} // namespace
XfbObjectState& CurrentXfb() {
auto& state = ActiveXfbState();
const Uint64 lifetime = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId;
if (state.Current != lifetime || state.Cached == nullptr || state.NeedsBind)
BindTransformFeedback(0); // identity is the applier's lifetime, not this argument
auto& g_currentXfbState = state.Cached;
auto& g_xfbObjects = state.Objects;
const auto g_currentXfbName = state.Current;
if (g_currentXfbState == nullptr) {
g_currentXfbState = &g_xfbObjects[g_currentXfbName];
}
return *g_currentXfbState;
XfbObjectState& CurrentXfb() {
auto& state = ActiveXfbState();
const Uint64 lifetime = MG_Pipe::MGPipeApplier().BoundStreamOutputLifetimeId;
if (state.Current != lifetime || state.Cached == nullptr || state.NeedsBind)
BindTransformFeedback(0); // identity is the applier's lifetime, not this argument
auto& g_currentXfbState = state.Cached;
auto& g_xfbObjects = state.Objects;
const auto g_currentXfbName = state.Current;
if (g_currentXfbState == nullptr) {
g_currentXfbState = &g_xfbObjects[g_currentXfbName];
}
return *g_currentXfbState;
}
namespace {
Bool AreTransformFeedbackObjectsSupported() {
@@ -338,9 +333,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT bytes = std::min(step, size - off);
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(
res, target.start + off,
MG_Pipe::MGPBlobRef{
reinterpret_cast<Uint64>(static_cast<Uint8*>(mapped) + off),
static_cast<Uint64>(bytes), MG_Pipe::kMGHostSpanSegNone, 0});
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(static_cast<Uint8*>(mapped) + off),
static_cast<Uint64>(bytes), MG_Pipe::kMGHostSpanSegNone, 0});
}
} else {
MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the "
@@ -391,9 +385,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId);
const void* packed = g_GLESFuncs.glMapBufferRange(BufferImpl::TempBufferTarget, 0,
static_cast<GLsizeiptr>(packedStride * vertices),
GL_MAP_READ_BIT);
const void* packed = g_GLESFuncs.glMapBufferRange(
BufferImpl::TempBufferTarget, 0, static_cast<GLsizeiptr>(packedStride * vertices), GL_MAP_READ_BIT);
if (packed == nullptr) {
MGLOG_E_ONCE("EndTransformFeedback: failed to map the scatter capture buffer");
return;
@@ -432,8 +425,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
// P8-C: made current first - a store a shader wrote here is refreshed.
const Uint8* hostBytes = BufferImpl::SplitHostBytesForCpuRead(*splitResource, splitRes,
"xfb_scatter_pre_capture");
const Uint8* hostBytes =
BufferImpl::SplitHostBytesForCpuRead(*splitResource, splitRes, "xfb_scatter_pre_capture");
// A NULL SHADOW IS NOT A LOST CAPTURE. It is the ORPHANED store
// (glBufferData(size, NULL) with no resource_subdata since), which
// M-3 declares legal: every byte the application has not staged is
@@ -453,8 +446,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// it is silent data loss (Fatal by name); for an orphaned one the
// gap is its own undefined content and the read is legal.
if (BufferImpl::ResourceContentIsDeclared(splitRes)) {
BufferImpl::RequireStagedCoverage(*splitResource, hostBytes, target.start,
target.end, "xfb_scatter_pre_capture");
BufferImpl::RequireStagedCoverage(*splitResource, hostBytes, target.start, target.end,
"xfb_scatter_pre_capture");
}
Memcpy(staged.data(), hostBytes + target.start, rangeBytes);
}
@@ -485,8 +478,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(
splitRes, target.start + off,
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(staged.data() + off),
static_cast<Uint64>(bytes),
MG_Pipe::kMGHostSpanSegNone, 0});
static_cast<Uint64>(bytes), MG_Pipe::kMGHostSpanSegNone, 0});
}
} else {
MGLOG_E_ONCE("EndTransformFeedback: no reverse channel is installed, so the "
@@ -504,8 +496,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
BufferImpl::BumpBufferMutationEpoch();
if (g_GLESFuncs.glBufferSubData != nullptr) {
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, target.backendId);
g_GLESFuncs.glBufferSubData(BufferImpl::TempBufferTarget,
static_cast<GLintptr>(target.start),
g_GLESFuncs.glBufferSubData(BufferImpl::TempBufferTarget, static_cast<GLintptr>(target.start),
static_cast<GLsizeiptr>(rangeBytes), staged.data());
}
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_scatterBufferId);
@@ -516,8 +507,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace
Bool AreTransformFeedbacksSupported() {
return g_GLESFuncs.glBeginTransformFeedback != nullptr &&
g_GLESFuncs.glEndTransformFeedback != nullptr &&
return g_GLESFuncs.glBeginTransformFeedback != nullptr && g_GLESFuncs.glEndTransformFeedback != nullptr &&
g_GLESFuncs.glTransformFeedbackVaryings != nullptr;
}
@@ -598,8 +588,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!AreTransformFeedbackObjectsSupported()) {
// Without driver objects there is only the default span; keep the frontend
// name so the bookkeeping below stays consistent.
g_currentXfbName =
key;
g_currentXfbName = key;
return;
}
auto& xfb = g_xfbObjects[key];
@@ -608,8 +597,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glGenTransformFeedbacks(1, &xfb.esId);
}
g_GLESFuncs.glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb.esId);
g_currentXfbName =
key;
g_currentXfbName = key;
}
void DeleteTransformFeedback(GLuint name) {
@@ -629,9 +617,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_currentXfbState = nullptr; // erase shifts the probe cluster, moving other entries
g_xfbObjects.erase(it);
// The frontend reverts to the default object when the bound one is deleted.
if (g_currentXfbName ==
key
) {
if (g_currentXfbName == key) {
// The following BindStreamOutput names this context's actual
// default lifetime. Never create a native object for key 0.
g_currentXfbName = 0;
@@ -644,7 +630,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The ES context went away (or is being torn down): the spans, their buffer ids, the
// driver objects and the frontend objects they pinned all belonged to it.
void OnBackendContextDestroyed() {
for (auto& state : g_roleXfbState) state = {};
for (auto& state : g_roleXfbState)
state = {};
g_currentXfbState = nullptr;
g_xfbObjects.clear();
g_currentXfbName = 0;
@@ -157,13 +157,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace {
std::atomic<Uint64> g_perNativeContextEpoch{1};
} // namespace
Uint64 PerNativeContextEpoch() {
return g_perNativeContextEpoch.load(std::memory_order_acquire);
}
void BumpPerNativeContextEpoch() {
g_perNativeContextEpoch.fetch_add(1, std::memory_order_acq_rel);
}
Uint64 PerNativeContextEpoch() { return g_perNativeContextEpoch.load(std::memory_order_acquire); }
void BumpPerNativeContextEpoch() { g_perNativeContextEpoch.fetch_add(1, std::memory_order_acq_rel); }
namespace {
std::atomic<Uint32> g_perNativeContextCacheSlots{0};
}
} // namespace
PerNativeContextBase::PerNativeContextBase()
: m_cacheSlot(g_perNativeContextCacheSlots.fetch_add(1, std::memory_order_relaxed) %
@@ -182,7 +186,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void ForgetNativeContextObjects(Uint64 serial) {
if (serial == 0) return;
const std::lock_guard<std::mutex> lock(PerNativeContextRegistryMutex());
for (PerNativeContextBase* holder : PerNativeContextRegistry()) holder->ForgetContext(serial);
for (PerNativeContextBase* holder : PerNativeContextRegistry())
holder->ForgetContext(serial);
}
// The tuple the calling thread works on, created on first use. Lazy creation is what keeps the
@@ -316,8 +321,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
EGLint width = 0;
EGLint height = 0;
if (!g_EGLFuncs.eglQuerySurface(g_Display, surface, EGL_WIDTH, &width) ||
!g_EGLFuncs.eglQuerySurface(g_Display, surface, EGL_HEIGHT, &height) ||
width <= 0 || height <= 0) {
!g_EGLFuncs.eglQuerySurface(g_Display, surface, EGL_HEIGHT, &height) || width <= 0 || height <= 0) {
return false;
}
@@ -376,9 +380,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depthBits);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL,
GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencilBits);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH,
GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE,
&depthComponentType);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(
GL_DRAW_FRAMEBUFFER, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &depthComponentType);
ClearGLErrors();
if (previousDrawFramebuffer != 0) {
@@ -428,9 +431,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
: TextureInternalFormat::Depth24Stencil8;
stencilFormat = depthFormat;
} else if (depthBits > 0) {
depthFormat = floatDepth ? TextureInternalFormat::DepthComponent32F
: (depthBits <= 16) ? TextureInternalFormat::DepthComponent16
: TextureInternalFormat::DepthComponent24;
depthFormat = floatDepth ? TextureInternalFormat::DepthComponent32F
: (depthBits <= 16) ? TextureInternalFormat::DepthComponent16
: TextureInternalFormat::DepthComponent24;
stencilFormat = depthFormat;
} else {
depthFormat = TextureInternalFormat::StencilIndex8;
@@ -472,8 +475,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
#endif
MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged(&info);
} else
{
} else {
if (depthTexture) depthTexture->SetInternalFormat(depthFormat);
if (stencilTexture) stencilTexture->SetInternalFormat(stencilFormat);
}
@@ -558,8 +560,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
using XCloseDisplayFn = int (*)(Display*);
auto* xOpenDisplay = reinterpret_cast<XOpenDisplayFn>(dlsym(x11Lib, "XOpenDisplay"));
auto* xGetWindowAttributes =
reinterpret_cast<XGetWindowAttributesFn>(dlsym(x11Lib, "XGetWindowAttributes"));
auto* xGetWindowAttributes = reinterpret_cast<XGetWindowAttributesFn>(dlsym(x11Lib, "XGetWindowAttributes"));
auto* xVisualIDFromVisual = reinterpret_cast<XVisualIDFromVisualFn>(dlsym(x11Lib, "XVisualIDFromVisual"));
auto* xCloseDisplay = reinterpret_cast<XCloseDisplayFn>(dlsym(x11Lib, "XCloseDisplay"));
if (!xOpenDisplay || !xGetWindowAttributes || !xVisualIDFromVisual || !xCloseDisplay) {
@@ -629,19 +630,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
// matches an alpha-0 config; keep those as a second candidate tier
// for the visual match or eglCreateWindowSurface hits BAD_CONFIG.
Vector<EGLConfig> alphaFreeConfigs;
const EGLint alphaFreeAttribs[] = {EGL_SURFACE_TYPE, surfaceBit, EGL_RENDERABLE_TYPE,
const EGLint alphaFreeAttribs[] = {EGL_SURFACE_TYPE,
surfaceBit,
EGL_RENDERABLE_TYPE,
EGL_OPENGL_ES3_BIT,
EGL_RED_SIZE, 8, EGL_GREEN_SIZE,
EGL_RED_SIZE,
8,
EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_DEPTH_SIZE,
24,
EGL_STENCIL_SIZE, 8, EGL_NONE};
EGL_STENCIL_SIZE,
8,
EGL_NONE};
EGLint numAlphaFree = 0;
if (g_EGLFuncs.eglChooseConfig(g_Display, alphaFreeAttribs, nullptr, 0, &numAlphaFree) &&
numAlphaFree > 0) {
alphaFreeConfigs.resize(static_cast<SizeT>(numAlphaFree));
if (!g_EGLFuncs.eglChooseConfig(g_Display, alphaFreeAttribs, alphaFreeConfigs.data(),
numAlphaFree, &numAlphaFree)) {
if (!g_EGLFuncs.eglChooseConfig(g_Display, alphaFreeAttribs, alphaFreeConfigs.data(), numAlphaFree,
&numAlphaFree)) {
numAlphaFree = 0;
}
alphaFreeConfigs.resize(static_cast<SizeT>(numAlphaFree));
@@ -713,13 +722,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
// EGL_EXT_create_context_robustness (eglext.h is not part of this file's include set).
constexpr EGLint kEglResetNotificationStrategyExt = 0x3138;
constexpr EGLint kEglLoseContextOnResetExt = 0x31BF;
enum class ResetNotification : Uint8 { Undecided, On, Off };
enum class ResetNotification : Uint8 {
Undecided,
On,
Off
};
std::atomic<ResetNotification> g_resetNotification{ResetNotification::Undecided};
Bool ResetNotificationWanted() {
const ResetNotification decided = g_resetNotification.load(std::memory_order_acquire);
if (decided != ResetNotification::Undecided) return decided == ResetNotification::On;
if (const char* value = std::getenv("MOBILEGL_ESPRYT_RESET_NOTIFICATION"); value != nullptr && value[0] == '0')
if (const char* value = std::getenv("MOBILEGL_ESPRYT_RESET_NOTIFICATION");
value != nullptr && value[0] == '0')
return false;
if (g_Display == EGL_NO_DISPLAY || !g_EGLFuncs.eglQueryString) return false;
const char* extensions = g_EGLFuncs.eglQueryString(g_Display, EGL_EXTENSIONS);
@@ -734,7 +748,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void NoteResetNotificationRefused() {
ResetNotification expected = ResetNotification::Undecided;
if (g_resetNotification.compare_exchange_strong(expected, ResetNotification::Off, std::memory_order_acq_rel))
if (g_resetNotification.compare_exchange_strong(expected, ResetNotification::Off,
std::memory_order_acq_rel))
MGLOG_W("DirectGLES: the driver refused EGL_LOSE_CONTEXT_ON_RESET; a context that hangs the GPU is "
"not told, and its session cannot be ended for it");
}
@@ -772,9 +787,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// by CreateNativeContextFor before this runs; a tuple nobody named a group for starts one.
EGLContext share = EGL_NO_CONTEXT;
for (auto* other : NativeContexts()) {
if (other != &tuple && other->SessionKey == tuple.SessionKey &&
other->Context != EGL_NO_CONTEXT && tuple.ShareGroupToken != 0 &&
other->ShareGroupToken == tuple.ShareGroupToken) {
if (other != &tuple && other->SessionKey == tuple.SessionKey && other->Context != EGL_NO_CONTEXT &&
tuple.ShareGroupToken != 0 && other->ShareGroupToken == tuple.ShareGroupToken) {
share = other->Context;
break;
}
@@ -812,8 +826,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// must agree on the strategy, so it is decided once, by the first context, and kept.
for (const EGLint minorVersion : {2, 1, 0}) {
const Bool notify = ResetNotificationWanted();
const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, minorVersion,
const EGLint contextAttribs[] = {EGL_CONTEXT_MAJOR_VERSION,
3,
EGL_CONTEXT_MINOR_VERSION,
minorVersion,
notify ? kEglResetNotificationStrategyExt : EGL_NONE,
kEglLoseContextOnResetExt,
EGL_NONE};
@@ -912,7 +928,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// its surfaceless stand-in) bound a surface the driver no longer had. The session's own
// teardown terminates the display once its surfaces are gone (DestroyNativeStateOfSession).
Bool surfacesRemain = false;
for (const auto* session : NativeSessions()) surfacesRemain = surfacesRemain || !session->Surfaces.empty();
for (const auto* session : NativeSessions())
surfacesRemain = surfacesRemain || !session->Surfaces.empty();
if (g_displayUsers == 0 && !surfacesRemain && g_Display != EGL_NO_DISPLAY && g_EGLFuncs.eglTerminate) {
g_EGLFuncs.eglTerminate(g_Display);
NoteDisplayTerminated();
@@ -956,9 +973,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
return MakeCurrent();
}
EGLSurface CurrentSessionDrawSurface() { return ActiveNativeSession().Draw; }
EGLSurface CurrentSessionDrawSurface() {
return ActiveNativeSession().Draw;
}
EGLSurface CurrentContextDrawSurface() { return ActiveNativeContext().Draw; }
EGLSurface CurrentContextDrawSurface() {
return ActiveNativeContext().Draw;
}
namespace {
// Whether the driver of `display` has EGL_EXT_buffer_age (or EGL_KHR_partial_update, which
@@ -1001,13 +1022,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} // namespace
// The window surface whose age the driver was asked in the frame being drawn on this (the
// session's apply) thread; the present ends the frame.
thread_local EGLSurface t_ageAskedSurface = EGL_NO_SURFACE;
// The window surface whose age the driver was asked in the frame being drawn on this (the
// session's apply) thread; the present ends the frame.
thread_local EGLSurface t_ageAskedSurface = EGL_NO_SURFACE;
// Whether the frame being drawn on this thread has had its flush ahead of the window
// buffer (FlushAheadOfWindowBufferUse); the present ends the frame.
thread_local Bool t_windowBufferFlushIssued = false;
// Whether the frame being drawn on this thread has had its flush ahead of the window
// buffer (FlushAheadOfWindowBufferUse); the present ends the frame.
thread_local Bool t_windowBufferFlushIssued = false;
namespace {
// A WINDOW SURFACE'S BUFFER AGE, on unless MOBILEGL_ESPRYT_WINDOW_AGE=0 (or, on Android, the
@@ -1099,7 +1120,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
EGLint rects[MG_Util::Damage::kMaxRects * 4] = {};
const EGLint count =
region.IsFull() ? 0 : static_cast<EGLint>(MG_Util::Damage::PackRects(region, rects, MG_Util::Damage::kMaxRects));
region.IsFull()
? 0
: static_cast<EGLint>(MG_Util::Damage::PackRects(region, rects, MG_Util::Damage::kMaxRects));
if (support.SetDamageRegion(g_Display, surface, count > 0 ? rects : nullptr, count) != EGL_TRUE) {
MGLOG_W_ONCE("DirectGLES: eglSetDamageRegionKHR refused a %d-rectangle region (EGL error 0x%x)", count,
g_EGLFuncs.eglGetError ? static_cast<unsigned>(g_EGLFuncs.eglGetError()) : 0u);
@@ -1151,10 +1174,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (tuple->Read == previous) tuple->Read = replacement;
}
auto& entries = session.Surfaces;
entries.erase(std::remove_if(entries.begin(), entries.end(),
[&](const NativeSessionState::SurfaceEntry& entry) {
return entry.Surface == previous;
}),
entries.erase(std::remove_if(
entries.begin(), entries.end(),
[&](const NativeSessionState::SurfaceEntry& entry) { return entry.Surface == previous; }),
entries.end());
g_nativeRegistryEpoch.fetch_add(1, std::memory_order_acq_rel);
}
@@ -1166,7 +1188,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The calling thread drew to the old one: draw to the new one from here (the old is then
// no longer current, and its destruction below is immediate).
if (t_native.boundDraw == previous && ActiveNativeContext().Context != EGL_NO_CONTEXT) (void)MakeCurrent();
if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY) g_EGLFuncs.eglDestroySurface(g_Display, previous);
if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY)
g_EGLFuncs.eglDestroySurface(g_Display, previous);
}
void DestroySessionSurface(EGLSurface surface) {
@@ -1175,8 +1198,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
{
const std::lock_guard<std::mutex> lock(g_nativeRegistryMutex);
auto& entries = session.Surfaces;
const auto it = std::find_if(entries.begin(), entries.end(),
[&](const NativeSessionState::SurfaceEntry& entry) { return entry.Surface == surface; });
const auto it =
std::find_if(entries.begin(), entries.end(),
[&](const NativeSessionState::SurfaceEntry& entry) { return entry.Surface == surface; });
if (it == entries.end()) return;
entries.erase(it);
for (auto* tuple : NativeContexts()) {
@@ -1199,7 +1223,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// A surface still current on this thread is destroyed by EGL once it is not (EGL 1.5
// 3.5.6), so this needs no unbind first.
if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY) g_EGLFuncs.eglDestroySurface(g_Display, surface);
if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY)
g_EGLFuncs.eglDestroySurface(g_Display, surface);
if (t_native.boundDraw == surface && session.Draw == EGL_NO_SURFACE) t_native.boundDraw = EGL_NO_SURFACE;
}
@@ -1214,8 +1239,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const EGLSurface surface = ActiveNativeSession().Draw;
if (!g_EGLFuncs.eglSwapInterval || g_Display == EGL_NO_DISPLAY || surface == EGL_NO_SURFACE) return;
const EGLBoolean ok = g_EGLFuncs.eglSwapInterval(g_Display, g_requestedSwapInterval);
MGLOG_D("DirectGLES: applied native swap interval %d (%s)", g_requestedSwapInterval,
ok ? "ok" : "failed");
MGLOG_D("DirectGLES: applied native swap interval %d (%s)", g_requestedSwapInterval, ok ? "ok" : "failed");
}
} // namespace
@@ -1225,7 +1249,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
#if MOBILEGL_BUILD_DISAGGREGATED
void ForgetRequestedSwapInterval() { g_requestedSwapInterval = -1; }
void ForgetRequestedSwapInterval() {
g_requestedSwapInterval = -1;
}
#endif
// A crashed predecessor session may have left its surface bound to this window (the
@@ -1394,32 +1420,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
surface, tuple.Context, width, height);
return true;
}
// The single backend ES context migrates between app threads (FCL/pojav-style
// LWJGL hands the EGL context from JVM thread to JVM thread). Ownership must
// live in ONE global slot: a per-thread flag can never be cleared on the
// LOSING thread when another thread takes (or destroys/releases) the context,
// leaving a stale "current" claim behind. A stale claim makes buffer ops issue
// GL calls that silently no-op (no context is current on that thread) while
// still updating shadow bookkeeping (bind cache, synced serials), permanently
// desynchronizing backend buffer state.
std::atomic<std::thread::id> g_backendContextOwnerThread{};
// The single backend ES context migrates between app threads (FCL/pojav-style
// LWJGL hands the EGL context from JVM thread to JVM thread). Ownership must
// live in ONE global slot: a per-thread flag can never be cleared on the
// LOSING thread when another thread takes (or destroys/releases) the context,
// leaving a stale "current" claim behind. A stale claim makes buffer ops issue
// GL calls that silently no-op (no context is current on that thread) while
// still updating shadow bookkeeping (bind cache, synced serials), permanently
// desynchronizing backend buffer state.
std::atomic<std::thread::id> g_backendContextOwnerThread{};
// Bumped whenever the backend ES context is destroyed; fence and
// timer-query handles created under an older generation belong to a
// dead context and must never be passed back to GL (mirrors
// BufferImpl's context tracking).
Uint g_syncContextGeneration = 1;
// Bumped whenever the backend ES context is destroyed; fence and
// timer-query handles created under an older generation belong to a
// dead context and must never be passed back to GL (mirrors
// BufferImpl's context tracking).
Uint g_syncContextGeneration = 1;
// One-fence-per-frame ring driving the buffer-storage pool's recycle gate.
// A buffer retired during frame N is safe to reuse once frame N's fence has
// signaled. Touched only in Present()/DestroyEGLContext() on the owning
// thread -> no lock. Each fence carries the sync generation so a dead-context
// GLsync is never polled/deleted. Depth 4 >> the 2-3 frames Adreno keeps in
// flight; wrap-before-signal only happens during a stall and just degrades to
// the allocate path.
std::atomic<Uint64> g_currentFrameSerial{0};
std::atomic<Uint64> g_completedFrameSerial{0};
FrameFence g_frameFenceRing[kFrameFenceRingDepth];
// One-fence-per-frame ring driving the buffer-storage pool's recycle gate.
// A buffer retired during frame N is safe to reuse once frame N's fence has
// signaled. Touched only in Present()/DestroyEGLContext() on the owning
// thread -> no lock. Each fence carries the sync generation so a dead-context
// GLsync is never polled/deleted. Depth 4 >> the 2-3 frames Adreno keeps in
// flight; wrap-before-signal only happens during a stall and just degrades to
// the allocate path.
std::atomic<Uint64> g_currentFrameSerial{0};
std::atomic<Uint64> g_completedFrameSerial{0};
FrameFence g_frameFenceRing[kFrameFenceRingDepth];
// Defined next to the stamp globals below; every owner-id writer must reset the
// EGL verification stamp BEFORE publishing the new owner.
@@ -1479,8 +1505,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E_ONCE("DirectGLES::MakeCurrent failed: native eglMakeCurrent returned error 0x%04x", error);
return false;
}
g_EGLFuncs.eglSwapBuffersWithDamageEXT =
MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(g_EGLFuncs);
g_EGLFuncs.eglSwapBuffersWithDamageEXT = MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(g_EGLFuncs);
InvalidateEglVerifiedStamp();
t_native.boundContext = tuple.Context;
t_native.boundDraw = session.Draw;
@@ -1557,8 +1582,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DestroyEGLContext() {
#if MOBILEGL_BUILD_DISAGGREGATED
if (MG_Config::Transport != MG_Config::TransportMode::Monolith)
MG_Pipe::MGPipeServerSetContextLive(false);
if (MG_Config::Transport != MG_Config::TransportMode::Monolith) MG_Pipe::MGPipeServerSetContextLive(false);
#endif
// P14: ANOTHER SESSION'S CONTEXT STILL LIVES. Every native context is in one share group, so
// the buffers, textures and syncs this process's backend holds outlive this session's
@@ -1603,9 +1627,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The frame-fence ring's syncs belong to the dead context too; abandon them
// (the context reclaims its syncs) and floor the completed watermark to the
// current serial so buffers retired under the old context read as GPU-idle.
for (FrameFence& slot : g_frameFenceRing) slot = {};
g_completedFrameSerial.store(g_currentFrameSerial.load(std::memory_order_relaxed),
std::memory_order_relaxed);
for (FrameFence& slot : g_frameFenceRing)
slot = {};
g_completedFrameSerial.store(g_currentFrameSerial.load(std::memory_order_relaxed), std::memory_order_relaxed);
DestroyNativeStateOfSession(CurrentNativeSessionKey());
}
+127 -131
View File
@@ -12,7 +12,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace BufferImpl {
// Called by the application's own glMemoryBarrier: a barrier that orders storage writes
// already did the work.
static void NoteShaderStorageWritesOrdered() { g_shaderStorageWritesUnordered = false; }
static void NoteShaderStorageWritesOrdered() {
g_shaderStorageWritesUnordered = false;
}
void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) {
#ifdef TRACY_ENABLE
@@ -26,10 +28,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// is why the uniform class is emitted at all: compute does NOT go through the
// per-program block remap in BindCurrentProgramWithResources, so these are the
// points a compute shader actually reads.
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassUniform,
GL_UNIFORM_BUFFER);
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassShaderStorage,
GL_SHADER_STORAGE_BUFFER);
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassUniform, GL_UNIFORM_BUFFER);
SyncBufferBindingPointsByRecord(MG_Pipe::kMGPipeShaderBufferClassShaderStorage, GL_SHADER_STORAGE_BUFFER);
if (includeDispatchIndirectBuffer) {
// P5c (hd, CONTRACT-P5C §3.5): see the draw-indirect twin above - the verb
// record's handle, never the frontend binding slot or the client allocator.
@@ -85,8 +85,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// frontend link/SPIR-V gate below, which already has a record arm of its own. These are
// the 7 strict-lane entries of GetProgramForDispatch@DispatchCompute.
const SharedPtr<MG_State::GLState::ProgramObject> noFrontendProgram;
const auto& currentProgram =
noFrontendProgram;
const auto& currentProgram = noFrontendProgram;
const TextureImpl::DrawTextureSyncKeys textureKeys = TextureImpl::CaptureDrawTextureSyncKeys();
// Read BEFORE this dispatch's own binding walk sets it again (see the flag's comment).
@@ -107,8 +106,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5e (pg), kimi row 92: the DISPATCH half of the same retirement -
// MGPipeApplier().DispatchProgram, which set_dispatch_program carried. A compute-only
// pipeline has no draw program at all, which is why the two applier fields are separate.
const MG_Pipe::MGPipeHandle dispatchCso =
MG_Pipe::MGPipeApplier().DispatchProgram;
const MG_Pipe::MGPipeHandle dispatchCso = MG_Pipe::MGPipeApplier().DispatchProgram;
PrgramImpl::SyncCurrentProgramByHandle(dispatchCso, false);
// The same gate as the frontend one below, from the record: a dispatch of a program
@@ -127,9 +125,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Compute programs need the same per-program resource sync as draws:
// uniform-block bindings and sampler units only exist through the API
// because layout(binding) is stripped from the transpiled ESSL.
BindCurrentProgramWithResources(currentProgram, textureKeys,
dispatchCso
);
BindCurrentProgramWithResources(currentProgram, textureKeys, dispatchCso);
}
Bool UpdateTextureBindingAtTarget(GLenum target) {
@@ -141,7 +137,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target);
if (!TextureImpl::IsSupportedTextureTarget(textureTarget)) {
MGLOG_E_ONCE(" Texture target %s is not supported, skipping.",
MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
return false;
}
@@ -211,18 +207,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
// sync; it is file-local there, and a header for eleven cases would be the wrong trade).
static TextureTarget TextureTargetForPipeResourceTarget(Uint8 pipeResourceTarget) {
switch (static_cast<MG_Pipe::MGPipeResourceTarget>(pipeResourceTarget)) {
case MG_Pipe::MGPipeResourceTarget::Tex1D: return TextureTarget::Texture1D;
case MG_Pipe::MGPipeResourceTarget::Tex2D: return TextureTarget::Texture2D;
case MG_Pipe::MGPipeResourceTarget::Tex3D: return TextureTarget::Texture3D;
case MG_Pipe::MGPipeResourceTarget::Tex1DArray: return TextureTarget::Texture1DArray;
case MG_Pipe::MGPipeResourceTarget::Tex2DArray: return TextureTarget::Texture2DArray;
case MG_Pipe::MGPipeResourceTarget::TexCube: return TextureTarget::TextureCubeMap;
case MG_Pipe::MGPipeResourceTarget::TexCubeArray: return TextureTarget::TextureCubeMapArray;
case MG_Pipe::MGPipeResourceTarget::Tex2DMS: return TextureTarget::Texture2DMultisample;
case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray: return TextureTarget::Texture2DMultisampleArray;
case MG_Pipe::MGPipeResourceTarget::TexRect: return TextureTarget::TextureRectangle;
case MG_Pipe::MGPipeResourceTarget::TexBuffer: return TextureTarget::TextureBuffer;
default: return TextureTarget::Unknown;
case MG_Pipe::MGPipeResourceTarget::Tex1D:
return TextureTarget::Texture1D;
case MG_Pipe::MGPipeResourceTarget::Tex2D:
return TextureTarget::Texture2D;
case MG_Pipe::MGPipeResourceTarget::Tex3D:
return TextureTarget::Texture3D;
case MG_Pipe::MGPipeResourceTarget::Tex1DArray:
return TextureTarget::Texture1DArray;
case MG_Pipe::MGPipeResourceTarget::Tex2DArray:
return TextureTarget::Texture2DArray;
case MG_Pipe::MGPipeResourceTarget::TexCube:
return TextureTarget::TextureCubeMap;
case MG_Pipe::MGPipeResourceTarget::TexCubeArray:
return TextureTarget::TextureCubeMapArray;
case MG_Pipe::MGPipeResourceTarget::Tex2DMS:
return TextureTarget::Texture2DMultisample;
case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray:
return TextureTarget::Texture2DMultisampleArray;
case MG_Pipe::MGPipeResourceTarget::TexRect:
return TextureTarget::TextureRectangle;
case MG_Pipe::MGPipeResourceTarget::TexBuffer:
return TextureTarget::TextureBuffer;
default:
return TextureTarget::Unknown;
}
}
@@ -249,11 +257,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (desc.Width == 0 || desc.Levels == 0 || window.Base >= desc.Levels) return window;
Uint32 end = std::min<Uint32>(applier.VerbMipLevelCount, desc.Levels);
if (record.ParamsSerial) end = std::min<Uint32>(end, static_cast<Uint32>(record.Params.MaxLevel) + 1u);
const IntVec3 baseExtent = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, window.Base);
const IntVec3 baseExtent =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, window.Base);
const Int shrinking = MG_Record::StagedTextureShrinkingAxisCount(desc.Target);
Int largest = 1;
for (Int axis = 0; axis < shrinking && axis < 3; ++axis) largest = std::max<Int>(largest, baseExtent[axis]);
for (Int axis = 0; axis < shrinking && axis < 3; ++axis)
largest = std::max<Int>(largest, baseExtent[axis]);
Uint32 chain = 1;
while (largest > 1) {
largest /= 2;
@@ -265,8 +274,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static void AssertNoGLError(const char* operation) {
const GLenum err = g_GLESFuncs.glGetError();
MOBILEGL_ASSERT(err == GL_NO_ERROR, "%s failed: %s", operation,
MG_Util::ConvertGLEnumToString(err).c_str());
MOBILEGL_ASSERT(err == GL_NO_ERROR, "%s failed: %s", operation, MG_Util::ConvertGLEnumToString(err).c_str());
}
static ErrorCode ConvertGLESErrorToErrorCode(GLenum err) {
@@ -291,14 +299,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
MGLOG_E_ONCE("%s failed: %s. target=%s, format=%s", operation,
MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(target).c_str(),
MG_Util::ConvertTextureInternalFormatToString(format).c_str());
MGLOG_E_ONCE("%s failed: %s. target=%s, format=%s", operation, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(target).c_str(),
MG_Util::ConvertTextureInternalFormatToString(format).c_str());
MG_Pipe::gPipeInputs->RecordError(
ConvertGLESErrorToErrorCode(err),
MakeUnique<GenericErrorInfo>("DirectGLES", operation,
MG_Util::ConvertGLEnumToString(err)));
MakeUnique<GenericErrorInfo>("DirectGLES", operation, MG_Util::ConvertGLEnumToString(err)));
return false;
}
@@ -338,8 +344,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& st = MG_Pipe::MGPipeApplier();
FramebufferImpl::g_backendFramebufferObjects.ForEachLive(
[&](MG_Pipe::MGPipeHandle fbo,
const SharedPtr<FramebufferImpl::BackendFramebufferObject>& backendFBO) {
[&](MG_Pipe::MGPipeHandle fbo, const SharedPtr<FramebufferImpl::BackendFramebufferObject>& backendFBO) {
if (!backendFBO) return;
const auto* record = st.FramebufferRecordFor(fbo);
if (record == nullptr || record->IsDefault != 0) return;
@@ -370,8 +375,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (surface.Layered != 0) {
g_GLESFuncs.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, backendAttachment, 0, 0);
} else {
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, backendAttachment,
textureTarget, 0, 0);
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, backendAttachment, textureTarget, 0,
0);
}
ClearGLErrors();
m_detachedAttachments.push_back({backendFBOId, backendAttachment, textureTarget,
@@ -380,9 +385,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
};
for (SizeT i = 0; i < 8; ++i) {
detachPoint(record->Color[i],
static_cast<FramebufferAttachmentType>(
static_cast<SizeT>(FramebufferAttachmentType::Color0) + i));
detachPoint(record->Color[i], static_cast<FramebufferAttachmentType>(
static_cast<SizeT>(FramebufferAttachmentType::Color0) + i));
}
detachPoint(record->Depth, FramebufferAttachmentType::Depth);
detachPoint(record->Stencil, FramebufferAttachmentType::Stencil);
@@ -393,12 +397,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (const auto& attachment : m_detachedAttachments) {
FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, attachment.framebuffer);
if (attachment.layered) {
g_GLESFuncs.glFramebufferTexture(
GL_DRAW_FRAMEBUFFER, attachment.attachment, attachment.texture, attachment.level);
g_GLESFuncs.glFramebufferTexture(GL_DRAW_FRAMEBUFFER, attachment.attachment, attachment.texture,
attachment.level);
} else {
g_GLESFuncs.glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, attachment.attachment, attachment.textureTarget,
attachment.texture, attachment.level);
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, attachment.attachment,
attachment.textureTarget, attachment.texture, attachment.level);
}
}
}
@@ -467,9 +470,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"Depth blit draw framebuffer is incomplete.");
AssertNoGLError("check depth blit draw framebuffer");
g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight,
dstX, dstY, dstX + dstWidth, dstY + dstHeight,
GL_DEPTH_BUFFER_BIT, GL_NEAREST);
g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY, dstX + dstWidth,
dstY + dstHeight, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
AssertNoGLError("depth texture blit");
}
@@ -501,9 +503,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"Color blit draw framebuffer is incomplete.");
AssertNoGLError("check color blit draw framebuffer");
g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight,
dstX, dstY, dstX + dstWidth, dstY + dstHeight,
GL_COLOR_BUFFER_BIT, filter);
g_GLESFuncs.glBlitFramebuffer(srcX, srcY, srcX + srcWidth, srcY + srcHeight, dstX, dstY, dstX + dstWidth,
dstY + dstHeight, GL_COLOR_BUFFER_BIT, filter);
AssertNoGLError("color texture blit");
}
@@ -613,13 +614,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLint layer = static_cast<GLint>(window.MinLayer + i);
if (window.Depth) {
BlitDepthTextureLayer(window.StorageTextureId, srcLevel, layer, static_cast<GLsizei>(srcSize.x()),
static_cast<GLsizei>(srcSize.y()), window.StorageTextureId, dstLevel,
layer, static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()));
static_cast<GLsizei>(srcSize.y()), window.StorageTextureId, dstLevel, layer,
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()));
} else {
BlitColorTextureLayer(window.StorageTextureId, srcLevel, layer, static_cast<GLsizei>(srcSize.x()),
static_cast<GLsizei>(srcSize.y()), window.StorageTextureId, dstLevel,
layer, static_cast<GLsizei>(dstSize.x()),
static_cast<GLsizei>(dstSize.y()), window.Filter);
static_cast<GLsizei>(srcSize.y()), window.StorageTextureId, dstLevel, layer,
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()),
window.Filter);
}
}
}
@@ -707,8 +708,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"mipmap-view-storage\"} {%u,%u}", mipRes.Slot, mipRes.Gen);
std::abort();
}
if (view.MinLayer == 0 && view.NumLayers >= std::max<Uint32>(storageRecord->Desc.ArrayLayers, 1u))
return false;
if (view.MinLayer == 0 && view.NumLayers >= std::max<Uint32>(storageRecord->Desc.ArrayLayers, 1u)) return false;
const auto& applier = MG_Pipe::MGPipeApplier();
MipmapViewWindow window;
@@ -720,8 +720,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// frontend already clipped it to the object's own range; the view's window is the bound
// only this side can apply.
window.LogicalEnd = std::min({static_cast<Uint32>(applier.VerbMipLevelCount),
static_cast<Uint32>(record.Desc.Levels),
static_cast<Uint32>(view.NumLevels)});
static_cast<Uint32>(record.Desc.Levels), static_cast<Uint32>(view.NumLevels)});
window.Depth = depth;
window.Filter = IsIntegerColorFormat(format) ? GL_NEAREST : GL_LINEAR;
if (window.LogicalEnd <= window.LogicalBase + 1) return true;
@@ -731,13 +730,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& storage = TextureImpl::SyncTextureToBackendByHandle(record.Desc.ViewOf);
if (!storage || storage->GetBackendTextureId() == 0) {
MGLOG_E_ONCE("MGPipe: generate_mipmap through view {%u, %u} has no storage texture to write; "
"the call is dropped", mipRes.Slot, mipRes.Gen);
"the call is dropped",
mipRes.Slot, mipRes.Gen);
return true;
}
window.StorageTextureId = storage->GetBackendTextureId();
GenerateMipmapThroughViewWindow(window, [&record](Uint32 level) {
return MG_Record::StagedTextureMipExtent(record.Desc.Target, record.Desc.Width,
record.Desc.Height, record.Desc.Depth, level);
return MG_Record::StagedTextureMipExtent(record.Desc.Target, record.Desc.Width, record.Desc.Height,
record.Desc.Depth, level);
});
return true;
}
@@ -757,14 +757,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& desc = record.Desc;
const GLuint textureId = backendTexture->GetBackendTextureId();
for (Uint32 level = window.Base + 1; level < window.End; ++level) {
const IntVec3 srcSize = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, level - 1);
const IntVec3 dstSize = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, level);
BlitDepthTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0,
static_cast<GLsizei>(srcSize.x()), static_cast<GLsizei>(srcSize.y()), textureId,
static_cast<GLint>(level), 0, 0, static_cast<GLsizei>(dstSize.x()),
static_cast<GLsizei>(dstSize.y()));
const IntVec3 srcSize =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, level - 1);
const IntVec3 dstSize =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, level);
BlitDepthTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0, static_cast<GLsizei>(srcSize.x()),
static_cast<GLsizei>(srcSize.y()), textureId, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()));
}
}
@@ -776,10 +775,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
IsIntegerColorFormat(static_cast<TextureInternalFormat>(desc.InternalFormat)) ? GL_NEAREST : GL_LINEAR;
const GLuint textureId = backendTexture->GetBackendTextureId();
for (Uint32 level = window.Base + 1; level < window.End; ++level) {
const IntVec3 srcSize = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, level - 1);
const IntVec3 dstSize = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, level);
const IntVec3 srcSize =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, level - 1);
const IntVec3 dstSize =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, level);
BlitColorTexture2D(textureId, static_cast<GLint>(level - 1), 0, 0, static_cast<GLsizei>(srcSize.x()),
static_cast<GLsizei>(srcSize.y()), textureId, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(dstSize.x()), static_cast<GLsizei>(dstSize.y()), filter);
@@ -1015,8 +1014,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_Pipe::MGPipeResourceRecord& record,
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture,
TextureTarget textureTarget, TextureUploadTarget face, Uint32 level,
const IntVec3& extent, Bool driverWrittenBefore,
Vector<Float>& rgb, const char*& declined) {
const IntVec3& extent, Bool driverWrittenBefore, Vector<Float>& rgb,
const char*& declined) {
const auto format = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
const SizeT texels = static_cast<SizeT>(extent.x()) * static_cast<SizeT>(extent.y());
Vector<Uint8> bytes;
@@ -1066,10 +1065,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
static void GenerateThreeChannelFloatMipmapOnServer(MG_Pipe::MGPipeHandle mipRes,
const MG_Pipe::MGPipeResourceRecord& record,
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture,
Bool driverWrittenBefore, const char* why) {
static void GenerateThreeChannelFloatMipmapOnServer(
MG_Pipe::MGPipeHandle mipRes, const MG_Pipe::MGPipeResourceRecord& record,
const SharedPtr<TextureImpl::BackendTextureObject>& backendTexture, Bool driverWrittenBefore, const char* why) {
const auto& desc = record.Desc;
const auto format = static_cast<TextureInternalFormat>(desc.InternalFormat);
const auto textureTarget = TextureTargetForPipeResourceTarget(desc.Target);
@@ -1080,8 +1078,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
faces.push_back(TextureUploadTarget::Texture2D);
} else if (textureTarget == TextureTarget::TextureCubeMap) {
for (Int face = 0; face < 6; ++face)
faces.push_back(static_cast<TextureUploadTarget>(
static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + face));
faces.push_back(
static_cast<TextureUploadTarget>(static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + face));
}
// The upload spelling the driver's storage takes: GL_FLOAT is legal for all four float
// internal formats (ES 3.2 table 8.2), and the channel count follows the storage.
@@ -1096,13 +1094,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
MGLOG_I_ONCE("MGPipe: generate-mipmap-server-cpu: %s levels filtered on the server's CPU from its own "
"level (%s)", MG_Util::ConvertTextureInternalFormatToString(format).c_str(), why);
"level (%s)",
MG_Util::ConvertTextureInternalFormatToString(format).c_str(), why);
const GLuint textureId = backendTexture->GetBackendTextureId();
const GLenum bindTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(textureTarget);
for (const TextureUploadTarget face : faces) {
IntVec3 extent = MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height,
desc.Depth, window.Base);
IntVec3 extent =
MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, window.Base);
Vector<Float> level;
const char* declined = nullptr;
if (!ReadThreeChannelFloatLevelOnServer(mipRes, record, backendTexture, textureTarget, face, window.Base,
@@ -1130,7 +1129,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Int y0 = std::min(y * 2, extent.y() - 1), y1 = std::min(y * 2 + 1, extent.y() - 1);
for (Int c = 0; c < 3; ++c)
next[(static_cast<SizeT>(y) * static_cast<SizeT>(dstExtent.x()) + static_cast<SizeT>(x)) *
3 + static_cast<SizeT>(c)] =
3 +
static_cast<SizeT>(c)] =
0.25f * (at(x0, y0, c) + at(x1, y0, c) + at(x0, y1, c) + at(x1, y1, c));
}
}
@@ -1157,13 +1157,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<GLsizei>(dstExtent.y()), fourChannel ? GL_RGBA : GL_RGB,
GL_FLOAT, fourChannel ? upload.data() : next.data());
const GLenum err = g_GLESFuncs.glGetError();
auto* cachedBound = TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit]
[static_cast<SizeT>(textureTarget)];
auto* cachedBound =
TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit][static_cast<SizeT>(textureTarget)];
g_GLESFuncs.glBindTexture(bindTarget, cachedBound ? cachedBound->GetBackendTextureId() : 0);
if (err != GL_NO_ERROR) {
MGLOG_E_ONCE("MGPipe: generate-mipmap-server-cpu: uploading level %u of texture {%u, %u} "
"failed: %s", dst, mipRes.Slot, mipRes.Gen,
MG_Util::ConvertGLEnumToString(err).c_str());
"failed: %s",
dst, mipRes.Slot, mipRes.Gen, MG_Util::ConvertGLEnumToString(err).c_str());
return;
}
level = Move(next);
@@ -1263,8 +1263,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto mipRes = MG_Pipe::MGPipeApplier().VerbMipRes;
// P8-SE: whether the staged store still stands for the texture is read BEFORE the note
// below sets the mark, or the CPU arm's store route could never answer at all.
const Bool driverWrittenBefore = MG_Record::ServerStagedTexture().IsDriverWritten(
MG_Record::StagedTextureStore::KeyForHandle(mipRes));
const Bool driverWrittenBefore =
MG_Record::ServerStagedTexture().IsDriverWritten(MG_Record::StagedTextureStore::KeyForHandle(mipRes));
// Every arm GenerateMipmapByRecord takes writes the levels above the base on the
// driver only (P8-B2's CPU arm uploads its levels there too), so the note is
// unconditional.
@@ -1367,7 +1367,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint z, GLESCopyImageEndpoint& out) {
if (endpoint.IsRenderbuffer()) {
if (!PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)) return false;
auto* slot = RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle);
auto* slot =
RenderbufferImpl::g_backendRenderbufferObjects.GetOrCreateByHandle(endpoint.RenderbufferHandle);
if (!slot) return false;
if (!*slot) *slot = MakeShared<RenderbufferImpl::BackendRenderbufferObject>();
out.renderbuffer = *slot;
@@ -1409,9 +1410,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
static TextureInternalFormat GetCopyImageEndpointFormat(const CopyImageEndpoint& endpoint) {
const auto* record = endpoint.IsRenderbuffer()
? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)
: PipeTextureRecordForHandle(endpoint.TextureHandle);
const auto* record = endpoint.IsRenderbuffer() ? PipeRenderbufferRecordForHandle(endpoint.RenderbufferHandle)
: PipeTextureRecordForHandle(endpoint.TextureHandle);
return record ? static_cast<TextureInternalFormat>(record->Desc.InternalFormat)
: TextureInternalFormat::Unknown;
if (endpoint.IsRenderbuffer()) return endpoint.Renderbuffer->GetInternalFormat();
@@ -1440,8 +1440,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// self-copy within one level, where the row copies could overlap.
static void MirrorCopyImageIntoDestinationShadow(const CopyImageEndpoint& srcEndpoint, GLint srcLevel, GLint srcX,
GLint srcY, GLint srcZ, const CopyImageEndpoint& dstEndpoint,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei width, GLsizei height, GLsizei depth) {
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei width,
GLsizei height, GLsizei depth) {
if (!CanMirrorCopyImageShadow(srcEndpoint.Texture) || !CanMirrorCopyImageShadow(dstEndpoint.Texture)) return;
if (srcEndpoint.Texture == dstEndpoint.Texture && srcLevel == dstLevel) return;
if (width <= 0 || height <= 0 || depth <= 0) return;
@@ -1467,11 +1467,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dstEndpoint,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint, GLenum srcTarget, GLint srcLevel, GLint srcX,
GLint srcY, GLint srcZ, const CopyImageEndpoint& dstEndpoint, GLenum dstTarget,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight,
GLsizei srcDepth) {
GLESCopyImageEndpoint src{};
GLESCopyImageEndpoint dst{};
// The DirectVulkan half of this entry point died exactly here, on a texture whose sync
@@ -1504,10 +1503,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (handleArmDestination) {
const Bool followed =
!srcEndpoint.IsRenderbuffer() && !MG_Pipe::MGPipeHandleIsNull(srcEndpoint.TextureHandle) &&
TextureImpl::FollowCopyImageInStagedStore(
srcEndpoint.TextureHandle, static_cast<Uint32>(srcLevel), IntVec3{srcX, srcY, srcZ},
dstEndpoint.TextureHandle, static_cast<Uint32>(dstLevel), IntVec3{dstX, dstY, dstZ},
IntVec3{srcWidth, srcHeight, srcDepth});
TextureImpl::FollowCopyImageInStagedStore(srcEndpoint.TextureHandle, static_cast<Uint32>(srcLevel),
IntVec3{srcX, srcY, srcZ}, dstEndpoint.TextureHandle,
static_cast<Uint32>(dstLevel), IntVec3{dstX, dstY, dstZ},
IntVec3{srcWidth, srcHeight, srcDepth});
if (!followed) TextureImpl::NoteDriverSideTextureWriteByHandle(dstEndpoint.TextureHandle);
}
@@ -1535,8 +1534,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"DirectGLES depth CopyImageSubData only supports GL_TEXTURE_2D.");
MOBILEGL_ASSERT(src.z == 0 && dst.z == 0 && copyDepth == 1,
"DirectGLES depth CopyImageSubData only supports single-layer copies.");
BlitDepthTexture2D(src.Name(), srcLevel, src.x, src.y, srcWidth, copyHeight,
dst.Name(), dstLevel, dst.x, dst.y, srcWidth, copyHeight);
BlitDepthTexture2D(src.Name(), srcLevel, src.x, src.y, srcWidth, copyHeight, dst.Name(), dstLevel, dst.x,
dst.y, srcWidth, copyHeight);
return;
}
@@ -1547,9 +1546,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// with the always-live helper so a stale flag cannot misroute a
// succeeded native copy into the 2D-only fallback.
ClearGLErrors();
g_GLESFuncs.glCopyImageSubData(src.Name(), src.target, srcLevel, src.x, src.y, src.z,
dst.Name(), dst.target, dstLevel, dst.x, dst.y, dst.z,
srcWidth, copyHeight, copyDepth);
g_GLESFuncs.glCopyImageSubData(src.Name(), src.target, srcLevel, src.x, src.y, src.z, dst.Name(),
dst.target, dstLevel, dst.x, dst.y, dst.z, srcWidth, copyHeight, copyDepth);
const GLenum copyImageError = g_GLESFuncs.glGetError();
if (copyImageError == GL_NO_ERROR) {
return;
@@ -1560,15 +1558,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
"DirectGLES color CopyImageSubData only supports GL_TEXTURE_2D.");
MOBILEGL_ASSERT(src.z == 0 && dst.z == 0 && copyDepth == 1,
"DirectGLES color CopyImageSubData only supports single-layer copies.");
CopyR32FTexture2D(src.Name(), srcLevel, src.x, src.y, srcWidth, copyHeight,
dst.Name(), dst.target, dstLevel, dst.x, dst.y);
CopyR32FTexture2D(src.Name(), srcLevel, src.x, src.y, srcWidth, copyHeight, dst.Name(), dst.target,
dstLevel, dst.x, dst.y);
return;
}
ClearGLErrors();
g_GLESFuncs.glCopyImageSubData(src.Name(), src.target, srcLevel, src.x, src.y, src.z,
dst.Name(), dst.target, dstLevel, dst.x, dst.y, dst.z,
srcWidth, copyHeight, copyDepth);
g_GLESFuncs.glCopyImageSubData(src.Name(), src.target, srcLevel, src.x, src.y, src.z, dst.Name(), dst.target,
dstLevel, dst.x, dst.y, dst.z, srcWidth, copyHeight, copyDepth);
// Every error condition glCopyImageSubData has was already ruled out by the frontend
// validator, so a driver error here is an internal invariant violation, not something
// the application can provoke. Say so where an INFO build can still see it, then trap
@@ -1576,12 +1573,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// destination that silently kept its old contents.
const GLenum copyImageError = g_GLESFuncs.glGetError();
if (copyImageError != GL_NO_ERROR) {
MGLOG_E_ONCE("glCopyImageSubData failed: %s. src target=%s (app %s), dst target=%s (app %s)",
MG_Util::ConvertGLEnumToString(copyImageError).c_str(),
MG_Util::ConvertGLEnumToString(src.target).c_str(),
MG_Util::ConvertGLEnumToString(srcTarget).c_str(),
MG_Util::ConvertGLEnumToString(dst.target).c_str(),
MG_Util::ConvertGLEnumToString(dstTarget).c_str());
MGLOG_E_ONCE(
"glCopyImageSubData failed: %s. src target=%s (app %s), dst target=%s (app %s)",
MG_Util::ConvertGLEnumToString(copyImageError).c_str(),
MG_Util::ConvertGLEnumToString(src.target).c_str(), MG_Util::ConvertGLEnumToString(srcTarget).c_str(),
MG_Util::ConvertGLEnumToString(dst.target).c_str(), MG_Util::ConvertGLEnumToString(dstTarget).c_str());
MOBILEGL_ASSERT(false, "glCopyImageSubData failed after frontend validation accepted the request.");
// P8-E: the store already made the move the driver just refused.
if (handleArmDestination) {
@@ -1593,8 +1589,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// CPU shadow is what glGetTexImage answers from, so it has to follow the same move -
// otherwise it hands back whatever the level held before this copy.
if (MG_Util::PixelStoreProcessor::HasRedundantPackedEncoding(dstFormat)) {
MirrorCopyImageIntoDestinationShadow(srcEndpoint, srcLevel, srcX, srcY, srcZ, dstEndpoint, dstLevel,
dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth);
MirrorCopyImageIntoDestinationShadow(srcEndpoint, srcLevel, srcX, srcY, srcZ, dstEndpoint, dstLevel, dstX,
dstY, dstZ, srcWidth, srcHeight, srcDepth);
}
}
@@ -1654,8 +1650,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto handle = MG_Pipe::MGPipeApplier().VerbStorageBlockProgram;
auto* slot = PrgramImpl::g_backendProgramObjects.FindByHandle(handle);
if (slot && *slot && (*slot)->GetBackendProgramId()) {
PrgramImpl::ApplyShaderStorageBlockBinding((*slot)->GetBackendProgramId(),
storageBlockName, storageBlockBinding);
PrgramImpl::ApplyShaderStorageBlockBinding((*slot)->GetBackendProgramId(), storageBlockName,
storageBlockBinding);
}
return;
}
+87 -105
View File
@@ -39,9 +39,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E_ONCE("%s skipped: invalid GL_DRAW_INDIRECT_BUFFER binding or range", label);
return nullptr;
}
BufferImpl::RequireStagedCoverage(*resource, base, commandOffset,
commandOffset + requiredBytes, "indirect_command_bytes");
return base + commandOffset; }
BufferImpl::RequireStagedCoverage(*resource, base, commandOffset, commandOffset + requiredBytes,
"indirect_command_bytes");
return base + commandOffset;
}
// No buffer was bound: the frontend already raised the GL error; keep the old
// fall-through shape (a null indirect pointer is the same skip it was).
if (!indirect) {
@@ -157,10 +158,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint32 indexMask = 0;
};
static constexpr Uint32 kAllViewportsMask =
(RenderStateParameters::MAX_VIEWPORTS >= 32)
? 0xFFFFFFFFu
: ((1u << RenderStateParameters::MAX_VIEWPORTS) - 1u);
static constexpr Uint32 kAllViewportsMask = (RenderStateParameters::MAX_VIEWPORTS >= 32)
? 0xFFFFFFFFu
: ((1u << RenderStateParameters::MAX_VIEWPORTS) - 1u);
// The plan for the draw currently being issued. A file-scope buffer rather than a return
// value because Begin/Apply/End are three calls around a draw the caller writes, and a
@@ -185,20 +185,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Resolving them here rather than deferring to SyncRenderState is what makes the grouping
// below correct: two indices that differ only in a field that resolves to the same
// rectangle really do rasterize identically and must share one pass.
static RoutingPass ResolveIndexState(const RenderStateParameters& parameters, Uint index,
Int surfaceWidth, Int surfaceHeight) {
static RoutingPass ResolveIndexState(const RenderStateParameters& parameters, Uint index, Int surfaceWidth,
Int surfaceHeight) {
RoutingPass pass;
const FloatVec4& viewport = parameters.Viewports[index];
pass.viewport = IntVec4(static_cast<Int>(std::lround(viewport.x())),
static_cast<Int>(std::lround(viewport.y())),
static_cast<Int>(std::lround(viewport.z())),
static_cast<Int>(std::lround(viewport.w())));
pass.viewport =
IntVec4(static_cast<Int>(std::lround(viewport.x())), static_cast<Int>(std::lround(viewport.y())),
static_cast<Int>(std::lround(viewport.z())), static_cast<Int>(std::lround(viewport.w())));
if ((pass.viewport.z() <= 0 || pass.viewport.w() <= 0) && surfaceWidth > 0 && surfaceHeight > 0) {
pass.viewport = IntVec4(0, 0, surfaceWidth, surfaceHeight);
}
pass.scissorBox = parameters.ScissorBoxes[index];
if ((parameters.ScissorBoxWrittenMask & (1u << index)) == 0 && surfaceWidth > 0 &&
surfaceHeight > 0) {
if ((parameters.ScissorBoxWrittenMask & (1u << index)) == 0 && surfaceWidth > 0 && surfaceHeight > 0) {
pass.scissorBox = IntVec4(0, 0, surfaceWidth, surfaceHeight);
}
pass.depthRange = parameters.DepthRanges[index];
@@ -207,8 +205,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
static Bool SameState(const RoutingPass& a, const RoutingPass& b) {
return a.viewport == b.viewport && a.scissorBox == b.scissorBox &&
a.depthRange == b.depthRange && a.scissorTest == b.scissorTest;
return a.viewport == b.viewport && a.scissorBox == b.scissorBox && a.depthRange == b.depthRange &&
a.scissorTest == b.scissorTest;
}
} // namespace ViewportRoutingImpl
@@ -256,8 +254,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint count = 0;
for (Uint index = 0; index < RenderStateParameters::MAX_VIEWPORTS; ++index) {
const RoutingPass resolved =
ResolveIndexState(parameters, index, surfaceWidth, surfaceHeight);
const RoutingPass resolved = ResolveIndexState(parameters, index, surfaceWidth, surfaceHeight);
Uint existing = 0;
for (; existing < count; ++existing) {
if (SameState(g_passes[existing], resolved)) break;
@@ -287,10 +284,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
const RoutingPass& entry = g_passes[pass];
g_GLESFuncs.glViewport(entry.viewport.x(), entry.viewport.y(), entry.viewport.z(),
entry.viewport.w());
g_GLESFuncs.glScissor(entry.scissorBox.x(), entry.scissorBox.y(), entry.scissorBox.z(),
entry.scissorBox.w());
g_GLESFuncs.glViewport(entry.viewport.x(), entry.viewport.y(), entry.viewport.z(), entry.viewport.w());
g_GLESFuncs.glScissor(entry.scissorBox.x(), entry.scissorBox.y(), entry.scissorBox.z(), entry.scissorBox.w());
// ES has one scissor-test enable where GL has sixteen, so the per-index bit becomes a
// per-pass glEnable/glDisable. This is the half DirectVulkan cannot do at all (Vulkan has
// no per-viewport scissor toggle either and has to widen a disabled index's rectangle to
@@ -382,10 +377,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// handle the verb record carried (MGPipeApplier's verb stash) and the SSBO view's
// resource resolves from it by handle - the frontend binding slot and the client slot
// allocator are never read (T2).
const MG_Pipe::MGPipeHandle verbBufferHandle =
MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
const Bool useNative =
!MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws();
const MG_Pipe::MGPipeHandle verbBufferHandle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
const Bool useNative = !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws();
commandBytes = CurrentIndirectCommandBytes(verbBufferHandle, commandBytes, commandOffset, useNative,
/*indexed=*/true);
if (useNative) {
@@ -394,8 +387,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto backendProgram = GetCurrentBackendProgram();
const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1;
if (paramsBinding >= 0) {
auto* resource =
BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle);
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle);
if (resource && resource->id != 0) {
BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<GLuint>(paramsBinding),
resource->id);
@@ -468,18 +460,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
SetCurrentBaseVertex(0);
// P5c (hd, CONTRACT-P5C §3.5): see ExecuteIndexedIndirectCommands - the verb's handle,
// never the frontend binding slot or the client allocator.
const MG_Pipe::MGPipeHandle verbBufferHandle =
MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
const Bool useNative =
!MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws();
const MG_Pipe::MGPipeHandle verbBufferHandle = MG_Pipe::MGPipeApplier().VerbIndirectBuffer;
const Bool useNative = !MG_Pipe::MGPipeHandleIsNull(verbBufferHandle) && SupportsNativeIndirectDraws();
commandBytes = CurrentIndirectCommandBytes(verbBufferHandle, commandBytes, commandOffset, useNative,
/*indexed=*/false);
if (useNative) {
const auto backendProgram = GetCurrentBackendProgram();
const Int paramsBinding = backendProgram ? backendProgram->GetIndirectParamsBinding() : -1;
if (paramsBinding >= 0) {
auto* resource =
BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle);
auto* resource = BufferImpl::EnsureBufferResourceForHandle(nullptr, verbBufferHandle);
if (resource && resource->id != 0) {
BufferImpl::BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast<GLuint>(paramsBinding),
resource->id);
@@ -497,8 +486,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
SetCurrentBaseInstance(cmd.baseInstance);
}
ForEachViewportRoutingPass([&] {
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
commandBytes, commandOffset, stride, i, 0, drawIndirectBuffer))
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(commandBytes, commandOffset,
stride, i, 0, drawIndirectBuffer))
return;
g_GLESFuncs.glDrawArraysIndirect(mode, reinterpret_cast<const void*>(cmdByteOffset));
});
@@ -513,8 +502,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
SetCurrentDrawID(static_cast<Uint32>(i));
SetCurrentBaseInstance(cmd.baseInstance);
ForEachViewportRoutingPass([&] {
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(
commandBytes, commandOffset, stride, i, 0, drawIndirectBuffer))
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndirectFetch(commandBytes, commandOffset,
stride, i, 0, drawIndirectBuffer))
return;
g_GLESFuncs.glDrawArraysInstanced(mode, static_cast<GLint>(cmd.first),
static_cast<GLsizei>(cmd.count),
@@ -526,7 +515,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
SetCurrentBaseInstance(0);
}
void Clear(GLbitfield mask) {
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER
DebugImpl::OpenGLScopeMarker marker(__func__);
@@ -559,7 +547,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glGetIntegerv(GL_SCISSOR_BOX, box);
g_GLESFuncs.glGetBooleanv(GL_COLOR_WRITEMASK, cmask);
g_GLESFuncs.glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
MGLOG_D("CLEAR fbo=%d color0=%d depth=%d mask=0x%x scissor=%d box=(%d,%d,%d,%d) cmask=%d%d%d%d cc=(%g,%g,%g,%g)",
MGLOG_D("CLEAR fbo=%d color0=%d depth=%d mask=0x%x scissor=%d box=(%d,%d,%d,%d) cmask=%d%d%d%d "
"cc=(%g,%g,%g,%g)",
fboId, color0, depthName, mask, (int)scissor, box[0], box[1], box[2], box[3], (int)cmask[0],
(int)cmask[1], (int)cmask[2], (int)cmask[3], cc[0], cc[1], cc[2], cc[3]);
}
@@ -637,8 +626,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// under a transport the same four numbers come off the draw record -
// the external index has no record counterpart and stays 0, which reads
// as "the record did not say".
if (const auto* rec = BoundFramebufferRecord(
FramebufferTarget::Draw)) {
if (const auto* rec = BoundFramebufferRecord(FramebufferTarget::Draw)) {
feVer = static_cast<Uint>(rec->ContentHash);
feDb0 = (int)rec->DrawBuffers[0];
feDb1 = (int)rec->DrawBuffers[1];
@@ -695,15 +683,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
// spell 0xFFFF cannot collide with a 16-bit sentinel, and likewise 8 -> 16.
GLenum WiderIndexType(GLenum indexType) {
switch (indexType) {
case GL_UNSIGNED_BYTE: return GL_UNSIGNED_SHORT;
case GL_UNSIGNED_SHORT: return GL_UNSIGNED_INT;
default: return 0;
case GL_UNSIGNED_BYTE:
return GL_UNSIGNED_SHORT;
case GL_UNSIGNED_SHORT:
return GL_UNSIGNED_INT;
default:
return 0;
}
}
Uint32 ReadIndex(const Uint8* source, SizeT i, SizeT indexSize) {
switch (indexSize) {
case 1: return source[i];
case 1:
return source[i];
case 2: {
Uint16 narrow = 0;
std::memcpy(&narrow, source + i * 2, sizeof(narrow));
@@ -719,21 +711,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
void WriteIndex(Uint8* destination, SizeT i, SizeT indexSize, Uint32 value) {
switch (indexSize) {
case 1: destination[i] = static_cast<Uint8>(value); break;
case 1:
destination[i] = static_cast<Uint8>(value);
break;
case 2: {
const Uint16 narrow = static_cast<Uint16>(value);
std::memcpy(destination + i * 2, &narrow, sizeof(narrow));
break;
}
default: std::memcpy(destination + i * 4, &value, sizeof(value)); break;
default:
std::memcpy(destination + i * 4, &value, sizeof(value));
break;
}
}
// True when any index in the range already holds the type's all-ones value, i.e. when
// that value is doing double duty as a real vertex index and so cannot also be the
// restart sentinel. Only asked on the rare substitution path.
Bool ContainsFixedRestartIndex(const Uint8* source, SizeT indexCount, SizeT indexSize,
Uint32 fixedMax) {
Bool ContainsFixedRestartIndex(const Uint8* source, SizeT indexCount, SizeT indexSize, Uint32 fixedMax) {
for (SizeT i = 0; i < indexCount; ++i) {
if (ReadIndex(source, i, indexSize) == fixedMax) return true;
}
@@ -781,7 +776,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, g_restartIndices.id);
SizeT capacity = g_restartIndices.capacity == 0 ? bytes : g_restartIndices.capacity;
while (capacity < bytes) capacity *= 2;
while (capacity < bytes)
capacity *= 2;
g_GLESFuncs.glBufferData(BufferImpl::TempBufferTarget, static_cast<GLsizeiptr>(capacity), nullptr,
GL_STREAM_DRAW);
g_restartIndices.capacity = capacity;
@@ -836,8 +832,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
}
ScopedRestartIndexSubstitution::ScopedRestartIndexSubstitution(GLenum indexType, GLsizei count,
const void* indices)
ScopedRestartIndexSubstitution::ScopedRestartIndexSubstitution(GLenum indexType, GLsizei count, const void* indices)
: m_kind(ResolveRestartSubstitution(indexType)), m_capOverride(m_kind), m_indices(indices),
m_indexType(indexType) {
if (m_kind != RestartSubstitutionKind::RewriteIndices) {
@@ -893,8 +888,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// GL buffer first - no writeback ever reaches the server's copy.
const Uint8* hostBytes =
elementResource
? BufferImpl::SplitHostBytesForCpuRead(*elementResource, elements,
"primitive_restart_substitution")
? BufferImpl::SplitHostBytesForCpuRead(*elementResource, elements, "primitive_restart_substitution")
: nullptr;
if (hostBytes == nullptr) {
MGLOG_E_ONCE("Draw skipped: GL_PRIMITIVE_RESTART with restart index %u needs a CPU-readable copy of "
@@ -1004,9 +998,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
PrepareForDraw(syncBit);
const ScopedRestartIndexSubstitution restart(type, count, indices);
if (!restart.DrawIsValid()) return;
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawElements(mode, count, restart.IndexType(), restart.Indices());
});
ForEachViewportRoutingPass(
[&] { g_GLESFuncs.glDrawElements(mode, count, restart.IndexType(), restart.Indices()); });
}
void DrawArrays(GLenum mode, GLint first, GLsizei count) {
@@ -1016,9 +1009,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
DrawSyncFlags syncBit = DrawSyncBit::None;
PrepareForDraw(syncBit);
VertexArrayImpl::SyncClientSideVertexArraysForDrawArrays(first, count);
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawArrays(mode, first, count);
});
ForEachViewportRoutingPass([&] { g_GLESFuncs.glDrawArrays(mode, first, count); });
}
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
@@ -1053,9 +1044,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Client-side arrays are uploaded per sub-draw range, like the single DrawArrays path.
VertexArrayImpl::SyncClientSideVertexArraysForDrawArrays(first[i], count[i]);
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawArrays(mode, first[i], count[i]);
});
ForEachViewportRoutingPass([&] { g_GLESFuncs.glDrawArrays(mode, first[i], count[i]); });
}
if (feedDrawID) SetCurrentDrawID(0);
}
@@ -1079,8 +1068,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
stride = sizeof(DrawElementsIndirectCommand);
}
if (stride < static_cast<GLsizei>(sizeof(DrawElementsIndirectCommand))) {
MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawElementsIndirectCommand));
MGLOG_E_ONCE("MultiDrawElementsIndirect skipped: stride %d is smaller than command size %zu", stride,
sizeof(DrawElementsIndirectCommand));
return;
}
@@ -1122,8 +1111,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
stride = sizeof(DrawElementsIndirectCommand);
}
if (stride < static_cast<GLsizei>(sizeof(DrawElementsIndirectCommand))) {
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawElementsIndirectCommand));
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: stride %d is smaller than command size %zu", stride,
sizeof(DrawElementsIndirectCommand));
return;
}
@@ -1144,18 +1133,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& applierState = MG_Pipe::MGPipeApplier();
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
sizeof(DrawElementsIndirectCommand);
sizeof(DrawElementsIndirectCommand);
auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer);
auto* paramResource =
BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
auto* paramResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
// P8-C: the count is CONSUMED here, so the parameter buffer is made current first
// (a store a shader wrote on this side is read back); the command buffer only gets a
// first copy when it has none - its fields are made current by the executor, and
// only when it consumes them (CurrentIndirectCommandBytes).
const Uint8* const parameterBytes =
paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource,
applierState.VerbIndirectParameterBuffer,
"indirect_count_parameter")
paramResource ? BufferImpl::SplitHostBytesForCpuRead(
*paramResource, applierState.VerbIndirectParameterBuffer, "indirect_count_parameter")
: nullptr;
const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr;
if (drawBytes == nullptr && drawResource != nullptr) {
@@ -1175,7 +1162,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// own rule for a buffer with no CPU shadow.
if (parameterBytes == nullptr || drawBytes == nullptr) {
MGLOG_E_ONCE("MultiDrawElementsIndirectCount skipped: CPU fallback cannot read the parameter or "
"draw-indirect buffer");
"draw-indirect buffer");
return;
}
BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes,
@@ -1186,9 +1173,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint32 actualDrawCount = 0;
std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount));
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset,
nullptr, static_cast<GLsizei>(actualDrawCount), stride,
"MultiDrawElementsIndirectCount");
ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset, nullptr,
static_cast<GLsizei>(actualDrawCount), stride, "MultiDrawElementsIndirectCount");
return;
}
@@ -1203,8 +1189,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
stride = sizeof(DrawArraysIndirectCommand);
}
if (stride < static_cast<GLsizei>(sizeof(DrawArraysIndirectCommand))) {
MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawArraysIndirectCommand));
MGLOG_E_ONCE("MultiDrawArraysIndirect skipped: stride %d is smaller than command size %zu", stride,
sizeof(DrawArraysIndirectCommand));
return;
}
@@ -1246,8 +1232,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
stride = sizeof(DrawArraysIndirectCommand);
}
if (stride < static_cast<GLsizei>(sizeof(DrawArraysIndirectCommand))) {
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu",
stride, sizeof(DrawArraysIndirectCommand));
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: stride %d is smaller than command size %zu", stride,
sizeof(DrawArraysIndirectCommand));
return;
}
@@ -1259,18 +1245,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto& applierState = MG_Pipe::MGPipeApplier();
const SizeT commandOffset = reinterpret_cast<SizeT>(indirect);
const SizeT commandBytes = commandOffset + static_cast<SizeT>(stride) * static_cast<SizeT>(maxdrawcount - 1) +
sizeof(DrawArraysIndirectCommand);
sizeof(DrawArraysIndirectCommand);
auto* drawResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectBuffer);
auto* paramResource =
BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
auto* paramResource = BufferImpl::FindBufferResourceForHandle(applierState.VerbIndirectParameterBuffer);
// P8-C: the count is CONSUMED here, so the parameter buffer is made current first
// (a store a shader wrote on this side is read back); the command buffer only gets a
// first copy when it has none - its fields are made current by the executor, and
// only when it consumes them (CurrentIndirectCommandBytes).
const Uint8* const parameterBytes =
paramResource ? BufferImpl::SplitHostBytesForCpuRead(*paramResource,
applierState.VerbIndirectParameterBuffer,
"indirect_count_parameter")
paramResource ? BufferImpl::SplitHostBytesForCpuRead(
*paramResource, applierState.VerbIndirectParameterBuffer, "indirect_count_parameter")
: nullptr;
const Uint8* drawBytes = drawResource ? BufferImpl::SplitHostBytes(*drawResource) : nullptr;
if (drawBytes == nullptr && drawResource != nullptr) {
@@ -1288,7 +1272,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (parameterBytes == nullptr || drawBytes == nullptr) {
MGLOG_E_ONCE("MultiDrawArraysIndirectCount skipped: CPU fallback cannot read the parameter or "
"draw-indirect buffer");
"draw-indirect buffer");
return;
}
BufferImpl::RequireStagedCoverage(*drawResource, drawBytes, commandOffset, commandBytes,
@@ -1300,8 +1284,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
std::memcpy(&actualDrawCount, parameterBytes + drawcount, sizeof(actualDrawCount));
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
ExecuteArraysIndirectCommands(mode, drawBytes + commandOffset, commandOffset, nullptr,
static_cast<GLsizei>(actualDrawCount), stride,
"MultiDrawArraysIndirectCount");
static_cast<GLsizei>(actualDrawCount), stride, "MultiDrawArraysIndirectCount");
return;
}
@@ -1326,9 +1309,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
PrepareForDraw(syncBit);
const ScopedRestartIndexSubstitution restart(type, count, indices);
if (!restart.DrawIsValid()) return;
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawRangeElements(mode, start, end, count, restart.IndexType(), restart.Indices());
});
ForEachViewportRoutingPass(
[&] { g_GLESFuncs.glDrawRangeElements(mode, start, end, count, restart.IndexType(), restart.Indices()); });
}
// True when the driver will apply baseInstance to the vertex fetch itself, in which case the
@@ -1355,7 +1337,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, basevertex,
baseinstance)) return;
baseinstance))
return;
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
PrepareForDraw(syncBit);
@@ -1365,9 +1348,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
SetCurrentBaseVertex(basevertex);
ForEachViewportRoutingPass([&] {
if (UseNativeBaseInstance()) {
g_GLESFuncs.glDrawElementsInstancedBaseVertexBaseInstanceEXT(mode, count, restart.IndexType(),
restart.Indices(), instancecount,
basevertex, baseinstance);
g_GLESFuncs.glDrawElementsInstancedBaseVertexBaseInstanceEXT(
mode, count, restart.IndexType(), restart.Indices(), instancecount, basevertex, baseinstance);
} else {
g_GLESFuncs.glDrawElementsInstancedBaseVertex(mode, count, restart.IndexType(), restart.Indices(),
instancecount, basevertex);
@@ -1380,7 +1362,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex) {
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, basevertex,
0)) return;
0))
return;
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
PrepareForDraw(syncBit);
const ScopedRestartIndexSubstitution restart(type, count, indices);
@@ -1396,7 +1379,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLuint baseinstance) {
if (!VertexArrayImpl::SyncClientSideVertexArraysForIndexedFetch(type, count, indices, instancecount, 0,
baseinstance)) return;
baseinstance))
return;
DrawSyncFlags syncBit = DrawSyncBit::IndexBuffer | DrawSyncBit::Instancing;
MGL_SCOPED_FETCH_BASE_INSTANCE(fetchScope, EmulatedFetchBaseInstance(baseinstance));
PrepareForDraw(syncBit);
@@ -1406,7 +1390,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
ForEachViewportRoutingPass([&] {
if (UseNativeBaseInstance()) {
g_GLESFuncs.glDrawElementsInstancedBaseInstanceEXT(mode, count, restart.IndexType(), restart.Indices(),
instancecount, baseinstance);
instancecount, baseinstance);
} else {
g_GLESFuncs.glDrawElementsInstanced(mode, count, restart.IndexType(), restart.Indices(), instancecount);
}
@@ -1471,9 +1455,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) {
DrawSyncFlags syncBit = DrawSyncBit::Instancing;
PrepareForDraw(syncBit);
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawArraysInstanced(mode, first, count, instancecount);
});
ForEachViewportRoutingPass([&] { g_GLESFuncs.glDrawArraysInstanced(mode, first, count, instancecount); });
}
void DrawArraysIndirect(GLenum mode, const void* indirect) {
@@ -271,8 +271,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLenum TakeXfbDriverError() {
const GLenum first = g_GLESFuncs.glGetError();
if (first == GL_NO_ERROR) return GL_NO_ERROR;
for (Int i = 0; i < kMaxDrainedXfbErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
}
for (Int i = 0; i < kMaxDrainedXfbErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {}
return first;
}
} // namespace
@@ -354,7 +353,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// InvalidateFramebufferBindingCache so no caller can forget; it is one line in D's
// file and it is on D's rework list.
static void InvalidateFramebufferHandleArmMemos() {
for (auto& memo : g_fboSyncedSerials) memo.valid = false;
for (auto& memo : g_fboSyncedSerials)
memo.valid = false;
g_fboRecordsTrusted = false;
}
@@ -521,7 +521,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
void DrainDriverErrors(const char* site);
static void DrainBlitErrors() { DrainDriverErrors("BlitFramebuffer"); }
static void DrainBlitErrors() {
DrainDriverErrors("BlitFramebuffer");
}
// ---------------------------------------------------------------------------------
// Shared guard for the emulation passes that have to DRAW to get their work done: the
@@ -622,9 +624,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glUseProgram(static_cast<GLuint>(m_program));
// Put back whatever the binding cache says lives on the borrowed unit, not what
// the driver happened to hold: see the class comment.
auto* cachedBound =
TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit]
[static_cast<SizeT>(TextureTarget::Texture2D)];
auto* cachedBound = TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit]
[static_cast<SizeT>(TextureTarget::Texture2D)];
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, cachedBound ? cachedBound->GetBackendTextureId() : 0);
TextureImpl::ActivateTextureUnit(m_activeTextureUnit);
VertexArrayImpl::BindBackendVAOId(static_cast<GLuint>(m_vertexArray));
@@ -636,8 +637,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLenum faces[2] = {GL_FRONT, GL_BACK};
for (SizeT face = 0; face < 2; ++face) {
g_GLESFuncs.glStencilFuncSeparate(faces[face], static_cast<GLenum>(m_stencilFunc[face]),
m_stencilRef[face],
static_cast<GLuint>(m_stencilValueMask[face]));
m_stencilRef[face], static_cast<GLuint>(m_stencilValueMask[face]));
g_GLESFuncs.glStencilOpSeparate(faces[face], static_cast<GLenum>(m_stencilFail[face]),
static_cast<GLenum>(m_stencilDepthFail[face]),
static_cast<GLenum>(m_stencilPass[face]));
@@ -717,15 +717,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
static constexpr Uint kClipDistanceCapabilityCount = 8;
Uint m_capabilityCount = kBaseCapabilityCount;
CapabilityState m_capabilities[kBaseCapabilityCount + kClipDistanceCapabilityCount] = {
{GL_SCISSOR_TEST, GL_FALSE}, {GL_DEPTH_TEST, GL_FALSE},
{GL_STENCIL_TEST, GL_FALSE}, {GL_CULL_FACE, GL_FALSE},
{GL_BLEND, GL_FALSE}, {GL_RASTERIZER_DISCARD, GL_FALSE},
{GL_POLYGON_OFFSET_FILL, GL_FALSE}, {GL_SAMPLE_ALPHA_TO_COVERAGE, GL_FALSE},
{GL_SAMPLE_COVERAGE, GL_FALSE}, {GL_SAMPLE_MASK, GL_FALSE},
{0x3000, GL_FALSE}, {0x3001, GL_FALSE},
{0x3002, GL_FALSE}, {0x3003, GL_FALSE},
{0x3004, GL_FALSE}, {0x3005, GL_FALSE},
{0x3006, GL_FALSE}, {0x3007, GL_FALSE},
{GL_SCISSOR_TEST, GL_FALSE},
{GL_DEPTH_TEST, GL_FALSE},
{GL_STENCIL_TEST, GL_FALSE},
{GL_CULL_FACE, GL_FALSE},
{GL_BLEND, GL_FALSE},
{GL_RASTERIZER_DISCARD, GL_FALSE},
{GL_POLYGON_OFFSET_FILL, GL_FALSE},
{GL_SAMPLE_ALPHA_TO_COVERAGE, GL_FALSE},
{GL_SAMPLE_COVERAGE, GL_FALSE},
{GL_SAMPLE_MASK, GL_FALSE},
{0x3000, GL_FALSE},
{0x3001, GL_FALSE},
{0x3002, GL_FALSE},
{0x3003, GL_FALSE},
{0x3004, GL_FALSE},
{0x3005, GL_FALSE},
{0x3006, GL_FALSE},
{0x3007, GL_FALSE},
};
};
@@ -870,7 +879,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLenum m_target;
};
static void ClearGLErrors() { DrainDriverErrors("DirectGLES"); }
static void ClearGLErrors() {
DrainDriverErrors("DirectGLES");
}
// DirectGLESReadback.cpp defines it: E's extent-scoped stand-in for a
// driver that refuses to read a colour level back, asked here too so a host reaches the store
@@ -945,7 +956,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
extern std::atomic<Uint64> g_nativeRegistryEpoch;
extern std::atomic<Uint64> g_surfaceSizeEpoch;
static void NoteDrawSurfaceMoved() { g_surfaceSizeEpoch.fetch_add(1, std::memory_order_acq_rel); }
static void NoteDrawSurfaceMoved() {
g_surfaceSizeEpoch.fetch_add(1, std::memory_order_acq_rel);
}
Vector<NativeContextTuple*>& NativeContexts();
@@ -1071,7 +1084,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#if MOBILEGL_BUILD_DISAGGREGATED
namespace SharedImageImpl {
#if defined(__ANDROID__)
using MG_Util::BackendLoader::HasExtensionWord;
using MG_Util::BackendLoader::HasExtensionWord;
#endif
} // namespace SharedImageImpl
#endif
@@ -57,8 +57,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
const SizeT rowBytes = static_cast<SizeT>(width) * dstPixelBytes;
const SizeT packedSize = dstOffset + static_cast<SizeT>(height - 1) * dstRowStride + rowBytes;
const auto& pixelPackBufferObject =
SplitReadbackPackBuffer();
const auto& pixelPackBufferObject = SplitReadbackPackBuffer();
const SizeT pboOffset = reinterpret_cast<SizeT>(pixels);
if (pixelPackBufferObject && pboOffset + packedSize > pixelPackBufferObject->GetSize()) {
MGLOG_E_ONCE("ReadPixels: %s readback PBO is too small", what);
@@ -164,7 +163,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
static StageSlot s_slots[2]; // [0] depth, [1] stencil
// Each slot's own framebuffer (see StageSlot), one per native context.
static NativeContextName s_slotFramebuffers[2];
static NativeContextName& SlotFramebuffer(const StageSlot& slot) { return s_slotFramebuffers[&slot - s_slots]; }
static NativeContextName& SlotFramebuffer(const StageSlot& slot) {
return s_slotFramebuffers[&slot - s_slots];
}
// `precision highp int` is not decoration: ESSL 3.00 defaults integers to mediump in
// the fragment language, which is allowed to be 16 bits - it would saw the top half
@@ -293,15 +294,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLint stencilBits = 0;
GLint componentType = GL_UNSIGNED_NORMALIZED;
ClearGLErrors();
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,
AttachmentPointFor(isDefault, false),
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, AttachmentPointFor(isDefault, false),
GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depthBits);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER,
AttachmentPointFor(isDefault, true),
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, AttachmentPointFor(isDefault, true),
GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencilBits);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(
GL_READ_FRAMEBUFFER, AttachmentPointFor(isDefault, false),
GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &componentType);
g_GLESFuncs.glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, AttachmentPointFor(isDefault, false),
GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &componentType);
ClearGLErrors();
const GLint aspectBits = stencilAspect ? stencilBits : depthBits;
@@ -439,9 +437,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!EnsureStageTexture(slot, format, width, height)) {
continue;
}
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER,
ReplicateBlitImpl::ScratchAttachmentFor(format), GL_TEXTURE_2D,
slot.texture, 0);
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, ReplicateBlitImpl::ScratchAttachmentFor(format),
GL_TEXTURE_2D, slot.texture, 0);
if (g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
continue;
}
@@ -473,8 +470,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Float uvScaleX = static_cast<Float>(width) / static_cast<Float>(slot.width);
const Float uvScaleY = static_cast<Float>(height) / static_cast<Float>(slot.height);
g_GLESFuncs.glUseProgram(stencilAspect ? s_stencilProgram : s_depthProgram);
g_GLESFuncs.glUniform4f(stencilAspect ? s_stencilUvTransform : s_depthUvTransform, uvScaleX, uvScaleY,
0.0f, 0.0f);
g_GLESFuncs.glUniform4f(stencilAspect ? s_stencilUvTransform : s_depthUvTransform, uvScaleX, uvScaleY, 0.0f,
0.0f);
g_GLESFuncs.glViewport(0, 0, width, height);
ClearGLErrors();
g_GLESFuncs.glDrawArrays(GL_TRIANGLES, 0, 3);
@@ -496,7 +493,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLenum readError = g_GLESFuncs.glGetError();
if (readError != GL_NO_ERROR) {
MGLOG_E_ONCE("ReadPixels: could not read the %s conversion target back: %s",
stencilAspect ? "stencil" : "depth", MG_Util::ConvertGLEnumToString(readError).c_str());
stencilAspect ? "stencil" : "depth", MG_Util::ConvertGLEnumToString(readError).c_str());
return false;
}
return true;
@@ -516,7 +513,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, SlotFramebuffer(slot));
if (!StageAspect(slot, candidates, stencilAspect, isDefault, x, y, width, height)) {
MGLOG_E_ONCE("ReadPixels: no ES-compatible scratch format for the %s source",
stencilAspect ? "stencil" : "depth");
stencilAspect ? "stencil" : "depth");
return false;
}
@@ -525,8 +522,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
FramebufferImpl::BindFramebufferId(GL_DRAW_FRAMEBUFFER, s_colorFramebuffer);
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
s_colorTexture, 0);
g_GLESFuncs.glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, s_colorTexture,
0);
if (g_GLESFuncs.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
MGLOG_E_ONCE("ReadPixels: the depth/stencil conversion target is not renderable");
return false;
@@ -634,8 +631,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
MGLOG_E_ONCE("ReadPixels: no depth readback path is available: native reads failed with %s and the "
"sampling emulation could not service the source",
MG_Util::ConvertGLEnumToString(floatError).c_str());
"sampling emulation could not service the source",
MG_Util::ConvertGLEnumToString(floatError).c_str());
return false;
}
@@ -645,19 +642,25 @@ namespace MobileGL::MG_Backend::DirectGLES {
// client asked for otherwise - 2^n - 1 for an unsigned width, 2^(n-1) - 1 for a signed one.
// Sharing one denominator across the two signednesses would put a 0.5 depth at 32768, which
// as a GLshort is -32768, so GL_BYTE/GL_SHORT/GL_INT each keep their own.
static Bool ReadPixelsDepthComponent(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type,
void* pixels) {
static Bool ReadPixelsDepthComponent(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) {
SizeT dstPixelBytes = 0;
switch (type) {
case GL_BYTE:
case GL_UNSIGNED_BYTE: dstPixelBytes = sizeof(Uint8); break;
case GL_UNSIGNED_BYTE:
dstPixelBytes = sizeof(Uint8);
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
case GL_HALF_FLOAT: dstPixelBytes = sizeof(Uint16); break;
case GL_HALF_FLOAT:
dstPixelBytes = sizeof(Uint16);
break;
case GL_INT:
case GL_UNSIGNED_INT:
case GL_FLOAT: dstPixelBytes = sizeof(Uint32); break;
default: return false;
case GL_FLOAT:
dstPixelBytes = sizeof(Uint32);
break;
default:
return false;
}
if (width <= 0 || height <= 0) {
return true;
@@ -668,47 +671,41 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true; // already reported; nothing was written, as before
}
StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "depth",
[&](GLsizei row, Uint8* dst) {
const Float* srcRow =
depth.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
for (GLsizei col = 0; col < width; ++col) {
const Float depthValue = srcRow[col];
switch (type) {
case GL_BYTE:
reinterpret_cast<Int8*>(dst)[col] = static_cast<Int8>(
NormalizedDepthToUnsigned(depthValue, 127.0));
break;
case GL_UNSIGNED_BYTE:
dst[col] = static_cast<Uint8>(
NormalizedDepthToUnsigned(depthValue, 255.0));
break;
case GL_SHORT:
reinterpret_cast<Int16*>(dst)[col] = static_cast<Int16>(
NormalizedDepthToUnsigned(depthValue, 32767.0));
break;
case GL_UNSIGNED_SHORT:
reinterpret_cast<Uint16*>(dst)[col] = static_cast<Uint16>(
NormalizedDepthToUnsigned(depthValue, 65535.0));
break;
case GL_INT:
reinterpret_cast<Int32*>(dst)[col] = static_cast<Int32>(
NormalizedDepthToUnsigned(depthValue, 2147483647.0));
break;
case GL_UNSIGNED_INT:
reinterpret_cast<Uint32*>(dst)[col] =
NormalizedDepthToUnsigned(depthValue, 4294967295.0);
break;
case GL_HALF_FLOAT:
reinterpret_cast<Uint16*>(dst)[col] =
MG_Util::EncodeFloatToHalfBits(depthValue);
break;
default:
reinterpret_cast<GLfloat*>(dst)[col] = depthValue;
break;
}
}
});
StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "depth", [&](GLsizei row, Uint8* dst) {
const Float* srcRow = depth.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
for (GLsizei col = 0; col < width; ++col) {
const Float depthValue = srcRow[col];
switch (type) {
case GL_BYTE:
reinterpret_cast<Int8*>(dst)[col] = static_cast<Int8>(NormalizedDepthToUnsigned(depthValue, 127.0));
break;
case GL_UNSIGNED_BYTE:
dst[col] = static_cast<Uint8>(NormalizedDepthToUnsigned(depthValue, 255.0));
break;
case GL_SHORT:
reinterpret_cast<Int16*>(dst)[col] =
static_cast<Int16>(NormalizedDepthToUnsigned(depthValue, 32767.0));
break;
case GL_UNSIGNED_SHORT:
reinterpret_cast<Uint16*>(dst)[col] =
static_cast<Uint16>(NormalizedDepthToUnsigned(depthValue, 65535.0));
break;
case GL_INT:
reinterpret_cast<Int32*>(dst)[col] =
static_cast<Int32>(NormalizedDepthToUnsigned(depthValue, 2147483647.0));
break;
case GL_UNSIGNED_INT:
reinterpret_cast<Uint32*>(dst)[col] = NormalizedDepthToUnsigned(depthValue, 4294967295.0);
break;
case GL_HALF_FLOAT:
reinterpret_cast<Uint16*>(dst)[col] = MG_Util::EncodeFloatToHalfBits(depthValue);
break;
default:
reinterpret_cast<GLfloat*>(dst)[col] = depthValue;
break;
}
}
});
return true;
}
@@ -762,16 +759,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
MGLOG_E_ONCE("ReadPixels: no stencil readback path is available: native reads failed with %s and the "
"sampling emulation could not service the source",
MG_Util::ConvertGLEnumToString(packedError).c_str());
"sampling emulation could not service the source",
MG_Util::ConvertGLEnumToString(packedError).c_str());
return false;
}
// GL_STENCIL_INDEX readback into the client's integer layout, honouring the PACK
// pixel-store parameters. Handles the widths desktop clients ask for; the stencil
// values themselves are always 8 bits.
static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type,
void* pixels) {
static Bool ReadPixelsStencilViaNative(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type, void* pixels) {
// GL 4.6 core 18.2.8: a stencil index is written unconverted into whichever integer width
// the client asked for, and converted to a float value for the floating-point widths - both
// GL_FLOAT and GL_HALF_FLOAT. The signed widths are as legal as the unsigned ones - the CTS
@@ -781,14 +777,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT dstPixelBytes = 0;
switch (type) {
case GL_UNSIGNED_BYTE:
case GL_BYTE: dstPixelBytes = sizeof(Uint8); break;
case GL_BYTE:
dstPixelBytes = sizeof(Uint8);
break;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
case GL_HALF_FLOAT: dstPixelBytes = sizeof(Uint16); break;
case GL_HALF_FLOAT:
dstPixelBytes = sizeof(Uint16);
break;
case GL_UNSIGNED_INT:
case GL_INT: dstPixelBytes = sizeof(Uint32); break;
case GL_FLOAT: dstPixelBytes = sizeof(GLfloat); break;
default: return false;
case GL_INT:
dstPixelBytes = sizeof(Uint32);
break;
case GL_FLOAT:
dstPixelBytes = sizeof(GLfloat);
break;
default:
return false;
}
if (width <= 0 || height <= 0) {
return true;
@@ -799,33 +804,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "stencil",
[&](GLsizei row, Uint8* dst) {
const Uint8* srcRow =
raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
for (GLsizei col = 0; col < width; ++col) {
switch (type) {
case GL_UNSIGNED_BYTE:
case GL_BYTE:
dst[static_cast<SizeT>(col)] = srcRow[col];
break;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
reinterpret_cast<Uint16*>(dst)[col] = srcRow[col];
break;
case GL_FLOAT:
reinterpret_cast<GLfloat*>(dst)[col] = static_cast<GLfloat>(srcRow[col]);
break;
case GL_HALF_FLOAT:
reinterpret_cast<Uint16*>(dst)[col] =
MG_Util::EncodeFloatToHalfBits(static_cast<Float>(srcRow[col]));
break;
default:
reinterpret_cast<Uint32*>(dst)[col] = srcRow[col];
break;
}
}
});
StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "stencil", [&](GLsizei row, Uint8* dst) {
const Uint8* srcRow = raw.data() + static_cast<SizeT>(row) * static_cast<SizeT>(width);
for (GLsizei col = 0; col < width; ++col) {
switch (type) {
case GL_UNSIGNED_BYTE:
case GL_BYTE:
dst[static_cast<SizeT>(col)] = srcRow[col];
break;
case GL_UNSIGNED_SHORT:
case GL_SHORT:
reinterpret_cast<Uint16*>(dst)[col] = srcRow[col];
break;
case GL_FLOAT:
reinterpret_cast<GLfloat*>(dst)[col] = static_cast<GLfloat>(srcRow[col]);
break;
case GL_HALF_FLOAT:
reinterpret_cast<Uint16*>(dst)[col] =
MG_Util::EncodeFloatToHalfBits(static_cast<Float>(srcRow[col]));
break;
default:
reinterpret_cast<Uint32*>(dst)[col] = srcRow[col];
break;
}
}
});
return true;
}
@@ -837,9 +840,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
void* pixels) {
SizeT dstPixelBytes = 0;
switch (type) {
case GL_UNSIGNED_INT_24_8: dstPixelBytes = sizeof(Uint32); break;
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: dstPixelBytes = sizeof(Float) + sizeof(Uint32); break;
default: return false;
case GL_UNSIGNED_INT_24_8:
dstPixelBytes = sizeof(Uint32);
break;
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
dstPixelBytes = sizeof(Float) + sizeof(Uint32);
break;
default:
return false;
}
if (width <= 0 || height <= 0) {
return true;
@@ -999,7 +1007,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
data = std::move(expanded);
}
static void DrainESErrors() { DrainDriverErrors("ReadPixels"); }
static void DrainESErrors() {
DrainDriverErrors("ReadPixels");
}
static GLenum QueryReadAttachmentComponentType() {
GLint framebufferId = 0;
@@ -1019,7 +1029,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return componentType != 0 ? static_cast<GLenum>(componentType) : GL_UNSIGNED_NORMALIZED;
}
// Reads the current READ framebuffer as wide RGBA(_INTEGER) and repacks the pixels into the client's
// (format, type) layout. Returns false when the combination is not convertible (the caller keeps its
// "not implemented" skip); returns true when the request was handled, even if it degraded to a logged no-op.
@@ -1062,28 +1071,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (width <= 0 || height <= 0) {
return true;
}
const auto& pixelPackBufferObject =
SplitReadbackPackBuffer();
const auto& pixelPackBufferObject = SplitReadbackPackBuffer();
if (!pixelPackBufferObject && pixels == nullptr) {
return true;
}
const GLenum attachmentComponentType = QueryReadAttachmentComponentType();
const Bool integerAttachment =
attachmentComponentType == GL_INT || attachmentComponentType == GL_UNSIGNED_INT;
const Bool integerAttachment = attachmentComponentType == GL_INT || attachmentComponentType == GL_UNSIGNED_INT;
// A normalized image carrier is EXACTLY the case where the two disagree on purpose, and
// it is the caller - which knows the TEXTURE being read, not just the attachment - that
// says so. An integer client format through such a carrier is not a shape GL can ask for
// (the frontend format is normalized), so it is refused here rather than converted.
if (normalizedCarrier.Active() && (mapping.isInteger || !integerAttachment)) {
MGLOG_E_ONCE("Readback conversion: a normalized image carrier was read as %s, which is not a "
"normalized client format; skipping",
MG_Util::ConvertGLEnumToString(format).c_str());
"normalized client format; skipping",
MG_Util::ConvertGLEnumToString(format).c_str());
return true;
}
if (!normalizedCarrier.Active() && mapping.isInteger != integerAttachment) {
MGLOG_E_ONCE("Readback conversion: integer-ness of format %s does not match the read buffer, skipping",
MG_Util::ConvertGLEnumToString(format).c_str());
MG_Util::ConvertGLEnumToString(format).c_str());
return true;
}
@@ -1111,9 +1118,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
(implType == GL_INT || implType == GL_UNSIGNED_INT)) {
candidates[candidateCount++] = {static_cast<GLenum>(implFormat), static_cast<GLenum>(implType)};
}
candidates[candidateCount++] = {
GL_RGBA_INTEGER,
attachmentComponentType == GL_INT ? static_cast<GLenum>(GL_INT) : static_cast<GLenum>(GL_UNSIGNED_INT)};
candidates[candidateCount++] = {GL_RGBA_INTEGER, attachmentComponentType == GL_INT
? static_cast<GLenum>(GL_INT)
: static_cast<GLenum>(GL_UNSIGNED_INT)};
} else {
if (GetWideReadChannelCount(static_cast<GLenum>(implFormat)) > 0 && !IsIntegerReadFormat(implFormat) &&
(CanDecodeWideSourceType(static_cast<GLenum>(implType)) ||
@@ -1152,8 +1159,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const Int channels = GetWideReadChannelCount(candidate.format);
const SizeT candidateComponentSize = GetReadbackComponentSize(candidate.type);
wide.resize(static_cast<SizeT>(width) * static_cast<SizeT>(height) *
static_cast<SizeT>(channels) * candidateComponentSize);
wide.resize(static_cast<SizeT>(width) * static_cast<SizeT>(height) * static_cast<SizeT>(channels) *
candidateComponentSize);
g_GLESFuncs.glReadPixels(x, y, width, height, candidate.format, candidate.type, wide.data());
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
wideType = candidate.type;
@@ -1164,7 +1171,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (wideType == GL_NONE) {
MGLOG_E_ONCE("Readback conversion: ES accepted no wide read type for format %s type %s, skipping readback",
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
return true;
}
@@ -1183,8 +1190,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto denominator = static_cast<Float>(normalizedCarrier.ChannelMax[channel]);
if (normalizedCarrier.SignedNormalized) {
const auto signedCode = static_cast<Int16>(static_cast<Uint16>(code));
dst[i * 4 + channel] =
std::max(static_cast<Float>(signedCode) / denominator, -1.0f);
dst[i * 4 + channel] = std::max(static_cast<Float>(signedCode) / denominator, -1.0f);
} else {
dst[i * 4 + channel] = static_cast<Float>(code) / denominator;
}
@@ -1236,7 +1242,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
switch (wideType) {
case GL_FLOAT: {
auto* values = reinterpret_cast<Float*>(wide.data());
for (SizeT i = 0; i < valueCount; ++i) values[i] = std::clamp(values[i], 0.0f, 1.0f);
for (SizeT i = 0; i < valueCount; ++i)
values[i] = std::clamp(values[i], 0.0f, 1.0f);
break;
}
case GL_HALF_FLOAT: {
@@ -1250,12 +1257,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
case GL_SHORT: {
// Signed normalized: the negative half is exactly what the clamp removes.
auto* values = reinterpret_cast<Int16*>(wide.data());
for (SizeT i = 0; i < valueCount; ++i) values[i] = std::max<Int16>(values[i], 0);
for (SizeT i = 0; i < valueCount; ++i)
values[i] = std::max<Int16>(values[i], 0);
break;
}
case GL_BYTE: {
auto* values = reinterpret_cast<Int8*>(wide.data());
for (SizeT i = 0; i < valueCount; ++i) values[i] = std::max<Int8>(values[i], 0);
for (SizeT i = 0; i < valueCount; ++i)
values[i] = std::max<Int8>(values[i], 0);
break;
}
default:
@@ -1335,7 +1344,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Re-bind whatever the binding cache says lives on the temp unit, so the cache stays
// truthful without a driver query (same discipline as CopyR32FTexture2D).
auto* cachedBound = TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit]
[static_cast<SizeT>(TextureTarget::Texture2D)];
[static_cast<SizeT>(TextureTarget::Texture2D)];
g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, cachedBound ? cachedBound->GetBackendTextureId() : 0);
if (storageError != GL_NO_ERROR) {
g_GLESFuncs.glDeleteTextures(1, &texture);
@@ -1361,9 +1370,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// 64 MiB image parked for the rest of the process. The cap is 1 MiB of GL_R32UI, with
// 4 MiB of staging behind it because the read lands four words per texel.
constexpr SizeT kMaxScratchTexels = SizeT{1} << 18;
const GLsizei bandRows = std::max<GLsizei>(
1, static_cast<GLsizei>(std::min<SizeT>(kMaxScratchTexels / static_cast<SizeT>(width),
static_cast<SizeT>(height))));
const GLsizei bandRows =
std::max<GLsizei>(1, static_cast<GLsizei>(std::min<SizeT>(kMaxScratchTexels / static_cast<SizeT>(width),
static_cast<SizeT>(height))));
const GLuint scratch = EnsurePackedWordScratchTexture(width, bandRows);
if (scratch == 0) return false;
@@ -1390,13 +1399,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
ClearGLErrors();
for (GLsizei y = 0; y < height; y += bandRows) {
const GLsizei rows = std::min(bandRows, height - y);
g_GLESFuncs.glCopyImageSubData(texture, backendTarget, level, 0, y, slice, scratch, GL_TEXTURE_2D, 0, 0,
0, 0, width, rows, 1);
g_GLESFuncs.glCopyImageSubData(texture, backendTarget, level, 0, y, slice, scratch, GL_TEXTURE_2D, 0, 0, 0,
0, width, rows, 1);
g_GLESFuncs.glReadPixels(0, 0, width, rows, GL_RGBA_INTEGER, GL_UNSIGNED_INT, staging.data());
for (GLsizei row = 0; row < rows; ++row) {
const Uint32* srcRow = staging.data() + static_cast<SizeT>(row) * wordsPerRow;
Uint32* dstRow = outWords + static_cast<SizeT>(y + row) * static_cast<SizeT>(width);
for (GLsizei x = 0; x < width; ++x) dstRow[x] = srcRow[static_cast<SizeT>(x) * 4];
for (GLsizei x = 0; x < width; ++x)
dstRow[x] = srcRow[static_cast<SizeT>(x) * 4];
}
}
const GLenum error = g_GLESFuncs.glGetError();
@@ -1409,22 +1419,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
// Included in DirectGLES.cpp after the native packed-word readback helper.
// A storage promotion needs the driver's own texels as owned tight rows, not
// client PACK layout or a fallback into a frontend texture's potentially stale
// shadow.
// Included in DirectGLES.cpp after the native packed-word readback helper.
// A storage promotion needs the driver's own texels as owned tight rows, not
// client PACK layout or a fallback into a frontend texture's potentially stale
// shadow.
class ScopedWireTextureReadLevel {
public:
ScopedWireTextureReadLevel(GLuint texture, GLenum target, GLint level)
: m_texture(texture), m_target(target) {
ScopedWireTextureReadLevel(GLuint texture, GLenum target, GLint level) : m_texture(texture), m_target(target) {
switch (target) {
case GL_TEXTURE_2D: m_binding = GL_TEXTURE_BINDING_2D; break;
case GL_TEXTURE_3D: m_binding = GL_TEXTURE_BINDING_3D; break;
case GL_TEXTURE_2D_ARRAY: m_binding = GL_TEXTURE_BINDING_2D_ARRAY; break;
case GL_TEXTURE_CUBE_MAP: m_binding = GL_TEXTURE_BINDING_CUBE_MAP; break;
case GL_TEXTURE_CUBE_MAP_ARRAY: m_binding = GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; break;
default: return;
case GL_TEXTURE_2D:
m_binding = GL_TEXTURE_BINDING_2D;
break;
case GL_TEXTURE_3D:
m_binding = GL_TEXTURE_BINDING_3D;
break;
case GL_TEXTURE_2D_ARRAY:
m_binding = GL_TEXTURE_BINDING_2D_ARRAY;
break;
case GL_TEXTURE_CUBE_MAP:
m_binding = GL_TEXTURE_BINDING_CUBE_MAP;
break;
case GL_TEXTURE_CUBE_MAP_ARRAY:
m_binding = GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
break;
default:
return;
}
GLint previous = 0;
DrainESErrors();
@@ -1468,22 +1487,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool m_saved = false, m_valid = false;
};
static Bool ReadWireColorRowsTight(GLsizei width, GLsizei height, GLenum format, GLenum type,
Bool forceOpaqueAlpha, const NormalizedImageCarrierRead& carrier,
Vector<Uint8>& bytes) {
static Bool ReadWireColorRowsTight(GLsizei width, GLsizei height, GLenum format, GLenum type, Bool forceOpaqueAlpha,
const NormalizedImageCarrierRead& carrier, Vector<Uint8>& bytes) {
ReadbackChannelMapping mapping{};
if (!GetReadbackChannelMapping(format, mapping)) return false;
const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, type);
if (!pixelBytes || width <= 0 || height <= 0) return false;
const GLenum componentType = QueryReadAttachmentComponentType();
const Bool integerAttachment = componentType == GL_INT || componentType == GL_UNSIGNED_INT;
if (carrier.Active() ? (mapping.isInteger || !integerAttachment)
: mapping.isInteger != integerAttachment) return false;
if (carrier.Active() ? (mapping.isInteger || !integerAttachment) : mapping.isInteger != integerAttachment)
return false;
GLint implFormat = 0, implType = 0;
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_FORMAT, &implFormat);
g_GLESFuncs.glGetIntegerv(GL_IMPLEMENTATION_COLOR_READ_TYPE, &implType);
struct Candidate { GLenum format, type; };
struct Candidate {
GLenum format, type;
};
Vector<Candidate> candidates;
if (carrier.Active()) {
candidates.push_back({GL_RGBA_INTEGER, GL_UNSIGNED_INT});
@@ -1501,7 +1521,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (componentType == GL_SIGNED_NORMALIZED) {
candidates.push_back({GL_RGBA, GL_SHORT});
candidates.push_back({GL_RGBA, GL_BYTE});
} else candidates.push_back({GL_RGBA, GL_UNSIGNED_BYTE});
} else
candidates.push_back({GL_RGBA, GL_UNSIGNED_BYTE});
}
ScopedPixelPackBuffer packBuffer(0);
ScopedPackState packState(PixelStoreImpl::PackState{1, 0, 0, 0});
@@ -1514,7 +1535,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
channels = GetWideReadChannelCount(candidate.format);
wide.resize(pixels * static_cast<SizeT>(channels) * GetReadbackComponentSize(candidate.type));
g_GLESFuncs.glReadPixels(0, 0, width, height, candidate.format, candidate.type, wide.data());
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) { wideType = candidate.type; break; }
if (g_GLESFuncs.glGetError() == GL_NO_ERROR) {
wideType = candidate.type;
break;
}
}
if (wideType == GL_NONE) return false;
@@ -1525,12 +1549,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT i = 0; i < pixels; ++i) {
for (SizeT c = 0; c < 4; ++c) {
const Float divisor = static_cast<Float>(carrier.ChannelMax[c]);
dst[i * 4 + c] = carrier.SignedNormalized
? std::max(static_cast<Float>(static_cast<Int16>(static_cast<Uint16>(src[i * 4 + c]))) / divisor, -1.0f)
: static_cast<Float>(src[i * 4 + c]) / divisor;
dst[i * 4 + c] =
carrier.SignedNormalized
? std::max(static_cast<Float>(static_cast<Int16>(static_cast<Uint16>(src[i * 4 + c]))) /
divisor,
-1.0f)
: static_cast<Float>(src[i * 4 + c]) / divisor;
}
}
wide = Move(decoded); wideType = GL_FLOAT; channels = 4;
wide = Move(decoded);
wideType = GL_FLOAT;
channels = 4;
} else if (wideType == GL_UNSIGNED_INT_2_10_10_10_REV) {
Vector<Uint8> decoded(pixels * 4 * sizeof(Float));
auto* dst = reinterpret_cast<Float*>(decoded.data());
@@ -1542,7 +1571,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
dst[i * 4 + 2] = static_cast<Float>((word >> 20) & 1023u) / 1023.0f;
dst[i * 4 + 3] = static_cast<Float>((word >> 30) & 3u) / 3.0f;
}
wide = Move(decoded); wideType = GL_FLOAT; channels = 4;
wide = Move(decoded);
wideType = GL_FLOAT;
channels = 4;
}
if (channels < 4) ExpandNarrowWideRead(wide, pixels, channels, wideType);
if (forceOpaqueAlpha) ForceWideReadAlphaToOne(wide, pixels, wideType);
@@ -1550,30 +1581,33 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT sourceRowBytes = static_cast<SizeT>(width) * 4 * GetReadbackComponentSize(wideType);
for (GLsizei row = 0; row < height; ++row)
ClientReadback::ConvertWideReadbackRow(wide.data() + static_cast<SizeT>(row) * sourceRowBytes,
converted.data() + static_cast<SizeT>(row) * width * pixelBytes, width, wideType, mapping, type);
converted.data() + static_cast<SizeT>(row) * width * pixelBytes,
width, wideType, mapping, type);
bytes = Move(converted);
return true;
}
static Bool ReadWireDepthStencilLevelTight(GLuint texture, TextureTarget target,
TextureUploadTarget uploadTarget, GLint level,
const IntVec3& extent, GLenum format, GLenum type,
static Bool ReadWireDepthStencilLevelTight(GLuint texture, TextureTarget target, TextureUploadTarget uploadTarget,
GLint level, const IntVec3& extent, GLenum format, GLenum type,
Vector<Uint8>& bytes) {
const SizeT pixelBytes = MG_Util::GetInputBytesPerPixel(
MG_Util::ConvertGLEnumToTextureInputFormat(format), MG_Util::ConvertGLEnumToTexturePixelDataType(type));
const SizeT pixelBytes = MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format),
MG_Util::ConvertGLEnumToTexturePixelDataType(type));
if (!pixelBytes) return false;
const Bool needDepth = format == GL_DEPTH_COMPONENT || format == GL_DEPTH_STENCIL;
const Bool needStencil = format == GL_STENCIL_INDEX || format == GL_DEPTH_STENCIL;
ScopedFramebufferBinding bindings(true, false);
GLuint fbo = 0;
g_GLESFuncs.glGenFramebuffers(1, &fbo);
struct DeleteFbo { GLuint id; ~DeleteFbo() { g_GLESFuncs.glDeleteFramebuffers(1, &id); } } cleanup{fbo};
struct DeleteFbo {
GLuint id;
~DeleteFbo() { g_GLESFuncs.glDeleteFramebuffers(1, &id); }
} cleanup{fbo};
FramebufferImpl::BindFramebufferId(GL_READ_FRAMEBUFFER, fbo);
ScopedPixelPackBuffer packBuffer(0);
ScopedPackState packState(PixelStoreImpl::PackState{1, 0, 0, 0});
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
const GLenum point = needDepth ? (needStencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT)
: GL_STENCIL_ATTACHMENT;
const GLenum point =
needDepth ? (needStencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT) : GL_STENCIL_ATTACHMENT;
const SizeT count = static_cast<SizeT>(extent.x()) * extent.y();
Vector<Uint8> result(count * extent.z() * pixelBytes);
if (g_GLESFuncs.glMemoryBarrier) g_GLESFuncs.glMemoryBarrier(GL_ALL_BARRIER_BITS);
@@ -1584,7 +1618,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, point, texture, level, z);
else
g_GLESFuncs.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, point,
TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget), texture, level);
TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget),
texture, level);
g_GLESFuncs.glReadBuffer(GL_NONE);
if (g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false;
Vector<Float> depth;
@@ -1601,24 +1636,33 @@ namespace MobileGL::MG_Backend::DirectGLES {
} else if (type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV) {
Memcpy(dst + i * pixelBytes, &depth[i], sizeof(Float));
Memcpy(dst + i * pixelBytes + sizeof(Float), &s, sizeof(s));
} else return false;
} else
return false;
} else if (type == GL_FLOAT) {
const Float value = needDepth ? depth[i] : static_cast<Float>(stencil[i]);
Memcpy(dst + i * pixelBytes, &value, sizeof(value));
} else if (type == GL_HALF_FLOAT) {
const Uint16 value = MG_Util::EncodeFloatToHalfBits(
needDepth ? depth[i] : static_cast<Float>(stencil[i]));
const Uint16 value =
MG_Util::EncodeFloatToHalfBits(needDepth ? depth[i] : static_cast<Float>(stencil[i]));
Memcpy(dst + i * pixelBytes, &value, sizeof(value));
} else {
const Bool signedType = type == GL_BYTE || type == GL_SHORT || type == GL_INT;
const Double maximum = signedType
? (pixelBytes == 1 ? 127.0 : pixelBytes == 2 ? 32767.0 : 2147483647.0)
: (pixelBytes == 1 ? 255.0 : pixelBytes == 2 ? 65535.0 : 4294967295.0);
const Double maximum = signedType ? (pixelBytes == 1 ? 127.0
: pixelBytes == 2 ? 32767.0
: 2147483647.0)
: (pixelBytes == 1 ? 255.0
: pixelBytes == 2 ? 65535.0
: 4294967295.0);
const Uint32 value = needDepth ? NormalizedDepthToUnsigned(depth[i], maximum) : stencil[i];
if (pixelBytes == 1) dst[i] = static_cast<Uint8>(value);
else if (pixelBytes == 2) { const Uint16 v = static_cast<Uint16>(value); Memcpy(dst + i * 2, &v, 2); }
else if (pixelBytes == 4) Memcpy(dst + i * 4, &value, 4);
else return false;
if (pixelBytes == 1)
dst[i] = static_cast<Uint8>(value);
else if (pixelBytes == 2) {
const Uint16 v = static_cast<Uint16>(value);
Memcpy(dst + i * 2, &v, 2);
} else if (pixelBytes == 4)
Memcpy(dst + i * 4, &value, 4);
else
return false;
}
}
}
@@ -1641,8 +1685,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, type);
if (!pixelBytes || extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return false;
const GLenum faceTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
const auto widening = sourceUsesImageCarrier
? TextureImpl::GetImageBindableStorageWidening(logicalFormat) : TextureImpl::ImageBindableStorageWidening{};
const auto widening = sourceUsesImageCarrier ? TextureImpl::GetImageBindableStorageWidening(logicalFormat)
: TextureImpl::ImageBindableStorageWidening{};
const SizeT sliceBytes = static_cast<SizeT>(extent.x()) * extent.y() * pixelBytes;
Vector<Uint8> result(sliceBytes * static_cast<SizeT>(extent.z()));
if (g_GLESFuncs.glMemoryBarrier) g_GLESFuncs.glMemoryBarrier(GL_ALL_BARRIER_BITS);
@@ -1651,28 +1695,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
// A raw packed word may have redundant encodings or NaN payloads. Read
// it through R32UI when the source really still uses that native layout.
const Bool rawPacked = !widening && MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(
logicalFormat, MG_Util::ConvertGLEnumToTextureInputFormat(format),
MG_Util::ConvertGLEnumToTexturePixelDataType(type));
logicalFormat, MG_Util::ConvertGLEnumToTextureInputFormat(format),
MG_Util::ConvertGLEnumToTexturePixelDataType(type));
if (rawPacked && pixelBytes == sizeof(Uint32)) {
const GLint face = uploadTarget >= TextureUploadTarget::CubeMapPositiveX &&
uploadTarget <= TextureUploadTarget::CubeMapNegativeZ
? static_cast<GLint>(uploadTarget) - static_cast<GLint>(TextureUploadTarget::CubeMapPositiveX) : 0;
const GLint face =
uploadTarget >= TextureUploadTarget::CubeMapPositiveX &&
uploadTarget <= TextureUploadTarget::CubeMapNegativeZ
? static_cast<GLint>(uploadTarget) - static_cast<GLint>(TextureUploadTarget::CubeMapPositiveX)
: 0;
for (GLint layer = 0; layer < extent.z(); ++layer)
if (!ReadPackedLevelWordsViaScratch(texture, nativeTarget, level, face + layer, extent.x(), extent.y(),
reinterpret_cast<Uint32*>(result.data() + static_cast<SizeT>(layer) * sliceBytes))) return false;
if (!ReadPackedLevelWordsViaScratch(
texture, nativeTarget, level, face + layer, extent.x(), extent.y(),
reinterpret_cast<Uint32*>(result.data() + static_cast<SizeT>(layer) * sliceBytes)))
return false;
bytes = Move(result);
return true;
}
NormalizedImageCarrierRead carrier{};
if (widening && widening.CarriesNormalizedCodes()) {
for (SizeT c = 0; c < 4; ++c) carrier.ChannelMax[c] = widening.ChannelMax[c];
for (SizeT c = 0; c < 4; ++c)
carrier.ChannelMax[c] = widening.ChannelMax[c];
carrier.SignedNormalized = widening.SignedNormalized;
}
TempFBOBinder binder(true);
auto& framebuffer = binder.Framebuffer();
for (GLint layer = 0; layer < extent.z(); ++layer) {
if (nativeTarget == GL_TEXTURE_3D || nativeTarget == GL_TEXTURE_2D_ARRAY || nativeTarget == GL_TEXTURE_CUBE_MAP_ARRAY)
if (nativeTarget == GL_TEXTURE_3D || nativeTarget == GL_TEXTURE_2D_ARRAY ||
nativeTarget == GL_TEXTURE_CUBE_MAP_ARRAY)
ScratchFBOImpl::EnsureColorAttachmentLayer(framebuffer, GL_READ_FRAMEBUFFER, texture, level, layer);
else
ScratchFBOImpl::EnsureColorAttachment2D(framebuffer, GL_READ_FRAMEBUFFER, texture, faceTarget, level);
@@ -1680,13 +1730,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLenum status = g_GLESFuncs.glCheckFramebufferStatus(GL_READ_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
MGLOG_E_ONCE("Espryt wire texture readback: framebuffer status=0x%x texture=%u level=%d target=0x%x",
status, texture, level, nativeTarget);
status, texture, level, nativeTarget);
return false;
}
Vector<Uint8> slice;
if (!ReadWireColorRowsTight(extent.x(), extent.y(), format, type,
TextureImpl::BackendTextureFormatAddsAlpha(logicalFormat, target), carrier, slice) ||
slice.size() != sliceBytes) return false;
TextureImpl::BackendTextureFormatAddsAlpha(logicalFormat, target), carrier,
slice) ||
slice.size() != sliceBytes)
return false;
Memcpy(result.data() + static_cast<SizeT>(layer) * sliceBytes, slice.data(), sliceBytes);
}
bytes = Move(result);
@@ -1705,16 +1757,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
const GLenum nativeTarget = TextureImpl::ConvertTextureTargetToBackendGLEnum(target);
GLenum binding = GL_NONE;
switch (nativeTarget) {
case GL_TEXTURE_2D: binding = GL_TEXTURE_BINDING_2D; break;
case GL_TEXTURE_3D: binding = GL_TEXTURE_BINDING_3D; break;
case GL_TEXTURE_2D_ARRAY: binding = GL_TEXTURE_BINDING_2D_ARRAY; break;
case GL_TEXTURE_CUBE_MAP: binding = GL_TEXTURE_BINDING_CUBE_MAP; break;
case GL_TEXTURE_CUBE_MAP_ARRAY: binding = GL_TEXTURE_BINDING_CUBE_MAP_ARRAY; break;
default: return true;
case GL_TEXTURE_2D:
binding = GL_TEXTURE_BINDING_2D;
break;
case GL_TEXTURE_3D:
binding = GL_TEXTURE_BINDING_3D;
break;
case GL_TEXTURE_2D_ARRAY:
binding = GL_TEXTURE_BINDING_2D_ARRAY;
break;
case GL_TEXTURE_CUBE_MAP:
binding = GL_TEXTURE_BINDING_CUBE_MAP;
break;
case GL_TEXTURE_CUBE_MAP_ARRAY:
binding = GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
break;
default:
return true;
}
// A cube's levels are per face; every other target answers for the whole level.
const GLenum queryTarget = nativeTarget == GL_TEXTURE_CUBE_MAP
? TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget) : nativeTarget;
? TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget)
: nativeTarget;
DrainESErrors();
GLint previous = 0;
g_GLESFuncs.glGetIntegerv(binding, &previous);
@@ -1768,7 +1832,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto target = static_cast<Uint16>(uploadTarget);
const auto mip = static_cast<Uint16>(level);
ReadbackChannelMapping mapping{};
const SizeT pixelBytes = GetReadbackChannelMapping(format, mapping) ? GetReadbackDstPixelSize(mapping, type) : 0;
const SizeT pixelBytes =
GetReadbackChannelMapping(format, mapping) ? GetReadbackDstPixelSize(mapping, type) : 0;
const SizeT texels = static_cast<SizeT>(std::max(extent.x(), 0)) * static_cast<SizeT>(std::max(extent.y(), 0)) *
static_cast<SizeT>(std::max(extent.z(), 0));
const char* declined = nullptr;
@@ -1799,7 +1864,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool isInteger = false;
Bool isSigned = false;
if (!MG_Util::PixelStoreProcessor::DecodeShadowDataToWideRGBA(logicalFormat, staged, texels, wide,
isInteger, isSigned)) {
isInteger, isSigned)) {
declined = "the staged format has no wide decoding";
} else if (mapping.isInteger != isInteger) {
declined = "an integer read of a normalized level, or the reverse";
@@ -1811,8 +1876,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
out.resize(texels * pixelBytes);
for (SizeT row = 0; row < rows; ++row) {
ClientReadback::ConvertWideReadbackRow(wide.data() + row * wideRowBytes,
out.data() + row * rowTexels * pixelBytes, rowTexels,
wideType, mapping, type);
out.data() + row * rowTexels * pixelBytes, rowTexels,
wideType, mapping, type);
}
}
}
@@ -1842,7 +1907,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!twin) return false;
const Bool wholeCube = info.Target == GL_TEXTURE_CUBE_MAP;
const IntVec3 extent{static_cast<Int>(info.Box.W), static_cast<Int>(info.Box.H),
wholeCube ? 1 : static_cast<Int>(info.Box.D)};
wholeCube ? 1 : static_cast<Int>(info.Box.D)};
const auto logicalFormat = static_cast<TextureInternalFormat>(record->Desc.InternalFormat);
// The R32UI word leg never goes through a framebuffer, so the knob leaves it alone (it is
// ReadTextureLevelTight's first choice for exactly these reads).
@@ -1856,15 +1921,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool refusedForTesting = !packedWordRead && WireTextureReadbackRefusedForTesting(extent);
Vector<Uint8> result;
for (Int face = 0; face < (wholeCube ? 6 : 1); ++face) {
const auto uploadTarget = wholeCube
? static_cast<TextureUploadTarget>(static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + face)
: MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target);
const auto uploadTarget =
wholeCube
? static_cast<TextureUploadTarget>(static_cast<Int>(TextureUploadTarget::CubeMapPositiveX) + face)
: MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target);
Vector<Uint8> slice;
if (refusedForTesting ||
!ReadTextureLevelTight(twin->GetBackendTextureId(), target, uploadTarget, logicalFormat, info.Level,
extent, twin->RequiresImageBindableStorage(), info.Format, info.Type, slice)) {
extent, twin->RequiresImageBindableStorage(), info.Format, info.Type, slice)) {
if (!ReadWireLevelFromStagedStore(info.Res, logicalFormat, uploadTarget, info.Level, extent,
info.Format, info.Type, slice)) return false;
info.Format, info.Type, slice))
return false;
}
result.insert(result.end(), slice.begin(), slice.end());
}
@@ -1896,9 +1963,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool IsSupportedDepthStencilReadPixelsPair(GLenum format, GLenum type) {
switch (format) {
case GL_DEPTH_COMPONENT:
return type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT ||
type == GL_UNSIGNED_SHORT || type == GL_INT || type == GL_UNSIGNED_INT ||
type == GL_FLOAT || type == GL_HALF_FLOAT;
return type == GL_BYTE || type == GL_UNSIGNED_BYTE || type == GL_SHORT || type == GL_UNSIGNED_SHORT ||
type == GL_INT || type == GL_UNSIGNED_INT || type == GL_FLOAT || type == GL_HALF_FLOAT;
case GL_STENCIL_INDEX:
return type == GL_UNSIGNED_BYTE || type == GL_BYTE || type == GL_UNSIGNED_SHORT || type == GL_SHORT ||
type == GL_UNSIGNED_INT || type == GL_INT || type == GL_FLOAT || type == GL_HALF_FLOAT;
@@ -1927,7 +1993,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
GetReadbackDstPixelSize(conversionMapping, type) != 0;
if (!useNativeReadback && !convertible) {
MGLOG_E_ONCE("ReadPixels: format %s with type %s is not implemented yet, skipping readback",
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
return;
}
@@ -1973,7 +2039,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
MGLOG_E_ONCE("ReadPixels: format %s with type %s is not implemented yet, skipping readback",
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str());
return;
}
// Every depth and stencil read goes through the helpers, not just the widening ones:
@@ -1997,8 +2063,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// on every exit path, so a later readback can never land in a stale PBO
// (the driver-level binding used to stay on the user PBO after this call,
// capturing subsequent client-memory readbacks into it).
auto& pixelPackBufferObject =
SplitReadbackPackBuffer();
auto& pixelPackBufferObject = SplitReadbackPackBuffer();
Bool usePBO = false;
GLuint packBufferId = 0;
// P13: SplitReadbackPackBuffer() is always null - a pack buffer reaches the readback
@@ -2028,9 +2093,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
MGLOG_E_ONCE("ReadPixels: native read of %s/%s failed (%s) and no conversion path covers it, "
"skipping readback",
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str(),
MG_Util::ConvertGLEnumToString(nativeReadError).c_str());
"skipping readback",
MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertGLEnumToString(type).c_str(),
MG_Util::ConvertGLEnumToString(nativeReadError).c_str());
return;
}
if (usePBO) {
@@ -74,8 +74,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
entry->Why = "the loader has no eglGetProcAddress/eglQueryString/glGetString";
return *entry;
}
const char* egl = g_Display != EGL_NO_DISPLAY ? g_EGLFuncs.eglQueryString(g_Display, EGL_EXTENSIONS)
: nullptr;
const char* egl =
g_Display != EGL_NO_DISPLAY ? g_EGLFuncs.eglQueryString(g_Display, EGL_EXTENSIONS) : nullptr;
const char* gl = reinterpret_cast<const char*>(g_GLESFuncs.glGetString(GL_EXTENSIONS));
if (egl == nullptr || gl == nullptr) {
entry->Why = "no current display and context to read the extension strings from";
@@ -133,8 +133,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
void DrainErrors() {
for (Uint guard = 0; guard < 16 && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++guard) {
}
for (Uint guard = 0; guard < 16 && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++guard) {}
}
} // namespace
#endif
@@ -432,7 +431,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// A session that renders into images (it ends frames with a publishing flush) may be
// about to overwrite this one: what other sessions still sample goes first.
if (frame.ImplicitSync) {
for (const int read : SI::DupPendingReadFences(*state.Image)) WaitForFenceBeforeLaterCommands(read);
for (const int read : SI::DupPendingReadFences(*state.Image))
WaitForFenceBeforeLaterCommands(read);
}
frame.Tracker.NoteRead(state.Image);
state.NotedFrame = frame.Id;
@@ -620,7 +620,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glDrawArrays(GL_TRIANGLES, 0, 3);
const GLenum error = g_GLESFuncs.glGetError();
if (error != GL_NO_ERROR) {
MGLOG_E_ONCE("Shared images: the YUV conversion draw failed (0x%04x)", static_cast<unsigned>(error));
MGLOG_E_ONCE("Shared images: the YUV conversion draw failed (0x%04x)",
static_cast<unsigned>(error));
}
} else {
MGLOG_E_ONCE("Shared images: a YUV conversion target is not renderable (0x%04x)",
@@ -721,7 +722,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the driver has no fence to queue. Outside the targets' lock, which every session's
// present takes.
if (ResolvedEntryPoints().FencesUsable) {
for (const int fence : SI::DupPendingReadFences(*image)) WaitForFenceBeforeLaterCommands(fence);
for (const int fence : SI::DupPendingReadFences(*image))
WaitForFenceBeforeLaterCommands(fence);
} else if (!SI::WaitForReads(*image, kFenceCpuWaitMs)) {
MGLOG_E_ONCE("Shared image present: image %llu's readers did not finish within %u ms",
static_cast<unsigned long long>(view.Id), static_cast<unsigned>(kFenceCpuWaitMs));
@@ -820,8 +822,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (readBuffer != GL_BACK) g_GLESFuncs.glReadBuffer(static_cast<GLenum>(readBuffer));
if (!copied) {
MGLOG_E_ONCE("Shared image present: glBlitFramebuffer %dx%d -> %ux%u failed (0x%04x)",
sourceWidth, sourceHeight, view.Width, view.Height,
static_cast<unsigned>(error));
sourceWidth, sourceHeight, view.Width, view.Height, static_cast<unsigned>(error));
}
} else {
MGLOG_E_ONCE("Shared image present: image %llu's framebuffer is incomplete (0x%04x)",
@@ -847,7 +848,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& targets = Targets();
const std::lock_guard<std::mutex> lock(targets.Mutex);
// TargetIsCurrent is already false for every entry, so nothing reaches the driver.
for (auto& entry : targets.ById) DeleteTarget(entry.second);
for (auto& entry : targets.ById)
DeleteTarget(entry.second);
targets.ById.clear();
}
#else
@@ -864,7 +866,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
(void)view, (void)region;
return false;
}
void AcquireForSampling(const EglImageRef& image) { (void)image; }
void AcquireForSampling(const EglImageRef& image) {
(void)image;
}
Bool IsYuv(const EglImageRef& image) {
(void)image;
@@ -875,7 +879,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
(void)image, (void)target, (void)width, (void)height;
}
void PublishPendingReads() {}
Bool PublishPendingAccesses() { return true; }
Bool PublishPendingAccesses() {
return true;
}
Bool ExportNativeFence(int* fence) {
*fence = -1;
return true;
@@ -105,8 +105,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool SyncedFramebufferSerialIsCurrent(FramebufferTarget target, Uint64 serial) {
const auto& memo = g_fboSyncedSerials[SizeT(target)];
return memo.valid && memo.serial == serial &&
memo.backendIdGeneration == g_attachmentBackendIdGeneration &&
return memo.valid && memo.serial == serial && memo.backendIdGeneration == g_attachmentBackendIdGeneration &&
memo.contextGeneration == g_backendContextGeneration;
}
@@ -158,23 +157,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
"binding has no record while %s names {slot %u, gen %u} - a "
"half-described applier; running the pre-handle framebuffer sync.",
drawUnrecorded ? "DRAW" : "READ", drawUnrecorded ? "READ" : "DRAW",
static_cast<unsigned>(described.Fbo.Slot),
static_cast<unsigned>(described.Fbo.Gen));
static_cast<unsigned>(described.Fbo.Slot), static_cast<unsigned>(described.Fbo.Gen));
} else {
MGLOG_E_ONCE("A framebuffer record does not describe the binding it names: NEITHER "
"the DRAW nor the READ binding has one while the framebuffer "
"subsystem bit is set (bound handles {slot %u, gen %u} / "
"{slot %u, gen %u}, %llu stale-generation lookup(s) so far); "
"running the pre-handle framebuffer sync.",
static_cast<unsigned>(
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Slot),
static_cast<unsigned>(
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Gen),
static_cast<unsigned>(
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Slot),
static_cast<unsigned>(
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Gen),
static_cast<unsigned long long>(st.StaleFramebufferRecordLookups));
MGLOG_E_ONCE(
"A framebuffer record does not describe the binding it names: NEITHER "
"the DRAW nor the READ binding has one while the framebuffer "
"subsystem bit is set (bound handles {slot %u, gen %u} / "
"{slot %u, gen %u}, %llu stale-generation lookup(s) so far); "
"running the pre-handle framebuffer sync.",
static_cast<unsigned>(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Slot),
static_cast<unsigned>(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Gen),
static_cast<unsigned>(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Slot),
static_cast<unsigned>(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Gen),
static_cast<unsigned long long>(st.StaleFramebufferRecordLookups));
}
return false;
}
@@ -249,10 +244,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"table (live generation %u); running the pre-handle framebuffer "
"sync.",
target == FramebufferTarget::Read ? "READ" : "DRAW",
static_cast<unsigned>(record.Fbo.Slot),
static_cast<unsigned>(record.Fbo.Gen),
static_cast<unsigned>(
g_backendFramebufferObjects.LiveGenAt(record.Fbo.Slot)));
static_cast<unsigned>(record.Fbo.Slot), static_cast<unsigned>(record.Fbo.Gen),
static_cast<unsigned>(g_backendFramebufferObjects.LiveGenAt(record.Fbo.Slot)));
g_fboRecordsTrusted = false;
return false;
}
@@ -285,10 +278,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// out of MGPFramebufferState::ReadSurface, which was already the record's
// answer, and the by-handle entry is what reaches it here.
const auto& boundHandles = st.BoundFramebuffer;
const Bool sameObjectOnBothBindings =
target == FramebufferTarget::Read &&
boundHandles[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)] ==
boundHandles[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)];
const Bool sameObjectOnBothBindings = target == FramebufferTarget::Read &&
boundHandles[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)] ==
boundHandles[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)];
if (sameObjectOnBothBindings) {
backendObj->SyncReadBufferToBackendByHandle(record.Fbo);
@@ -485,7 +477,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// struct, i.e. in the tail span.
if (tailSpanDirty) {
#define SYNC_CAPABILITY(cap_mg, cap_gl) \
if (forceFullPush || parameters.cap_mg##Enabled != g_syncedRenderStateParameters.cap_mg##Enabled) { \
if (forceFullPush || parameters.cap_mg##Enabled != g_syncedRenderStateParameters.cap_mg##Enabled) { \
if (parameters.cap_mg##Enabled) { \
g_GLESFuncs.glEnable(cap_gl); \
} else { \
@@ -515,8 +507,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// why the multi-viewport half of KHR-GL43.viewport_array stays red on Espryt.
{
const Bool scissorTest = (parameters.ScissorTestEnabledMask & 1u) != 0;
const Bool syncedScissorTest =
(g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0;
const Bool syncedScissorTest = (g_syncedRenderStateParameters.ScissorTestEnabledMask & 1u) != 0;
if (forceFullPush || scissorTest != syncedScissorTest) {
scissorTest ? g_GLESFuncs.glEnable(GL_SCISSOR_TEST) : g_GLESFuncs.glDisable(GL_SCISSOR_TEST);
}
@@ -553,15 +544,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool srgbWrites = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb);
if (g_GLESCapabilities.SupportsSrgbWriteControl &&
(forceFullPush || srgbWrites != g_syncedSrgbFramebufferWrites)) {
srgbWrites ? g_GLESFuncs.glEnable(GL_FRAMEBUFFER_SRGB)
: g_GLESFuncs.glDisable(GL_FRAMEBUFFER_SRGB);
srgbWrites ? g_GLESFuncs.glEnable(GL_FRAMEBUFFER_SRGB) : g_GLESFuncs.glDisable(GL_FRAMEBUFFER_SRGB);
g_syncedSrgbFramebufferWrites = srgbWrites;
}
}
if (tailSpanDirty) { // Primitive restart. GLES core has only GL_PRIMITIVE_RESTART_FIXED_INDEX (fixed
// all-ones value); both the fixed cap and the (fixed-valued) arbitrary GL_PRIMITIVE_RESTART map to
// it. An arbitrary non-fixed restart index is rejected at draw time (see DrawElements).
// all-ones value); both the fixed cap and the (fixed-valued) arbitrary GL_PRIMITIVE_RESTART map to
// it. An arbitrary non-fixed restart index is rejected at draw time (see DrawElements).
const Bool restart = parameters.PrimitiveRestartFixedIndexEnabled || parameters.PrimitiveRestartEnabled;
const Bool syncedRestart = g_syncedRenderStateParameters.PrimitiveRestartFixedIndexEnabled ||
g_syncedRenderStateParameters.PrimitiveRestartEnabled;
@@ -621,14 +611,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// in the first mask but not the second had blending off anyway, so nothing
// was blended and nothing was dropped - only the unusable enum was kept out
// of the driver.
MGLOG_E_ONCE(
"SyncRenderState: a GL_SRC1_* (dual-source) blend factor was set on draw buffer "
"mask 0x%x, but the GLES driver does not expose GL_EXT_blend_func_extended (see "
"the dual-source blend row in the driver POST). Those draw buffers are pushed "
"with neutral One/Zero factors instead. Blending was actually ENABLED on mask "
"0x%x, and only there is anything lost: the fragment's first output is written "
"unblended and the second source is dropped.",
dualSourceDeclinedMask, declinedWithBlendingOnMask);
MGLOG_E_ONCE("SyncRenderState: a GL_SRC1_* (dual-source) blend factor was set on draw buffer "
"mask 0x%x, but the GLES driver does not expose GL_EXT_blend_func_extended (see "
"the dual-source blend row in the driver POST). Those draw buffers are pushed "
"with neutral One/Zero factors instead. Blending was actually ENABLED on mask "
"0x%x, and only there is anything lost: the fragment's first output is written "
"unblended and the second source is dropped.",
dualSourceDeclinedMask, declinedWithBlendingOnMask);
g_dualSourceDeclinedBlendStates = parameters.BlendStates;
for (Uint i = 0; i < FBO::MAX_DRAW_BUFFERS; ++i) {
if ((dualSourceDeclinedMask & (1u << i)) == 0) continue;
@@ -730,8 +719,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto& syn = syncedStates[i];
if (forceFullPush || cur.SrcFactorRGB != syn.SrcFactorRGB ||
cur.DstFactorRGB != syn.DstFactorRGB ||
cur.SrcFactorAlpha != syn.SrcFactorAlpha ||
cur.DstFactorRGB != syn.DstFactorRGB || cur.SrcFactorAlpha != syn.SrcFactorAlpha ||
cur.DstFactorAlpha != syn.DstFactorAlpha) {
syn.SrcFactorRGB = cur.SrcFactorRGB;
syn.DstFactorRGB = cur.DstFactorRGB;
@@ -814,9 +802,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (forceFullPush || current.Func != synced.Func || current.Ref != synced.Ref ||
current.ValueMask != synced.ValueMask) {
g_GLESFuncs.glStencilFuncSeparate(
glFace, MG_Util::ConvertDepthTestFuncToGLEnum(current.Func), current.Ref,
current.ValueMask);
g_GLESFuncs.glStencilFuncSeparate(glFace, MG_Util::ConvertDepthTestFuncToGLEnum(current.Func),
current.Ref, current.ValueMask);
}
if (forceFullPush || current.WriteMask != synced.WriteMask) {
g_GLESFuncs.glStencilMaskSeparate(glFace, current.WriteMask);
@@ -833,9 +820,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty || colorMaskWidenDirty) { // Color mask. Uniform masks use the non-indexed glColorMask
// (works everywhere); divergent per-draw-buffer masks use the indexed glColorMaski when
// draw_buffers_indexed is available, otherwise fall back to broadcasting draw buffer 0.
// Mirrors the blend block.
// (works everywhere); divergent per-draw-buffer masks use the indexed glColorMaski when
// draw_buffers_indexed is available, otherwise fall back to broadcasting draw buffer 0.
// Mirrors the blend block.
using FBO = MG_State::GLState::FramebufferObject;
const auto& targetMasks = parameters.ColorMasks;
const auto& syncedMasks = g_syncedRenderStateParameters.ColorMasks;
@@ -889,8 +876,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_syncedColorMaskAlphaWidenMask = appliedWidenMask;
}
if (tailSpanDirty) { // Polygon mode. GLES core has no glPolygonMode; use NV/ANGLE_polygon_mode when present.
// Without the extension the mode stays FILL and non-FILL requests are dropped.
if (tailSpanDirty) { // Polygon mode. GLES core has no glPolygonMode; use NV/ANGLE_polygon_mode when
// present.
// Without the extension the mode stays FILL and non-FILL requests are dropped.
if ((forceFullPush || parameters.PolygonModeFront != g_syncedRenderStateParameters.PolygonModeFront) &&
g_GLESCapabilities.SupportsPolygonMode) {
const auto polygonModeFn =
@@ -917,41 +905,43 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Cull face mode
if (forceFullPush || parameters.CullFaceModeSetting != g_syncedRenderStateParameters.CullFaceModeSetting) {
if (forceFullPush ||
parameters.CullFaceModeSetting != g_syncedRenderStateParameters.CullFaceModeSetting) {
const CullFaceMode& cfm = parameters.CullFaceModeSetting;
g_GLESFuncs.glCullFace(MG_Util::ConvertCullFaceModeToGLEnum(cfm));
}
if (forceFullPush || parameters.FrontFaceModeSetting != g_syncedRenderStateParameters.FrontFaceModeSetting) {
if (forceFullPush ||
parameters.FrontFaceModeSetting != g_syncedRenderStateParameters.FrontFaceModeSetting) {
const FrontFaceMode& ffm = parameters.FrontFaceModeSetting;
g_GLESFuncs.glFrontFace(MG_Util::ConvertFrontFaceModeToGLEnum(ffm));
}
}
if (tailSpanDirty) { // Scissor box. Resolved and shadowed like the viewport above, and
// for the same reason: what has to reach the driver is NOT simply the parameter
// field. RenderStateParameters::ScissorBoxes starts all-zero, which means "the
// application has never called glScissor" - it is not a GL scissor box. GL's
// initial box is the whole window, which the frontend has no way to spell before a
// surface exists. The pre-resync code got away with pushing the field verbatim only
// by accident: the shadow held the same default, the field never compared unequal,
// and the ES context kept its own correct default. Under the forced full push that
// accident is gone, glScissor(0,0,0,0) shrinks the scissor to an EMPTY rectangle,
// and everything drawn with GL_SCISSOR_TEST enabled before the app's first
// glScissor is clipped away - Minecraft 26.2 keeps only its unscissored sky and
// hand and loses the terrain and the whole GUI.
//
// The condition is the WRITTEN FLAG, not the extent. An empty rectangle is a
// perfectly legal thing to ask for - glScissor(0,0,0,0) means "the scissor test
// rejects every fragment" - so testing `width <= 0 || height <= 0` substituted the
// whole surface for a deliberately empty box and inverted the request into "accept
// every fragment", no matter how many times the application had already called
// glScissor. KHR-GL43.viewport_array.scissor_zero_dimension is exactly that: all 16
// boxes zero-sized with the test enabled, requiring the draw to be clipped away
// entirely. Reading the flag preserves the Minecraft protection bit-for-bit - before
// the first glScissor the bit is clear and the surface size is still substituted -
// while an explicit empty box now reaches the driver verbatim. Negative extents
// cannot arrive here at all: all three entry points reject them with
// GL_INVALID_VALUE before storing (GL_RenderState.cpp's ValidateNonNegativeExtent).
// for the same reason: what has to reach the driver is NOT simply the parameter
// field. RenderStateParameters::ScissorBoxes starts all-zero, which means "the
// application has never called glScissor" - it is not a GL scissor box. GL's
// initial box is the whole window, which the frontend has no way to spell before a
// surface exists. The pre-resync code got away with pushing the field verbatim only
// by accident: the shadow held the same default, the field never compared unequal,
// and the ES context kept its own correct default. Under the forced full push that
// accident is gone, glScissor(0,0,0,0) shrinks the scissor to an EMPTY rectangle,
// and everything drawn with GL_SCISSOR_TEST enabled before the app's first
// glScissor is clipped away - Minecraft 26.2 keeps only its unscissored sky and
// hand and loses the terrain and the whole GUI.
//
// The condition is the WRITTEN FLAG, not the extent. An empty rectangle is a
// perfectly legal thing to ask for - glScissor(0,0,0,0) means "the scissor test
// rejects every fragment" - so testing `width <= 0 || height <= 0` substituted the
// whole surface for a deliberately empty box and inverted the request into "accept
// every fragment", no matter how many times the application had already called
// glScissor. KHR-GL43.viewport_array.scissor_zero_dimension is exactly that: all 16
// boxes zero-sized with the test enabled, requiring the draw to be clipped away
// entirely. Reading the flag preserves the Minecraft protection bit-for-bit - before
// the first glScissor the bit is clear and the surface size is still substituted -
// while an explicit empty box now reaches the driver verbatim. Negative extents
// cannot arrive here at all: all three entry points reject them with
// GL_INVALID_VALUE before storing (GL_RenderState.cpp's ValidateNonNegativeExtent).
IntVec4 backendScissorBox = parameters.ScissorBoxes[0];
if ((parameters.ScissorBoxWrittenMask & 1u) == 0) {
Int surfaceWidth = 0;
@@ -970,10 +960,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Logic op (first field of the tail span)
// glLogicOp is GLES 1.x / EXT only - eglGetProcAddress returns null for it on a
// plain ES 3.x driver. Before the forced resync it was reached only when an app
// actually set a logic op; now every MakeCurrent would call it, so the null check
// is mandatory rather than defensive.
// glLogicOp is GLES 1.x / EXT only - eglGetProcAddress returns null for it on a
// plain ES 3.x driver. Before the forced resync it was reached only when an app
// actually set a logic op; now every MakeCurrent would call it, so the null check
// is mandatory rather than defensive.
if (g_GLESFuncs.glLogicOp &&
(forceFullPush || parameters.LogicOp != g_syncedRenderStateParameters.LogicOp)) {
g_GLESFuncs.glLogicOp(MG_Util::ConvertLogicOperationToGLEnum(parameters.LogicOp));
@@ -981,7 +971,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (headSpanDirty) { // Polygon offset (head-span scalars, like line width / point size below)
if (forceFullPush || parameters.PolygonOffsetFactor != g_syncedRenderStateParameters.PolygonOffsetFactor ||
if (forceFullPush ||
parameters.PolygonOffsetFactor != g_syncedRenderStateParameters.PolygonOffsetFactor ||
parameters.PolygonOffsetUnits != g_syncedRenderStateParameters.PolygonOffsetUnits ||
parameters.PolygonOffsetClamp != g_syncedRenderStateParameters.PolygonOffsetClamp) {
// glPolygonOffset IS glPolygonOffsetClampEXT(factor, units, 0), so where the
@@ -998,7 +989,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (headSpanDirty) { // Clip control. GL_ARB_clip_control is only advertised where the
// driver has GL_EXT_clip_control, so without it the state can only be the default.
// driver has GL_EXT_clip_control, so without it the state can only be the default.
if (g_GLESCapabilities.SupportsClipControl &&
(forceFullPush || parameters.ClipOrigin != g_syncedRenderStateParameters.ClipOrigin ||
parameters.ClipDepthMode != g_syncedRenderStateParameters.ClipDepthMode)) {
@@ -1013,7 +1004,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (headSpanDirty) { // Point size (GLES 1.x only - ES 2+ sets it from gl_PointSize,
// so the entry point is absent on most drivers; see the glLogicOp note above)
// so the entry point is absent on most drivers; see the glLogicOp note above)
if (g_GLESFuncs.glPointSize &&
(forceFullPush || parameters.PointSize != g_syncedRenderStateParameters.PointSize)) {
g_GLESFuncs.glPointSize(parameters.PointSize);
@@ -1021,10 +1012,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (tailSpanDirty) { // Sample coverage
if (forceFullPush || parameters.SampleCoverageValue != g_syncedRenderStateParameters.SampleCoverageValue ||
if (forceFullPush ||
parameters.SampleCoverageValue != g_syncedRenderStateParameters.SampleCoverageValue ||
parameters.SampleCoverageInvert != g_syncedRenderStateParameters.SampleCoverageInvert) {
g_GLESFuncs.glSampleCoverage(parameters.SampleCoverageValue,
ToGLBoolean(parameters.SampleCoverageInvert));
ToGLBoolean(parameters.SampleCoverageInvert));
}
}
@@ -1051,8 +1043,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glDisable(GL_SAMPLE_SHADING);
}
}
if (forceFullPush || parameters.MinSampleShadingValue !=
g_syncedRenderStateParameters.MinSampleShadingValue) {
if (forceFullPush ||
parameters.MinSampleShadingValue != g_syncedRenderStateParameters.MinSampleShadingValue) {
g_GLESFuncs.glMinSampleShading(parameters.MinSampleShadingValue);
}
}
@@ -1069,8 +1061,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (forceFullPush ||
parameters.ShadingRateCombinerOp0 != g_syncedRenderStateParameters.ShadingRateCombinerOp0 ||
parameters.ShadingRateCombinerOp1 != g_syncedRenderStateParameters.ShadingRateCombinerOp1) {
g_GLESFuncs.glShadingRateCombinerOpsEXT(MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp0),
MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp1));
g_GLESFuncs.glShadingRateCombinerOpsEXT(
MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp0),
MGShadingRateCombinerOpEnum(parameters.ShadingRateCombinerOp1));
}
}
@@ -1199,8 +1192,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"composite table holds %llu - the band base or the decode is wrong, "
"not the emission; running the frontend's own uniform block.",
static_cast<unsigned>(handle.Slot), static_cast<unsigned>(handle.Gen),
static_cast<unsigned>(index),
static_cast<unsigned long long>(table->size()));
static_cast<unsigned>(index), static_cast<unsigned long long>(table->size()));
} else {
MGLOG_E_ONCE("A program record does not describe the binding it names: ShaderCso "
"{slot %u, gen %u} is above the applier's table of %llu record(s), so "
@@ -1271,8 +1263,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"record at {slot %u, gen %u} names {slot %u, gen %u} in its own "
"descriptor; running the frontend's own uniform block.",
static_cast<unsigned>(handle.Slot), static_cast<unsigned>(handle.Gen),
static_cast<unsigned>(record->Desc.Cso.Slot),
static_cast<unsigned>(record->Desc.Cso.Gen));
static_cast<unsigned>(record->Desc.Cso.Slot), static_cast<unsigned>(record->Desc.Cso.Gen));
return nullptr;
}
// P4a decline-site P6: M, then S - FLIPPED AT THE VERIFICATION ROUND, and the
@@ -1350,7 +1341,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// untrusted here and a refusal would abort a correct program. The
// previous resolution stands, which is what the monolith arm
// effectively does too when nothing rebound between the two calls.
enum class BroadcastCountPolicy { FrontendFallback, RefuseFallback, RecordOnly };
enum class BroadcastCountPolicy {
FrontendFallback,
RefuseFallback,
RecordOnly
};
static void ResolveFragColorBroadcastCount(BroadcastCountPolicy policy) {
Bool broadcastCountResolved = false;
@@ -1358,8 +1353,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// SyncCurrentFBO immediately before this, so the records have just been
// checked against the two bindings.
if (FramebufferImpl::g_fboRecordsTrusted) {
const MG_Pipe::MGPFramebufferState* const recordPtr =
BoundFramebufferRecord(FramebufferTarget::Draw);
const MG_Pipe::MGPFramebufferState* const recordPtr = BoundFramebufferRecord(FramebufferTarget::Draw);
// P4a decline-site F7: FLIPPED AT THE VERIFICATION ROUND from a silent
// decline to an assertion, because it is unreachable and a silent decline
// here is indistinguishable from a legitimate one. g_fboRecordsTrusted
@@ -1407,8 +1401,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint16 slotVersion = drawSlot.GetVersion();
const Uint16 objectVersion = drawFBO ? drawFBO->GetObjectVersion() : 0;
if (!g_broadcastMemoValid || g_broadcastMemoFbo != drawFBO.get() ||
g_broadcastMemoSlotVersion != slotVersion ||
g_broadcastMemoObjectVersion != objectVersion) {
g_broadcastMemoSlotVersion != slotVersion || g_broadcastMemoObjectVersion != objectVersion) {
Uint enabledDrawBuffers = 0;
if (drawFBO) {
const auto& drawBuffers = drawFBO->GetDrawBuffers();
@@ -1475,8 +1468,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_lastUsedBackendProgramId = 0;
return;
}
if (!twin->GetBackendProgramId() ||
twin->GetSyncedShaderCsoSerial() != record->Serial ||
if (!twin->GetBackendProgramId() || twin->GetSyncedShaderCsoSerial() != record->Serial ||
twin->GetSyncedBindingsSerial() != record->BindingsSerial ||
twin->GetSnormFallbackClampOutputMask() != g_snormFallbackClampOutputMask ||
twin->GetUnormFallbackClampOutputMask() != g_unormFallbackClampOutputMask ||
@@ -33,22 +33,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
// drivers that otherwise fully support these core ES queries.
GLenum coreTarget = 0;
};
}
// EGL ground-truth verification stamp. glvnd's eglGetCurrentContext performs
// fork detection with a real getpid() syscall on every call, and this
// predicate sits 2-3 deep in every draw - measured at 16% of the render
// thread on a live workload. NOT thread_local although the stamp is
// per-owner-thread state: a shared-library thread_local costs a
// __tls_get_addr call per access, which itself showed at 1.5% of the draw
// loop. Plain globals are equivalent because only the thread that passes the
// owner-identity check below can ever stamp or trust them, only one thread
// can be the owner at a time, and the ONLY writers of the owner id -
// MakeCurrent/ReleaseCurrent - reset the stamps before publishing a new
// owner, so a stamp can never leak across an ownership change. Atomics
// (relaxed) because the resets may come from a non-owner thread; the
// owner-id release/acquire pairing orders them.
std::atomic<Uint64> g_eglVerifiedFrameSerial{~0ull};
std::atomic<Uint> g_eglVerifiedContextGeneration{0};
} // namespace
// EGL ground-truth verification stamp. glvnd's eglGetCurrentContext performs
// fork detection with a real getpid() syscall on every call, and this
// predicate sits 2-3 deep in every draw - measured at 16% of the render
// thread on a live workload. NOT thread_local although the stamp is
// per-owner-thread state: a shared-library thread_local costs a
// __tls_get_addr call per access, which itself showed at 1.5% of the draw
// loop. Plain globals are equivalent because only the thread that passes the
// owner-identity check below can ever stamp or trust them, only one thread
// can be the owner at a time, and the ONLY writers of the owner id -
// MakeCurrent/ReleaseCurrent - reset the stamps before publishing a new
// owner, so a stamp can never leak across an ownership change. Atomics
// (relaxed) because the resets may come from a non-owner thread; the
// owner-id release/acquire pairing orders them.
std::atomic<Uint64> g_eglVerifiedFrameSerial{~0ull};
std::atomic<Uint> g_eglVerifiedContextGeneration{0};
Bool IsBackendContextCurrentOnThisThread() {
const NativeContextTuple& tuple = ActiveNativeContext();
@@ -160,8 +160,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool AreTimerQueriesSupported() {
return g_GLESCapabilities.SupportsDisjointTimerQuery && g_GLESFuncs.glGenQueries &&
g_GLESFuncs.glDeleteQueries && g_GLESFuncs.glBeginQuery && g_GLESFuncs.glEndQuery &&
g_GLESFuncs.glGetQueryObjectuiv && g_GLESFuncs.glQueryCounterEXT &&
g_GLESFuncs.glGetQueryObjectui64vEXT;
g_GLESFuncs.glGetQueryObjectuiv && g_GLESFuncs.glQueryCounterEXT && g_GLESFuncs.glGetQueryObjectui64vEXT;
}
namespace {
@@ -185,11 +184,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
using Tier = MG_External::GLESCapabilities::TextureBufferTier;
switch (g_GLESCapabilities.TextureBufferSupport) {
case Tier::ExtensionEXT:
return g_GLESFuncs.glTexBufferRangeEXT ? g_GLESFuncs.glTexBufferRangeEXT
: g_GLESFuncs.glTexBufferRange;
return g_GLESFuncs.glTexBufferRangeEXT ? g_GLESFuncs.glTexBufferRangeEXT : g_GLESFuncs.glTexBufferRange;
case Tier::ExtensionOES:
return g_GLESFuncs.glTexBufferRangeOES ? g_GLESFuncs.glTexBufferRangeOES
: g_GLESFuncs.glTexBufferRange;
return g_GLESFuncs.glTexBufferRangeOES ? g_GLESFuncs.glTexBufferRangeOES : g_GLESFuncs.glTexBufferRange;
case Tier::CoreEs32:
return g_GLESFuncs.glTexBufferRange;
case Tier::None:
@@ -204,8 +201,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// once the capabilities have been filled in; the resolved pointer is what MobileGL can
// actually call, through the spelling that tier's support ships. Gating on the
// unsuffixed name alone would call an entry point an EXT/OES driver never exported.
return g_GLESCapabilities.TextureBufferSupport !=
MG_External::GLESCapabilities::TextureBufferTier::None &&
return g_GLESCapabilities.TextureBufferSupport != MG_External::GLESCapabilities::TextureBufferTier::None &&
ResolveTexBufferEntryPoint() != nullptr;
}
@@ -305,9 +301,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static void EndCoreQuery(BackendQueryHandle handle) {
const auto* query = static_cast<GLESQueryObject*>(handle);
if (query == nullptr || query->coreTarget == 0 ||
query->contextGeneration != g_syncContextGeneration || !IsBackendContextCurrentOnThisThread() ||
!g_GLESFuncs.glEndQuery) {
if (query == nullptr || query->coreTarget == 0 || query->contextGeneration != g_syncContextGeneration ||
!IsBackendContextCurrentOnThisThread() || !g_GLESFuncs.glEndQuery) {
return;
}
g_GLESFuncs.glEndQuery(query->coreTarget);
@@ -320,7 +315,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return BeginCoreQuery(GL_ANY_SAMPLES_PASSED);
}
void EndOcclusionQuery(BackendQueryHandle handle) { EndCoreQuery(handle); }
void EndOcclusionQuery(BackendQueryHandle handle) {
EndCoreQuery(handle);
}
// GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN / GL_PRIMITIVES_GENERATED. The frontend
// otherwise counts primitives on the CPU from the draw calls, which cannot see a
@@ -336,7 +333,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return BeginCoreQuery(generated ? GL_PRIMITIVES_GENERATED : GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
}
void EndXfbPrimitivesQuery(BackendQueryHandle handle) { EndCoreQuery(handle); }
void EndXfbPrimitivesQuery(BackendQueryHandle handle) {
EndCoreQuery(handle);
}
Bool IsQueryResultAvailable(BackendQueryHandle handle) {
const auto* query = static_cast<GLESQueryObject*>(handle);
@@ -364,8 +363,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// report it as produced and let the frontend cache it and release
// the handle.
if (query == nullptr || query->contextGeneration != g_syncContextGeneration ||
!g_GLESFuncs.glGetQueryObjectuiv ||
(query->coreTarget == 0 && !g_GLESFuncs.glGetQueryObjectui64vEXT)) {
!g_GLESFuncs.glGetQueryObjectuiv || (query->coreTarget == 0 && !g_GLESFuncs.glGetQueryObjectui64vEXT)) {
return true;
}
// A thread that does not own the ES context cannot issue GL calls,
@@ -432,8 +430,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Int64 GetGpuTimestampNs() {
// Synchronous GPU clock sample; 0 tells the frontend GL_TIMESTAMP
// getter to fall back.
if (!IsBackendContextCurrentOnThisThread() || !AreTimerQueriesSupported() ||
!g_GLESFuncs.glGetInteger64v) {
if (!IsBackendContextCurrentOnThisThread() || !AreTimerQueriesSupported() || !g_GLESFuncs.glGetInteger64v) {
return 0;
}
GLint64 timestamp = 0;
@@ -441,8 +438,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
return static_cast<Int64>(timestamp);
}
Uint64 CurrentFrameSerial() { return g_currentFrameSerial.load(std::memory_order_relaxed); }
Uint64 CompletedFrameSerial() { return g_completedFrameSerial.load(std::memory_order_relaxed); }
Uint64 CurrentFrameSerial() {
return g_currentFrameSerial.load(std::memory_order_relaxed);
}
Uint64 CompletedFrameSerial() {
return g_completedFrameSerial.load(std::memory_order_relaxed);
}
namespace {
// THE WATERMARK IS CONTIGUOUS. Fences signal in submission order only within ONE context,
@@ -544,7 +545,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (g_GLESFuncs.glGetGraphicsResetStatus != nullptr && IsBackendContextCurrentOnThisThread()) {
status = g_GLESFuncs.glGetGraphicsResetStatus();
}
if (status == GL_NO_ERROR && debugKnob && MG_Pipe::MGPipeDebugDeviceLossDue()) status = GL_UNKNOWN_CONTEXT_RESET;
if (status == GL_NO_ERROR && debugKnob && MG_Pipe::MGPipeDebugDeviceLossDue())
status = GL_UNKNOWN_CONTEXT_RESET;
return LatchOnResetStatus(site, status);
}
@@ -621,7 +623,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const MG_Util::Damage::Region& damage = MG_Backend::CurrentPresentDamage();
EGLint rects[MG_Util::Damage::kMaxRects * 4] = {};
const EGLint count = static_cast<EGLint>(MG_Util::Damage::NativeSwapRects(windowSurface, damage, rects));
mglSwapped = g_EGLFuncs.eglSwapBuffersWithDamageEXT(g_Display, drawSurface, count > 0 ? rects : nullptr, count);
mglSwapped =
g_EGLFuncs.eglSwapBuffersWithDamageEXT(g_Display, drawSurface, count > 0 ? rects : nullptr, count);
// A DRIVER THAT NAMES THE ENTRY POINT AND THEN REFUSES THE SWAP. Some vendor EGLs
// export eglSwapBuffersWithDamageEXT and answer EGL_FALSE on a window surface without
// setting an error (measured on a 4.14 vendor kernel with an Adreno driver; the plain
@@ -651,9 +654,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// platform's own queueBuffer throttle already waits for S-1, so only N = 1 is stricter
// than what Android does by itself; larger N is the same contract stated, not a change.
const Uint32 framesInFlight = MG_Config::Features.FramesInFlight;
if (presentedSerial != 0 && framesInFlight != 0 &&
framesInFlight < static_cast<Uint32>(kFrameFenceRingDepth) && g_GLESFuncs.glClientWaitSync &&
presentedSerial + 1 > framesInFlight) {
if (presentedSerial != 0 && framesInFlight != 0 && framesInFlight < static_cast<Uint32>(kFrameFenceRingDepth) &&
g_GLESFuncs.glClientWaitSync && presentedSerial + 1 > framesInFlight) {
const Uint64 target = presentedSerial + 1 - framesInFlight;
const FrameFence& oldest = g_frameFenceRing[target % kFrameFenceRingDepth];
if (oldest.sync && oldest.serial == target && oldest.contextGeneration == g_syncContextGeneration) {
@@ -418,7 +418,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
/*imageBindableStorageRequired=*/false);
list.push_back({surface.Res, twin.get()});
};
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) add(record.Color[i]);
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i)
add(record.Color[i]);
add(record.Depth);
add(record.Stencil);
}
@@ -496,14 +497,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// question with a spelling a recycled slot cannot fool.
static void SyncFramebufferAttachmentTexturesByRecord() {
const auto& st = MG_Pipe::MGPipeApplier();
const MG_Pipe::MGPipeHandle drawHandle =
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)];
const MG_Pipe::MGPipeHandle readHandle =
st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)];
const MG_Pipe::MGPFramebufferState* const drawRecord =
BoundFramebufferRecord(FramebufferTarget::Draw);
const MG_Pipe::MGPFramebufferState* const readRecord =
BoundFramebufferRecord(FramebufferTarget::Read);
const MG_Pipe::MGPipeHandle drawHandle = st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)];
const MG_Pipe::MGPipeHandle readHandle = st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)];
const MG_Pipe::MGPFramebufferState* const drawRecord = BoundFramebufferRecord(FramebufferTarget::Draw);
const MG_Pipe::MGPFramebufferState* const readRecord = BoundFramebufferRecord(FramebufferTarget::Read);
const Uint64 contextSerial = st.ContextSerial;
if (drawRecord == nullptr) {
@@ -513,8 +510,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_fboAttachmentSyncListValid = false;
g_fboAttachmentSyncList.clear();
} else {
const Bool listValid = g_fboAttachmentSyncListValid &&
g_fboAttachmentSyncListFbo == drawRecord->Fbo &&
const Bool listValid = g_fboAttachmentSyncListValid && g_fboAttachmentSyncListFbo == drawRecord->Fbo &&
g_fboAttachmentSyncListContentHash == drawRecord->ContentHash &&
g_fboAttachmentSyncListContextSerial == contextSerial &&
g_fboAttachmentSyncListContextGeneration == g_backendContextGeneration;
@@ -597,8 +593,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_unitTextureSyncListByHandleValid = false;
g_unitTextureSyncListByHandle.clear();
const Uint32 windowEnd = st.SamplerViewStart + st.SamplerViewCount;
for (Uint32 index = st.SamplerViewStart;
index < windowEnd && index < st.BoundSamplerViews.size(); ++index) {
for (Uint32 index = st.SamplerViewStart; index < windowEnd && index < st.BoundSamplerViews.size();
++index) {
const auto& view = st.BoundSamplerViews[index];
if (MG_Pipe::MGPipeHandleIsNull(view.Texture)) continue;
auto& twin = SyncTextureToBackendByHandle(view.Texture);
@@ -635,7 +631,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
void SyncNeccessaryTextures() { SyncNeccessaryTextures(CaptureDrawTextureSyncKeys()); }
void SyncNeccessaryTextures() {
SyncNeccessaryTextures(CaptureDrawTextureSyncKeys());
}
// Whether glBindImageTexture's `layered` means anything for this target - asked of the
// target the DRIVER will see, not the one the application named. A GL_TEXTURE_1D_ARRAY
@@ -672,8 +670,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// sweep re-reads the binding, finds nothing to mark, and CLEARS the bit on its way past.
// So the error is self-healing, and in the direction that costs one wasted look rather
// than one missed write.
static Array<Bool, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
g_writableImageBufferUnits{};
static Array<Bool, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_writableImageBufferUnits{};
static Uint g_writableImageBufferUnitCount = 0;
static void TrackWritableImageBufferUnit(Uint unit, Bool writableBufferTexture) {
@@ -725,10 +722,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// so it has no symbol of its own there and the code it generates is the code that was
// written inline before. Named here rather than discovered by a G1 diff.
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
static void IssueImageTextureBind(Uint unit, BackendTextureObject& backendTexture,
TextureTarget textureTarget, TextureInternalFormat textureFormat,
GLenum appFormat, GLint level, Bool layeredRequested,
GLint layerRequested, GLenum access) {
static void IssueImageTextureBind(Uint unit, BackendTextureObject& backendTexture, TextureTarget textureTarget,
TextureInternalFormat textureFormat, GLenum appFormat, GLint level,
Bool layeredRequested, GLint layerRequested, GLenum access) {
const Bool layerable = SupportsLayeredImageBinding(textureTarget);
const GLboolean layered = layerable ? static_cast<GLboolean>(layeredRequested) : GL_FALSE;
const GLint layer = layerable ? layerRequested : 0;
@@ -776,8 +772,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertGLEnumToTextureInternalFormat(appFormat));
boundFormatSplit != GL_UNKNOWN_MGL) {
bindFormat = boundFormatSplit;
if (const Uint splitViewId = backendTexture.GetBufferImageSplitViewId();
splitViewId != 0) {
if (const Uint splitViewId = backendTexture.GetBufferImageSplitViewId(); splitViewId != 0) {
bindTextureId = splitViewId;
}
}
@@ -805,10 +800,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
std::abort();
}
switch (MGPipeDecodeImageAccess(view.Access)) {
case MGPipeImageAccess::WriteOnly: return GL_WRITE_ONLY;
case MGPipeImageAccess::ReadWrite: return GL_READ_WRITE;
case MGPipeImageAccess::WriteOnly:
return GL_WRITE_ONLY;
case MGPipeImageAccess::ReadWrite:
return GL_READ_WRITE;
case MGPipeImageAccess::ReadOnly:
case MGPipeImageAccess::Count: break;
case MGPipeImageAccess::Count:
break;
}
return GL_READ_ONLY;
}
@@ -845,9 +843,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!first.Live || first.Gen != handle.Gen) return false;
// GL ignores Layer for a target with no layers, and for a layered bind.
const auto firstTarget = static_cast<T>(first.Desc.Target);
const Bool selectsLayer = !image.Layered &&
(firstTarget == T::Tex1DArray || firstTarget == T::Tex2DArray || firstTarget == T::Tex2DMSArray ||
firstTarget == T::Tex3D || firstTarget == T::TexCube || firstTarget == T::TexCubeArray);
const Bool selectsLayer = !image.Layered && (firstTarget == T::Tex1DArray || firstTarget == T::Tex2DArray ||
firstTarget == T::Tex2DMSArray || firstTarget == T::Tex3D ||
firstTarget == T::TexCube || firstTarget == T::TexCubeArray);
Uint32 level = image.Level;
Uint32 layer = selectsLayer ? image.Layer : 0;
for (SizeT hop = 0; hop <= state.TextureResources.size(); ++hop) {
@@ -899,16 +897,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
GLenum target = 0;
GLsizei depth = 1;
switch (TextureImpl::MapToBackendTextureTarget(textureTarget)) {
case TextureTarget::Texture2D: target = GL_TEXTURE_2D; break;
case TextureTarget::Texture2DArray: target = GL_TEXTURE_2D_ARRAY; break;
case TextureTarget::Texture3D: target = GL_TEXTURE_3D; break;
case TextureTarget::TextureCubeMap: target = GL_TEXTURE_CUBE_MAP; break;
case TextureTarget::TextureCubeMapArray: target = GL_TEXTURE_CUBE_MAP_ARRAY; depth = 6; break;
default: return 0;
case TextureTarget::Texture2D:
target = GL_TEXTURE_2D;
break;
case TextureTarget::Texture2DArray:
target = GL_TEXTURE_2D_ARRAY;
break;
case TextureTarget::Texture3D:
target = GL_TEXTURE_3D;
break;
case TextureTarget::TextureCubeMap:
target = GL_TEXTURE_CUBE_MAP;
break;
case TextureTarget::TextureCubeMapArray:
target = GL_TEXTURE_CUBE_MAP_ARRAY;
depth = 6;
break;
default:
return 0;
}
GLenum format = 0, type = 0;
MG_Util::TextureFormatProcessor::NormalizePixelFormat(internalFormat, Flags<PixelFormatNormalizeOptionBit>{},
nullptr, &format, &type);
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
internalFormat, Flags<PixelFormatNormalizeOptionBit>{}, nullptr, &format, &type);
if (format == 0 || type == 0) return 0;
InvalidImagePlaceholder& slot = g_invalidImagePlaceholders[unit];
if (slot.Id != 0 && (slot.Target != target || slot.InternalFormat != internalFormat)) {
@@ -916,11 +926,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
slot = {};
}
GLint previous = 0;
const GLenum bindingQuery = target == GL_TEXTURE_2D ? GL_TEXTURE_BINDING_2D
: target == GL_TEXTURE_2D_ARRAY ? GL_TEXTURE_BINDING_2D_ARRAY
: target == GL_TEXTURE_3D ? GL_TEXTURE_BINDING_3D
: target == GL_TEXTURE_CUBE_MAP ? GL_TEXTURE_BINDING_CUBE_MAP
: GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
const GLenum bindingQuery = target == GL_TEXTURE_2D ? GL_TEXTURE_BINDING_2D
: target == GL_TEXTURE_2D_ARRAY ? GL_TEXTURE_BINDING_2D_ARRAY
: target == GL_TEXTURE_3D ? GL_TEXTURE_BINDING_3D
: target == GL_TEXTURE_CUBE_MAP ? GL_TEXTURE_BINDING_CUBE_MAP
: GL_TEXTURE_BINDING_CUBE_MAP_ARRAY;
g_GLESFuncs.glGetIntegerv(bindingQuery, &previous);
DebugImpl::ErrorLopper::Clear();
if (slot.Id == 0) {
@@ -1138,10 +1148,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// window keeps whatever MGPImageView the last set that covered it left
// there (D-J2: entries outside the window are not cleared), which is the
// record of the very binding that raised the mark.
const Uint32 end = std::min<Uint32>(
std::max<Uint32>(st.ShaderImageStart + st.ShaderImageCount,
static_cast<Uint32>(g_imageUnitHighWaterMark)),
static_cast<Uint32>(unitCount));
const Uint32 end = std::min<Uint32>(std::max<Uint32>(st.ShaderImageStart + st.ShaderImageCount,
static_cast<Uint32>(g_imageUnitHighWaterMark)),
static_cast<Uint32>(unitCount));
for (Uint32 unit = 0; unit < end; ++unit) {
if (unit >= st.BoundShaderImages.size()) break;
// The union can include a slot no set_shader_images
+51 -52
View File
@@ -24,8 +24,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool g_anyProgramRoutesViewportIndex = false;
GLenum ResolveBackendMinFilter(const SamplerParameters& samplerParams, Bool avoidMipmapMinFilter) {
GLenum filter = MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.minFilter,
samplerParams.mipmapMode);
GLenum filter = MG_Util::ConvertSamplerFilterModeToGLEnum(samplerParams.minFilter, samplerParams.mipmapMode);
if (!avoidMipmapMinFilter) {
return filter;
}
@@ -44,11 +43,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
String ReplaceIdentifier(String source, const String& from, const String& to) {
SizeT pos = 0;
while ((pos = source.find(from, pos)) != String::npos) {
const Bool leftIsIdent = pos > 0 &&
(std::isalnum(static_cast<unsigned char>(source[pos - 1])) || source[pos - 1] == '_');
const Bool leftIsIdent =
pos > 0 && (std::isalnum(static_cast<unsigned char>(source[pos - 1])) || source[pos - 1] == '_');
const SizeT end = pos + from.size();
const Bool rightIsIdent = end < source.size() &&
(std::isalnum(static_cast<unsigned char>(source[end])) || source[end] == '_');
const Bool rightIsIdent =
end < source.size() && (std::isalnum(static_cast<unsigned char>(source[end])) || source[end] == '_');
if (!leftIsIdent && !rightIsIdent) {
source.replace(pos, from.size(), to);
pos += to.size();
@@ -73,7 +72,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return source;
}
namespace {
namespace {
Bool g_processTeardown = false;
std::once_flag g_teardownSentinelOnce;
@@ -85,14 +84,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace
#if MOBILEGL_BUILD_DISAGGREGATED
Bool InProcessTeardown() { return g_processTeardown || g_serverSessionTwinTeardown; }
Bool InProcessTeardown() {
return g_processTeardown || g_serverSessionTwinTeardown;
}
#else
Bool InProcessTeardown() { return g_processTeardown; }
Bool InProcessTeardown() {
return g_processTeardown;
}
#endif
Bool InProcessExit() { return g_processTeardown; }
Bool InProcessExit() {
return g_processTeardown;
}
void EnsureProcessTeardownSentinel() {
std::call_once(g_teardownSentinelOnce,
[] { std::atexit(+[] { g_processTeardown = true; }); });
std::call_once(g_teardownSentinelOnce, [] { std::atexit(+[] { g_processTeardown = true; }); });
}
namespace {
@@ -105,8 +109,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// A notice that arrives after exit() has begun is dropped: past that point the twin's
// destructor must not call into the driver (see InProcessTeardown()), and the process
// is about to hand every GPU object back anyway. That twin is a deliberate leak.
void OnFrontendStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId,
MG_Pipe::MGPipeHandle handle) {
void OnFrontendStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, MG_Pipe::MGPipeHandle handle) {
if (InProcessTeardown()) return;
#if MOBILEGL_BUILD_DISAGGREGATED
// P5c (ct), CONTRACT-P5C.md §5.2: with an active transport the death crosses AS A
@@ -267,9 +270,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
EsprytSlotArmVerdict CurrentEsprytSlotArmVerdict() {
return ClassifyEsprytSlotArm(
(MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemEsprytSlots) != 0,
/*legacyMemosEnabled=*/true);
return ClassifyEsprytSlotArm((MG_Config::Features.PipePush & MG_Pipe::kMGPipeSubsystemEsprytSlots) != 0,
/*legacyMemosEnabled=*/true);
}
void DiagnoseEsprytSlotArm() {
@@ -373,8 +375,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return source;
}
for (const char* name : {DRAW_ID_UNIFORM_NAME, BASE_VERTEX_UNIFORM_NAME}) {
for (const char* declPrefix : {"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ",
"mediump uint ", "uint "}) {
for (const char* declPrefix :
{"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ", "mediump uint ", "uint "}) {
const String declaration = String(declPrefix) + name + ";";
const SizeT pos = source.find(declaration);
if (pos == String::npos) {
@@ -383,8 +385,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Only promote a standalone global declaration, not a uniform we already emitted.
const Bool alreadyUniform = pos >= 8 && source.compare(pos - 8, 8, "uniform ") == 0;
if (!alreadyUniform) {
const Bool hasPrecision = std::strncmp(declPrefix, "int ", 4) != 0 &&
std::strncmp(declPrefix, "uint ", 5) != 0;
const Bool hasPrecision =
std::strncmp(declPrefix, "int ", 4) != 0 && std::strncmp(declPrefix, "uint ", 5) != 0;
const String qualifier = hasPrecision ? "uniform " : "uniform highp ";
source.replace(pos, declaration.size(), qualifier + declaration);
}
@@ -454,9 +456,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
break;
}
machinery += String("uniform highp int ") + BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + ";\n";
machinery += String("layout(std430, binding = ") + std::to_string(paramsBinding) +
") readonly buffer " + INDIRECT_PARAMS_BLOCK_NAME +
" { highp uint mg_indirectWords[]; };\n";
machinery += String("layout(std430, binding = ") + std::to_string(paramsBinding) + ") readonly buffer " +
INDIRECT_PARAMS_BLOCK_NAME + " { highp uint mg_indirectWords[]; };\n";
if (rebaseInstanceId) {
machinery += String("#define ") + ZERO_BASED_INSTANCE_ID_NAME + " (gl_InstanceID - ((" +
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " > 0) ? int(mg_indirectWords[uint(" +
@@ -523,8 +524,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (pos != 0 && source[pos - 1] != '\n') {
continue;
}
source.replace(pos, declaration.size(),
String("flat out highp int ") + VIEWPORT_INDEX_VARYING_NAME + ";");
source.replace(pos, declaration.size(), String("flat out highp int ") + VIEWPORT_INDEX_VARYING_NAME + ";");
return true;
}
return false;
@@ -576,8 +576,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
gated += "\n";
gated += String(ESSL_ENTRY_POINT_SIGNATURE) + "\n";
gated += "{\n";
gated += String(" if (((") + VIEWPORT_PASS_MASK_UNIFORM_NAME + " >> (" +
VIEWPORT_INDEX_VARYING_NAME + " & 15)) & 1) == 0)\n";
gated += String(" if (((") + VIEWPORT_PASS_MASK_UNIFORM_NAME + " >> (" + VIEWPORT_INDEX_VARYING_NAME +
" & 15)) & 1) == 0)\n";
gated += " {\n";
gated += " discard;\n";
gated += " }\n";
@@ -621,7 +621,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// This closes espryt D14 / closure row 7 and, with it, row 6's "LEGACY_MEMOS=0 +
// PUSH=0x7f must name a verdict": that combination now names NoArm and stops.
enum class PipeSubsystemArmVerdict { Handles, Legacy, NoArm };
enum class PipeSubsystemArmVerdict {
Handles,
Legacy,
NoArm
};
// The one voice for an armless verdict, so the two families cannot disagree about what
// it means or about whether it stops. `what` names the family and the bit.
@@ -673,10 +677,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"MG_Pipe/SubsystemDeps.def says it requires (requires 0x%llx, missing 0x%llx): "
"%s - REFUSING the dependent bit and running the legacy arm. Set every bit of "
"the row, or clear the family's own",
what, static_cast<unsigned long long>(mask),
static_cast<unsigned long long>(required),
static_cast<unsigned long long>(required & ~mask),
MG_Pipe::MGPipeSubsystemDependencyWhy(family));
what, static_cast<unsigned long long>(mask), static_cast<unsigned long long>(required),
static_cast<unsigned long long>(required & ~mask), MG_Pipe::MGPipeSubsystemDependencyWhy(family));
return true;
}
@@ -732,9 +734,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// make the record silently truncate, and truncating silently is the bug class this
// phase is closing, while widening the payload is a wire change nobody has evidence
// for. So: refuse the bit, name the cap, run the legacy arm.
if (!refused &&
static_cast<Uint32>(std::max<Int>(g_GLESCapabilities.MaxColorAttachments, 0)) >
MG_Pipe::kMGPipeMaxColorAttachments) {
if (!refused && static_cast<Uint32>(std::max<Int>(g_GLESCapabilities.MaxColorAttachments, 0)) >
MG_Pipe::kMGPipeMaxColorAttachments) {
MGLOG_E("MGPipe: this driver reports GL_MAX_COLOR_ATTACHMENTS = %d, above "
"MGPFramebufferState::Color[%u]'s wire width - REFUSING "
"kMGPipeSubsystemFramebuffer (bit 9) rather than truncating the record, "
@@ -743,9 +744,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
refused = true;
}
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
const BufferImpl::PipeSubsystemArmVerdict verdict =
BufferImpl::ClassifyPipeSubsystemArm(bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemFramebuffer (bit 9) clear (or refuses "
@@ -784,9 +785,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
refused = PipeSubsystemDependencyMissing(mask, MG_Pipe::kMGPipeSubsystemTextureResources,
"kMGPipeSubsystemTextureResources (bit 10)");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
const BufferImpl::PipeSubsystemArmVerdict verdict =
BufferImpl::ClassifyPipeSubsystemArm(bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemTextureResources (bit 10) clear (or "
@@ -817,9 +818,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
refused = PipeSubsystemDependencyMissing(mask, MG_Pipe::kMGPipeSubsystemSamplers,
"kMGPipeSubsystemSamplers (bit 11)");
}
const BufferImpl::PipeSubsystemArmVerdict verdict = BufferImpl::ClassifyPipeSubsystemArm(
bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
const BufferImpl::PipeSubsystemArmVerdict verdict =
BufferImpl::ClassifyPipeSubsystemArm(bitSet && !refused, /*legacyMemosEnabled=*/true,
/*legacyArmSurvivesLegacyMemos=*/false);
if (verdict == BufferImpl::PipeSubsystemArmVerdict::NoArm) {
BufferImpl::StopOnArmlessPipeSubsystem(
"MOBILEGL_PIPE_PUSH leaves kMGPipeSubsystemSamplers (bit 11) clear (or refuses "
@@ -874,7 +875,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint32 cap = static_cast<Uint32>(std::max<Int>(g_GLESCapabilities.MaxColorAttachments, 0));
if (cap > MG_Pipe::kMGPipeMaxColorAttachments) {
MGLOG_E("MGPipe: this driver reports GL_MAX_COLOR_ATTACHMENTS = %u, above "
"MGPFramebufferState::Color[%u]'s wire width", cap, MG_Pipe::kMGPipeMaxColorAttachments);
"MGPFramebufferState::Color[%u]'s wire width",
cap, MG_Pipe::kMGPipeMaxColorAttachments);
BufferImpl::StopOnArmlessPipeSubsystem(
"kMGPipeSubsystemFramebuffer (bit 9) cannot carry this driver's colour attachments, and "
"the record arm is the only arm since P13 W6");
@@ -897,8 +899,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// was added to only one of them.
for (SizeT i = 0; i < static_cast<SizeT>(MobileGL::TextureTarget::TextureTargetCount); ++i) {
const auto target = static_cast<MobileGL::TextureTarget>(i);
if (MG_Pipe::MGPipeResourceTargetForTextureTarget(target) ==
static_cast<Uint32>(record->Desc.Target)) {
if (MG_Pipe::MGPipeResourceTargetForTextureTarget(target) == static_cast<Uint32>(record->Desc.Target)) {
return target;
}
}
@@ -950,8 +951,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E_ONCE("MGPipe: a pending upload for level %u carries resource target %u while its "
"resource's descriptor says %u - D matches on the upload half only, so this "
"entry was matched anyway; the two sides disagree about what the resource is",
static_cast<Uint>(level), static_cast<Uint>(carried),
static_cast<Uint>(record.Desc.Target));
static_cast<Uint>(level), static_cast<Uint>(carried), static_cast<Uint>(record.Desc.Target));
}
const MG_Pipe::MGPipeResourceRecord::PendingUpload* FindPipeTextureUpload(
@@ -1045,8 +1045,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!record.Live || record.Gen != res.Gen) return false;
const Uint8 uploadHalf = MG_Pipe::MGPipeSubDataUploadTargetOf(packedTarget);
for (auto& pending : record.PendingUploads) {
if (MG_Pipe::MGPipeSubDataUploadTargetOf(pending.UploadTarget) != uploadHalf ||
pending.Level != level) {
if (MG_Pipe::MGPipeSubDataUploadTargetOf(pending.UploadTarget) != uploadHalf || pending.Level != level) {
continue;
}
pending.UnionBox = wholeLevel;
+45 -62
View File
@@ -307,7 +307,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
template <typename StateObject, typename BackendObject MGB_TWIN_KIND_PARAM>
class StateBackendObjectRegistry {
public:
using BackendPtr = SharedPtr<BackendObject>;
using SlotTable = BackendSlotTable<StateObject, BackendObject, kKind>;
// P14 S6: the ONE bucket of this kind the calling thread is in. Every handle-keyed entry
@@ -435,13 +434,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (EsprytSlotTablesEnabled()) return m_slotTables.Snapshot();
return {};
}
SizeT BucketCount() const {
return EsprytSlotTablesEnabled() ? m_slotTables.BucketCount() : 0;
}
SizeT BucketCount() const { return EsprytSlotTablesEnabled() ? m_slotTables.BucketCount() : 0; }
// Drop one {session, share group}. Answers whether such a bucket existed.
Bool DropBucket(const TwinKey& key) {
return EsprytSlotTablesEnabled() && m_slotTables.Drop(key);
}
Bool DropBucket(const TwinKey& key) { return EsprytSlotTablesEnabled() && m_slotTables.Drop(key); }
// Drop every bucket of one session - the difference between "this session's twins go"
// and "the process's do" - and answer how many groups that took with it.
Uint32 DropBucketsOfSession(Uint64 sessionKey) {
@@ -634,8 +629,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// view record is live with Desc.ViewOf naming a freed slot. No draw can land in between, and
// PipeTextureRecordForHandle tests Live and Gen, so that window answers null (= not clean)
// rather than a recycled stranger's record.
const MG_Pipe::MGPipeResourceRecord* PipeTextureStorageRecordForRecord(
const MG_Pipe::MGPipeResourceRecord& record);
const MG_Pipe::MGPipeResourceRecord* PipeTextureStorageRecordForRecord(const MG_Pipe::MGPipeResourceRecord& record);
// P5e (fb): the texture's own TARGET, from its descriptor. The image-unit bind needs it
// (glBindImageTexture's layered/format rules are per target) and MGPImageView has no room
@@ -748,8 +742,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The DISTINCT buffers the draw's enabled attributes fetch from, deduped on {slot, gen}.
// A null Res is skipped: it is a disabled slot below the window's high-water mark, or a
// client-memory array whose bytes have no store to ensure (P8 stages those).
Uint ResolveDrawVertexBuffersFromRecord(const MG_Pipe::MGPipeApplierState& st,
DrawVertexBufferRequest* out, Uint capacity);
Uint ResolveDrawVertexBuffersFromRecord(const MG_Pipe::MGPipeApplierState& st, DrawVertexBufferRequest* out,
Uint capacity);
struct DrawIndexBufferRequest {
MG_Pipe::MGPipeHandle Res = MG_Pipe::kMGPipeNullHandle;
@@ -895,8 +889,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// P5c (hd): the staged-coverage assertion (StagedShadowStore::RequireCoverage) for a
// read of the server shadow outside the upload ladders - the indirect command-byte
// resolver. A no-op for a base that is not this resource's server shadow.
void RequireStagedCoverage(GLESBufferResource& resource, const Uint8* hostBase, SizeT start,
SizeT end, const char* site);
void RequireStagedCoverage(GLESBufferResource& resource, const Uint8* hostBase, SizeT start, SizeT end,
const char* site);
// P11 B2: THE CPU-READABLE BYTES OF A SPLIT SERVER'S BUFFER TWIN, for the handle-arm
// readers that have no frontend object (indirect commands, primitive-restart
@@ -965,9 +959,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// Returns the glMapBufferRange access bits, or 0 when tier 1 must not be taken.
inline constexpr SizeT kEsprytInvalidateRangeMinBytes = 128u * 1024u;
constexpr GLbitfield InvalidateFlushAccessFor(SizeT queuedStart, SizeT queuedEnd, SizeT mapStart,
SizeT mapEnd, SizeT limit, SizeT storageSize,
Bool ringCopyInFlight) {
constexpr GLbitfield InvalidateFlushAccessFor(SizeT queuedStart, SizeT queuedEnd, SizeT mapStart, SizeT mapEnd,
SizeT limit, SizeT storageSize, Bool ringCopyInFlight) {
if (mapEnd <= mapStart) return 0u;
// THE WIDENING REFUSAL. Not >=, not "covers": exactly, in both directions. A map
// narrower than the queued range leaves bytes unwritten inside a range it has just
@@ -986,8 +979,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// instead, GPU-ordered behind the copies. The whole-buffer orphan-map is exempt: it
// rewrites every byte from the shadow the queued copies were taken from.
if (!wholeBuffer && ringCopyInFlight) return 0u;
return GL_MAP_WRITE_BIT |
(wholeBuffer ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_INVALIDATE_RANGE_BIT);
return GL_MAP_WRITE_BIT | (wholeBuffer ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_INVALIDATE_RANGE_BIT);
}
// P15: WRITE RENAMING FOR A SMALL STORE. The draw-time drain sends a partial range below
@@ -1142,8 +1134,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// frames, so re-referencing them would need per-bind pinning — rewriting
// GetUBOSize() bytes once per program per frame is far cheaper.
struct UboRingAllocation {
Uint32 contentVersion = ~0u; // frontend UBO content version held at `offset`
Uint32 ringGeneration = 0; // ring identity the slot lives in (0 = never valid)
Uint32 contentVersion = ~0u; // frontend UBO content version held at `offset`
Uint32 ringGeneration = 0; // ring identity the slot lives in (0 = never valid)
Uint64 frameSerial = ~Uint64{0}; // frame the slot was written in
SizeT offset = 0;
};
@@ -1161,7 +1153,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// this frame and ring generation, Allocated when a fresh slot was carved (`outWrite`
// is where its bytes go), Failed when allocation failed. `outBufferId` names the ring
// store for Reused and Allocated.
enum class UboRingSlotState : Uint8 { Unavailable, Reused, Allocated, Failed };
enum class UboRingSlotState : Uint8 {
Unavailable,
Reused,
Allocated,
Failed
};
UboRingSlotState UboRingPrepareSlot(UboRingAllocation& slot, Uint32 contentVersion, Uint64 frameSerial,
SizeT bindSize, void*& outWrite, Uint& outBufferId);
void* UboRingMappedPtr();
@@ -1509,7 +1506,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 m_syncedContextSerial = 0;
};
extern TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso>
extern TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject,
MG_Pipe::MGPipeKind::VertexElementsCso>
g_backendVertexArrayObjects;
// P5e (id), CONTRACT-P5E §4.1 / §4.2: THE VAO TWIN, RESOLVED BY THE HANDLE THE RECORD
@@ -1628,8 +1626,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
inline Bool IsMultisampleTextureTarget(TextureTarget target) {
return target == TextureTarget::Texture2DMultisample ||
target == TextureTarget::Texture2DMultisampleArray;
return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray;
}
inline Bool SupportsWrapR(TextureTarget target) {
@@ -1833,8 +1830,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// The contextId / samplingGeneration arguments are GONE: the applier's ContextSerial
// and TextureShutterSerial are what the caller's keys already carry.
Bool IsDrawSyncCleanByRecord(MG_Pipe::MGPipeHandle res,
const MG_Pipe::MGPipeResourceRecord& record) const;
Bool IsDrawSyncCleanByRecord(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPipeResourceRecord& record) const;
private:
void RecreateBackendTexture();
@@ -2069,8 +2065,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ResolveTextureTwin is the lookup without the sync, for the callers that need the twin
// to answer a shape question (an image bind's target and storage format come off the
// twin, not off a new wire field) after tx2's sync has already run this frame.
SharedPtr<BackendTextureObject>& SyncTextureToBackendByHandle(
MG_Pipe::MGPipeHandle texture, Bool imageBindableStorageRequired = false);
SharedPtr<BackendTextureObject>& SyncTextureToBackendByHandle(MG_Pipe::MGPipeHandle texture,
Bool imageBindableStorageRequired = false);
BackendTextureObject* ResolveTextureTwin(MG_Pipe::MGPipeHandle texture);
// NoteDriverSideTextureWrite's HANDLE ARM, for the same paths when the caller holds a
// Texture handle (a framebuffer surface's Res, an image unit's Res, VerbMipRes,
@@ -2186,9 +2182,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
*/
GLenum m_backendDrawBuffers[MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS] = {GL_NONE};
static constexpr Uint MAX_COLOR_ATTACHMENT_SLOTS =
static_cast<Uint>(FramebufferAttachmentType::Color31) -
static_cast<Uint>(FramebufferAttachmentType::Color0) + 1;
static constexpr Uint MAX_COLOR_ATTACHMENT_SLOTS = static_cast<Uint>(FramebufferAttachmentType::Color31) -
static_cast<Uint>(FramebufferAttachmentType::Color0) + 1;
/* Where each frontend GL_COLOR_ATTACHMENTn image physically lives in the backend ES
framebuffer, as a GL_COLOR_ATTACHMENTm enum. ES only accepts glDrawBuffers bufs[s] ==
GL_COLOR_ATTACHMENTs, so a GL draw-buffer slot s naming attachment a forces a's image
@@ -2272,7 +2267,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void ApplyReadBufferFromRecord(const MG_Pipe::MGPFramebufferState& record, Uint glNameForDiag);
};
extern TwinRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject, MG_Pipe::MGPipeKind::Framebuffer>
extern TwinRegistry<MG_State::GLState::FramebufferObject, BackendFramebufferObject,
MG_Pipe::MGPipeKind::Framebuffer>
g_backendFramebufferObjects;
// P4a (D-C2 as corrected by ID-19): the applier's record for THE FRAMEBUFFER OBJECT this
@@ -2306,8 +2302,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The index answers about a TEXTURE SLOT and returns whole FRAMEBUFFER handles, so a
// recycled framebuffer slot never answers for its predecessor. See the definition for
// why nothing prunes a dead row.
void NoteFramebufferTextureAttachments(MG_Pipe::MGPipeHandle fbo,
const MG_Pipe::MGPFramebufferState& record);
void NoteFramebufferTextureAttachments(MG_Pipe::MGPipeHandle fbo, const MG_Pipe::MGPFramebufferState& record);
// The record's surface for an attachment POINT, or null when this record does not
// describe that point at all (Color8..Color31, and the default framebuffer's FRONT/BACK
// tokens). MGPFramebufferState carries Color[8] + Depth + Stencil, which is every point
@@ -2539,7 +2534,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
namespace SamplerImpl {
class BackendSamplerObject; // for PrgramImpl's sampler-pass memo rows below
}
} // namespace SamplerImpl
// Image uniforms take their unit from the layout(binding=N) qualifier baked into
// the transpiled ESSL; unlike samplers they must not (and in ES cannot) be
@@ -2686,9 +2681,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool PointSizeDemoted() const { return PointSizeWasDemoted; }
Uint GetUBOSize() const { return GlobalUboSize; }
const Vector<ShaderStage>& GetLinkedShaderStages() const { return LinkedStages; }
const UnorderedMap<String, Int>& GetShaderStorageBlockBindingOverrides() const {
return StorageOverrides;
}
const UnorderedMap<String, Int>& GetShaderStorageBlockBindingOverrides() const { return StorageOverrides; }
// THE DEBUG LOG IS DELETED ON THIS ARM (CONTRACT-P5E §5.5). The snapshot is
// GL-thread-owned ShaderObject SharedPtrs - the exact shape rule C forbids an
// applier entry point to reach - and its one consumer in the build is an MGLOG_D
@@ -2734,9 +2727,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// arrays is keyed by its full "[0]"-terminated spelling - which is why the
// suffixed lookup is tried before the trailing subscript is read as an index.
Int GetUniformLocation(const String& name) const;
Int GetActiveUniformBlocksCount() const {
return static_cast<Int>(Link->glBlockIndexToTProgram.size());
}
Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Link->glBlockIndexToTProgram.size()); }
const String& GetUniformBlockName(Uint index) const;
GLenum GetTransformFeedbackBufferMode() const { return Link->xfbBufferMode; }
@@ -2878,20 +2869,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// `layout(vertices = N) out`, so a program built for one patch size is stale for
// another and the draw path has to say so. -1 compares equal to itself for every
// program that has a control stage of its own, i.e. for all but a handful.
Int GetPassthroughTessControlPatchVertices() const {
return m_passthroughTessControlPatchVertices;
}
Int GetPassthroughTessControlPatchVertices() const { return m_passthroughTessControlPatchVertices; }
// GL_PATCH_DEFAULT_{OUTER,INNER}_LEVEL the same synthesized stage was built with, for
// the same reason: ES has neither the state nor an entry point to forward it to, so
// glPatchParameterfv's values are compiled in as literals and a program built with one
// set is stale for another. Meaningless (and never read) when the patch-vertices field
// above is -1, which is the gate the draw path tests first.
const FloatVec4& GetPassthroughTessControlOuterLevel() const {
return m_passthroughTessControlOuterLevel;
}
const FloatVec2& GetPassthroughTessControlInnerLevel() const {
return m_passthroughTessControlInnerLevel;
}
const FloatVec4& GetPassthroughTessControlOuterLevel() const { return m_passthroughTessControlOuterLevel; }
const FloatVec2& GetPassthroughTessControlInnerLevel() const { return m_passthroughTessControlInnerLevel; }
Bool HasGlobalUboBlock() const { return m_globalUboBackendBlockIndex >= 0; }
const Vector<Int>& GetUniformBlockBackendIndices() const { return m_uniformBlockBackendIndices; }
@@ -2969,10 +2954,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// own - which ES 3.2 rejects outright. Called from SyncToBackend after every real
// stage has been attached and before the link; see the definition for why it cannot
// regress a program that works today.
void AttachPassthroughTessControlStage(
const ProgramBuildSource& src, Int tessEvalShaderIndex,
const Vector<Vector<unsigned int>>& shaderSpirvs, const String& vertexStageEssl,
const String& tessEvalStageEssl);
void AttachPassthroughTessControlStage(const ProgramBuildSource& src, Int tessEvalShaderIndex,
const Vector<Vector<unsigned int>>& shaderSpirvs,
const String& vertexStageEssl, const String& tessEvalStageEssl);
// One stage's SPIR-V through the DirectGLES pass chain and SPIRV-Cross, producing
// the raw emitted ESSL and the interface blocks this stage's XFB flattening
@@ -2985,10 +2969,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const ImageFormatBakeInputs& imageFormatBake,
const UnorderedMap<String, Int>& storageBlockBindingOverrides,
const std::map<String, String>& inputBlockRenames,
const std::map<String, String>& outputBlockRenames,
Bool stripInputBlockLocations, Bool stripOutputBlockLocations,
Int atomicCounterEsslBindingTop, Bool enableSpirvValidation,
String& outSource,
const std::map<String, String>& outputBlockRenames, Bool stripInputBlockLocations,
Bool stripOutputBlockLocations, Int atomicCounterEsslBindingTop,
Bool enableSpirvValidation, String& outSource,
std::set<String>& outFlattenedXfbBlockNames,
Vector<Int>& outAtomicCounterGlBindings, String& outError) const;
@@ -3309,9 +3292,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// look at it.
// P14 S6: and a bucket registry for the same reason the buffer one above is - a sampler
// view's twin is a driver object of the texture's group.
using BackendSamplerViewTable = SlotTableRegistry<MG_State::GLState::ITextureObject,
BackendSamplerViewObject,
MG_Pipe::MGPipeKind::SamplerViewCso>;
using BackendSamplerViewTable = SlotTableRegistry<MG_State::GLState::ITextureObject, BackendSamplerViewObject,
MG_Pipe::MGPipeKind::SamplerViewCso>;
extern BackendSamplerViewTable g_backendSamplerViews;
BackendSamplerViewObject* FindSamplerViewForHandle(MG_Pipe::MGPipeHandle view);
@@ -3358,7 +3340,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Uint64 m_syncedResourceSerial = 0;
};
extern TwinRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject, MG_Pipe::MGPipeKind::Renderbuffer>
extern TwinRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject,
MG_Pipe::MGPipeKind::Renderbuffer>
g_backendRenderbufferObjects;
} // namespace RenderbufferImpl
File diff suppressed because it is too large Load Diff
@@ -96,9 +96,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Cold path: pin the shadow from the driver once (init probes and
// pre-shadow code bind raw but restore what they found).
GLint binding = 0;
g_GLESFuncs.glGetIntegerv(
target == FramebufferTarget::Read ? GL_READ_FRAMEBUFFER_BINDING : GL_DRAW_FRAMEBUFFER_BINDING,
&binding);
g_GLESFuncs.glGetIntegerv(target == FramebufferTarget::Read ? GL_READ_FRAMEBUFFER_BINDING
: GL_DRAW_FRAMEBUFFER_BINDING,
&binding);
g_driverFBOBindings[idx] = static_cast<Uint>(binding);
g_driverFBOBindingKnown[idx] = true;
}
@@ -370,8 +370,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool SyncAttachmentSurface(GLenum glFBOTarget, const MG_Pipe::MGPSurface& surface,
GLenum glBackendAttachment,
const MG_State::GLState::FramebufferAttachmentObject* monolithAttachment) {
if (surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
MG_Pipe::MGPipeHandleIsNull(surface.Res)) {
if (surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone || MG_Pipe::MGPipeHandleIsNull(surface.Res)) {
// An empty point: the caller detached it on exactly this test, so attaching
// nothing here is the whole of the agreement. (The legacy arm does the same.)
return true;
@@ -419,10 +418,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
uploadTarget == TextureUploadTarget::Texture1DArray ||
uploadTarget == TextureUploadTarget::CubeMapArray ||
uploadTarget == TextureUploadTarget::Texture2DMultisampleArray) {
g_GLESFuncs.glFramebufferTextureLayer(glFBOTarget, glBackendAttachment,
backendTextureObject->GetBackendTextureId(),
static_cast<GLint>(surface.Level),
static_cast<GLint>(surface.Layer));
g_GLESFuncs.glFramebufferTextureLayer(
glFBOTarget, glBackendAttachment, backendTextureObject->GetBackendTextureId(),
static_cast<GLint>(surface.Level), static_cast<GLint>(surface.Layer));
} else {
auto glTextureTarget = TextureImpl::ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
if (glTextureTarget == GL_UNKNOWN_MGL) {
@@ -449,8 +447,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"renderbuffer")) {
return false;
}
auto* twinSlot = AdoptTwinByHandle(RenderbufferImpl::g_backendRenderbufferObjects, surface.Res,
"renderbuffer");
auto* twinSlot =
AdoptTwinByHandle(RenderbufferImpl::g_backendRenderbufferObjects, surface.Res, "renderbuffer");
if (twinSlot == nullptr) {
return false;
}
@@ -495,7 +493,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool IsFixedPointFallbackReadAttachment() {
const auto* record = MG_Pipe::MGPipeApplier().ReadFramebuffer();
return record && record->ReadSurface.Kind != MG_Pipe::kMGPipeSurfaceKindNone &&
IsSnormFormat(static_cast<TextureInternalFormat>(record->ReadSurface.InternalFormat));
IsSnormFormat(static_cast<TextureInternalFormat>(record->ReadSurface.InternalFormat));
const auto& readFBO =
MG_Pipe::gPipeInputs->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject();
if (!readFBO) {
@@ -581,8 +579,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) {
const auto& color = record.Color[i];
if (color.Res == record.ReadSurface.Res && color.Kind == record.ReadSurface.Kind &&
color.Layered == record.ReadSurface.Layered &&
color.Level == record.ReadSurface.Level && color.Layer == record.ReadSurface.Layer &&
color.Layered == record.ReadSurface.Layered && color.Level == record.ReadSurface.Level &&
color.Layer == record.ReadSurface.Layer &&
color.UploadTarget == record.ReadSurface.UploadTarget) {
pushedReadBuf = static_cast<FramebufferAttachmentType>(
static_cast<Int>(FramebufferAttachmentType::Color0) + static_cast<Int>(i));
@@ -655,15 +653,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
frontendBuf > FramebufferAttachmentType::Color31) {
continue; // GL_NONE, or a default-framebuffer FRONT/BACK token: never relocated.
}
const Uint a =
static_cast<Uint>(frontendBuf) - static_cast<Uint>(FramebufferAttachmentType::Color0);
const Uint a = static_cast<Uint>(frontendBuf) - static_cast<Uint>(FramebufferAttachmentType::Color0);
// Neither guard may ever fire: a duplicate draw buffer is already INVALID_OPERATION
// and an out-of-range one is rejected by ValidateColorAttachmentInRange. If one did
// fire the table would disagree with the glDrawBuffers the caller already issued,
// which is the exact non-injectivity this table exists to remove.
MOBILEGL_ASSERT(a < slotCount && !assigned[a],
"Draw buffer %u names colour attachment %u which is out of range or duplicated.", s,
a);
"Draw buffer %u names colour attachment %u which is out of range or duplicated.", s, a);
if (a >= slotCount || assigned[a]) {
continue;
}
@@ -758,8 +754,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} // namespace
void NoteFramebufferTextureAttachments(MG_Pipe::MGPipeHandle fbo,
const MG_Pipe::MGPFramebufferState& record) {
void NoteFramebufferTextureAttachments(MG_Pipe::MGPipeHandle fbo, const MG_Pipe::MGPFramebufferState& record) {
if (MG_Pipe::MGPipeHandleIsNull(fbo)) return;
ForgetFramebufferTextureAttachmentsLocked(fbo.Slot);
Vector<Uint32> textureSlots;
@@ -772,7 +767,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
textureSlots.push_back(surface.Res.Slot);
g_framebuffersByAttachedTextureSlot[surface.Res.Slot].push_back(fbo);
};
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) note(record.Color[i]);
for (Uint i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i)
note(record.Color[i]);
note(record.Depth);
note(record.Stencil);
if (textureSlots.empty()) return; // the common Minecraft frame: renderbuffers only
@@ -797,8 +793,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// of this arm. The two SERVER-owned gates stay, because no client-side value can answer
// what they answer: g_attachmentBackendIdGeneration ("did I re-mint a driver id") and
// the re-entry it drives.
void BackendFramebufferObject::SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo,
FramebufferTarget asTarget) {
void BackendFramebufferObject::SyncToBackendByHandle(MG_Pipe::MGPipeHandle fbo, FramebufferTarget asTarget) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -904,28 +899,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (idGenerationMoved) {
m_syncedBackendIdGeneration = g_attachmentBackendIdGeneration;
}
if (idGenerationMoved || attachmentPointsMoved ||
m_syncedAttachmentRecordHash != record.ContentHash) {
const auto applyPoint = [&](FramebufferAttachmentType point,
const MG_Pipe::MGPSurface& surface) {
const Bool isColorPoint = point >= FramebufferAttachmentType::Color0 &&
point <= FramebufferAttachmentType::Color31;
const GLenum glBackendAttachment =
isColorPoint ? GetBackendAttachmentType(point)
: MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(point);
if (idGenerationMoved || attachmentPointsMoved || m_syncedAttachmentRecordHash != record.ContentHash) {
const auto applyPoint = [&](FramebufferAttachmentType point, const MG_Pipe::MGPSurface& surface) {
const Bool isColorPoint =
point >= FramebufferAttachmentType::Color0 && point <= FramebufferAttachmentType::Color31;
const GLenum glBackendAttachment = isColorPoint
? GetBackendAttachmentType(point)
: MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(point);
if (glBackendAttachment == GL_NONE || glBackendAttachment == GL_UNKNOWN_MGL) return;
const Bool empty = surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone ||
MG_Pipe::MGPipeHandleIsNull(surface.Res);
const Bool empty =
surface.Kind == MG_Pipe::kMGPipeSurfaceKindNone || MG_Pipe::MGPipeHandleIsNull(surface.Res);
// The detach that keeps m_backendColorSlots a permutation of the PHYSICAL
// layout: a colour point whose record says empty must not keep the previous
// owner's image, or glReadBuffer returns it. Bounded by the driver's cap,
// because GL_COLOR_ATTACHMENTn above it is INVALID_ENUM.
if (isColorPoint && empty) {
const Int index =
static_cast<Int>(point) - static_cast<Int>(FramebufferAttachmentType::Color0);
const Int index = static_cast<Int>(point) - static_cast<Int>(FramebufferAttachmentType::Color0);
if (index < g_GLESCapabilities.MaxColorAttachments) {
g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment,
GL_RENDERBUFFER, 0);
g_GLESFuncs.glFramebufferRenderbuffer(glFBOTarget, glBackendAttachment, GL_RENDERBUFFER, 0);
}
return;
}
@@ -961,8 +952,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (!hasAnyAttachment) {
const Array<Int, 5> defaults = {
static_cast<Int>(record.Width), static_cast<Int>(record.Height),
static_cast<Int>(record.Layers),
static_cast<Int>(record.Width), static_cast<Int>(record.Height), static_cast<Int>(record.Layers),
std::min<Int>(static_cast<Int>(record.Samples), g_GLESCapabilities.MaxFramebufferSamples),
static_cast<Int>(record.FixedSampleLocations != 0 ? GL_TRUE : GL_FALSE)};
if (!m_syncedNoAttachmentDefaultsValid || m_syncedNoAttachmentDefaults != defaults) {
@@ -1057,8 +1047,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// earlier operation left queued; drain before attaching (rare path -
// only runs when the attachment actually changes).
void DrainPendingGLErrors() {
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
}
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {}
}
// Record the color point as detached when the shadow said something was
@@ -1105,8 +1094,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return fb.id;
}
void EnsureColorAttachment2D(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLenum texTarget,
GLint level) {
void EnsureColorAttachment2D(ScratchFramebuffer& fb, GLenum fbTarget, Uint tex, GLenum texTarget, GLint level) {
PrepareForUse(fb, fbTarget);
if (fb.depthTex != 0) {
g_GLESFuncs.glFramebufferTexture2D(fbTarget, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
@@ -1163,9 +1151,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glFramebufferTexture2D(fbTarget, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
RecordNoColor(fb);
}
if (fb.depthTex == tex && fb.depthTarget == texTarget && fb.depthLevel == level &&
fb.depthLayer < 0 && fb.depthHasStencil == withStencil &&
fb.depthIsStencilOnly == stencilOnly) {
if (fb.depthTex == tex && fb.depthTarget == texTarget && fb.depthLevel == level && fb.depthLayer < 0 &&
fb.depthHasStencil == withStencil && fb.depthIsStencilOnly == stencilOnly) {
return;
}
if (fb.depthTex != 0) {
@@ -1176,11 +1163,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
DrainPendingGLErrors();
// A stencil-only texture has no depth point to attach at, and a depth one has no
// stencil point; GL_DEPTH_STENCIL_ATTACHMENT is only the combined texture's form.
g_GLESFuncs.glFramebufferTexture2D(fbTarget,
stencilOnly ? GL_STENCIL_ATTACHMENT
: (withStencil ? GL_DEPTH_STENCIL_ATTACHMENT
: GL_DEPTH_ATTACHMENT),
texTarget, tex, level);
g_GLESFuncs.glFramebufferTexture2D(
fbTarget,
stencilOnly ? GL_STENCIL_ATTACHMENT : (withStencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT),
texTarget, tex, level);
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
RecordNoDepth(fb);
return;
@@ -1383,13 +1369,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the same four fields, read out of the same record type, with Desc.Target telling the
// applier which of the three per-kind tables the record lives in.
#define MGB_RBO_FORMAT \
(pushedRecord != nullptr ? static_cast<TextureInternalFormat>(pushedRecord->Desc.InternalFormat) \
(pushedRecord != nullptr ? static_cast<TextureInternalFormat>(pushedRecord->Desc.InternalFormat) \
: stateRBOObject->GetInternalFormat())
#define MGB_RBO_WIDTH (pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Width) : static_cast<Int>(stateRBOObject->GetWidth()))
#define MGB_RBO_WIDTH \
(pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Width) \
: static_cast<Int>(stateRBOObject->GetWidth()))
#define MGB_RBO_HEIGHT \
(pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Height) : static_cast<Int>(stateRBOObject->GetHeight()))
(pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Height) \
: static_cast<Int>(stateRBOObject->GetHeight()))
#define MGB_RBO_SAMPLES \
(pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Samples) : static_cast<Int>(stateRBOObject->GetSamples()))
(pushedRecord != nullptr ? static_cast<Int>(pushedRecord->Desc.Samples) \
: static_cast<Int>(stateRBOObject->GetSamples()))
#undef MGB_RBO_FORMAT
#undef MGB_RBO_WIDTH
@@ -1407,16 +1397,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
const MG_Pipe::MGPipeResourceRecord* const pushedRecord =
PipeRenderbufferRecordForHandle(renderbuffer);
const MG_Pipe::MGPipeResourceRecord* const pushedRecord = PipeRenderbufferRecordForHandle(renderbuffer);
if (pushedRecord == nullptr) {
MGLOG_E_ONCE("MGPipe: renderbuffer handle {%u, %u} has no applier record, so its "
"storage cannot be allocated from the pushed descriptor",
renderbuffer.Slot, renderbuffer.Gen);
return;
}
if (m_isInitialized && m_syncedResourceSerial != 0 &&
m_syncedResourceSerial == pushedRecord->Serial) {
if (m_isInitialized && m_syncedResourceSerial != 0 && m_syncedResourceSerial == pushedRecord->Serial) {
return;
}
@@ -1446,8 +1434,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto backendSamples = static_cast<GLsizei>(ClampSamplesToBackendSupport(
GetRenderbufferFormatCapabilityTargetIndex(), internalFormat, glFormat, samples));
g_GLESFuncs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, backendSamples, glInternalFormat,
static_cast<GLsizei>(width),
static_cast<GLsizei>(height));
static_cast<GLsizei>(width), static_cast<GLsizei>(height));
} else {
g_GLESFuncs.glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, static_cast<GLsizei>(width),
static_cast<GLsizei>(height));
@@ -1473,7 +1460,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_isInitialized = true;
}
TwinRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject, MG_Pipe::MGPipeKind::Renderbuffer>
TwinRegistry<MG_State::GLState::RenderbufferObject, BackendRenderbufferObject,
MG_Pipe::MGPipeKind::Renderbuffer>
g_backendRenderbufferObjects;
} // namespace RenderbufferImpl
} // namespace MobileGL::MG_Backend::DirectGLES
+211 -194
View File
@@ -244,224 +244,224 @@ namespace MobileGL::MG_Backend::DirectGLES {
RequireStagedCoverageForPendingRanges((res), (base), (frontendSize), (site))
} // namespace
// Ring machinery shared with the UBO/unpack rings; defined further down in
// this same unnamed namespace.
Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset);
// Ring machinery shared with the UBO/unpack rings; defined further down in
// this same unnamed namespace.
Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset);
// Push every queued range of `resource` from the shadow into the backend
// store, without ever letting a driver resolve the WAR hazard against
// in-flight frames at the WHOLE BUFFER's expense. Three tiers:
// Push every queued range of `resource` from the shadow into the backend
// store, without ever letting a driver resolve the WAR hazard against
// in-flight frames at the WHOLE BUFFER's expense. Three tiers:
//
// 1. glMapBufferRange(WRITE | INVALIDATE_RANGE) + memcpy. The entire
// mapped range is rewritten from the authoritative shadow, so
// declaring its old bytes dead is exact - and it lets the driver
// swap fresh pages in for JUST that range. This is the only tier
// whose cost scales with the RANGE on this Mali driver: both the
// immediate glBufferSubData (pre-queueing) and a staged
// glCopyBufferSubData into a busy MUTABLE store ghost the whole
// destination with a worker-thread memcpy - Minecraft 26.3 streams
// ~1MB section meshes into 128MB arenas about nine times a frame
// during a camera pan, and 9 x 128MB of ghosting per frame is
// ~380ms, the measured 2-4 fps. (Backing the arenas with immutable
// stores also kills the ghost, but eagerly commits every arena's
// full extent - +hundreds of MB - which LMK'd the whole device.)
// 2. The staging ring + glCopyBufferSubData: the copy is ordered on
// the GPU timeline, no CPU wait (MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH
// forces this tier as the map path's negative control).
// 3. Direct glBufferSubData (potentially stalling) when neither the
// map entry points nor the ring exist.
//
// The ranges are flushed AS QUEUED (VecRange1D::Add already merges
// near-adjacent ones): bytes, not flush calls, are the cost axis here,
// and collapsing a scattered flush into its union re-copied nearly whole
// chunk-mesh arenas every frame.
// The caller owns syncedChangeSerial; this only drains the queue.
//
// ONE body for both arms, and deliberately so: the three-tier decision (whole-buffer
// orphan map / >= 128 KiB range-invalidating map / staged ring copy) is what the MC
// 26.3 p99 depends on, and a second copy of it for the handle arm is exactly how a
// tier silently changes. The arms differ only in where `hostBase` and `frontendSize`
// come from.
//
// R-11'S PARAMETER, AND IT IS A PARAMETER RATHER THAN AN ASSUMPTION (P5 b1).
// `hostBase` is re-read at every use today precisely because a shadow resize or an
// adoption moves what an earlier base pointed at. Under split the server may hold
// no pointer into the client's shadow at all, so the base becomes a SNAPSHOT taken
// into SEG_STAGE at emission - and a snapshot covers a RANGE, not the store. The
// two extra arguments say which range `hostBase` is good for; the default is the
// whole store, which is exactly what a live shadow is, so every caller today is
// byte-identical. The moment w1 passes a real snapshot extent, tier 1's widening
// refusal below stops being unreachable and a too-narrow snapshot is named instead
// of silently clobbering GPU-written bytes with stale ones.
constexpr SizeT kHostBaseCoversWholeStore = ~static_cast<SizeT>(0);
void FlushPendingRangesFrom(GLESBufferResource& resource, const Uint8* hostBase, SizeT frontendSize,
SizeT hostBaseFrom = 0, SizeT hostBaseTo = kHostBaseCoversWholeStore);
void DrainResidentWritesNow(GLESBufferResource& resource);
namespace {
// The two-argument spelling the legacy arm's call sites use, kept so those sites
// are the SAME text in both builds (G1). The object was never read.
void DrainResidentWritesNow(GLESBufferResource& resource, BufferObject& bufferObject) {
(void)bufferObject;
DrainResidentWritesNow(resource);
}
// ---- P3a: the same seven ops plus the create/unmap pair, BY HANDLE ------------
//
// 1. glMapBufferRange(WRITE | INVALIDATE_RANGE) + memcpy. The entire
// mapped range is rewritten from the authoritative shadow, so
// declaring its old bytes dead is exact - and it lets the driver
// swap fresh pages in for JUST that range. This is the only tier
// whose cost scales with the RANGE on this Mali driver: both the
// immediate glBufferSubData (pre-queueing) and a staged
// glCopyBufferSubData into a busy MUTABLE store ghost the whole
// destination with a worker-thread memcpy - Minecraft 26.3 streams
// ~1MB section meshes into 128MB arenas about nine times a frame
// during a camera pan, and 9 x 128MB of ghosting per frame is
// ~380ms, the measured 2-4 fps. (Backing the arenas with immutable
// stores also kills the ghost, but eagerly commits every arena's
// full extent - +hundreds of MB - which LMK'd the whole device.)
// 2. The staging ring + glCopyBufferSubData: the copy is ordered on
// the GPU timeline, no CPU wait (MOBILEGL_ESPRYT_DISABLE_INVALIDATE_FLUSH
// forces this tier as the map path's negative control).
// 3. Direct glBufferSubData (potentially stalling) when neither the
// map entry points nor the ring exist.
// Every body below is its Ops_* counterpart above with exactly the substitutions
// D-A2's table names and nothing else: the frontend object's GetSize() / GetUsage()
// / HasDefinedContent() / MappedData() / GetChangeSerial() become the applier
// record's Desc.Width / Desc.Usage / Desc.HasDefinedContent, the shadow base the
// call carried, and the record's server-owned Serial. Every branch survives -
// pendingRespecify early-out, the off-thread queue, the adopted zero-copy stamp,
// the upload-ring kill switch, the Mali WAR-stall queue-only default.
//
// The ranges are flushed AS QUEUED (VecRange1D::Add already merges
// near-adjacent ones): bytes, not flush calls, are the cost axis here,
// and collapsing a scattered flush into its union re-copied nearly whole
// chunk-mesh arenas every frame.
// The caller owns syncedChangeSerial; this only drains the queue.
//
// ONE body for both arms, and deliberately so: the three-tier decision (whole-buffer
// orphan map / >= 128 KiB range-invalidating map / staged ring copy) is what the MC
// 26.3 p99 depends on, and a second copy of it for the handle arm is exactly how a
// tier silently changes. The arms differ only in where `hostBase` and `frontendSize`
// come from.
//
// R-11'S PARAMETER, AND IT IS A PARAMETER RATHER THAN AN ASSUMPTION (P5 b1).
// `hostBase` is re-read at every use today precisely because a shadow resize or an
// adoption moves what an earlier base pointed at. Under split the server may hold
// no pointer into the client's shadow at all, so the base becomes a SNAPSHOT taken
// into SEG_STAGE at emission - and a snapshot covers a RANGE, not the store. The
// two extra arguments say which range `hostBase` is good for; the default is the
// whole store, which is exactly what a live shadow is, so every caller today is
// byte-identical. The moment w1 passes a real snapshot extent, tier 1's widening
// refusal below stops being unreachable and a too-narrow snapshot is named instead
// of silently clobbering GPU-written bytes with stale ones.
constexpr SizeT kHostBaseCoversWholeStore = ~static_cast<SizeT>(0);
// NOTHING here reads a frontend type. The one thing a handle op cannot do is ask
// an object for its shadow, which is why GLESBufferResource::hostBytes exists: the
// three content-carrying calls hand the base over and the later drains read it.
void FlushPendingRangesFrom(GLESBufferResource& resource, const Uint8* hostBase, SizeT frontendSize,
SizeT hostBaseFrom = 0, SizeT hostBaseTo = kHostBaseCoversWholeStore);
// The applier's record for this resource, or null when the client never created it
// (or created it into a slot that has since been recycled).
const MG_Pipe::MGPipeResourceRecord* ResourceRecordOf(MG_Pipe::MGPipeHandle res) {
if (MG_Pipe::MGPipeHandleIsNull(res)) return nullptr;
const auto& records = MG_Pipe::MGPipeApplier().Resources;
if (res.Slot >= records.size()) return nullptr;
const auto& record = records[res.Slot];
if (!record.Live || record.Gen != res.Gen) return nullptr;
return &record;
}
void DrainResidentWritesNow(GLESBufferResource& resource);
namespace {
// The two-argument spelling the legacy arm's call sites use, kept so those sites
// are the SAME text in both builds (G1). The object was never read.
void DrainResidentWritesNow(GLESBufferResource& resource, BufferObject& bufferObject) {
(void)bufferObject;
DrainResidentWritesNow(resource);
}
// ---- P3a: the same seven ops plus the create/unmap pair, BY HANDLE ------------
//
// Every body below is its Ops_* counterpart above with exactly the substitutions
// D-A2's table names and nothing else: the frontend object's GetSize() / GetUsage()
// / HasDefinedContent() / MappedData() / GetChangeSerial() become the applier
// record's Desc.Width / Desc.Usage / Desc.HasDefinedContent, the shadow base the
// call carried, and the record's server-owned Serial. Every branch survives -
// pendingRespecify early-out, the off-thread queue, the adopted zero-copy stamp,
// the upload-ring kill switch, the Mali WAR-stall queue-only default.
//
// NOTHING here reads a frontend type. The one thing a handle op cannot do is ask
// an object for its shadow, which is why GLESBufferResource::hostBytes exists: the
// three content-carrying calls hand the base over and the later drains read it.
// The applier's record for this resource, or null when the client never created it
// (or created it into a slot that has since been recycled).
const MG_Pipe::MGPipeResourceRecord* ResourceRecordOf(MG_Pipe::MGPipeHandle res) {
if (MG_Pipe::MGPipeHandleIsNull(res)) return nullptr;
const auto& records = MG_Pipe::MGPipeApplier().Resources;
if (res.Slot >= records.size()) return nullptr;
const auto& record = records[res.Slot];
if (!record.Live || record.Gen != res.Gen) return nullptr;
return &record;
}
// The server-owned MGGen this backend mirrors in syncedChangeSerial. Zero for a
// resource with no record, which is the same "never synced" answer a frontend
// change serial of zero gave.
Uint64 ResourceSerialOf(MG_Pipe::MGPipeHandle res) {
const auto* record = ResourceRecordOf(res);
return record != nullptr ? record->Serial : 0;
}
SizeT ResourceWidthOf(MG_Pipe::MGPipeHandle res) {
const auto* record = ResourceRecordOf(res);
return record != nullptr ? static_cast<SizeT>(record->Desc.Width) : 0;
}
} // namespace
// The server-owned MGGen this backend mirrors in syncedChangeSerial. Zero for a
// resource with no record, which is the same "never synced" answer a frontend
// change serial of zero gave.
Uint64 ResourceSerialOf(MG_Pipe::MGPipeHandle res) {
const auto* record = ResourceRecordOf(res);
return record != nullptr ? record->Serial : 0;
}
SizeT ResourceWidthOf(MG_Pipe::MGPipeHandle res) {
const auto* record = ResourceRecordOf(res);
return record != nullptr ? static_cast<SizeT>(record->Desc.Width) : 0;
}
} // namespace
#if MOBILEGL_BUILD_DISAGGREGATED && defined(__ANDROID__)
using MG_Util::BackendLoader::HasExtensionWord;
using MG_Util::BackendLoader::HasExtensionWord;
#endif // MOBILEGL_BUILD_DISAGGREGATED && __ANDROID__
const Vector<TextureUploadTarget>& StagedUploadTargetsForPipeTarget(Uint8 pipeResourceTarget);
const Vector<TextureUploadTarget>& StagedUploadTargetsForPipeTarget(Uint8 pipeResourceTarget);
TextureTarget StagedTextureTargetForPipeTarget(Uint8 pipeResourceTarget);
TextureTarget StagedTextureTargetForPipeTarget(Uint8 pipeResourceTarget);
void BumpBufferMutationEpoch();
void BumpBufferMutationEpoch();
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
GLESBufferResource* FindBufferResourceForHandle(MG_Pipe::MGPipeHandle res);
SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res);
SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res);
Uint64 ResourceSerialForHandle(MG_Pipe::MGPipeHandle res);
Uint64 ResourceSerialForHandle(MG_Pipe::MGPipeHandle res);
GLESBufferResource* EnsureBufferResourceForHandle(
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, MG_Pipe::MGPipeHandle res);
GLESBufferResource* EnsureBufferResourceForHandle(
const SharedPtr<MG_State::GLState::BufferObject>& bufferObject, MG_Pipe::MGPipeHandle res);
void BindBufferId(GLenum target, Uint id);
void BindBufferId(GLenum target, Uint id);
void BindPixelUnpackBufferId(Uint id);
void BindPixelUnpackBufferId(Uint id);
void NoteBufferIdDeleted(Uint id);
void NoteBufferIdDeleted(Uint id);
Bool CreateRingStorage(PersistentRing& ring, SizeT minBytes);
Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool CreateRingStorage(PersistentRing& ring, SizeT minBytes);
Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset);
Bool UnpackRingAvailable();
Bool UnpackRingAvailable();
Bool UnpackRingAllocate(SizeT size, SizeT& outOffset);
Bool UnpackRingAllocate(SizeT size, SizeT& outOffset);
void* UnpackRingMappedPtr();
void* UnpackRingMappedPtr();
Uint UnpackRingBufferId();
Uint UnpackRingBufferId();
SizeT UnpackRingMaxBytes();
SizeT UnpackRingMaxBytes();
} // namespace BufferImpl
// The release-build VOICE for StateBackendObjectRegistry::GetOrCreateByHandle's two
// silent refusals, shared by the kinds P4a re-keys so the wording cannot drift between
// them. It is the shape GetOrCreateBufferResourceForHandle gives P3a's buffer family,
// lifted into one template because a copy per kind is a chance per kind to write one of
// them differently.
//
// ITS CALLERS IN THIS PACKAGE ARE THE FRAMEBUFFER ATTACHMENT WALK'S TWO
// (FramebufferImpl::SyncAttachmentSurface: the texture registry and the renderbuffer
// registry), because that is where a HANDLE arrives in a payload - MGPSurface::Res -
// and an ADOPTION is what the arm owes rather than a lookup. The other three re-keyed
// kinds resolve at sites in DirectGLES.cpp (package E's SyncCurrentFBO, the lazy
// sampler mint and SyncCurrentProgram), so their first adoption is E's to write; this
// template is what they call so all five say the same thing.
//
// WHY A VOICE AT ALL: the table asserts, and MOBILEGL_ASSERT compiles out at INFO -
// which all three gate builds and every shipped build are - so an object that silently
// stops being twinned is exactly the failure mode the refusal exists to replace.
//
// Returns null on the legacy arm too, so a caller that reaches this without checking
// its family's arm gets nothing rather than a twin the legacy arm would never see.
//
// SPLIT IN TWO (review N-3), and the split is what makes the two refusals REACHABLE.
// Both of this package's call sites stand behind a cross-check that the record's Res
// equals registry.HandleOf(frontendObject) - the LIVE handle - so by the time the
// adoption ran, the slot was in bounds and the generation was current BY CONSTRUCTION
// and neither refusal below could ever fire: the letter of M-4 was closed and its
// substance was not. The record's handle is the UNTRUSTED input and the frontend is the
// corroboration, so the validity question is asked FIRST, on its own, at both sites;
// the adoption then performs the resolve. Asking it first is also side-effect free,
// which the ordering rule N-1 states for the framebuffer refusal wants here too:
// GetOrCreateByHandle can reset an incumbent twin, and a handle this predicate rejects
// must not reach it.
template <typename Registry>
Bool PipeTwinHandleIsAdoptable(Registry& registry, MG_Pipe::MGPipeHandle handle, const char* kindName) {
if (MG_Pipe::MGPipeHandleIsNull(handle)) return false;
if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) {
// The release-build VOICE for StateBackendObjectRegistry::GetOrCreateByHandle's two
// silent refusals, shared by the kinds P4a re-keys so the wording cannot drift between
// them. It is the shape GetOrCreateBufferResourceForHandle gives P3a's buffer family,
// lifted into one template because a copy per kind is a chance per kind to write one of
// them differently.
//
// ITS CALLERS IN THIS PACKAGE ARE THE FRAMEBUFFER ATTACHMENT WALK'S TWO
// (FramebufferImpl::SyncAttachmentSurface: the texture registry and the renderbuffer
// registry), because that is where a HANDLE arrives in a payload - MGPSurface::Res -
// and an ADOPTION is what the arm owes rather than a lookup. The other three re-keyed
// kinds resolve at sites in DirectGLES.cpp (package E's SyncCurrentFBO, the lazy
// sampler mint and SyncCurrentProgram), so their first adoption is E's to write; this
// template is what they call so all five say the same thing.
//
// WHY A VOICE AT ALL: the table asserts, and MOBILEGL_ASSERT compiles out at INFO -
// which all three gate builds and every shipped build are - so an object that silently
// stops being twinned is exactly the failure mode the refusal exists to replace.
//
// Returns null on the legacy arm too, so a caller that reaches this without checking
// its family's arm gets nothing rather than a twin the legacy arm would never see.
//
// SPLIT IN TWO (review N-3), and the split is what makes the two refusals REACHABLE.
// Both of this package's call sites stand behind a cross-check that the record's Res
// equals registry.HandleOf(frontendObject) - the LIVE handle - so by the time the
// adoption ran, the slot was in bounds and the generation was current BY CONSTRUCTION
// and neither refusal below could ever fire: the letter of M-4 was closed and its
// substance was not. The record's handle is the UNTRUSTED input and the frontend is the
// corroboration, so the validity question is asked FIRST, on its own, at both sites;
// the adoption then performs the resolve. Asking it first is also side-effect free,
// which the ordering rule N-1 states for the framebuffer refusal wants here too:
// GetOrCreateByHandle can reset an incumbent twin, and a handle this predicate rejects
// must not reach it.
template <typename Registry>
Bool PipeTwinHandleIsAdoptable(Registry& registry, MG_Pipe::MGPipeHandle handle, const char* kindName) {
if (MG_Pipe::MGPipeHandleIsNull(handle)) return false;
if (handle.Slot >= Registry::SlotTable::kMaxHandleSlot) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"%s handle slot %u is past this table's %u bound",
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"} - "
"%s handle slot %u is past this table's %u bound",
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
#else
MGLOG_E_ONCE("MGPipe: %s handle slot %u is past the backend table's %u bound - "
"refusing to twin it",
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
return false;
MGLOG_E_ONCE("MGPipe: %s handle slot %u is past the backend table's %u bound - "
"refusing to twin it",
kindName, handle.Slot, Registry::SlotTable::kMaxHandleSlot);
return false;
#endif
}
const Uint32 liveGen = registry.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"%s handle {%u, %u} names generation behind live generation %u",
kindName, handle.Slot, handle.Gen, liveGen);
#else
// SlotTables.h:301-321: forward is a recycle and resets the twin, BACKWARD is
// refused, because adopting it would release the incumbent LIVE twin's driver
// id and then stamp the slot back to the dead object's generation, after which
// the incumbent's own FindByHandle refuses it and it is silently handed a
// fresh, empty twin - a leak AND an object that loses its storage with no
// diagnostic. That is the shape commit d7655247 fixed on the buffer side.
MGLOG_E_ONCE("MGPipe: %s handle {%u, %u} names a generation BEHIND the live "
"twin's %u - refusing rather than dropping the incumbent's driver "
"object",
kindName, handle.Slot, handle.Gen, liveGen);
return false;
#endif
}
return true;
}
const Uint32 liveGen = registry.LiveGenAt(handle.Slot);
if (liveGen != 0 && liveGen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Pipe::MGPipeSessionFail( // @Ph-declined (ID-P7-1): PH-2 stays Fatal, CONTRACT-P7 §12
MG_Pipe::MGPipeFatalFamily::ProtocolCorruption,
"MGPipe: Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"} - "
"%s handle {%u, %u} names generation behind live generation %u",
kindName, handle.Slot, handle.Gen, liveGen);
#else
// SlotTables.h:301-321: forward is a recycle and resets the twin, BACKWARD is
// refused, because adopting it would release the incumbent LIVE twin's driver
// id and then stamp the slot back to the dead object's generation, after which
// the incumbent's own FindByHandle refuses it and it is silently handed a
// fresh, empty twin - a leak AND an object that loses its storage with no
// diagnostic. That is the shape commit d7655247 fixed on the buffer side.
MGLOG_E_ONCE("MGPipe: %s handle {%u, %u} names a generation BEHIND the live "
"twin's %u - refusing rather than dropping the incumbent's driver "
"object",
kindName, handle.Slot, handle.Gen, liveGen);
return false;
#endif
}
return true;
}
namespace {
template <typename Registry>
@@ -565,12 +565,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
static void Apply(GLint alignment, GLint rowLength, GLint skipRows, GLint skipPixels, GLint imageHeight,
GLint skipImages) {
if (alignment != s_alignment) { g_GLESFuncs.glPixelStorei(GL_UNPACK_ALIGNMENT, alignment); s_alignment = alignment; }
if (rowLength != s_rowLength) { g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength); s_rowLength = rowLength; }
if (skipRows != s_skipRows) { g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_ROWS, skipRows); s_skipRows = skipRows; }
if (skipPixels != s_skipPixels) { g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_PIXELS, skipPixels); s_skipPixels = skipPixels; }
if (imageHeight != s_imageHeight) { g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, imageHeight); s_imageHeight = imageHeight; }
if (skipImages != s_skipImages) { g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_IMAGES, skipImages); s_skipImages = skipImages; }
if (alignment != s_alignment) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
s_alignment = alignment;
}
if (rowLength != s_rowLength) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, rowLength);
s_rowLength = rowLength;
}
if (skipRows != s_skipRows) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_ROWS, skipRows);
s_skipRows = skipRows;
}
if (skipPixels != s_skipPixels) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_PIXELS, skipPixels);
s_skipPixels = skipPixels;
}
if (imageHeight != s_imageHeight) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, imageHeight);
s_imageHeight = imageHeight;
}
if (skipImages != s_skipImages) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_SKIP_IMAGES, skipImages);
s_skipImages = skipImages;
}
}
GLint m_prevAlignment = 4;
@@ -635,8 +653,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Returns true when the caller must decline (no handle noted: the caller's bug it always
// was, named by the caller's own log line), false when the record arm can serve the call,
// and never returns at all when a handle was noted on an arm that cannot serve it.
static Bool RefuseNullFrontendTextureOffTheHandleArm(const char* entry,
MG_Pipe::MGPipeHandle notedHandle) {
static Bool RefuseNullFrontendTextureOffTheHandleArm(const char* entry, MG_Pipe::MGPipeHandle notedHandle) {
if (MG_Pipe::MGPipeHandleIsNull(notedHandle)) return true;
return false;
}
@@ -145,7 +145,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// context never answers GL_NO_ERROR, and the build runs on the thread that would
// then spin forever.
constexpr Int kMaxDrainedProgramErrors = 32;
TwinRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl, MG_Pipe::MGPipeKind::ShaderCso> g_backendProgramObjects;
TwinRegistry<MG_State::GLState::ProgramObject, BackendProgramObjectImpl, MG_Pipe::MGPipeKind::ShaderCso>
g_backendProgramObjects;
// ---- P5e (pg): the source, both ways round ---------------------------------------
@@ -245,9 +246,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (SamplerUnitOverlay == nullptr) return -1;
const auto it = std::lower_bound(
SamplerUnitOverlay->begin(), SamplerUnitOverlay->end(), location,
[](const MG_Pipe::MGPProgramSamplerUnit& entry, Uint value) {
return entry.Location < value;
});
[](const MG_Pipe::MGPProgramSamplerUnit& entry, Uint value) { return entry.Location < value; });
if (it == SamplerUnitOverlay->end() || it->Location != location) return -1;
return static_cast<Int>(it->Unit);
}
@@ -255,8 +254,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return Link->uniformSamplerOrImageUnitIndex[location];
}
ProgramArchiveSource ProgramArchiveSource::FromRecord(
MG_Pipe::MGPipeHandle cso, const MG_Pipe::MGPipeShaderCsoRecord& record) {
ProgramArchiveSource ProgramArchiveSource::FromRecord(MG_Pipe::MGPipeHandle cso,
const MG_Pipe::MGPipeShaderCsoRecord& record) {
// THE HANDLE ARM. Every field below is server-owned: the archive the create adopted,
// the descriptor the create stored, and the three tails set_program_bindings
// carried. Nothing here touches a ProgramObject, which is the property the strict
@@ -312,9 +311,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (record.BindingsSerial != 0) {
// The tail is the whole set; a block past it is a record and an archive that
// disagree about the block count, and GL's own default of 0 is the safe answer.
return index < record.BlockBindings.size()
? static_cast<Uint>(record.BlockBindings[index])
: 0;
return index < record.BlockBindings.size() ? static_cast<Uint>(record.BlockBindings[index]) : 0;
}
if (!record.Archive || index >= record.Archive->Link.uniformBlockBinding.size()) return 0;
return static_cast<Uint>(record.Archive->Link.uniformBlockBinding[index]);
@@ -324,14 +321,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (record.BindingsSerial != 0) {
const auto it = std::lower_bound(
record.SamplerUnits.begin(), record.SamplerUnits.end(), location,
[](const MG_Pipe::MGPProgramSamplerUnit& entry, Uint value) {
return entry.Location < value;
});
[](const MG_Pipe::MGPProgramSamplerUnit& entry, Uint value) { return entry.Location < value; });
if (it == record.SamplerUnits.end() || it->Location != location) return -1;
return static_cast<Int>(it->Unit);
}
if (!record.Archive ||
location >= record.Archive->Link.uniformSamplerOrImageUnitIndex.size()) {
if (!record.Archive || location >= record.Archive->Link.uniformSamplerOrImageUnitIndex.size()) {
return -1;
}
return record.Archive->Link.uniformSamplerOrImageUnitIndex[location];
@@ -406,8 +400,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
void ReseedShaderStorageBlockBindings(Uint backendProgramId,
const ProgramBuildSource& src) {
void ReseedShaderStorageBlockBindings(Uint backendProgramId, const ProgramBuildSource& src) {
const auto& overrides = src.GetShaderStorageBlockBindingOverrides();
if (overrides.empty()) return; // the overwhelming majority of programs
for (const auto& [blockName, binding] : overrides) {
@@ -499,13 +492,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// build whose MOBILEGL_LOG_ACTIVE_LEVEL is above ERROR.
[[maybe_unused]] Int AdvertisedStageImageUniformLimit(ShaderStage stage) {
switch (stage) {
case ShaderStage::Vertex: return g_GLESCapabilities.MaxVertexImageUniforms;
case ShaderStage::TessControl: return g_GLESCapabilities.MaxTessControlImageUniforms;
case ShaderStage::TessEval: return g_GLESCapabilities.MaxTessEvaluationImageUniforms;
case ShaderStage::Geometry: return g_GLESCapabilities.MaxGeometryImageUniforms;
case ShaderStage::Fragment: return g_GLESCapabilities.MaxFragmentImageUniforms;
case ShaderStage::Compute: return g_GLESCapabilities.MaxComputeImageUniforms;
default: return -1;
case ShaderStage::Vertex:
return g_GLESCapabilities.MaxVertexImageUniforms;
case ShaderStage::TessControl:
return g_GLESCapabilities.MaxTessControlImageUniforms;
case ShaderStage::TessEval:
return g_GLESCapabilities.MaxTessEvaluationImageUniforms;
case ShaderStage::Geometry:
return g_GLESCapabilities.MaxGeometryImageUniforms;
case ShaderStage::Fragment:
return g_GLESCapabilities.MaxFragmentImageUniforms;
case ShaderStage::Compute:
return g_GLESCapabilities.MaxComputeImageUniforms;
default:
return -1;
}
}
@@ -548,41 +548,67 @@ namespace MobileGL::MG_Backend::DirectGLES {
// one. That is exactly what r11f_g11f_b10f did until it was added here.
Uint GLInternalFormatOfLayoutFormat(glslang::TLayoutFormat format) {
switch (format) {
case glslang::ElfRg32f: return 0x8230; // GL_RG32F
case glslang::ElfRg16f: return 0x822F; // GL_RG16F
case glslang::ElfR16f: return 0x822D; // GL_R16F
case glslang::ElfRg8: return 0x822B; // GL_RG8
case glslang::ElfR8: return 0x8229; // GL_R8
case glslang::ElfRg8Snorm: return 0x8F95; // GL_RG8_SNORM
case glslang::ElfR8Snorm: return 0x8F94; // GL_R8_SNORM
case glslang::ElfRg32i: return 0x823B; // GL_RG32I
case glslang::ElfRg16i: return 0x8239; // GL_RG16I
case glslang::ElfR16i: return 0x8233; // GL_R16I
case glslang::ElfRg8i: return 0x8237; // GL_RG8I
case glslang::ElfR8i: return 0x8231; // GL_R8I
case glslang::ElfRg32ui: return 0x823C; // GL_RG32UI
case glslang::ElfRg16ui: return 0x823A; // GL_RG16UI
case glslang::ElfR16ui: return 0x8234; // GL_R16UI
case glslang::ElfRg8ui: return 0x8238; // GL_RG8UI
case glslang::ElfR8ui: return 0x8232; // GL_R8UI
case glslang::ElfRg32f:
return 0x8230; // GL_RG32F
case glslang::ElfRg16f:
return 0x822F; // GL_RG16F
case glslang::ElfR16f:
return 0x822D; // GL_R16F
case glslang::ElfRg8:
return 0x822B; // GL_RG8
case glslang::ElfR8:
return 0x8229; // GL_R8
case glslang::ElfRg8Snorm:
return 0x8F95; // GL_RG8_SNORM
case glslang::ElfR8Snorm:
return 0x8F94; // GL_R8_SNORM
case glslang::ElfRg32i:
return 0x823B; // GL_RG32I
case glslang::ElfRg16i:
return 0x8239; // GL_RG16I
case glslang::ElfR16i:
return 0x8233; // GL_R16I
case glslang::ElfRg8i:
return 0x8237; // GL_RG8I
case glslang::ElfR8i:
return 0x8231; // GL_R8I
case glslang::ElfRg32ui:
return 0x823C; // GL_RG32UI
case glslang::ElfRg16ui:
return 0x823A; // GL_RG16UI
case glslang::ElfR16ui:
return 0x8234; // GL_R16UI
case glslang::ElfRg8ui:
return 0x8238; // GL_RG8UI
case glslang::ElfR8ui:
return 0x8232; // GL_R8UI
// Not a channel widening but a lossless re-encoding into rgba16f - the one entry
// here whose carrier has a different per-channel layout. See
// WidenImageFormatsPass.h.
case glslang::ElfR11fG11fB10f: return 0x8C3A; // GL_R11F_G11F_B10F
case glslang::ElfR11fG11fB10f:
return 0x8C3A; // GL_R11F_G11F_B10F
// 10/10/10/2 unsigned INTEGER channels in an rgba16ui: same component type, same
// channel count, every value representable. Only the transfer is re-encoded.
case glslang::ElfRgb10a2ui: return 0x906F; // GL_RGB10_A2UI
case glslang::ElfRgb10a2ui:
return 0x906F; // GL_RGB10_A2UI
// The seven NORMALIZED formats, carried in an rgba16ui as their own channel CODES.
// These are the entries whose carrier changes the shader-visible type as well as
// the qualifier (image2D becomes uimage2D), so every access through them is
// wrapped in the GL 4.6 2.3.5 conversion - see WidenImageFormatsPass.h.
case glslang::ElfRgba16: return 0x805B; // GL_RGBA16
case glslang::ElfRg16: return 0x822C; // GL_RG16
case glslang::ElfR16: return 0x822A; // GL_R16
case glslang::ElfRgb10A2: return 0x8059; // GL_RGB10_A2
case glslang::ElfRgba16Snorm: return 0x8F9B; // GL_RGBA16_SNORM
case glslang::ElfRg16Snorm: return 0x8F99; // GL_RG16_SNORM
case glslang::ElfR16Snorm: return 0x8F98; // GL_R16_SNORM
case glslang::ElfRgba16:
return 0x805B; // GL_RGBA16
case glslang::ElfRg16:
return 0x822C; // GL_RG16
case glslang::ElfR16:
return 0x822A; // GL_R16
case glslang::ElfRgb10A2:
return 0x8059; // GL_RGB10_A2
case glslang::ElfRgba16Snorm:
return 0x8F9B; // GL_RGBA16_SNORM
case glslang::ElfRg16Snorm:
return 0x8F99; // GL_RG16_SNORM
case glslang::ElfR16Snorm:
return 0x8F98; // GL_R16_SNORM
default:
return 0;
}
@@ -604,8 +630,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return false;
}
return !g_GLESCapabilities.SupportsExtendedImageFormats ||
!MG_Util::ShaderTranspiler::ShaderCompiler::SpirvCrossCanPrintEsslImageFormat(
glInternalFormat);
!MG_Util::ShaderTranspiler::ShaderCompiler::SpirvCrossCanPrintEsslImageFormat(glInternalFormat);
}
} // namespace
@@ -655,8 +680,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// From the OWNED TypeFacts, not from a live TType: the reflection
// snapshot already carries the declared layout format, and there
// is no glslang object to ask on a translation-cache L1 hit.
recordUnspellableFormat(
name, glslang::TQualifier::getLayoutFormatString(declaredFormat));
recordUnspellableFormat(name,
glslang::TQualifier::getLayoutFormatString(declaredFormat));
}
}
}
@@ -796,9 +821,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
ImageArrayUnitPlan plan;
plan.name = baseName;
for (Uint element = loc; element <= maxUniformLoc &&
src.UniformLocationsAliasSameUniform(
static_cast<Int>(loc), static_cast<Int>(element));
for (Uint element = loc;
element <= maxUniformLoc &&
src.UniformLocationsAliasSameUniform(static_cast<Int>(loc), static_cast<Int>(element));
++element) {
plan.units.push_back(src.GetUniformSamplerOrImageUnitIndex(element));
}
@@ -833,7 +858,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
return ComputeImageUnitFormatSignature() == m_imageUnitFormatSignature;
}
// ===== THE MEMOIZED SEGMENT (shader translation memo, level 2) =====
//
// One stage's sanitized SPIR-V turned into the ESSL SPIRV-Cross emits, through the
@@ -862,9 +886,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Vector<unsigned int>& spirvCode, const GLenum glShaderType,
const std::set<String>& xfbCaptureBlockNames, const ImageFormatBakeInputs& imageFormatBake,
const UnorderedMap<String, Int>& storageBlockBindingOverrides,
const std::map<String, String>& inputBlockRenames,
const std::map<String, String>& outputBlockRenames, const Bool stripInputBlockLocations,
const Bool stripOutputBlockLocations,
const std::map<String, String>& inputBlockRenames, const std::map<String, String>& outputBlockRenames,
const Bool stripInputBlockLocations, const Bool stripOutputBlockLocations,
const Int atomicCounterEsslBindingTop, const Bool enableSpirvValidation, String& outSource,
std::set<String>& outFlattenedXfbBlockNames, Vector<Int>& outAtomicCounterGlBindings,
String& outError) const {
@@ -873,7 +896,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<unsigned int> loweredSpirv;
const Vector<unsigned int>* effectiveSpirv = &spirvCode;
if (glShaderType == GL_VERTEX_SHADER &&
MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv, enableSpirvValidation) &&
MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv,
enableSpirvValidation) &&
!loweredSpirv.empty()) {
effectiveSpirv = &loweredSpirv;
}
@@ -947,8 +971,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// this is backend code and must not reach into the GL frontend. 4 is that
// translation unit's kFrontendMaxSamples, which is the source of truth -
// keep the two in step.
const Int advertisedMaxSamples =
std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
const Int advertisedMaxSamples = std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
// Armed by the EMULATION as well as by the missing extension, and the emulation is on
// by default (MOBILEGL_ESPRYT_FORCE_VIEWPORT_ARRAY_EMULATION). Having the extension is not a
// reason to keep the builtin: it only ever gave the SHADER a compilable name, while
@@ -969,10 +992,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool viewportLoweringArmed =
stageCanWriteViewportIndex &&
(ViewportArrayEmulationEnabled() || !g_GLESCapabilities.SupportsViewportArray);
const Bool sampleClampArmed =
g_GLESCapabilities.MaxColorTextureSamples < advertisedMaxSamples ||
g_GLESCapabilities.MaxIntegerSamples < advertisedMaxSamples ||
g_GLESCapabilities.MaxDepthTextureSamples < advertisedMaxSamples;
const Bool sampleClampArmed = g_GLESCapabilities.MaxColorTextureSamples < advertisedMaxSamples ||
g_GLESCapabilities.MaxIntegerSamples < advertisedMaxSamples ||
g_GLESCapabilities.MaxDepthTextureSamples < advertisedMaxSamples;
// The image-format widening is armed on EVERY driver, so its probe has to ride the
// shared parse rather than add one: it is asked of every stage of every program, and
// a BuildModule per stage per gate is exactly what cost compile-heavy CTS cases ~10%
@@ -982,8 +1004,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool widenOnlyUnprintableImageFormats = g_GLESCapabilities.SupportsExtendedImageFormats;
MG_Util::ShaderTranspiler::ShaderCompiler::SpirvGateFeatures spirvGates;
if (viewportLoweringArmed || sampleClampArmed) {
spirvGates = MG_Util::ShaderTranspiler::ShaderCompiler::ProbeSpirvGateFeatures(
*effectiveSpirv);
spirvGates = MG_Util::ShaderTranspiler::ShaderCompiler::ProbeSpirvGateFeatures(*effectiveSpirv);
}
Vector<unsigned int> loweredViewportSpirv;
@@ -994,11 +1015,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
effectiveSpirv = &loweredViewportSpirv;
MGLOG_D("Program %u stage %s writes gl_ViewportIndex, which ESSL has no core "
"spelling for. The builtin was demoted to a plain global; %s.",
m_backendProgramId,
MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
ViewportArrayEmulationEnabled()
? "the ESSL passes below promote it to a routing varying"
: "every invocation renders into viewport 0");
m_backendProgramId, MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
ViewportArrayEmulationEnabled() ? "the ESSL passes below promote it to a routing varying"
: "every invocation renders into viewport 0");
}
// GL 4.6 core table 23.53 requires GL_MAX_SAMPLES >= 4, so every multisample
@@ -1017,11 +1036,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<unsigned int> clampedSampleSpirv;
if (sampleClampArmed && spirvGates.DeclaresMultisampledImage &&
MG_Util::ShaderTranspiler::ShaderCompiler::ClampMultisampleFetchesForEssl(
*effectiveSpirv, clampedSampleSpirv,
g_GLESCapabilities.MaxColorTextureSamples,
g_GLESCapabilities.MaxIntegerSamples,
g_GLESCapabilities.MaxDepthTextureSamples, advertisedMaxSamples,
enableSpirvValidation, ClampsUnsignedMultisampleFetchesAtRuntime()) &&
*effectiveSpirv, clampedSampleSpirv, g_GLESCapabilities.MaxColorTextureSamples,
g_GLESCapabilities.MaxIntegerSamples, g_GLESCapabilities.MaxDepthTextureSamples,
advertisedMaxSamples, enableSpirvValidation, ClampsUnsignedMultisampleFetchesAtRuntime()) &&
!clampedSampleSpirv.empty()) {
effectiveSpirv = &clampedSampleSpirv;
}
@@ -1090,8 +1107,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!inputBlockRenames.empty() || !outputBlockRenames.empty()) {
std::set<String> stageRenamedIoBlockNames;
if (MG_Util::ShaderTranspiler::ShaderCompiler::UniquifyIoBlockNamesForEssl(
*effectiveSpirv, inputBlockRenames, outputBlockRenames,
stageRenamedIoBlockNames, uniquifiedIoBlockSpirv, enableSpirvValidation) &&
*effectiveSpirv, inputBlockRenames, outputBlockRenames, stageRenamedIoBlockNames,
uniquifiedIoBlockSpirv, enableSpirvValidation) &&
!uniquifiedIoBlockSpirv.empty() && !stageRenamedIoBlockNames.empty()) {
effectiveSpirv = &uniquifiedIoBlockSpirv;
MGLOG_D("Program %u stage %s: %zu inter-stage interface block(s) renamed per "
@@ -1136,7 +1153,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// divides the coordinate of every normalized-coordinate lookup by the texture
// size, which is the whole of the difference between the two.
Vector<unsigned int> rectLoweredSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv, enableSpirvValidation) &&
if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(*effectiveSpirv, rectLoweredSpirv,
enableSpirvValidation) &&
!rectLoweredSpirv.empty()) {
effectiveSpirv = &rectLoweredSpirv;
}
@@ -1149,8 +1167,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// program with it. The pass does the conversion properly - type to 2D array,
// coordinate to (u, 0, layer) - before SPIRV-Cross can apply its own.
Vector<unsigned int> arrayImageSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DArrayImagesForEssl(*effectiveSpirv,
arrayImageSpirv, enableSpirvValidation) &&
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DArrayImagesForEssl(*effectiveSpirv, arrayImageSpirv,
enableSpirvValidation) &&
!arrayImageSpirv.empty()) {
effectiveSpirv = &arrayImageSpirv;
}
@@ -1170,8 +1188,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// on the module's own content (BinaryHasOffsetOrGrad1DSampledImage). The module is
// already the largest thing in the L2 key, so it is covered completely.
Vector<unsigned int> sampled1DSpirv;
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DSampledImagesForEssl(
*effectiveSpirv, sampled1DSpirv, enableSpirvValidation) &&
if (MG_Util::ShaderTranspiler::ShaderCompiler::Lower1DSampledImagesForEssl(*effectiveSpirv, sampled1DSpirv,
enableSpirvValidation) &&
!sampled1DSpirv.empty()) {
effectiveSpirv = &sampled1DSpirv;
}
@@ -1190,8 +1208,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (!imageFormatBake.glFormatByUniformName.empty() &&
MG_Util::ShaderTranspiler::ShaderCompiler::DeclaresFormatlessStorageImage(*effectiveSpirv) &&
MG_Util::ShaderTranspiler::ShaderCompiler::BakeImageFormatsForEssl(
*effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv,
enableSpirvValidation) &&
*effectiveSpirv, imageFormatBake.glFormatByUniformName, imageFormatSpirv, enableSpirvValidation) &&
!imageFormatSpirv.empty()) {
effectiveSpirv = &imageFormatSpirv;
}
@@ -1332,8 +1349,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (stripInputBlockLocations || stripOutputBlockLocations) {
Bool strippedAny = false;
if (MG_Util::ShaderTranspiler::ShaderCompiler::StripIoBlockLocationsForEssl(
*effectiveSpirv, stripInputBlockLocations, stripOutputBlockLocations,
strippedAny, strippedIoBlockLocationSpirv, enableSpirvValidation) &&
*effectiveSpirv, stripInputBlockLocations, stripOutputBlockLocations, strippedAny,
strippedIoBlockLocationSpirv, enableSpirvValidation) &&
!strippedIoBlockLocationSpirv.empty() && strippedAny) {
effectiveSpirv = &strippedIoBlockLocationSpirv;
// THE ARMING SIGNAL, and it is INFO on purpose: the per-stage line below is
@@ -1356,13 +1373,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options;
spvcSession.CreateOptions(&options);
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION,
ResolveBackendEsslVersion());
spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, ResolveBackendEsslVersion());
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_ES, SPVC_TRUE);
spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE);
@@ -1391,8 +1407,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// payload. A hit that replayed only the text would leave the bindings empty and
// every counter buffer unbound - the same class of silent loss the flattened XFB
// block names would have been.
spvcSession.SetAtomicCounterBlockBindings(atomicCounterEsslBindingTop,
outAtomicCounterGlBindings);
spvcSession.SetAtomicCounterBlockBindings(atomicCounterEsslBindingTop, outAtomicCounterGlBindings);
// `layout(index = 0)` is the GL default spelled out loud, and GLSL ES has no such
// qualifier in core - a stage that prints it is refused with "index layout
@@ -1464,9 +1479,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// patch size - so a program built for one value is stale for another. Recorded here
// and compared on the draw path (SyncCurrentProgram), the same shape as the
// storage-block and image-format signatures next to it.
const Uint patchVertices = MG_Pipe::gPipeInputs->IsLive()
? MG_Pipe::gPipeInputs->GetPatchVertices()
: 3u;
const Uint patchVertices = MG_Pipe::gPipeInputs->IsLive() ? MG_Pipe::gPipeInputs->GetPatchVertices() : 3u;
m_passthroughTessControlPatchVertices = static_cast<Int>(patchVertices);
// PATCH_DEFAULT_{OUTER,INNER}_LEVEL are the same kind of dynamic state and are baked
// into the same stage (ES has no such state and no entry point to forward them to), so
@@ -1479,8 +1492,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
? MG_Pipe::gPipeInputs->GetPatchDefaultInnerLevel()
: FloatVec2(1.0f, 1.0f);
if (tessEvalShaderIndex < 0 ||
static_cast<SizeT>(tessEvalShaderIndex) >= shaderSpirvs.size()) {
if (tessEvalShaderIndex < 0 || static_cast<SizeT>(tessEvalShaderIndex) >= shaderSpirvs.size()) {
MGLOG_E("Program %u has a tessellation evaluation stage with no control stage, but no "
"SPIR-V for it; the pass-through control stage GL describes cannot be checked, so "
"the program is left to fail its ES link.",
@@ -1513,15 +1525,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// stage's IN block (that is what is expected). A neighbour that redeclared nothing
// yields an empty list, which leaves the driver's own built-in declaration in place -
// which is exactly what matching it requires.
const String inMembers =
ExtractPerVertexBlockMembers(vertexStageEssl, /*input=*/false).value_or(String());
const String inMembers = ExtractPerVertexBlockMembers(vertexStageEssl, /*input=*/false).value_or(String());
const String outMembers =
ExtractPerVertexBlockMembers(tessEvalStageEssl, /*input=*/true).value_or(String());
String source = BuildPassthroughTessControlEssl(ResolveBackendEsslVersion(), patchVertices,
inMembers, outMembers,
m_passthroughTessControlOuterLevel,
m_passthroughTessControlInnerLevel);
String source =
BuildPassthroughTessControlEssl(ResolveBackendEsslVersion(), patchVertices, inMembers, outMembers,
m_passthroughTessControlOuterLevel, m_passthroughTessControlInnerLevel);
// The mirrored member lists can carry gl_PointSize - the neighbour stage declared it,
// so matching it is the whole point - and a redeclaration is exactly as illegal as a
// reference in ESSL without the extension. Same directive, same never-speculative
@@ -1606,13 +1616,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// answer, and this is where the ES backend expresses it.
if (!src.GetLinkStatus() || !src.GetSpirvStatus()) {
MGLOG_E_ONCE("Program object is not linked or has no generated SPIR-V, skipping backend sync. State "
"program ID: %u",
src.GetExternalIndex());
"program ID: %u",
src.GetExternalIndex());
return;
}
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u",
src.GetExternalIndex(), m_backendProgramId);
MGLOG_D("Syncing program to backend. State program ID: %u, Backend ID: %u", src.GetExternalIndex(),
m_backendProgramId);
// Every link-derived cache below (incl. m_samplerUniformBindings and its
// lastAssignedUnit/lastAssignedLodBias program-state mirrors) is rebuilt;
// the sampler-pass memo keyed on them must not survive.
@@ -1653,8 +1663,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// ...and once more for image ARRAYS whose per-element units are not consecutive, which
// ESSL has no way to express in one declaration. Program-wide, like the bake, and read
// from the same snapshot of the reflection; the per-stage rewrite happens below.
const Vector<ImageArrayUnitPlan> nonConsecutiveImageArrays =
CollectNonConsecutiveImageArrayPlans(src);
const Vector<ImageArrayUnitPlan> nonConsecutiveImageArrays = CollectNonConsecutiveImageArrayPlans(src);
// Detach all existing shaders
GLint attachedCount = 0;
@@ -1791,8 +1800,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// stage, this drops a qualifier from EVERY block of the program - so it does not
// wait for the collision probe to find anything.
const Bool ioBlockLocationStripArmed =
!g_GLESCapabilities.SupportsLocatedInterStageIoBlocks &&
anyStageCanDeclareBlocksInBothDirections;
!g_GLESCapabilities.SupportsLocatedInterStageIoBlocks && anyStageCanDeclareBlocksInBothDirections;
if (anyStageCanDeclareBlocksInBothDirections) {
for (SizeT index = 0; index < shaderSpirvs.size(); ++index) {
MG_Util::ShaderTranspiler::ShaderCompiler::ProbeIoBlockNamesForEssl(
@@ -1810,8 +1818,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// by pipeline stage `producerPipelineIndex`. "__" is reserved in GLSL, so a name
// already ending in '_' does not get another one, and the digit-suffix loop steps
// off any name the program already spells.
const auto uniqueIoBlockName = [&declaredIoBlockNames](const String& blockName,
Int producerPipelineIndex) {
const auto uniqueIoBlockName = [&declaredIoBlockNames](const String& blockName, Int producerPipelineIndex) {
const char* separator = (!blockName.empty() && blockName.back() == '_') ? "" : "_";
String candidate = blockName + separator + "mgio" + std::to_string(producerPipelineIndex);
while (declaredIoBlockNames.find(candidate) != declaredIoBlockNames.end()) {
@@ -1887,10 +1894,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// varying off a program to buy nothing. Both cases keep the pre-emulation behaviour,
// which is what a program with no fragment stage had anyway.
const Bool programHasFragmentStage =
std::find(linkedStages.begin(), linkedStages.end(), ShaderStage::Fragment) !=
linkedStages.end();
const Bool viewportEmulationForThisProgram =
ViewportArrayEmulationEnabled() && programHasFragmentStage;
std::find(linkedStages.begin(), linkedStages.end(), ShaderStage::Fragment) != linkedStages.end();
const Bool viewportEmulationForThisProgram = ViewportArrayEmulationEnabled() && programHasFragmentStage;
for (const SizeT index : stageOrder) {
GLenum glShaderType = MG_Util::ConvertShaderStageToGLEnum(linkedStages[index]);
@@ -1915,8 +1920,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("Program %u stage %s samples a buffer texture, which this ES driver "
"cannot provide (%s). The shader will not compile and the program will "
"not link; every draw using it is a no-op.",
m_backendProgramId,
MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
m_backendProgramId, MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
GetBufferTextureTierName());
m_backendProgramUsable = false;
g_GLESFuncs.glDeleteShader(backendShaderId);
@@ -1952,13 +1956,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
esslKeyInputs.subgroupEmulationArmed && g_GLESCapabilities.SharedMemoryLoadsGoStale;
esslKeyInputs.supportsNoperspectiveInterpolation =
g_GLESCapabilities.SupportsNoperspectiveInterpolation;
esslKeyInputs.supportsExtendedImageFormats =
g_GLESCapabilities.SupportsExtendedImageFormats;
esslKeyInputs.supportsExtendedImageFormats = g_GLESCapabilities.SupportsExtendedImageFormats;
esslKeyInputs.maxColorTextureSamples = g_GLESCapabilities.MaxColorTextureSamples;
esslKeyInputs.maxIntegerSamples = g_GLESCapabilities.MaxIntegerSamples;
esslKeyInputs.maxDepthTextureSamples = g_GLESCapabilities.MaxDepthTextureSamples;
esslKeyInputs.advertisedMaxSamples =
std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
esslKeyInputs.advertisedMaxSamples = std::max(g_GLESCapabilities.MaxSamples, kFrontendMaxSamples);
esslKeyInputs.xfbCaptureBlockNames = &xfbCaptureBlockNames;
esslKeyInputs.glFormatByUniformName = &imageFormatBake.glFormatByUniformName;
esslKeyInputs.storageBlockBindingOverrides = &storageBlockBindingOverrides;
@@ -1991,8 +1993,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool hasConsumerStage = false;
for (const Int otherPipelineIndex : stagePipelineIndices) {
if (otherPipelineIndex < 0) continue;
if (otherPipelineIndex < myPipelineIndex &&
otherPipelineIndex > producerPipelineIndex) {
if (otherPipelineIndex < myPipelineIndex && otherPipelineIndex > producerPipelineIndex) {
producerPipelineIndex = otherPipelineIndex;
}
if (otherPipelineIndex > myPipelineIndex) hasConsumerStage = true;
@@ -2066,14 +2067,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
stageAtomicCounterGlBindings = esslHit->atomicCounterGlBindings;
} else {
String transpileError;
if (!TranspileSpirvToEssl(spirvCode, glShaderType, xfbCaptureBlockNames,
imageFormatBake, storageBlockBindingOverrides,
inputBlockRenames, outputBlockRenames,
if (!TranspileSpirvToEssl(spirvCode, glShaderType, xfbCaptureBlockNames, imageFormatBake,
storageBlockBindingOverrides, inputBlockRenames, outputBlockRenames,
stripInputBlockLocations, stripOutputBlockLocations,
m_atomicCounterEsslBindingTop,
enableSpirvValidation, source,
stageFlattenedXfbBlockNames,
stageAtomicCounterGlBindings, transpileError)) {
m_atomicCounterEsslBindingTop, enableSpirvValidation, source,
stageFlattenedXfbBlockNames, stageAtomicCounterGlBindings,
transpileError)) {
// MGLOG_E, unlatched, like the compile- and link-failure diagnostics
// below: one line per failing stage is bounded by program count and
// naming the stage is the entire diagnostic value. A stage that never
@@ -2083,8 +2082,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// KHR-GL43.vertex_attrib_binding family behind "the draw captured zeros".
MGLOG_E("Shader transpilation to ESSL failed. State program ID: %u, stage: %s, "
"SPIRV-Cross error: %s",
src.GetExternalIndex(),
MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
src.GetExternalIndex(), MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
transpileError.c_str());
m_backendProgramUsable = false;
continue;
@@ -2094,26 +2092,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
payload->essl = source;
payload->flattenedXfbBlockNames = stageFlattenedXfbBlockNames;
payload->atomicCounterGlBindings = stageAtomicCounterGlBindings;
const SizeT payloadBytes =
MG_Util::ShaderTranspiler::EsslTranslationResultBytes(*payload);
esslCache.Insert(
esslCacheKey,
MG_Util::ShaderTranspiler::EsslTranslationResultPtr(Move(payload)),
payloadBytes);
const SizeT payloadBytes = MG_Util::ShaderTranspiler::EsslTranslationResultBytes(*payload);
esslCache.Insert(esslCacheKey,
MG_Util::ShaderTranspiler::EsslTranslationResultPtr(Move(payload)),
payloadBytes);
}
}
// Per stage, never cumulative: a fragment shader consuming the same block
// reports a name the vertex stage already reported, and its own rewrite must
// still be taken or the two stages stop matching. Done here rather than inside
// the transpile so a cache HIT contributes its names too.
flattenedXfbBlockNames.insert(stageFlattenedXfbBlockNames.begin(),
stageFlattenedXfbBlockNames.end());
flattenedXfbBlockNames.insert(stageFlattenedXfbBlockNames.begin(), stageFlattenedXfbBlockNames.end());
// Same rule for the atomic-counter bindings this stage declared, and for the
// same reason: the loop below de-duplicates across stages, so a hit that
// contributed nothing would silently drop a counter buffer the draw path has
// to bind.
m_atomicCounterGlBindings.insert(m_atomicCounterGlBindings.end(),
stageAtomicCounterGlBindings.begin(),
m_atomicCounterGlBindings.insert(m_atomicCounterGlBindings.end(), stageAtomicCounterGlBindings.begin(),
stageAtomicCounterGlBindings.end());
// SPIRV-Cross sizes written clip/cull distances with a standalone
@@ -2137,15 +2131,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// same length and stays an #extension line - so it commutes with every pass
// below, including ForceSupporterOutput's scan for the last directive. First,
// because a header concern reads better before the body ones.
source = RetargetTextureBufferExtension(std::move(source),
g_GLESCapabilities.TextureBufferSupport);
source = RetargetTextureBufferExtension(std::move(source), g_GLESCapabilities.TextureBufferSupport);
// The other header-level rewrite, and next to that one for the same reason. The
// formats it covers are both the ones the bake above put into the module and the
// ones the application declared itself - either can be outside the thirteen GLSL
// ES has in core, and neither reaches the driver without this directive.
source = RequestExtendedImageFormats(std::move(source),
imageFormatBake.needsExtendedImageFormats &&
g_GLESCapabilities.SupportsExtendedImageFormats);
source =
RequestExtendedImageFormats(std::move(source), imageFormatBake.needsExtendedImageFormats &&
g_GLESCapabilities.SupportsExtendedImageFormats);
// The third header-level rewrite, for the builtin SPIRV-Cross prints bare:
// gl_ViewportIndex is in no version of ESSL core, so without this directive the
// stage does not compile and the whole program - not just its viewport routing -
@@ -2173,8 +2166,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// keeps the line off every other program and PointSizeExtensionName returns
// nullptr - i.e. nothing is emitted - on a driver advertising neither spelling.
if (source.find("gl_PointSize") != String::npos) {
const Bool tessellationStage = glShaderType == GL_TESS_CONTROL_SHADER ||
glShaderType == GL_TESS_EVALUATION_SHADER;
const Bool tessellationStage =
glShaderType == GL_TESS_CONTROL_SHADER || glShaderType == GL_TESS_EVALUATION_SHADER;
if (tessellationStage || glShaderType == GL_GEOMETRY_SHADER) {
const auto tier = tessellationStage ? g_GLESCapabilities.TessellationPointSizeSupport
: g_GLESCapabilities.GeometryPointSizeSupport;
@@ -2190,8 +2183,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"will fail to compile. Point size from a non-vertex stage is not "
"available on this device.",
tessellationStage ? "tessellation" : "geometry",
tessellationStage ? "tessellation" : "geometry",
src.GetExternalIndex());
tessellationStage ? "tessellation" : "geometry", src.GetExternalIndex());
}
source = RequestPointSizeExtension(std::move(source), pointSizeExtension);
}
@@ -2205,8 +2197,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// their declarations already qualified. AFTER the rebind, so the layout qualifier
// it edits is the one that already exists; BEFORE the split and the binding
// strip, so both halves of a split image inherit the format.
source = BakeImageFormatQualifiers(std::move(source),
imageFormatBake.esslFormatQualifierByUniformName);
source = BakeImageFormatQualifiers(std::move(source), imageFormatBake.esslFormatQualifierByUniformName);
// An image ARRAY whose elements do not sit on consecutive units cannot be spelled
// by the single layout(binding=N) the rebind above stamped: ESSL gives element k
// the unit N+k and there is no glUniform1i to correct it with. Split the array
@@ -2216,8 +2207,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// element that is both read and written is split with its own binding on it.
if (!nonConsecutiveImageArrays.empty()) {
Vector<String> declinedImageArrays;
source = RemapImageArrayElementUnits(source, nonConsecutiveImageArrays,
&declinedImageArrays);
source = RemapImageArrayElementUnits(source, nonConsecutiveImageArrays, &declinedImageArrays);
for (const auto& declined : declinedImageArrays) {
// MGLOG_E, unlatched, like the transpile- and compile-failure diagnostics
// around it: this is the "linked, drew, produced wrong numbers, said
@@ -2283,8 +2273,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
source = ForceSupporterOutput(source);
source = ClampNormFallbackOutputs(std::move(source), glShaderType,
m_snormFallbackClampOutputMask,
source = ClampNormFallbackOutputs(std::move(source), glShaderType, m_snormFallbackClampOutputMask,
m_unormFallbackClampOutputMask);
// Patch for Photon compiler precision issue
@@ -2357,9 +2346,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
MGLOG_E("Shader compilation failed. State program ID: %u, stage: %s, backend shader ID: "
"%u, driver log: %s\nSource:\n%s",
src.GetExternalIndex(),
MG_Util::ConvertGLEnumToString(glShaderType).c_str(), backendShaderId,
log.data(), sourceForLog);
src.GetExternalIndex(), MG_Util::ConvertGLEnumToString(glShaderType).c_str(),
backendShaderId, log.data(), sourceForLog);
m_backendProgramUsable = false;
// Nothing will ever attach this one, so nothing else can free it.
g_GLESFuncs.glDeleteShader(backendShaderId);
@@ -2398,8 +2386,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (needsPassthroughTessControl) {
AttachPassthroughTessControlStage(src, tessEvalShaderIndex, shaderSpirvs,
vertexStageEssl, tessEvalStageEssl);
AttachPassthroughTessControlStage(src, tessEvalShaderIndex, shaderSpirvs, vertexStageEssl,
tessEvalStageEssl);
}
// A counter buffer declared by several stages was recorded once per stage; the draw
@@ -2418,8 +2406,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the transpiled ESSL (`out vec4 result_0;` stays `result_0`), so the
// frontend's requested names carry over unchanged.
SizeT declaredXfbVaryingCount = 0;
if (src.GetTransformFeedbackVaryingCount() > 0 &&
g_GLESFuncs.glTransformFeedbackVaryings != nullptr) {
if (src.GetTransformFeedbackVaryingCount() > 0 && g_GLESFuncs.glTransformFeedbackVaryings != nullptr) {
const auto& xfbVaryings = src.GetTransformFeedbackVaryings();
Vector<const GLchar*> xfbNames;
xfbNames.reserve(xfbVaryings.size());
@@ -2460,15 +2447,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (const auto& xfbName : rewrittenXfbNames) {
xfbNames.push_back(xfbName.c_str());
}
MGLOG_D("Declaring %zu transform feedback varyings on program %u", xfbNames.size(),
m_backendProgramId);
MGLOG_D("Declaring %zu transform feedback varyings on program %u", xfbNames.size(), m_backendProgramId);
// Bounded: a lost context never answers GL_NO_ERROR, and this runs on the
// thread that would then spin forever.
for (Int i = 0; i < kMaxDrainedProgramErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
}
for (Int i = 0; i < kMaxDrainedProgramErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {}
g_GLESFuncs.glTransformFeedbackVaryings(m_backendProgramId, static_cast<GLsizei>(xfbNames.size()),
xfbNames.data(),
src.GetTransformFeedbackBufferMode());
xfbNames.data(), src.GetTransformFeedbackBufferMode());
// Unchecked before. A rejected capture set leaves the program linking happily
// with NO capture set at all, and then every draw of every span records
// nothing while the application reads its buffer's pre-draw bytes and
@@ -2482,8 +2466,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E("The ES driver REJECTED the transform feedback capture set for backend program %u with "
"%s (mode %s): [%s]. Every capture made with GL program %u will record nothing.",
m_backendProgramId, MG_Util::ConvertGLEnumToString(xfbError).c_str(),
MG_Util::ConvertGLEnumToString(
src.GetTransformFeedbackBufferMode()).c_str(),
MG_Util::ConvertGLEnumToString(src.GetTransformFeedbackBufferMode()).c_str(),
declared.c_str(), src.GetExternalIndex());
}
declaredXfbVaryingCount = xfbNames.size();
@@ -2523,8 +2506,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
"program did; GL_MAX_*_IMAGE_UNIFORMS for it is %d. If the driver log names "
"image uniforms, that is the cause.",
split.splitCount,
MG_Util::ConvertGLEnumToString(
MG_Util::ConvertShaderStageToGLEnum(split.stage)).c_str(),
MG_Util::ConvertGLEnumToString(MG_Util::ConvertShaderStageToGLEnum(split.stage)).c_str(),
split.splitCount, AdvertisedStageImageUniformLimit(split.stage));
}
} else {
@@ -2541,12 +2523,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (declaredXfbVaryingCount > 0) {
GLint linkedXfbVaryings = 0;
GLint linkedXfbBufferMode = 0;
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_TRANSFORM_FEEDBACK_VARYINGS,
&linkedXfbVaryings);
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_TRANSFORM_FEEDBACK_VARYINGS, &linkedXfbVaryings);
g_GLESFuncs.glGetProgramiv(m_backendProgramId, GL_TRANSFORM_FEEDBACK_BUFFER_MODE,
&linkedXfbBufferMode);
for (Int i = 0; i < kMaxDrainedProgramErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {
}
for (Int i = 0; i < kMaxDrainedProgramErrors && g_GLESFuncs.glGetError() != GL_NO_ERROR; ++i) {}
const GLenum requestedMode = src.GetTransformFeedbackBufferMode();
if (static_cast<SizeT>(std::max(linkedXfbVaryings, 0)) != declaredXfbVaryingCount ||
static_cast<GLenum>(linkedXfbBufferMode) != requestedMode) {
@@ -2555,13 +2535,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
"in mode %s. Captures made with it will be empty or wrongly laid out.",
m_backendProgramId, src.GetExternalIndex(), declaredXfbVaryingCount,
MG_Util::ConvertGLEnumToString(requestedMode).c_str(), linkedXfbVaryings,
MG_Util::ConvertGLEnumToString(
static_cast<GLenum>(linkedXfbBufferMode)).c_str());
MG_Util::ConvertGLEnumToString(static_cast<GLenum>(linkedXfbBufferMode)).c_str());
} else {
MGLOG_D("Backend program %u capture set confirmed by the driver: %d varying(s), mode %s",
m_backendProgramId, linkedXfbVaryings,
MG_Util::ConvertGLEnumToString(
static_cast<GLenum>(linkedXfbBufferMode)).c_str());
MG_Util::ConvertGLEnumToString(static_cast<GLenum>(linkedXfbBufferMode)).c_str());
}
}
}
@@ -2586,11 +2564,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glUseProgram(0) and leave the failed program's previous executable running -
// the silent wrong-shader draw Use() exists to prevent.
m_rebindAfterRelink = true;
m_baseInstanceUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId,
BASE_INSTANCE_UNIFORM_NAME);
m_baseInstanceUniformLocation =
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, BASE_INSTANCE_UNIFORM_NAME);
m_drawIdUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId, DRAW_ID_UNIFORM_NAME);
m_baseVertexUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId,
BASE_VERTEX_UNIFORM_NAME);
m_baseVertexUniformLocation =
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, BASE_VERTEX_UNIFORM_NAME);
m_baseInstanceWordIndexUniformLocation =
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME);
// Asked of the DRIVER rather than remembered from the injection, deliberately: the
@@ -2725,7 +2703,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
} else {
MGLOG_W_ONCE("Program %u has frontend global UBO storage, but backend has no %s block.",
src.GetExternalIndex(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
src.GetExternalIndex(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME);
}
}
@@ -2743,9 +2721,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
m_uniformBlockBackendIndices[static_cast<SizeT>(i)] = static_cast<Int>(backendBlkIdx);
g_GLESFuncs.glUniformBlockBinding(m_backendProgramId, backendBlkIdx, lastUBOBinding);
MGLOG_D("CACHE prog=%u beProg=%u blk[%d]='%s' beIdx=%u -> bePoint=%u",
src.GetExternalIndex(), m_backendProgramId, i, name.c_str(), backendBlkIdx,
lastUBOBinding);
MGLOG_D("CACHE prog=%u beProg=%u blk[%d]='%s' beIdx=%u -> bePoint=%u", src.GetExternalIndex(),
m_backendProgramId, i, name.c_str(), backendBlkIdx, lastUBOBinding);
}
m_samplerUniformBindings.clear();
@@ -2817,8 +2794,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// app alternating a healthy and a broken program would otherwise log every
// single draw. Parked at MGLOG_I until the level ordering was fixed.
MGLOG_E_ONCE("Backend program for GL program %u is unusable (a shader failed to transpile, "
"compile or link); binding program 0 - draws with it will render nothing",
m_frontendProgramId);
"compile or link); binding program 0 - draws with it will render nothing",
m_frontendProgramId);
}
MGLOG_D("Using program %u", programToBind);
g_GLESFuncs.glUseProgram(programToBind);
@@ -2837,8 +2814,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// as "no indirect command" - see PromoteDrawParameterGlobalsToUniforms.
void BackendProgramObjectImpl::SetBaseInstanceWordIndex(Int32 wordIndex) const {
if (m_baseInstanceWordIndexUniformLocation >= 0) {
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation,
wordIndex < 0 ? 0 : wordIndex + 1);
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation, wordIndex < 0 ? 0 : wordIndex + 1);
}
}
@@ -51,9 +51,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_backendSamplerId = 0;
}
void BackendSamplerObject::SyncToBackend(
const SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject,
MG_Pipe::MGPipeHandle pushedCso) {
void BackendSamplerObject::SyncToBackend(const SharedPtr<MG_State::GLState::SamplerObject>& stateSamplerObject,
MG_Pipe::MGPipeHandle pushedCso) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -64,8 +63,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// not the pre-P4a error. Everything below that names the object is guarded on
// it; the pull build's text is the three lines the #if brackets.
if (MG_Pipe::MGPipeHandleIsNull(pushedCso)) {
MGLOG_E_ONCE("State sampler object is null, cannot sync to backend.");
return;
MGLOG_E_ONCE("State sampler object is null, cannot sync to backend.");
return;
}
}
@@ -107,8 +106,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return;
}
if (m_isInitialized && m_syncedSamplerSerial != 0 && m_syncedSamplerSerial == record->Serial) {
MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.",
samplerName);
MGLOG_D("Sampler parameters have not changed for sampler ID: %u, skipping sync.", samplerName);
return;
}
m_syncedSamplerSerial = record->Serial;
@@ -133,10 +131,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (m_cacheSamplerParameters.minFilter != samplerParams.minFilter ||
m_cacheSamplerParameters.mipmapMode != samplerParams.mipmapMode) {
g_GLESFuncs.glSamplerParameteri(m_backendSamplerId, GL_TEXTURE_MIN_FILTER,
(GLint)ResolveBackendMinFilter(
samplerParams,
ShouldAvoidSamplerMipmapMinFilterOnAngleLlvmpipe()));
g_GLESFuncs.glSamplerParameteri(
m_backendSamplerId, GL_TEXTURE_MIN_FILTER,
(GLint)ResolveBackendMinFilter(samplerParams, ShouldAvoidSamplerMipmapMinFilterOnAngleLlvmpipe()));
m_cacheSamplerParameters.minFilter = samplerParams.minFilter;
m_cacheSamplerParameters.mipmapMode = samplerParams.mipmapMode;
}
@@ -176,8 +173,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// glSamplerParameterIiv/Iuiv or an integer sampler reads the float's bit pattern
// back instead of the value.
if (g_GLESCapabilities.SupportsTextureBorderClamp) {
if (samplerParams.borderColorForm == BorderColorForm::Int &&
g_GLESFuncs.glSamplerParameterIiv) {
if (samplerParams.borderColorForm == BorderColorForm::Int && g_GLESFuncs.glSamplerParameterIiv) {
const GLint borderColorArray[4] = {
samplerParams.borderColorI.x(), samplerParams.borderColorI.y(),
samplerParams.borderColorI.z(), samplerParams.borderColorI.w()};
@@ -192,10 +188,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
borderColorArray);
} else if (g_GLESFuncs.glSamplerParameterfv) {
const GLfloat borderColorArray[4] = {
samplerParams.borderColor.x(), samplerParams.borderColor.y(),
samplerParams.borderColor.z(), samplerParams.borderColor.w()};
g_GLESFuncs.glSamplerParameterfv(m_backendSamplerId, GL_TEXTURE_BORDER_COLOR,
borderColorArray);
samplerParams.borderColor.x(), samplerParams.borderColor.y(), samplerParams.borderColor.z(),
samplerParams.borderColor.w()};
g_GLESFuncs.glSamplerParameterfv(m_backendSamplerId, GL_TEXTURE_BORDER_COLOR, borderColorArray);
}
}
m_cacheSamplerParameters.borderColor = samplerParams.borderColor;
@@ -232,7 +227,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Array<BackendSamplerObject*, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundSamplersCache;
TwinRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject, MG_Pipe::MGPipeKind::SamplerCso> g_backendSamplerObjects;
TwinRegistry<MG_State::GLState::SamplerObject, BackendSamplerObject, MG_Pipe::MGPipeKind::SamplerCso>
g_backendSamplerObjects;
// P4a fable seam F-4. THE TWIN FOR A CONTENT-ADDRESSED SamplerCso HANDLE, keyed by that
// handle and synced from its record - see the declaration for why the identity-keyed
@@ -25,13 +25,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// they use, so the type stays trivially default-constructible and the
// declaration costs nothing on the levels that never reach the ring.
struct UnpackStagingBlock {
const Uint8* src; // top-left texel of the region in the level's upload bytes
SizeT rowBytes; // bytes per region row (region width * bpp)
SizeT rows; // region height
SizeT slices; // region depth (1 for 2D)
SizeT srcRowStride; // level row pitch
SizeT srcSliceStride; // level slice pitch
SizeT offset; // out: byte offset into the ring store
const Uint8* src; // top-left texel of the region in the level's upload bytes
SizeT rowBytes; // bytes per region row (region width * bpp)
SizeT rows; // region height
SizeT slices; // region depth (1 for 2D)
SizeT srcRowStride; // level row pitch
SizeT srcSliceStride; // level slice pitch
SizeT offset; // out: byte offset into the ring store
};
// False (nothing staged, ring untouched) whenever the caller must keep the
@@ -109,8 +109,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return 2;
case TextureInternalFormat::RGB8Snorm:
case TextureInternalFormat::RGB16:
case TextureInternalFormat::RGB10: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB12: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB10: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB12: // stored as RGB16 (UNorm16 shadow)
case TextureInternalFormat::RGB16Snorm:
return 3;
case TextureInternalFormat::RGBA8Snorm:
@@ -218,10 +218,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// driver walks three texels' worth of data per four-texel row and the image shears.
// `componentCount` is the SOURCE component count and `byteSize` the source's size, so this
// runs after any type conversion (which keeps the component count) has already happened.
const void* PrepareChannelWidenedUpload(Uint componentCount, const IntVec3& texelSize,
const void* data, SizeT byteSize, GLenum uploadType,
Vector<Uint8>& widenedData, Bool integerData,
Uint32 alphaOneCodeOverride) {
const void* PrepareChannelWidenedUpload(Uint componentCount, const IntVec3& texelSize, const void* data,
SizeT byteSize, GLenum uploadType, Vector<Uint8>& widenedData,
Bool integerData, Uint32 alphaOneCodeOverride) {
Uint8 oneBits[8] = {};
SizeT componentSize = 0;
// One and two source components as well as three: the image-format widening carries
@@ -284,11 +283,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return widenedData.data();
}
static const void* PrepareNormFloatFallbackUpload(TextureInternalFormat format,
const IntVec3& texelSize,
const void* data,
SizeT byteSize,
GLenum uploadType,
static const void* PrepareNormFloatFallbackUpload(TextureInternalFormat format, const IntVec3& texelSize,
const void* data, SizeT byteSize, GLenum uploadType,
Vector<Float>& convertedData) {
const Uint componentCount = GetNormFallbackComponentCount(format);
if (componentCount == 0 || uploadType != GL_FLOAT || data == nullptr || byteSize == 0) {
@@ -389,8 +385,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// Sized from the LEVEL, not the source, for the reason PrepareChannelWidenedUpload is:
// the driver reads a full width*height*depth*4 floats for the transfer it was handed.
static const void* PreparePackedFloatWidenedUpload(const IntVec3& texelSize, const void* data,
SizeT byteSize, Vector<Uint8>& widenedData) {
static const void* PreparePackedFloatWidenedUpload(const IntVec3& texelSize, const void* data, SizeT byteSize,
Vector<Uint8>& widenedData) {
constexpr SizeT kSourceTexelBytes = sizeof(Uint32);
if (data == nullptr || byteSize < kSourceTexelBytes) {
return data;
@@ -436,8 +432,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
//
// Sized from the LEVEL, not the source, for the reason PrepareChannelWidenedUpload is: the
// driver reads a full width*height*depth*4 shorts for the transfer it was handed.
const void* PreparePackedIntWidenedUpload(const IntVec3& texelSize, const void* data,
SizeT byteSize, Vector<Uint8>& widenedData) {
const void* PreparePackedIntWidenedUpload(const IntVec3& texelSize, const void* data, SizeT byteSize,
Vector<Uint8>& widenedData) {
constexpr SizeT kSourceTexelBytes = sizeof(Uint32);
if (data == nullptr || byteSize < kSourceTexelBytes) {
return data;
@@ -655,8 +651,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// falls back to the frontend read when there is no active transport (the legacy arm, and
// monolith). MGB_STAGED_TEXTURE_LIVE is the runtime discriminator; pushedStorage/pushedRes
// are the function's own locals. All are #undef'd with the rest after the function.
#define MGB_STAGED_TEXTURE_LIVE \
(pushedStorage != nullptr && MG_Record::ServerStagedTexture().Holds())
#define MGB_STAGED_TEXTURE_LIVE (pushedStorage != nullptr && MG_Record::ServerStagedTexture().Holds())
#define MGB_TEXTURE_TARGET(obj) \
(MGB_STAGED_TEXTURE_LIVE ? BufferImpl::StagedTextureTargetForPipeTarget(pushedStorage->Desc.Target) \
: (obj)->GetTarget())
@@ -664,23 +659,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
(MGB_STAGED_TEXTURE_LIVE ? BufferImpl::StagedUploadTargetsForPipeTarget(pushedStorage->Desc.Target) \
: (obj)->GetUploadTargets())
#define MGB_LEVEL_TEXEL_SIZE(obj, tgt, lvl) \
(MGB_STAGED_TEXTURE_LIVE \
? MG_Record::ServerStagedTexture().LevelExtentOrUndefined( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl)) \
: (obj)->GetMipmapTexelSize(tgt, lvl))
(MGB_STAGED_TEXTURE_LIVE ? MG_Record::ServerStagedTexture().LevelExtentOrUndefined( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl)) \
: (obj)->GetMipmapTexelSize(tgt, lvl))
#define MGB_LEVEL_BYTE_SIZE(obj, tgt, lvl) \
(MGB_STAGED_TEXTURE_LIVE \
? MG_Record::ServerStagedTexture().LevelByteSize( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl)) \
? MG_Record::ServerStagedTexture().LevelByteSize(MG_Record::StagedTextureStore::KeyForHandle(pushedRes), \
static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)) \
: (obj)->GetMipmapByteSize(tgt, lvl))
#define MGB_LEVEL_TEXELS(obj, tgt, lvl, site) \
(MGB_STAGED_TEXTURE_LIVE \
? MG_Record::ServerStagedTexture().RequireLevelBytes( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl), site) \
: (obj)->MapMipmapData(tgt, lvl))
(MGB_STAGED_TEXTURE_LIVE ? MG_Record::ServerStagedTexture().RequireLevelBytes( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl), site) \
: (obj)->MapMipmapData(tgt, lvl))
// P5c (tx): the disaggregated pair adds ONE term and ONE clear to the P4a shapes - the
// staged-texture store's GPU-dirty mark (T5: a level the GPU generated dirties the SERVER's
@@ -690,10 +682,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// load when the store is empty.
#define MGB_LEVEL_NEEDS_UPLOAD(obj, tgt, lvl) \
(pushedStorage != nullptr \
? (FindPipeTextureUpload(*pushedStorage, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)) != nullptr || \
MG_Record::ServerStagedTexture().IsLevelGpuDirty( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl))) \
? (FindPipeTextureUpload(*pushedStorage, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)) != nullptr || \
MG_Record::ServerStagedTexture().IsLevelGpuDirty(MG_Record::StagedTextureStore::KeyForHandle(pushedRes), \
static_cast<Uint16>(tgt), static_cast<Uint16>(lvl))) \
: (obj)->IsStorageDirty(tgt, lvl))
// Re-resolves the record itself, so it is safe after any amount of driver work - and it
// invalidates any PendingUpload* taken earlier for THIS texture, which is why every such pointer
@@ -701,10 +692,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
#define MGB_LEVEL_UPLOAD_DONE(obj, tgt, lvl) \
do { \
if (pushedStorage != nullptr) { \
ConsumePipeTextureUpload(pushedRes, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)); \
ConsumePipeTextureUpload(pushedRes, static_cast<Uint16>(tgt), static_cast<Uint16>(lvl)); \
if (MGB_STAGED_TEXTURE_LIVE) { \
MG_Record::ServerStagedTexture().MarkLevelGpuDirty( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
MG_Record::ServerStagedTexture().MarkLevelGpuDirty( \
MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast<Uint16>(tgt), \
static_cast<Uint16>(lvl), false); \
} \
} else { \
@@ -745,13 +736,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// otherwise have been spelled on, so the pull expansion is the pre-P4a expression with
// one pair of parentheses around the receiver. All are #undef'd with the pair above.
#define MGB_STORAGE_FORMAT(obj) \
(pushedStorage != nullptr ? static_cast<TextureInternalFormat>(pushedStorage->Desc.InternalFormat) \
(pushedStorage != nullptr ? static_cast<TextureInternalFormat>(pushedStorage->Desc.InternalFormat) \
: (obj)->GetFormat())
#define MGB_STORAGE_BASE_SIZE(obj) \
(pushedStorage != nullptr ? IntVec3{static_cast<Int>(pushedStorage->Desc.Width), \
static_cast<Int>(pushedStorage->Desc.Height), \
static_cast<Int>(pushedStorage->Desc.Depth)} \
: (obj)->GetBaseSize())
(pushedStorage != nullptr \
? IntVec3{static_cast<Int>(pushedStorage->Desc.Width), static_cast<Int>(pushedStorage->Desc.Height), \
static_cast<Int>(pushedStorage->Desc.Depth)} \
: (obj)->GetBaseSize())
#define MGB_STORAGE_LEVELS(obj) \
(pushedStorage != nullptr ? static_cast<Uint>(pushedStorage->Desc.Levels) : (obj)->GetMipmapLevelCount())
#define MGB_STORAGE_SAMPLES(obj) \
@@ -761,7 +752,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
#define MGB_STORAGE_IMMUTABLE(obj) \
(pushedStorage != nullptr ? pushedStorage->Desc.Immutable != 0 : (obj)->IsImmutable())
#define MGB_STORAGE_KIND(obj) \
(pushedStorage != nullptr ? static_cast<TextureStorageType>(pushedStorage->Desc.StorageKind) \
(pushedStorage != nullptr ? static_cast<TextureStorageType>(pushedStorage->Desc.StorageKind) \
: (obj)->GetStorageType())
void BackendTextureObject::SyncMipmapsToBackend(
@@ -791,14 +782,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
pushedStorage = ResolveOwnRecord(stateTextureObject);
pushedRes = !MG_Pipe::MGPipeHandleIsNull(m_pushedSyncHandle)
? m_pushedSyncHandle
: (pushedStorage != nullptr ? pushedStorage->Desc.Resource
: MG_Pipe::kMGPipeNullHandle);
: (pushedStorage != nullptr ? pushedStorage->Desc.Resource : MG_Pipe::kMGPipeNullHandle);
if (pushedStorage == nullptr) {
MGLOG_E_ONCE("MGPipe: texture %u has no applier record on the handle arm, so its "
"storage and uploads cannot be driven from the pushed descriptor "
"(handle {%u, %u})",
TextureDiagName(stateTextureObject, pushedStorage), pushedRes.Slot,
pushedRes.Gen);
TextureDiagName(stateTextureObject, pushedStorage), pushedRes.Slot, pushedRes.Gen);
return;
}
#if MOBILEGL_BUILD_DISAGGREGATED
@@ -827,8 +816,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Ruling 1's arm: the push-monolith build keeps the frontend test below token for
// token, because there the two answers are the same answer and the verify comparator
// needs the frontend arm.
if (pushedStorage != nullptr &&
!MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) {
if (pushedStorage != nullptr && !MG_Pipe::MGPipeHandleIsNull(pushedStorage->Desc.ViewOf)) {
SyncTextureViewToBackendByRecord(pushedRes, *pushedStorage, stateTextureObject);
return;
}
@@ -888,8 +876,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// refreshes and skips) with the serial already stamped, and the set is what
// survives that. Empty means every level this record ever declared has been
// consumed by a sync that actually uploaded it.
if (m_isInitialized && m_syncedResourceSerial != 0 &&
m_syncedResourceSerial == pushedStorage->Serial &&
if (m_isInitialized && m_syncedResourceSerial != 0 && m_syncedResourceSerial == pushedStorage->Serial &&
pushedStorage->PendingUploads.empty()) {
return;
}
@@ -907,14 +894,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// LEGACY ARM ONLY from P4a on: the memos it reads are the pre-handle ones and the
// record above answers the same question in one compare.
if (pushedStorage == nullptr)
if (m_isInitialized && m_syncedShapeContextId != 0 && MG_Pipe::gPipeInputs->IsLive() &&
m_syncedShapeContextId == MG_Pipe::gPipeInputs->GetTextureContextId() &&
m_syncedShapeGeneration == MG_Pipe::gPipeInputs->GetSamplingResolutionGeneration() &&
m_syncedContentVersion == stateTextureObject->GetContentVersion() &&
m_syncedShapeParamsVersion == stateTextureObject->GetTextureParamsVersion() &&
stateTextureObject->GetStorageType() == TextureStorageType::Mipmap) {
return;
}
if (m_isInitialized && m_syncedShapeContextId != 0 && MG_Pipe::gPipeInputs->IsLive() &&
m_syncedShapeContextId == MG_Pipe::gPipeInputs->GetTextureContextId() &&
m_syncedShapeGeneration == MG_Pipe::gPipeInputs->GetSamplingResolutionGeneration() &&
m_syncedContentVersion == stateTextureObject->GetContentVersion() &&
m_syncedShapeParamsVersion == stateTextureObject->GetTextureParamsVersion() &&
stateTextureObject->GetStorageType() == TextureStorageType::Mipmap) {
return;
}
MGLOG_D("Syncing texture mipmaps with backend ID %u to backend for state ID %u", m_backendTextureId,
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
@@ -925,7 +912,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
if (!IsSupportedTextureTarget(targetInternal)) {
MGLOG_E_ONCE(" Texture target %s is not supported, skipping.",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
return;
}
@@ -959,8 +946,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (static_cast<TextureStorageType>(stagedDesc.StorageKind) == TextureStorageType::Buffer) {
// A buffer texture has no staged levels; its gate is the descriptor's
// format, which is what TextureObjectBase::IsComplete() reduces to here.
anyDefined = static_cast<TextureInternalFormat>(stagedDesc.InternalFormat) !=
TextureInternalFormat::Unknown;
anyDefined =
static_cast<TextureInternalFormat>(stagedDesc.InternalFormat) != TextureInternalFormat::Unknown;
} else {
auto& stagedStore = MG_Record::ServerStagedTexture();
const Uint64 stagedKey = MG_Record::StagedTextureStore::KeyForHandle(pushedRes);
@@ -980,8 +967,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
return;
}
} else
if (!stateTextureObject->IsComplete() && !HasAnyDefinedMipmapLevel(stateTextureObject.get())) {
} else if (!stateTextureObject->IsComplete() && !HasAnyDefinedMipmapLevel(stateTextureObject.get())) {
MGLOG_D("Texture object with ID: %u has no defined image level, skipping sync.",
stateTextureObject->GetExternalIndex());
return;
@@ -1005,34 +991,32 @@ namespace MobileGL::MG_Backend::DirectGLES {
// an answer already given, out of the very frontend state the arm exists to stop
// reading.
if (pushedStorage == nullptr)
if (m_isInitialized && stateTextureObject->GetStorageType() == TextureStorageType::Mipmap &&
m_syncedContentVersion != 0 &&
m_syncedContentVersion == stateTextureObject->GetContentVersion()) {
auto* mipmapObject =
static_cast<MG_State::GLState::TextureObjectMipmap*>(stateTextureObject.get());
const auto probeBaseSize = stateTextureObject->GetBaseSize();
StateTextureBasicInfo probe = {stateTextureObject->GetFormat(),
static_cast<SizeT>(probeBaseSize.x()),
static_cast<SizeT>(probeBaseSize.y()),
static_cast<SizeT>(probeBaseSize.z()),
static_cast<SizeT>(mipmapObject->GetMipmapLevelCount()),
0,
stateTextureObject->GetSamples(),
stateTextureObject->HasFixedSampleLocations()};
if (probe == m_prevTextureInfo) {
MGLOG_D("Texture ID %u already fully synced, skipping scratch bind + upload.",
m_backendTextureId);
// The probe just proved "fully synced" from the real state, so the cheap
// gate may be (re)stamped here: the coarse generation only ever goes stale
// from OTHER textures' churn, and this draw re-validated this one.
if (MG_Pipe::gPipeInputs->IsLive()) {
m_syncedShapeContextId = MG_Pipe::gPipeInputs->GetTextureContextId();
m_syncedShapeGeneration = MG_Pipe::gPipeInputs->GetSamplingResolutionGeneration();
m_syncedShapeParamsVersion = stateTextureObject->GetTextureParamsVersion();
if (m_isInitialized && stateTextureObject->GetStorageType() == TextureStorageType::Mipmap &&
m_syncedContentVersion != 0 && m_syncedContentVersion == stateTextureObject->GetContentVersion()) {
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(stateTextureObject.get());
const auto probeBaseSize = stateTextureObject->GetBaseSize();
StateTextureBasicInfo probe = {stateTextureObject->GetFormat(),
static_cast<SizeT>(probeBaseSize.x()),
static_cast<SizeT>(probeBaseSize.y()),
static_cast<SizeT>(probeBaseSize.z()),
static_cast<SizeT>(mipmapObject->GetMipmapLevelCount()),
0,
stateTextureObject->GetSamples(),
stateTextureObject->HasFixedSampleLocations()};
if (probe == m_prevTextureInfo) {
MGLOG_D("Texture ID %u already fully synced, skipping scratch bind + upload.",
m_backendTextureId);
// The probe just proved "fully synced" from the real state, so the cheap
// gate may be (re)stamped here: the coarse generation only ever goes stale
// from OTHER textures' churn, and this draw re-validated this one.
if (MG_Pipe::gPipeInputs->IsLive()) {
m_syncedShapeContextId = MG_Pipe::gPipeInputs->GetTextureContextId();
m_syncedShapeGeneration = MG_Pipe::gPipeInputs->GetSamplingResolutionGeneration();
m_syncedShapeParamsVersion = stateTextureObject->GetTextureParamsVersion();
}
return;
}
return;
}
}
Bind(target);
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
@@ -1070,9 +1054,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
: TextureImpl::ImageBindableStorageWidening{};
const Bool canAppendMipmaps =
m_isInitialized &&
!m_imageBindableStorageRequired &&
!MGB_STORAGE_IMMUTABLE(stateTextureObject) &&
m_isInitialized && !m_imageBindableStorageRequired && !MGB_STORAGE_IMMUTABLE(stateTextureObject) &&
currentTextureInfo.internalFormat == m_prevTextureInfo.internalFormat &&
currentTextureInfo.width == m_prevTextureInfo.width &&
currentTextureInfo.height == m_prevTextureInfo.height &&
@@ -1110,18 +1092,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto levelByteSize = MGB_LEVEL_BYTE_SIZE(textureMipmapObject, uploadTarget, level);
bool levelDirty = MGB_LEVEL_NEEDS_UPLOAD(textureMipmapObject, uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
auto* pData = (levelDirty && levelByteSize != 0)
? MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level,
"append-mips")
: nullptr;
auto* pData =
(levelDirty && levelByteSize != 0)
? MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level, "append-mips")
: nullptr;
Vector<Float> convertedUploadData;
Vector<Uint8> widenedUploadData;
const void* uploadData = PrepareFallbackUpload(
MGB_STORAGE_FORMAT(textureMipmapObject), targetInternal, levelTexelSize, pData,
levelByteSize, glType, convertedUploadData, widenedUploadData);
Vector<Uint8> packedUploadData;
uploadData = PreparePackedNormUpload(MGB_STORAGE_FORMAT(textureMipmapObject), levelTexelSize,
uploadData, levelByteSize, &glType, packedUploadData);
uploadData =
PreparePackedNormUpload(MGB_STORAGE_FORMAT(textureMipmapObject), levelTexelSize,
uploadData, levelByteSize, &glType, packedUploadData);
DebugImpl::ErrorLopper::Clear();
BufferImpl::BindPixelUnpackBufferId(0); // no-op once the resting 0 state is pinned
@@ -1130,10 +1113,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
switch (MapToBackendTextureTarget(MGB_TEXTURE_TARGET(stateTextureObject))) {
case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexImage2D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
0, glFormat, glType, uploadData);
g_GLESFuncs.glTexImage2D(glUploadTarget, static_cast<GLint>(level),
(GLint)glInternalFormat, static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()), 0, glFormat, glType,
uploadData);
break;
case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
@@ -1146,8 +1129,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData);
break;
default:
MGLOG_E_ONCE("Unhandled texture target %s",
MG_Util::ConvertTextureTargetToString(MGB_TEXTURE_TARGET(stateTextureObject)).c_str());
MGLOG_E_ONCE("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(
MGB_TEXTURE_TARGET(stateTextureObject))
.c_str());
break;
}
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__,
@@ -1222,15 +1206,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
switch (targetInternal) {
case TextureTarget::Texture2DMultisample:
g_GLESFuncs.glTexStorage2DMultisample(
target, backendSamples, glInternalFormat,
static_cast<GLsizei>(baseSize.x()), static_cast<GLsizei>(baseSize.y()),
target, backendSamples, glInternalFormat, static_cast<GLsizei>(baseSize.x()),
static_cast<GLsizei>(baseSize.y()),
MGB_STORAGE_FIXED_SAMPLE_LOCATIONS(stateTextureObject) ? GL_TRUE : GL_FALSE);
break;
case TextureTarget::Texture2DMultisampleArray:
g_GLESFuncs.glTexStorage3DMultisample(
target, backendSamples, glInternalFormat,
static_cast<GLsizei>(baseSize.x()), static_cast<GLsizei>(baseSize.y()),
static_cast<GLsizei>(baseSize.z()),
target, backendSamples, glInternalFormat, static_cast<GLsizei>(baseSize.x()),
static_cast<GLsizei>(baseSize.y()), static_cast<GLsizei>(baseSize.z()),
MGB_STORAGE_FIXED_SAMPLE_LOCATIONS(stateTextureObject) ? GL_TRUE : GL_FALSE);
break;
default:
@@ -1278,29 +1261,30 @@ namespace MobileGL::MG_Backend::DirectGLES {
break;
default:
MGLOG_E_ONCE("Unhandled immutable texture target %s",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
break;
}
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__, target,
glInternalFormat](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexStorage*: target=%s, internalformat=%s", func,
file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(target).c_str(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
});
DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__, target, glInternalFormat](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexStorage*: target=%s, internalformat=%s", func,
file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(target).c_str(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str());
});
m_backendStorageImmutable = true;
ScopedDefaultUnpackState unpackState;
for (auto& uploadTarget : uploadTargets) {
for (SizeT level = 0; level < mipmapCount; ++level) {
auto levelByteSize = MGB_LEVEL_BYTE_SIZE(textureMipmapObject, uploadTarget, level);
const bool levelDirty = MGB_LEVEL_NEEDS_UPLOAD(textureMipmapObject, uploadTarget, level);
const bool levelDirty =
MGB_LEVEL_NEEDS_UPLOAD(textureMipmapObject, uploadTarget, level);
if (levelDirty && levelByteSize != 0) {
auto levelTexelSize =
MGB_LEVEL_TEXEL_SIZE(textureMipmapObject, uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
auto* pData = MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level,
"immutable-regen");
auto* pData =
MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level, "immutable-regen");
Vector<Float> convertedUploadData;
Vector<Uint8> widenedUploadData;
const void* uploadData = PrepareFallbackUpload(
@@ -1316,38 +1300,35 @@ namespace MobileGL::MG_Backend::DirectGLES {
DebugImpl::ErrorLopper::Clear();
BufferImpl::BindPixelUnpackBufferId(0); // no-op once the resting 0 state is pinned
const IntVec3 uploadSize =
GetBackendUploadSize(targetInternal, levelTexelSize);
const IntVec3 uploadSize = GetBackendUploadSize(targetInternal, levelTexelSize);
switch (MapToBackendTextureTarget(targetInternal)) {
case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()), glFormat, glType, uploadData);
g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()), glFormat,
glType, uploadData);
break;
case TextureTarget::Texture3D:
case TextureTarget::Texture2DArray:
case TextureTarget::TextureCubeMapArray:
g_GLESFuncs.glTexSubImage3D(
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.z()), glFormat, glType, uploadData);
break;
default:
break;
}
DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__, glUploadTarget,
glFormat, glType, pData](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexSubImage*: target=%s, format=%s, "
"type=%s, pixels=%p",
func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(),
MG_Util::ConvertGLEnumToString(glFormat).c_str(),
MG_Util::ConvertGLEnumToString(glType).c_str(), pData);
});
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__,
glUploadTarget, glFormat, glType, pData](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexSubImage*: target=%s, format=%s, "
"type=%s, pixels=%p",
func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(),
MG_Util::ConvertGLEnumToString(glFormat).c_str(),
MG_Util::ConvertGLEnumToString(glType).c_str(), pData);
});
}
MGB_LEVEL_UPLOAD_DONE(textureMipmapObject, uploadTarget, level);
}
@@ -1360,18 +1341,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto levelTexelSize = MGB_LEVEL_TEXEL_SIZE(textureMipmapObject, uploadTarget, level);
// See the append-mips loop: an undefined level stays undefined on the
// driver rather than becoming a 0x0 image.
if (levelTexelSize.x() <= 0 || levelTexelSize.y() <= 0 ||
levelTexelSize.z() <= 0) {
if (levelTexelSize.x() <= 0 || levelTexelSize.y() <= 0 || levelTexelSize.z() <= 0) {
MGB_LEVEL_UPLOAD_DONE(textureMipmapObject, uploadTarget, level);
continue;
}
auto levelByteSize = MGB_LEVEL_BYTE_SIZE(textureMipmapObject, uploadTarget, level);
bool levelDirty = MGB_LEVEL_NEEDS_UPLOAD(textureMipmapObject, uploadTarget, level);
auto glUploadTarget = ConvertTextureUploadTargetToBackendGLEnum(uploadTarget);
auto* pData = (levelDirty && levelByteSize != 0)
? MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level,
"mutable-regen")
: nullptr;
auto* pData =
(levelDirty && levelByteSize != 0)
? MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level, "mutable-regen")
: nullptr;
Vector<Float> convertedUploadData;
Vector<Uint8> widenedUploadData;
const void* uploadData = PrepareFallbackUpload(
@@ -1396,8 +1376,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
case TextureTarget::TextureCubeMap: {
g_GLESFuncs.glTexImage2D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()), 0, glFormat, glType, uploadData);
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()), 0,
glFormat, glType, uploadData);
break;
}
case TextureTarget::Texture3D:
@@ -1405,27 +1385,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
case TextureTarget::TextureCubeMapArray: {
g_GLESFuncs.glTexImage3D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.z()), 0, glFormat, glType, uploadData);
break;
}
default: {
MGLOG_E_ONCE("Unhandled texture target %s",
MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
MG_Util::ConvertTextureTargetToString(textureTarget).c_str());
}
}
DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__, glUploadTarget,
glInternalFormat, glFormat, glType, pData](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexImage*: target=%s, internalformat=%s, "
"format=%s, type=%s, pixels=%p",
func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(),
MG_Util::ConvertGLEnumToString(glFormat).c_str(),
MG_Util::ConvertGLEnumToString(glType).c_str(), pData);
});
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__,
glUploadTarget, glInternalFormat, glFormat, glType,
pData](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexImage*: target=%s, internalformat=%s, "
"format=%s, type=%s, pixels=%p",
func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(),
MG_Util::ConvertGLEnumToString(glFormat).c_str(),
MG_Util::ConvertGLEnumToString(glType).c_str(), pData);
});
MGLOG_D("Regenerated mipmap level %d for texture with ID: %u", level,
m_backendTextureId);
MGB_LEVEL_UPLOAD_DONE(textureMipmapObject, uploadTarget, level);
@@ -1490,8 +1469,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertGLEnumToString(err).c_str());
});
auto texelSize = MGB_LEVEL_TEXEL_SIZE(textureMipmapObject, uploadTarget, level);
const void* mipData = MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level,
"dirty-level");
const void* mipData =
MGB_LEVEL_TEXELS(textureMipmapObject, uploadTarget, level, "dirty-level");
Vector<Float> convertedUploadData;
Vector<Uint8> widenedUploadData;
const void* uploadData = PrepareFallbackUpload(
@@ -1528,8 +1507,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT uploadBytes = 0;
if (uploadData == mipData) {
uploadBytes = byteSize;
} else if (!imageWidenedUploadData.empty() &&
uploadData == imageWidenedUploadData.data()) {
} else if (!imageWidenedUploadData.empty() && uploadData == imageWidenedUploadData.data()) {
uploadBytes = imageWidenedUploadData.size();
} else if (!packedUploadData.empty() && uploadData == packedUploadData.data()) {
uploadBytes = packedUploadData.size();
@@ -1581,10 +1559,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
// MGPBox is {origin, extent}; MipmapDirtyRegion is {lo, hi}. The
// conversion is the whole difference between the two spellings.
const auto& box = pendingUpload->UnionBox;
return MG_State::GLState::MipmapDirtyRegion{
IntVec3{box.X, box.Y, box.Z},
IntVec3{box.X + static_cast<Int32>(box.W), box.Y + static_cast<Int32>(box.H),
box.Z + static_cast<Int32>(box.D)}};
return MG_State::GLState::MipmapDirtyRegion{IntVec3{box.X, box.Y, box.Z},
IntVec3{box.X + static_cast<Int32>(box.W),
box.Y + static_cast<Int32>(box.H),
box.Z + static_cast<Int32>(box.D)}};
}();
const SizeT texelCount = static_cast<SizeT>(texelSize.x()) *
static_cast<SizeT>(texelSize.y()) *
@@ -1646,14 +1624,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Bool carriedLayoutApplies = uploadData == mipData;
const SizeT carriedRowBytes =
(pendingUpload != nullptr && pendingStridesAgree && carriedLayoutApplies &&
!pendingUpload->Regions.empty() &&
pendingUpload->Regions.front().SrcRowStride != 0)
!pendingUpload->Regions.empty() && pendingUpload->Regions.front().SrcRowStride != 0)
? static_cast<SizeT>(pendingUpload->Regions.front().SrcRowStride)
: 0;
const SizeT carriedSliceBytes =
(pendingUpload != nullptr && pendingStridesAgree && carriedLayoutApplies &&
!pendingUpload->Regions.empty() &&
pendingUpload->Regions.front().SrcSliceStride != 0)
!pendingUpload->Regions.empty() && pendingUpload->Regions.front().SrcSliceStride != 0)
? static_cast<SizeT>(pendingUpload->Regions.front().SrcSliceStride)
: 0;
const SizeT levelRowBytes =
@@ -1708,18 +1684,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
"level cut into stage-chunk pieces, whose regions index their "
"own run; otherwise this names a record whose regions and "
"origins disagree",
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), static_cast<Uint>(level),
static_cast<unsigned long long>(region.SrcOffset), region.X,
region.Y, region.Z,
static_cast<unsigned long long>(derivedOffset));
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject),
static_cast<Uint>(level),
static_cast<unsigned long long>(region.SrcOffset), region.X, region.Y,
region.Z, static_cast<unsigned long long>(derivedOffset));
break;
}
}
const Uint8* regionPtr =
static_cast<const Uint8*>(uploadData) +
static_cast<SizeT>(dirtyRegion.lo.z()) * levelSliceBytes +
static_cast<SizeT>(dirtyRegion.lo.y()) * levelRowBytes +
static_cast<SizeT>(dirtyRegion.lo.x()) * bpp;
const Uint8* regionPtr = static_cast<const Uint8*>(uploadData) +
static_cast<SizeT>(dirtyRegion.lo.z()) * levelSliceBytes +
static_cast<SizeT>(dirtyRegion.lo.y()) * levelRowBytes +
static_cast<SizeT>(dirtyRegion.lo.x()) * bpp;
// Scatter refinement behind the union box: ~100 sprite
// writes into an atlas leave a box that spans nearly the
// whole level while the touched texels are a few percent of
@@ -1754,10 +1729,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
summedTexels += static_cast<SizeT>(region.W) * static_cast<SizeT>(region.H) *
static_cast<SizeT>(std::max<Uint32>(region.D, 1));
}
const Bool listWithheld =
pendingUpload->Regions.size() < 2 ||
pendingUpload->Regions.size() > MG_State::GLState::MipmapStorage::kMaxDirtyRects ||
summedTexels * 4 >= dirtyRegion.TexelCount() * 3;
const Bool listWithheld = pendingUpload->Regions.size() < 2 ||
pendingUpload->Regions.size() >
MG_State::GLState::MipmapStorage::kMaxDirtyRects ||
summedTexels * 4 >= dirtyRegion.TexelCount() * 3;
dirtyRectCount = listWithheld ? 0 : pendingUpload->Regions.size();
for (SizeT r = 0; r < dirtyRectCount; ++r) {
const auto& region = pendingUpload->Regions[r];
@@ -1768,14 +1743,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
region.Z + static_cast<Int32>(region.D)}};
}
} else
// tx: with an active transport the rect list is the record's (the
// pendingUpload arm above) or nothing - the server's GPU-dirty mark
// is whole-level and has no scatter refinement to hand out, and the
// frontend's rect model is not this side's to read.
if (!MGB_STAGED_TEXTURE_LIVE)
dirtyRectCount = textureMipmapObject->GetStorageDirtyRects(
uploadTarget, level, dirtyRects,
MG_State::GLState::MipmapStorage::kMaxDirtyRects);
// tx: with an active transport the rect list is the record's (the
// pendingUpload arm above) or nothing - the server's GPU-dirty mark
// is whole-level and has no scatter refinement to hand out, and the
// frontend's rect model is not this side's to read.
if (!MGB_STAGED_TEXTURE_LIVE)
dirtyRectCount = textureMipmapObject->GetStorageDirtyRects(
uploadTarget, level, dirtyRects,
MG_State::GLState::MipmapStorage::kMaxDirtyRects);
// The scatter refinement pays only on the client-pointer path,
// where fewer bytes mean less driver-side copying. Through the
// unpack ring every glTexSubImage is a GPU copy job (Mali), so
@@ -1809,13 +1784,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
region.Z + static_cast<Int32>(region.D)}});
}
} else
// With an active transport and no record behind the level, the
// server's own whole-level GPU-dirty mark is the whole story (the
// same reason the rect list above is not read here).
if (MGB_STAGED_TEXTURE_LIVE) {
dirtyFootprint.clear();
} else
textureMipmapObject->GetStorageDirtyFootprint(uploadTarget, level, dirtyFootprint);
// With an active transport and no record behind the level, the
// server's own whole-level GPU-dirty mark is the whole story (the
// same reason the rect list above is not read here).
if (MGB_STAGED_TEXTURE_LIVE) {
dirtyFootprint.clear();
} else
textureMipmapObject->GetStorageDirtyFootprint(uploadTarget, level,
dirtyFootprint);
// One box is the union box itself, which the branches below already
// upload exactly (as the whole level when it is the whole level).
if (dirtyFootprint.size() >= 2) {
@@ -1885,8 +1861,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// block wants. It also stages strictly fewer bytes than the
// client-pointer path makes the driver walk, which strides over
// the whole level width.
UnpackStagingBlock
stagingBlockArray[MG_State::GLState::MipmapStorage::kMaxDirtyRects];
UnpackStagingBlock stagingBlockArray[MG_State::GLState::MipmapStorage::kMaxDirtyRects];
UnpackStagingBlock* stagingBlocks = stagingBlockArray;
SizeT stagingBlockCount = 0;
const Bool ringUsable = BufferImpl::UnpackRingAvailable();
@@ -1901,14 +1876,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
for (SizeT r = 0; r < uploadRectCount; ++r) {
const auto& rect = uploadRects[r];
stagingBlocks[r] = {
MGB_RECT_SRC_PTR(r, rect),
static_cast<SizeT>(rect.hi.x() - rect.lo.x()) * bpp,
static_cast<SizeT>(rect.hi.y() - rect.lo.y()),
static_cast<SizeT>(std::max(rect.hi.z() - rect.lo.z(), 1)),
levelRowBytes,
levelSliceBytes,
0};
stagingBlocks[r] = {MGB_RECT_SRC_PTR(r, rect),
static_cast<SizeT>(rect.hi.x() - rect.lo.x()) * bpp,
static_cast<SizeT>(rect.hi.y() - rect.lo.y()),
static_cast<SizeT>(std::max(rect.hi.z() - rect.lo.z(), 1)),
levelRowBytes,
levelSliceBytes,
0};
}
stagingBlockCount = uploadRectCount;
} else if (subRectEligible) {
@@ -1920,8 +1894,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
levelSliceBytes,
0};
stagingBlockCount = 1;
} else if (uploadData == mipData && texelCount > 0 &&
byteSize % texelCount == 0) {
} else if (uploadData == mipData && texelCount > 0 && byteSize % texelCount == 0) {
// Whole level, shadow bytes verbatim: `byteSize` is exactly
// what the driver would have read from the client pointer,
// and the shadow is already tightly packed. A CONVERTED
@@ -1933,18 +1906,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// hold. (A 1D array's upload size permutes height and depth,
// but the texel count and the tight byte order are the same,
// so the flat copy still describes what the driver reads.)
stagingBlocks[0] = {static_cast<const Uint8*>(uploadData),
static_cast<SizeT>(byteSize),
1,
1,
static_cast<SizeT>(byteSize),
static_cast<SizeT>(byteSize),
0};
stagingBlocks[0] = {
static_cast<const Uint8*>(uploadData), static_cast<SizeT>(byteSize), 1, 1,
static_cast<SizeT>(byteSize), static_cast<SizeT>(byteSize), 0};
stagingBlockCount = 1;
}
const Bool ringStaged =
stagingBlockCount > 0 &&
StageBlocksIntoUnpackRing(stagingBlocks, stagingBlockCount);
stagingBlockCount > 0 && StageBlocksIntoUnpackRing(stagingBlocks, stagingBlockCount);
if (ringStaged) {
BufferImpl::BindPixelUnpackBufferId(BufferImpl::UnpackRingBufferId());
}
@@ -1968,17 +1936,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<Uint64>(bpp);
}
} else if (subRectEligible) {
shippedBytes = static_cast<Uint64>(regionSize.x()) *
static_cast<Uint64>(regionSize.y()) *
static_cast<Uint64>(std::max(regionSize.z(), 1)) *
static_cast<Uint64>(bpp);
shippedBytes =
static_cast<Uint64>(regionSize.x()) * static_cast<Uint64>(regionSize.y()) *
static_cast<Uint64>(std::max(regionSize.z(), 1)) * static_cast<Uint64>(bpp);
} else {
shippedBytes = static_cast<Uint64>(byteSize);
}
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageTexture,
shippedBytes);
MG_Util::PipeStats::AddCalls(
MG_Util::PipeStats::CallClass::TextureUploadEmissions, 1);
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageTexture, shippedBytes);
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::TextureUploadEmissions, 1);
MG_Util::PipeStats::AddCalls(
rectShape ? MG_Util::PipeStats::CallClass::TextureUploadRectEmissions
: MG_Util::PipeStats::CallClass::TextureUploadBoxEmissions,
@@ -1994,10 +1959,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT r = 0; r < uploadRectCount; ++r) {
const auto& rect = uploadRects[r];
g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), rect.lo.x(),
rect.lo.y(), static_cast<GLsizei>(rect.hi.x() - rect.lo.x()),
static_cast<GLsizei>(rect.hi.y() - rect.lo.y()), glFormat,
glType,
glUploadTarget, static_cast<GLint>(level), rect.lo.x(), rect.lo.y(),
static_cast<GLsizei>(rect.hi.x() - rect.lo.x()),
static_cast<GLsizei>(rect.hi.y() - rect.lo.y()), glFormat, glType,
ringStaged ? UnpackRingPixelOffset(stagingBlocks[r].offset)
: static_cast<const void*>(MGB_RECT_SRC_PTR(r, rect)));
}
@@ -2014,13 +1978,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The surrounding ScopedDefaultUnpackState shadow says 0.
if (!ringStaged) g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
} else {
g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()), glFormat,
glType,
ringStaged
? UnpackRingPixelOffset(stagingBlocks[0].offset)
: uploadData);
g_GLESFuncs.glTexSubImage2D(
glUploadTarget, static_cast<GLint>(level), 0, 0,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
glFormat, glType,
ringStaged ? UnpackRingPixelOffset(stagingBlocks[0].offset) : uploadData);
}
break;
case TextureTarget::Texture3D:
@@ -2036,8 +1998,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT r = 0; r < uploadRectCount; ++r) {
const auto box = ToBackendBox(uploadRects[r]);
g_GLESFuncs.glTexSubImage3D(
glUploadTarget, static_cast<GLint>(level), box.x, box.y, box.z,
box.w, box.h, box.d, glFormat, glType,
glUploadTarget, static_cast<GLint>(level), box.x, box.y, box.z, box.w,
box.h, box.d, glFormat, glType,
ringStaged ? UnpackRingPixelOffset(stagingBlocks[r].offset)
: static_cast<const void*>(MGB_RECT_SRC_PTR(r, uploadRects[r])));
}
@@ -2051,29 +2013,27 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, imageHeight);
}
const auto box = ToBackendBox(dirtyRegion);
g_GLESFuncs.glTexSubImage3D(
glUploadTarget, static_cast<GLint>(level), box.x, box.y, box.z, box.w, box.h,
box.d, glFormat, glType,
ringStaged ? UnpackRingPixelOffset(stagingBlocks[0].offset)
: static_cast<const void*>(regionPtr));
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), box.x, box.y,
box.z, box.w, box.h, box.d, glFormat, glType,
ringStaged
? UnpackRingPixelOffset(stagingBlocks[0].offset)
: static_cast<const void*>(regionPtr));
if (!ringStaged) {
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
g_GLESFuncs.glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
}
} else {
g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
static_cast<GLsizei>(uploadSize.x()),
static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.z()), glFormat,
glType,
ringStaged
? UnpackRingPixelOffset(stagingBlocks[0].offset)
: uploadData);
g_GLESFuncs.glTexSubImage3D(
glUploadTarget, static_cast<GLint>(level), 0, 0, 0,
static_cast<GLsizei>(uploadSize.x()), static_cast<GLsizei>(uploadSize.y()),
static_cast<GLsizei>(uploadSize.z()), glFormat, glType,
ringStaged ? UnpackRingPixelOffset(stagingBlocks[0].offset) : uploadData);
}
break;
default:
MGLOG_E_ONCE("Unhandled texture target %s",
MG_Util::ConvertTextureTargetToString(MGB_TEXTURE_TARGET(stateTextureObject)).c_str());
MGLOG_E_ONCE("Unhandled texture target %s", MG_Util::ConvertTextureTargetToString(
MGB_TEXTURE_TARGET(stateTextureObject))
.c_str());
break;
}
if (ringStaged) {
@@ -2114,7 +2074,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Pipe::MGPipeHandleIsNull(pushedTexBuffer)) {
MGLOG_E_ONCE("MGPipe: buffer texture %u's descriptor names no backing buffer on "
"the handle arm, so it cannot be backed (handle {%u, %u})",
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), pushedRes.Slot, pushedRes.Gen);
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), pushedRes.Slot,
pushedRes.Gen);
return;
}
}
@@ -2127,8 +2088,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// transport the conjunct is already implied by `pushedStorage != nullptr`, so
// this changes no behaviour and closes no door; it says the thing instead of
// depending on it.
const Bool texBufferByRecord = pushedStorage != nullptr &&
MGB_TEXTURE_HANDLE_ARM_OFF(*this) == false;
const Bool texBufferByRecord = pushedStorage != nullptr && MGB_TEXTURE_HANDLE_ARM_OFF(*this) == false;
static const SharedPtr<MG_State::GLState::BufferObject> kNoFrontendBuffer{};
auto& buffer = texBufferByRecord ? kNoFrontendBuffer
: textureBufferObject->GetBufferBindingSlot().GetBoundObject();
@@ -2147,11 +2107,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo);
// Need to sync texture buffer if not synced yet
auto* backendBufferResource =
BufferImpl::EnsureBufferResourceForHandle(buffer, pushedTexBuffer);
auto* backendBufferResource = BufferImpl::EnsureBufferResourceForHandle(buffer, pushedTexBuffer);
if (!backendBufferResource || backendBufferResource->id == 0) {
MGLOG_E_ONCE("Failed to sync backing buffer for texture buffer with ID: %u",
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
return;
}
@@ -2159,8 +2118,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
auto backendId = backendBufferResource->id;
GLenum glInternalFormat, glType, glFormat;
TextureImpl::GenerateTextureFormatInfo(MGB_STORAGE_FORMAT(textureBufferObject), &glInternalFormat, &glFormat,
&glType, TextureTarget::TextureBuffer);
TextureImpl::GenerateTextureFormatInfo(MGB_STORAGE_FORMAT(textureBufferObject), &glInternalFormat,
&glFormat, &glType, TextureTarget::TextureBuffer);
// The view half of the buffer-image SPLIT. A buffer texture has no storage of its
// own to widen, but the VIEW its format describes can be re-described one
// component at a time over the same bytes - rg32f over N texels is r32f over 2N -
@@ -2228,12 +2187,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer)
: (textureBufferObject->GetBufferRangeOffset() == 0 &&
textureBufferObject->GetBufferRangeSizeInBytes() == buffer->GetSize());
const SizeT rangeSize =
pushedStorage == nullptr
? textureBufferObject->GetBufferRangeSizeInBytes()
: (pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer
? (buffer ? buffer->GetSize() : 0u)
: static_cast<SizeT>(pushedStorage->Desc.BufSize));
const SizeT rangeSize = pushedStorage == nullptr
? textureBufferObject->GetBufferRangeSizeInBytes()
: (pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer
? (buffer ? buffer->GetSize() : 0u)
: static_cast<SizeT>(pushedStorage->Desc.BufSize));
// Through CallTexBuffer/CallTexBufferRange rather than g_GLESFuncs directly:
// the unsuffixed entry points are the ES 3.2 core spelling, and a driver
// whose buffer textures come from EXT/OES_texture_buffer exports the
@@ -2244,15 +2202,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<GLintptr>(rangeOffset),
static_cast<GLsizeiptr>(rangeSize))) {
MGLOG_W_ONCE("Texture buffer %u names a sub-range but the driver has no "
"glTexBufferRange; binding the whole buffer instead",
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
"glTexBufferRange; binding the whole buffer instead",
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
CallTexBuffer(GL_TEXTURE_BUFFER, glInternalFormat, backendId);
}
DebugImpl::ErrorLopper::Loop(
[file = __FILE__, line = __LINE__, func = __func__, glInternalFormat, backendId](GLenum err) {
MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s",
func, file, line, MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(),
backendId, MG_Util::ConvertGLEnumToString(err).c_str());
MGLOG_D("%s(%s:%d) glTexBuffer(format=%s, buffer=%u) ES error: %s", func, file, line,
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(), backendId,
MG_Util::ConvertGLEnumToString(err).c_str());
});
// The image half of the SPLIT, on its own name over the same buffer. Minted
@@ -2281,8 +2239,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// rather than names: leaving it claiming THIS object is bound would
// make the next Bind(GL_TEXTURE_BUFFER) a no-op and leave the split
// view bound in the application texture's place.
g_boundTexturesCache[g_activeTextureUnit][static_cast<SizeT>(
TextureTarget::TextureBuffer)] = nullptr;
g_boundTexturesCache[g_activeTextureUnit]
[static_cast<SizeT>(TextureTarget::TextureBuffer)] = nullptr;
}
}
}
@@ -2293,9 +2251,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// backstop for a state object that grew a new storage kind. Skipping the upload
// renders wrong; throwing unwinds through the C GL ABI and kills the process.
MGLOG_E_ONCE("DirectGLES texture sync: no upload path for storage type %d on texture %u; "
"skipping this sync",
static_cast<int>(MGB_TEXTURE_STORAGE_KIND(pushedStorage, stateTextureObject)),
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
"skipping this sync",
static_cast<int>(MGB_TEXTURE_STORAGE_KIND(pushedStorage, stateTextureObject)),
MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject));
break;
}
@@ -107,8 +107,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// P5e (tx2), CONTRACT-P5E §5.2 / scout G-S2-5. See the declaration.
void BackendTextureObject::RequireImageBindableStorageByHandle(
MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPipeResourceRecord& record) {
void BackendTextureObject::RequireImageBindableStorageByHandle(MG_Pipe::MGPipeHandle res,
const MG_Pipe::MGPipeResourceRecord& record) {
if (m_imageBindableStorageRequired) {
return;
}
@@ -140,11 +140,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(res);
const auto target = BufferImpl::StagedTextureTargetForPipeTarget(record.Desc.Target);
GLenum canonicalFormat = GL_NONE, canonicalType = GL_NONE;
MG_Util::TextureFormatProcessor::NormalizePixelFormat(
MG_Util::ConvertTextureInternalFormatToGLEnum(format), {}, nullptr,
&canonicalFormat, &canonicalType);
for (const auto& uploadTarget :
BufferImpl::StagedUploadTargetsForPipeTarget(record.Desc.Target)) {
MG_Util::TextureFormatProcessor::NormalizePixelFormat(MG_Util::ConvertTextureInternalFormatToGLEnum(format),
{}, nullptr, &canonicalFormat, &canonicalType);
for (const auto& uploadTarget : BufferImpl::StagedUploadTargetsForPipeTarget(record.Desc.Target)) {
for (Uint32 level = 0; level < record.Desc.Levels; ++level) {
const auto targetCode = static_cast<Uint16>(uploadTarget);
const auto levelCode = static_cast<Uint16>(level);
@@ -152,18 +150,23 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) continue;
const auto* pending = FindPipeTextureUpload(record, targetCode, levelCode);
const Bool covered = store.IsCovered(key, targetCode, levelCode);
const MG_Pipe::MGPBox whole{0, 0, 0, static_cast<Uint32>(extent.x()),
static_cast<Uint32>(extent.y()), static_cast<Uint32>(extent.z())};
const MG_Pipe::MGPBox whole{0,
0,
0,
static_cast<Uint32>(extent.x()),
static_cast<Uint32>(extent.y()),
static_cast<Uint32>(extent.z())};
// A genuinely full pending write owns every byte. A
// union box covering the level is not sufficient when
// its explicit rectangles leave GPU-written gaps.
const auto coversWhole = [&](const auto& box) {
return box.X == 0 && box.Y == 0 && box.Z == 0 &&
box.W == whole.W && box.H == whole.H && box.D == whole.D;
return box.X == 0 && box.Y == 0 && box.Z == 0 && box.W == whole.W && box.H == whole.H &&
box.D == whole.D;
};
const Bool pendingWhole = pending && (pending->Regions.empty()
? coversWhole(pending->UnionBox)
: std::any_of(pending->Regions.begin(), pending->Regions.end(), coversWhole));
const Bool pendingWhole =
pending && (pending->Regions.empty()
? coversWhole(pending->UnionBox)
: std::any_of(pending->Regions.begin(), pending->Regions.end(), coversWhole));
// W2-a: ONLY A LEVEL THE DRIVER ACTUALLY HOLDS IS READ BACK. The store's
// Defined-ness follows the client's respecifies, the driver's follows this
// twin's last sync, and the two differ for a level defined (null data, or null
@@ -173,17 +176,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
// is undefined content with nothing to preserve. Reading it anyway was
// Fatal{ResourceUnavailable, "image-promotion-readback"} on legal GL.
const Bool nativeLevel =
m_isInitialized &&
NativeTextureLevelHasExtent(m_backendTextureId, target, uploadTarget,
static_cast<GLint>(level), extent);
m_isInitialized && NativeTextureLevelHasExtent(m_backendTextureId, target, uploadTarget,
static_cast<GLint>(level), extent);
if (nativeLevel && !(covered && pendingWhole)) {
Vector<Uint8> snapshot;
const Bool readBack =
!s_forceReadbackFailure &&
ReadTextureLevelTight(m_backendTextureId, target, uploadTarget, format,
static_cast<GLint>(level), extent,
/*sourceUsesImageCarrier=*/false, canonicalFormat,
canonicalType, snapshot);
!s_forceReadbackFailure && ReadTextureLevelTight(m_backendTextureId, target, uploadTarget,
format, static_cast<GLint>(level), extent,
/*sourceUsesImageCarrier=*/false,
canonicalFormat, canonicalType, snapshot);
if (!readBack && !covered) {
// The GPU holds this level, the driver will not hand it back, and no
// CPU byte of it exists anywhere: the one case with no honest answer.
@@ -216,16 +217,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
std::abort();
}
const SizeT bpp = stagedSize / pixels;
const Uint8* staged = store.RequireLevelBytes(key, targetCode, levelCode, "image-promotion");
const Uint8* staged =
store.RequireLevelBytes(key, targetCode, levelCode, "image-promotion");
const auto merge = [&](Int32 x, Int32 y, Int32 z, Uint32 w, Uint32 h, Uint32 d) {
if (x < 0 || y < 0 || z < 0 || Uint64(x) + w > whole.W ||
Uint64(y) + h > whole.H || Uint64(z) + d > whole.D) {
if (x < 0 || y < 0 || z < 0 || Uint64(x) + w > whole.W || Uint64(y) + h > whole.H ||
Uint64(z) + d > whole.D) {
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"image-promotion-region\"}");
std::abort();
}
for (Uint32 slice = 0; slice < d; ++slice) {
for (Uint32 row = 0; row < h; ++row) {
const SizeT offset = ((static_cast<SizeT>(z + slice) * whole.H + y + row) * whole.W + x) * bpp;
const SizeT offset =
((static_cast<SizeT>(z + slice) * whole.H + y + row) * whole.W + x) * bpp;
Memcpy(snapshot.data() + offset, staged + offset, static_cast<SizeT>(w) * bpp);
}
}
@@ -246,7 +249,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// first allocation can still be image-bindable without
// inventing an upload or consulting the frontend.
if (!store.IsCovered(key, targetCode, levelCode)) continue;
if (!RearmPipeTextureLevelUpload(res,
if (!RearmPipeTextureLevelUpload(
res,
MG_Pipe::MGPipePackSubDataTarget(record.Desc.Target, static_cast<Uint32>(uploadTarget)),
levelCode, whole)) {
MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"image-promotion-pending-capacity\"}");
@@ -293,10 +297,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
static Bool AdoptDriverLevelIntoShadow(GLuint backendTextureId, BackendTextureObject& backendTexture,
const MG_State::GLState::ITextureObject& stateTextureObject,
MG_State::GLState::TextureObjectMipmap& mipmapObject,
TextureUploadTarget uploadTarget, Uint level
,
const MG_Pipe::MGPipeResourceRecord::PendingUpload* pipePending
) {
TextureUploadTarget uploadTarget, Uint level,
const MG_Pipe::MGPipeResourceRecord::PendingUpload* pipePending) {
const IntVec3 texelSize = mipmapObject.GetMipmapTexelSize(uploadTarget, level);
const IntVec3 extent{texelSize.x(), texelSize.y(), std::max(texelSize.z(), 1)};
const SizeT texelCount =
@@ -485,7 +487,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// and image-accessed on another can fall into, and GL wants a fence between those anyway.
static Bool g_inPlaceTextureUploadPending = false;
void NoteInPlaceTextureUpload() { g_inPlaceTextureUploadPending = true; }
void NoteInPlaceTextureUpload() {
g_inPlaceTextureUploadPending = true;
}
GLbitfield TakeInPlaceTextureUploadBarrierBits() {
if (!g_inPlaceTextureUploadPending) return 0;
@@ -616,26 +620,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
const auto viewTarget = BufferImpl::StagedTextureTargetForPipeTarget(record.Desc.Target);
const GLenum nativeTarget = ConvertTextureTargetToBackendGLEnum(viewTarget);
GLenum internalFormat = 0, transferFormat = 0, transferType = 0;
GenerateTextureFormatInfo(static_cast<TextureInternalFormat>(view.InternalFormat),
&internalFormat, &transferFormat, &transferType, viewTarget);
GenerateTextureFormatInfo(static_cast<TextureInternalFormat>(view.InternalFormat), &internalFormat,
&transferFormat, &transferType, viewTarget);
// A name already bound or used as a view cannot be redefined by
// glTextureView. This also invalidates native binding/FBO caches.
RecreateBackendTexture();
DebugImpl::ErrorLopper::Clear();
ResolveTextureViewEntryPoint()(m_backendTextureId, nativeTarget, storageBackendTextureId,
internalFormat, view.MinLevel, view.NumLevels, view.MinLayer, view.NumLayers);
ResolveTextureViewEntryPoint()(m_backendTextureId, nativeTarget, storageBackendTextureId, internalFormat,
view.MinLevel, view.NumLevels, view.MinLayer, view.NumLayers);
const GLenum error = g_GLESFuncs.glGetError();
if (error != GL_NO_ERROR) {
MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"texture-view-native\"} {%u,%u} error=0x%x",
res.Slot, res.Gen, error);
MGLOG_F("MGPipe: Fatal{ResourceUnavailable, \"texture-view-native\"} {%u,%u} error=0x%x", res.Slot,
res.Gen, error);
std::abort();
}
m_viewSourceBackendTextureId = storageBackendTextureId;
m_isInitialized = true;
m_backendStorageImmutable = true;
m_prevTextureInfo = {static_cast<TextureInternalFormat>(record.Desc.InternalFormat),
record.Desc.Width, record.Desc.Height, record.Desc.Depth, view.NumLevels, 0,
record.Desc.Samples, record.Desc.FixedSampleLocations != 0};
record.Desc.Width,
record.Desc.Height,
record.Desc.Depth,
view.NumLevels,
0,
record.Desc.Samples,
record.Desc.FixedSampleLocations != 0};
// The storage's, for the steady arm's reason above (P3b/P4b D3).
m_syncedResourceSerial = storageSerial;
}
@@ -694,8 +703,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (SharedImageImpl::IsYuv(image)) {
// A YUV image is CONVERTED into the name's own RGBA8 level at each frame's first use
// (SharedImageImpl::ConvertYuvForSampling), never made its storage.
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
}
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {}
g_GLESFuncs.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, static_cast<GLsizei>(record.Desc.Width),
static_cast<GLsizei>(record.Desc.Height));
if (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
@@ -743,8 +751,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
boxes.emplace_back(uploadHalf, pending.UnionBox);
} else {
for (const auto& region : pending.Regions)
boxes.emplace_back(uploadHalf, MG_Pipe::MGPBox{region.X, region.Y, region.Z, region.W, region.H,
region.D});
boxes.emplace_back(uploadHalf,
MG_Pipe::MGPBox{region.X, region.Y, region.Z, region.W, region.H, region.D});
}
}
if (m_sharedImage != nullptr && !boxes.empty()) {
@@ -766,10 +774,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
const Uint8* origin =
bytes + (static_cast<SizeT>(box.Y) * static_cast<SizeT>(rowTexels) + static_cast<SizeT>(box.X)) * 4;
bytes +
(static_cast<SizeT>(box.Y) * static_cast<SizeT>(rowTexels) + static_cast<SizeT>(box.X)) * 4;
if (!m_sharedImageUploadsRgb) {
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {
}
while (g_GLESFuncs.glGetError() != GL_NO_ERROR) {}
g_GLESFuncs.glPixelStorei(GL_UNPACK_ROW_LENGTH, rowTexels);
g_GLESFuncs.glTexSubImage2D(GL_TEXTURE_2D, 0, box.X, box.Y, static_cast<GLsizei>(box.W),
static_cast<GLsizei>(box.H), GL_RGBA, GL_UNSIGNED_BYTE, origin);
@@ -797,7 +805,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_I_ONCE("Shared image texture %u: sub-data written into its image (%s texels)",
record.Desc.GlNameForDiag, m_sharedImageUploadsRgb ? "RGB" : "RGBA");
}
for (const auto& [uploadHalf, level] : keys) ConsumePipeTextureUpload(record.Desc.Resource, uploadHalf, level);
for (const auto& [uploadHalf, level] : keys)
ConsumePipeTextureUpload(record.Desc.Resource, uploadHalf, level);
}
#endif
@@ -875,8 +884,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// it is read here and never written back.
if (m_syncedBuiltinSampler == builtin && m_syncedBuiltinSamplerSerial == cso->Serial &&
record->Params.SamplerResync == 0 && !m_forceSamplerResync) {
MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.",
m_backendTextureId);
MGLOG_D("Sampler parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
return nullptr;
}
m_syncedBuiltinSampler = builtin;
@@ -928,7 +936,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
if (!IsSupportedTextureTarget(targetInternal)) {
MGLOG_E_ONCE(" Texture target %s is not supported, skipping.",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
return;
}
@@ -995,8 +1003,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
if (m_cacheSamplerParameters.maxAnisotropy != samplerParams.maxAnisotropy) {
if (g_GLESCapabilities.SupportsTextureFilterAnisotropy) {
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
samplerParams.maxAnisotropy);
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, samplerParams.maxAnisotropy);
}
// Unsupported GLES backends intentionally treat anisotropy as a
// frontend-only no-op; remember the observed value so the cache
@@ -1030,8 +1037,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// written back, while the twin's own flag is cleared here exactly as it was before.
if (m_syncedParamsSerial == record->ParamsSerial && record->Params.ForceResync == 0 &&
!m_forceTextureParamsResync) {
MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.",
m_backendTextureId);
MGLOG_D("Texture parameters have not changed for texture ID: %u, skipping sync.", m_backendTextureId);
return nullptr;
}
m_syncedParamsSerial = record->ParamsSerial;
@@ -1051,18 +1057,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
// FORMAT comes from the descriptor beside them, and the BORDER comes from the built-in
// sampler's CSO (D-F4), because a border colour is sampler state.
#define MGB_TEXPARAM_LEVEL_RANGE \
(pushedRecord != nullptr ? UintVec2{static_cast<Uint>(pushedRecord->Params.BaseLevel), \
(pushedRecord != nullptr ? UintVec2{static_cast<Uint>(pushedRecord->Params.BaseLevel), \
static_cast<Uint>(pushedRecord->Params.MaxLevel)} \
: stateTextureObject->GetLevelRange())
#define MGB_TEXPARAM_FORMAT \
(pushedRecord != nullptr ? static_cast<TextureInternalFormat>(pushedRecord->Desc.InternalFormat) \
(pushedRecord != nullptr ? static_cast<TextureInternalFormat>(pushedRecord->Desc.InternalFormat) \
: stateTextureObject->GetFormat())
#define MGB_TEXPARAM_SWIZZLE \
(pushedRecord != nullptr \
? Vec4<TextureSwizzleParam>{static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[0]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[1]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[2]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[3])} \
? Vec4<TextureSwizzleParam>{static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[0]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[1]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[2]), \
static_cast<TextureSwizzleParam>(pushedRecord->Params.Swizzle[3])} \
: stateTextureObject->GetAllSwizzleParams())
// The contract now spells this encoding (c0c, ID-12 DV-2): kMGPipeDepthStencilModeDepth = 0 =
// GL_DEPTH_COMPONENT, kMGPipeDepthStencilModeStencil = 1 = GL_STENCIL_INDEX. v1 open-coded the
@@ -1074,9 +1080,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
#define MGB_TEXPARAM_DS_MODE \
(pushedRecord != nullptr \
? (pushedRecord->Params.DepthStencilMode == MG_Pipe::kMGPipeDepthStencilModeStencil ? GL_STENCIL_INDEX \
: GL_DEPTH_COMPONENT) \
: GL_DEPTH_COMPONENT) \
: stateTextureObject->GetDepthStencilTextureMode())
#define MGB_TEXPARAM_BORDER_F (pushedBorder != nullptr ? pushedBorder->borderColor : stateTextureObject->GetBorderColor())
#define MGB_TEXPARAM_BORDER_F \
(pushedBorder != nullptr ? pushedBorder->borderColor : stateTextureObject->GetBorderColor())
#define MGB_TEXPARAM_BORDER_I \
(pushedBorder != nullptr ? pushedBorder->borderColorI : stateTextureObject->GetBorderColorI())
#define MGB_TEXPARAM_BORDER_UI \
@@ -1124,8 +1131,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_E_ONCE("MGPipe: texture %u's built-in sampler CSO {%u, %u} has no applier "
"record, so its border colour cannot be pushed",
TextureDiagName(stateTextureObject, pushedRecord),
pushedRecord->Params.BuiltinSampler.Slot,
pushedRecord->Params.BuiltinSampler.Gen);
pushedRecord->Params.BuiltinSampler.Slot, pushedRecord->Params.BuiltinSampler.Gen);
}
MGLOG_D("Syncing texture params with backend ID %u to backend for state ID %u", m_backendTextureId,
@@ -1137,7 +1143,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
if (!IsSupportedTextureTarget(targetInternal)) {
MGLOG_E_ONCE(" Texture target %s is not supported, skipping.",
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
MG_Util::ConvertTextureTargetToString(targetInternal).c_str());
return;
}
@@ -1201,10 +1207,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// channels are undefined. Composed with the application's own swizzle for the same
// reason as the alpha case above: GL_TEXTURE_SWIZZLE names a SOURCE channel of the
// logical texel, so it is the source that is substituted, never the destination.
if (const auto imageWidening =
m_imageBindableStorageRequired
? TextureImpl::GetImageBindableStorageWidening(MGB_TEXPARAM_FORMAT)
: TextureImpl::ImageBindableStorageWidening{}) {
if (const auto imageWidening = m_imageBindableStorageRequired
? TextureImpl::GetImageBindableStorageWidening(MGB_TEXPARAM_FORMAT)
: TextureImpl::ImageBindableStorageWidening{}) {
for (SizeT channel = 0; channel < 4; ++channel) {
switch (swizzleParams[channel]) {
case TextureSwizzleParam::Green:
@@ -1258,13 +1263,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// value nothing then used. The value a skipped block sees is the cache's own, which
// cannot make the condition true.
const auto borderColorForm = borderColorReadable ? MGB_TEXPARAM_BORDER_FORM : m_cacheBorderColorForm;
if (!isMultisampleTarget &&
borderColorReadable &&
g_GLESCapabilities.SupportsTextureBorderClamp &&
(m_cacheBorderColor != MGB_TEXPARAM_BORDER_F ||
m_cacheBorderColorI != MGB_TEXPARAM_BORDER_I ||
m_cacheBorderColorUI != MGB_TEXPARAM_BORDER_UI ||
m_cacheBorderColorForm != borderColorForm)) {
if (!isMultisampleTarget && borderColorReadable && g_GLESCapabilities.SupportsTextureBorderClamp &&
(m_cacheBorderColor != MGB_TEXPARAM_BORDER_F || m_cacheBorderColorI != MGB_TEXPARAM_BORDER_I ||
m_cacheBorderColorUI != MGB_TEXPARAM_BORDER_UI || m_cacheBorderColorForm != borderColorForm)) {
if (borderColorForm == BorderColorForm::Int && g_GLESFuncs.glTexParameterIiv) {
const auto& borderColorI = MGB_TEXPARAM_BORDER_I;
const GLint borderColorArray[4] = {borderColorI.x(), borderColorI.y(), borderColorI.z(),
@@ -1307,8 +1308,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
static_cast<GLint>(depthStencilTextureMode));
m_cacheDepthStencilTextureMode = depthStencilTextureMode;
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
MGLOG_D("%s(%s:%d) ES error %s", func, file, line,
MG_Util::ConvertGLEnumToString(err).c_str());
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
});
}
}
@@ -1343,7 +1343,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Array<Array<BackendTextureObject*, (SizeT)TextureTarget::TextureTargetCount>,
MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS>
g_boundTexturesCache;
TwinRegistry<MG_State::GLState::ITextureObject, BackendTextureObject, MG_Pipe::MGPipeKind::Texture> g_backendTextureObjects;
TwinRegistry<MG_State::GLState::ITextureObject, BackendTextureObject, MG_Pipe::MGPipeKind::Texture>
g_backendTextureObjects;
#if MOBILEGL_BUILD_DISAGGREGATED
void WritePendingSharedImageUploads() {
@@ -1430,9 +1431,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
return store.FollowCopy(MG_Record::StagedTextureStore::KeyForHandle(source), sourceTarget,
static_cast<Uint16>(sourceLevel), sourceOrigin,
MG_Record::StagedTextureStore::KeyForHandle(destination),
destinationTarget, static_cast<Uint16>(destinationLevel), destinationOrigin,
size);
MG_Record::StagedTextureStore::KeyForHandle(destination), destinationTarget,
static_cast<Uint16>(destinationLevel), destinationOrigin, size);
}
} // namespace TextureImpl
@@ -19,9 +19,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
// resident writes next, then the read, the serial stamp and - last - the epoch bump.
const Uint8* RefreshAliasedStoreFromGpu(GLESBufferResource& resource, MG_Pipe::MGPipeHandle res,
const char* site) {
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized ||
resource.pendingRespecify || resource.contextGeneration != g_bufferContextGeneration ||
!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) {
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized || resource.pendingRespecify ||
resource.contextGeneration != g_bufferContextGeneration || !g_GLESFuncs.glMapBufferRange ||
!g_GLESFuncs.glUnmapBuffer) {
MGLOG_E_ONCE("%s: buffer {%u, %u} was written on the GPU but its GL store cannot be read "
"back here, so the CPU reads the shadow as it is",
site, res.Slot, res.Gen);
@@ -39,18 +39,18 @@ namespace MobileGL::MG_Backend::DirectGLES {
FlushPendingRangesFrom(resource, resource.hostBytes, width);
DrainResidentWritesNow(resource);
BindBufferId(TempBufferTarget, resource.id);
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
GL_MAP_READ_BIT);
void* mapped =
g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size), GL_MAP_READ_BIT);
if (mapped == nullptr) {
MGLOG_E_ONCE("%s: glMapBufferRange(read) failed for GPU-written buffer %u; the CPU reads the "
"shadow as it is",
site, resource.id);
return SplitHostBytes(resource);
}
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(
res, 0,
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(mapped), static_cast<Uint64>(size),
MG_Pipe::kMGHostSpanSegNone, 0});
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(res, 0,
MG_Pipe::MGPBlobRef{reinterpret_cast<Uint64>(mapped),
static_cast<Uint64>(size),
MG_Pipe::kMGHostSpanSegNone, 0});
if (resource.hostBytes == nullptr) {
// Every content-carrying record and every respecify of a store with a size hands
// its base over on this arm, so no base here means a shape nothing recorded. Keep
@@ -94,9 +94,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (MG_Config::RecordArmAliasesFrontend()) return RefreshAliasedStoreFromGpu(resource, res, site);
return SplitHostBytes(resource);
}
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized ||
resource.pendingRespecify || resource.contextGeneration != g_bufferContextGeneration ||
!g_GLESFuncs.glMapBufferRange || !g_GLESFuncs.glUnmapBuffer) {
if (!CanTouchGLNow() || resource.id == 0 || !resource.storageInitialized || resource.pendingRespecify ||
resource.contextGeneration != g_bufferContextGeneration || !g_GLESFuncs.glMapBufferRange ||
!g_GLESFuncs.glUnmapBuffer) {
MGLOG_E_ONCE("%s: buffer {%u, %u} was written on the GPU but its GL store cannot be read "
"back here, so the CPU reads the staged copy as it is",
site, res.Slot, res.Gen);
@@ -118,8 +118,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
FlushPendingRangesFrom(resource, resource.hostBytes, width);
DrainResidentWritesNow(resource);
BindBufferId(TempBufferTarget, resource.id);
void* mapped = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size),
GL_MAP_READ_BIT);
void* mapped =
g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast<GLsizeiptr>(size), GL_MAP_READ_BIT);
if (mapped == nullptr) {
MGLOG_E_ONCE("%s: glMapBufferRange(read) failed for GPU-written buffer %u; the CPU reads the "
"staged copy as it is",
@@ -134,8 +134,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
resource.serverGpuWritten = false;
resource.syncedChangeSerial = ResourceSerialOf(res);
BumpBufferMutationEpoch();
MGLOG_D("%s: refreshed the staged copy of GPU-written buffer {%u, %u} (%zu bytes)", site, res.Slot,
res.Gen, size);
MGLOG_D("%s: refreshed the staged copy of GPU-written buffer {%u, %u} (%zu bytes)", site, res.Slot, res.Gen,
size);
return resource.hostBytes;
}
} // namespace BufferImpl
@@ -223,8 +223,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Memcpy(element, &word, sizeof(word));
}
void SwizzleBgraStreamToRgba(const Uint8* sourceBase, SizeT sourceStride, SizeT elementCount,
DataType type, Vector<Uint8>& outData) {
void SwizzleBgraStreamToRgba(const Uint8* sourceBase, SizeT sourceStride, SizeT elementCount, DataType type,
Vector<Uint8>& outData) {
outData.resize(elementCount * 4);
for (SizeT element = 0; element < elementCount; ++element) {
Uint8* destination = outData.data() + element * 4;
@@ -316,8 +316,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendVertexArrayObject::HostSetEnabled(Uint index, Bool enabled) {
const Uint32 bit = 1u << index;
if (VaoShadowOn() && (m_hostEnabledKnownMask & bit) && ((m_hostEnabledMask & bit) != 0) == enabled) return;
if (enabled) g_GLESFuncs.glEnableVertexAttribArray(index);
else g_GLESFuncs.glDisableVertexAttribArray(index);
if (enabled)
g_GLESFuncs.glEnableVertexAttribArray(index);
else
g_GLESFuncs.glDisableVertexAttribArray(index);
m_hostEnabledKnownMask |= bit;
m_hostEnabledMask = enabled ? (m_hostEnabledMask | bit) : (m_hostEnabledMask & ~bit);
}
@@ -333,7 +335,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendVertexArrayObject::HostForgetAll() {
m_hostEnabledKnownMask = 0;
m_hostDivisorKnownMask = 0;
for (auto& pointer : m_hostPointer) pointer.valid = false;
for (auto& pointer : m_hostPointer)
pointer.valid = false;
}
// ES 3.1 core. Queried through the loader rather than the version, because the whole
@@ -345,7 +348,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glVertexBindingDivisor != nullptr;
}
Bool BackendUsesNativeBaseInstance() { return g_GLESCapabilities.SupportsBaseInstance; }
Bool BackendUsesNativeBaseInstance() {
return g_GLESCapabilities.SupportsBaseInstance;
}
// The "+ baseInstance" of GL's instanced-array element index, expressed as a byte shift
// of the array's own offset. Only divisor'd arrays step per instance, so only they move.
@@ -391,8 +396,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (st.VertexBufferCount == 0) return nullptr;
const Uint32 begin = st.VertexBufferStart;
const Uint32 end = begin + st.VertexBufferCount;
if (attributeIndex >= begin && attributeIndex < end &&
attributeIndex < MG_Pipe::kMGPipeMaxVertexAttribs &&
if (attributeIndex >= begin && attributeIndex < end && attributeIndex < MG_Pipe::kMGPipeMaxVertexAttribs &&
st.VertexBuffers[attributeIndex].BindingIndex == attributeIndex) {
return &st.VertexBuffers[attributeIndex];
}
@@ -442,14 +446,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The resolved offset goes on the binding point, not into a relative offset (the
// relative offset is capped by GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, a buffer
// offset is not). BindBufferId is bypassed deliberately.
g_GLESFuncs.glBindVertexBuffer(attribIndex, backendResource->id,
static_cast<GLintptr>(attrib.Offset), 0);
g_GLESFuncs.glBindVertexBuffer(attribIndex, backendResource->id, static_cast<GLintptr>(attrib.Offset), 0);
return true;
}
// The record the applier holds for the bound vertex-elements CSO, or null.
const MG_Pipe::MGPipeVertexElementsRecord* BoundVertexElementsRecord(
const MG_Pipe::MGPipeApplierState& st) {
const MG_Pipe::MGPipeVertexElementsRecord* BoundVertexElementsRecord(const MG_Pipe::MGPipeApplierState& st) {
const MG_Pipe::MGPipeHandle cso = st.BoundVertexElements;
if (MG_Pipe::MGPipeHandleIsNull(cso)) return nullptr;
if (cso.Slot >= st.VertexElementsCsos.size()) return nullptr;
@@ -541,8 +543,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Emulation is server-owned: the client sends the draw's RAW base instance and never
// learns the answer. Applied here as well as in the applier so the decision is the
// same whichever side resolved it first - it is idempotent.
const Uint32 fetchBaseInstance =
BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
const Uint32 fetchBaseInstance = BackendUsesNativeBaseInstance() ? 0u : st.VertexFetchBaseInstance;
const Bool baseInstanceDirty = m_syncedFetchBaseInstance != fetchBaseInstance;
const Bool emitAttributes = attributesDirty || baseInstanceDirty || m_hasConvertedAttribute;
if (!emitAttributes && !indexBufferDirty) {
@@ -556,8 +557,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// path: the fp64, zero-stride, BGRA and no-buffer branches toggle enables or bind
// through other state, and the client-memory upload rewrites the VAO behind this
// gate, so any of them sends the next sync through the whole walk again.
const Bool buffersOnly = emitAttributes && !elementsDirty && m_lastWalkWasPlain &&
!m_hasConvertedAttribute;
const Bool buffersOnly = emitAttributes && !elementsDirty && m_lastWalkWasPlain && !m_hasConvertedAttribute;
Bool walkWasPlain = true;
m_hasConvertedAttribute = false;
// Rebuilt by every walk; a buffers-only one follows a plain walk, which set none.
@@ -565,7 +565,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Bind();
if (m_hostPointerBufferGeneration != currentBufferIdGeneration) {
for (auto& pointer : m_hostPointer) pointer.valid = false;
for (auto& pointer : m_hostPointer)
pointer.valid = false;
m_hostPointerBufferGeneration = currentBufferIdGeneration;
}
@@ -585,8 +586,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
++attribIndex) {
const MGPVertexAttribWire& attrib = rec->Attributes[attribIndex];
if (buffersOnly && !attrib.Enabled) continue;
const MG_Pipe::MGPVertexBuffer* binding =
VertexBufferForAttributeIndex(st, attribIndex);
const MG_Pipe::MGPVertexBuffer* binding = VertexBufferForAttributeIndex(st, attribIndex);
const Uint32 divisor = binding != nullptr ? binding->Divisor : 0u;
// The enable/disable block. On this arm there is no per-attribute version to
@@ -680,13 +680,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The pointer the driver VAO already holds for this attribute - the buffer it
// captured included - needs no glBindBuffer + pointer call.
auto& shadowPointer = m_hostPointer[attribIndex];
const Bool pointerHeld = VaoShadowOn() && shadowPointer.valid &&
shadowPointer.bufferId == attribBuffer->id && shadowPointer.size == glSize &&
shadowPointer.type == static_cast<Uint32>(glType) &&
shadowPointer.stride == static_cast<Int32>(attrib.Stride) &&
shadowPointer.offset == fetchOffset &&
shadowPointer.normalized == (attrib.Normalized != 0) &&
shadowPointer.integer == (attrib.IsInteger != 0);
const Bool pointerHeld =
VaoShadowOn() && shadowPointer.valid && shadowPointer.bufferId == attribBuffer->id &&
shadowPointer.size == glSize && shadowPointer.type == static_cast<Uint32>(glType) &&
shadowPointer.stride == static_cast<Int32>(attrib.Stride) && shadowPointer.offset == fetchOffset &&
shadowPointer.normalized == (attrib.Normalized != 0) &&
shadowPointer.integer == (attrib.IsInteger != 0);
if (!pointerHeld) {
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, attribBuffer->id);
if (!attrib.IsInteger) {
@@ -697,9 +696,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glVertexAttribIPointer(attribIndex, glSize, glType, attrib.Stride,
(const void*)fetchOffset);
}
shadowPointer = {attribBuffer->id, glSize, static_cast<Uint32>(glType),
static_cast<Int32>(attrib.Stride), fetchOffset, attrib.Normalized != 0,
attrib.IsInteger != 0, true};
shadowPointer = {attribBuffer->id,
glSize,
static_cast<Uint32>(glType),
static_cast<Int32>(attrib.Stride),
fetchOffset,
attrib.Normalized != 0,
attrib.IsInteger != 0,
true};
}
HostSetDivisor(attribIndex, divisor);
@@ -745,8 +749,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_hasSyncedElements = true;
// The window this walk emitted, for AdoptUnchangedBufferSets.
m_syncedBufferStart = st.VertexBufferStart;
m_syncedBufferCount = std::min<Uint32>(st.VertexBufferCount,
MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart);
m_syncedBufferCount =
std::min<Uint32>(st.VertexBufferCount, MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart);
m_syncedBufferBaseInstance = st.VertexFetchBaseInstance;
for (Uint32 i = 0; i < m_syncedBufferCount; ++i)
m_syncedBuffers[i] = st.VertexBuffers[m_syncedBufferStart + i];
@@ -763,24 +767,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
void BackendVertexArrayObject::AdoptUnchangedBufferSets(const MG_Pipe::MGPipeApplierState& st) {
if (!m_hasSyncedElements || m_syncedContextSerial != st.ContextSerial) return;
if (m_syncedBuffersValid && m_syncedVertexBuffersSerial != st.VertexBuffersSerial &&
st.VertexBufferStart == m_syncedBufferStart && st.VertexFetchBaseInstance == m_syncedBufferBaseInstance &&
st.VertexBufferStart == m_syncedBufferStart &&
st.VertexFetchBaseInstance == m_syncedBufferBaseInstance &&
std::min<Uint32>(st.VertexBufferCount, MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart) ==
m_syncedBufferCount) {
Bool same = true;
for (Uint32 i = 0; same && i < m_syncedBufferCount; ++i) {
const auto& a = m_syncedBuffers[i];
const auto& b = st.VertexBuffers[m_syncedBufferStart + i];
same = a.Res == b.Res && a.Offset == b.Offset && a.Stride == b.Stride &&
a.Divisor == b.Divisor && a.BindingIndex == b.BindingIndex;
same = a.Res == b.Res && a.Offset == b.Offset && a.Stride == b.Stride && a.Divisor == b.Divisor &&
a.BindingIndex == b.BindingIndex;
}
if (same) {
if (m_resolvedDrawBuffers.valid && m_resolvedDrawBuffers.buffersSerial == m_syncedVertexBuffersSerial)
if (m_resolvedDrawBuffers.valid &&
m_resolvedDrawBuffers.buffersSerial == m_syncedVertexBuffersSerial)
m_resolvedDrawBuffers.buffersSerial = st.VertexBuffersSerial;
m_syncedVertexBuffersSerial = st.VertexBuffersSerial;
}
}
if (m_syncedIndexValid && m_syncedIndexSerial != st.IndexBufferSerial && m_syncedIndexRes == st.IndexBuffer.Res) {
if (m_resolvedDrawBuffers.iboSerial == m_syncedIndexSerial) m_resolvedDrawBuffers.iboSerial = st.IndexBufferSerial;
if (m_syncedIndexValid && m_syncedIndexSerial != st.IndexBufferSerial &&
m_syncedIndexRes == st.IndexBuffer.Res) {
if (m_resolvedDrawBuffers.iboSerial == m_syncedIndexSerial)
m_resolvedDrawBuffers.iboSerial = st.IndexBufferSerial;
m_syncedIndexSerial = st.IndexBufferSerial;
}
}
@@ -841,8 +849,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
NarrowDoubleStreamToFloat32(sourceBase, sourceStride, componentCount, elementCount, converted);
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, bufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER,
static_cast<GLsizeiptr>(converted.size() * sizeof(Float)),
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(converted.size() * sizeof(Float)),
converted.data(), GL_STREAM_DRAW);
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
@@ -850,15 +857,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// GL ignores `normalized` for floating-point array types, so it is not
// forwarded here either.
g_GLESFuncs.glVertexAttribPointer(attribIndex, attrib.Size, GL_FLOAT, GL_FALSE,
static_cast<GLsizei>(componentCount * sizeof(Float)),
// The converted stream is tightly packed, so a divisor'd array's
// baseInstance shift is a whole number of CONVERTED elements -
// not BaseInstanceByteShift's source-stride arithmetic.
reinterpret_cast<const void*>(
attrib.Divisor == 0 ? 0u : static_cast<SizeT>(fetchBaseInstance) *
componentCount *
sizeof(Float)));
g_GLESFuncs.glVertexAttribPointer(
attribIndex, attrib.Size, GL_FLOAT, GL_FALSE,
static_cast<GLsizei>(componentCount * sizeof(Float)),
// The converted stream is tightly packed, so a divisor'd array's
// baseInstance shift is a whole number of CONVERTED elements -
// not BaseInstanceByteShift's source-stride arithmetic.
reinterpret_cast<const void*>(attrib.Divisor == 0 ? 0u
: static_cast<SizeT>(fetchBaseInstance) *
componentCount * sizeof(Float)));
// glVertexAttribPointer's final argument IS that shift. A client array
// declared through the pointer API keeps element 0 at the buffer's byte 0, so
// the shift rides in the pointer argument the call above passes.
@@ -899,8 +906,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
BufferImpl::BindBufferId(GL_ARRAY_BUFFER, bufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize),
bgra ? static_cast<const void*>(swizzled.data()) : clientData,
GL_STREAM_DRAW);
bgra ? static_cast<const void*>(swizzled.data()) : clientData, GL_STREAM_DRAW);
if (MG_Util::PipeStats::Enabled()) {
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient,
static_cast<Uint64>(uploadSize));
@@ -916,20 +922,20 @@ namespace MobileGL::MG_Backend::DirectGLES {
// the same arithmetic SyncToBackend applies to a VBO-backed array.
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance)));
} else {
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
static_cast<GLsizei>(stride),
// See the pointer call above: the divisor'd
// arrays' baseInstance shift is the pointer.
reinterpret_cast<const void*>(
BaseInstanceByteShift(attrib, fetchBaseInstance)));
g_GLESFuncs.glVertexAttribIPointer(
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
static_cast<GLsizei>(stride),
// See the pointer call above: the divisor'd
// arrays' baseInstance shift is the pointer.
reinterpret_cast<const void*>(BaseInstanceByteShift(attrib, fetchBaseInstance)));
}
}
}
Bool BackendVertexArrayObject::SyncConvertedAttributeByHandle(
Uint attribIndex, const MGPVertexAttribWire& attrib, const MG_Pipe::MGPVertexBuffer& binding,
Uint32 fetchBaseInstance) {
Bool BackendVertexArrayObject::SyncConvertedAttributeByHandle(Uint attribIndex,
const MGPVertexAttribWire& attrib,
const MG_Pipe::MGPVertexBuffer& binding,
Uint32 fetchBaseInstance) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -989,16 +995,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// recorded shadow base for such a resource read freed memory; hostBytes is nulled
// at adoption for the same reason, so the fallback below is a null and a refusal
// rather than a stale read.
const Bool sourceGpuWritten =
resource->serverGpuWritten;
const Bool sourceGpuWritten = resource->serverGpuWritten;
if (sourceGpuWritten) {
BufferImpl::SplitHostBytesForCpuRead(*resource, binding.Res,
bgra ? "bgra_vertex_swizzle" : "fp64_vertex_narrowing");
}
const Uint8* const sourceBase =
(resource->persistentMapped && resource->persistentPtr != nullptr)
? static_cast<const Uint8*>(resource->persistentPtr)
: resource->hostBytes;
const Uint8* const sourceBase = (resource->persistentMapped && resource->persistentPtr != nullptr)
? static_cast<const Uint8*>(resource->persistentPtr)
: resource->hostBytes;
const SizeT sourceSize = BufferImpl::ResourceWidthForHandle(binding.Res);
if (sourceBase == nullptr || attrib.Offset >= sourceSize) {
return false;
@@ -1035,13 +1039,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
// no serial on either side can prove the converted copy is current. The conversion
// itself is part of the key: one attribute index can carry both shapes over its life,
// over the same bytes.
const Bool memoHit = stream.valid && convertedBufferId != 0 && !resource->persistentMapped &&
!sourceGpuWritten && stream.sourceHandle == binding.Res &&
stream.sourceChangeSerial == sourceSerial &&
stream.sourceOffset == static_cast<SizeT>(attrib.Offset) &&
stream.sourceStride == sourceStride && stream.bgra == bgra &&
stream.sourceType == attrib.Type && stream.componentCount == componentCount &&
stream.elementCount == elementCount;
const Bool memoHit =
stream.valid && convertedBufferId != 0 && !resource->persistentMapped && !sourceGpuWritten &&
stream.sourceHandle == binding.Res && stream.sourceChangeSerial == sourceSerial &&
stream.sourceOffset == static_cast<SizeT>(attrib.Offset) && stream.sourceStride == sourceStride &&
stream.bgra == bgra && stream.sourceType == attrib.Type && stream.componentCount == componentCount &&
stream.elementCount == elementCount;
if (!memoHit) {
if (convertedBufferId == 0) {
g_GLESFuncs.glGenBuffers(1, &convertedBufferId);
@@ -1062,8 +1065,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
convertedData = swizzled.data();
convertedBytes = swizzled.size();
} else {
NarrowDoubleStreamToFloat32(sourceBase + attrib.Offset, sourceStride, componentCount,
elementCount, narrowed);
NarrowDoubleStreamToFloat32(sourceBase + attrib.Offset, sourceStride, componentCount, elementCount,
narrowed);
convertedData = narrowed.data();
convertedBytes = narrowed.size() * sizeof(Float);
}
@@ -1113,7 +1116,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject, MG_Pipe::MGPipeKind::VertexElementsCso>
TwinRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject,
MG_Pipe::MGPipeKind::VertexElementsCso>
g_backendVertexArrayObjects;
} // namespace VertexArrayImpl
} // namespace MobileGL::MG_Backend::DirectGLES
+71 -53
View File
@@ -25,10 +25,14 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
SizeT IndexTypeSize(GLenum type) {
switch (type) {
case GL_UNSIGNED_BYTE: return 1;
case GL_UNSIGNED_SHORT: return 2;
case GL_UNSIGNED_INT: return 4;
default: return 0;
case GL_UNSIGNED_BYTE:
return 1;
case GL_UNSIGNED_SHORT:
return 2;
case GL_UNSIGNED_INT:
return 4;
default:
return 0;
}
}
@@ -60,12 +64,18 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// first) and for GL_PATCHES, whose primitive size is dynamic tessellation state.
Uint32 ConcatenablePrimitiveSize(GLenum mode) {
switch (mode) {
case GL_POINTS: return 1;
case GL_LINES: return 2;
case GL_TRIANGLES: return 3;
case GL_LINES_ADJACENCY: return 4;
case GL_TRIANGLES_ADJACENCY: return 6;
default: return 0;
case GL_POINTS:
return 1;
case GL_LINES:
return 2;
case GL_TRIANGLES:
return 3;
case GL_LINES_ADJACENCY:
return 4;
case GL_TRIANGLES_ADJACENCY:
return 6;
default:
return 0;
}
}
@@ -112,10 +122,10 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// value is exactly the work its original call site did, in its original order, so the
// monolith arm makes the same calls in the same sequence it made before this package.
enum class IndexBufferQuestion {
Presence, // is an element array buffer bound at all
DriverName, // + the GL name currently bound to GL_ELEMENT_ARRAY_BUFFER
DriverNameAndSize,// + the store's size in bytes (the compute tier's source)
HostBytes, // a CPU-readable copy of the whole store, and its size
Presence, // is an element array buffer bound at all
DriverName, // + the GL name currently bound to GL_ELEMENT_ARRAY_BUFFER
DriverNameAndSize, // + the store's size in bytes (the compute tier's source)
HostBytes, // a CPU-readable copy of the whole store, and its size
};
struct BoundIndexBufferView {
@@ -150,8 +160,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// the other buffer target: a missing record on the handle arm aborts by name and never
// falls back to the frontend binding slot, which answers for whatever the CLIENT has
// bound NOW - a later verb than the one being applied.
[[noreturn]] void RefuseMissingIndirectBufferRecord(const char* entry,
MG_Pipe::MGPipeHandle res) {
[[noreturn]] void RefuseMissingIndirectBufferRecord(const char* entry, MG_Pipe::MGPipeHandle res) {
MGLOG_F("MGPipe: Fatal{RoleViolation, \"multidraw-indirect-buffer-arm\"} - %s found no "
"backend resource for the indirect buffer {%u, %u} this verb's record named. "
"The indirect buffer of this family is MGPipeApplier().VerbIndirectBuffer "
@@ -167,8 +176,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// server shadow is a missing record and Fatal by name; for one the application ORPHANED
// the gap is its own undefined content, and reading the shadow's zero-fill is exactly
// what the monolith arm's MappedData() hands back.
Bool IndexBufferRecordHasDefinedContent(const MG_Pipe::MGPipeApplierState& st,
MG_Pipe::MGPipeHandle res) {
Bool IndexBufferRecordHasDefinedContent(const MG_Pipe::MGPipeApplierState& st, MG_Pipe::MGPipeHandle res) {
if (res.Slot >= st.Resources.size()) return false;
const auto& candidate = st.Resources[res.Slot];
if (!candidate.Live || candidate.Gen != res.Gen) return false;
@@ -323,7 +331,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
BufferImpl::BindBufferId(BufferImpl::TempBufferTarget, buffer.id);
// Grow in powers of two so a batch that creeps up in size stops respecifying.
SizeT capacity = buffer.capacity == 0 ? bytes : buffer.capacity;
while (capacity < bytes) capacity *= 2;
while (capacity < bytes)
capacity *= 2;
g_GLESFuncs.glBufferData(BufferImpl::TempBufferTarget, static_cast<GLsizeiptr>(capacity), nullptr,
GL_STREAM_DRAW);
buffer.capacity = capacity;
@@ -357,7 +366,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
const SizeT aligned = (bytes + kRingAlignment - 1) & ~(kRingAlignment - 1);
if (buffer.capacity < aligned) {
SizeT capacity = buffer.capacity == 0 ? kMinRingBytes : buffer.capacity;
while (capacity < aligned) capacity *= 2;
while (capacity < aligned)
capacity *= 2;
g_GLESFuncs.glBufferData(BufferImpl::TempBufferTarget, static_cast<GLsizeiptr>(capacity), nullptr,
GL_STREAM_DRAW);
buffer.capacity = capacity;
@@ -422,9 +432,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
void ResolveTierOnce() {
if (g_tierResolved) return;
g_tierResolved = true;
g_resolvedTier =
ResolveTier(g_GLESCapabilities, g_GLESFuncs, MG_Config::Features.EsprytMultiDrawMode,
&g_tierResolution);
g_resolvedTier = ResolveTier(g_GLESCapabilities, g_GLESFuncs, MG_Config::Features.EsprytMultiDrawMode,
&g_tierResolution);
MGLOG_D("DirectGLES multi-draw: %s", g_tierResolution.c_str());
}
@@ -447,8 +456,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// the batch's own shape - not the driver - rules it out; the compute tier keeps
// its remaining feasibility checks inside its implementation, where the data it
// has to walk is already in hand.
GLESMultiDrawMode ResolveTierForBatch(Bool programReadsDrawID, Bool perSubDrawBaseVertex,
Bool hasIndexBuffer, Bool arbitraryRestart) {
GLESMultiDrawMode ResolveTierForBatch(Bool programReadsDrawID, Bool perSubDrawBaseVertex, Bool hasIndexBuffer,
Bool arbitraryRestart) {
ResolveTierOnce();
GLESMultiDrawMode tier = g_resolvedTier;
@@ -480,8 +489,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// The indirect tiers describe each sub-draw as an element offset into the
// bound element array buffer. A client-memory index array has no such buffer,
// and indirect draws are not defined without one.
if (!hasIndexBuffer &&
(tier == GLESMultiDrawMode::MultiIndirect || tier == GLESMultiDrawMode::Indirect)) {
if (!hasIndexBuffer && (tier == GLESMultiDrawMode::MultiIndirect || tier == GLESMultiDrawMode::Indirect)) {
tier = SupportsTier(GLESMultiDrawMode::BaseVertex) ? GLESMultiDrawMode::BaseVertex
: GLESMultiDrawMode::DrawElements;
}
@@ -503,14 +511,18 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
for (SizeT i = 0; i < sourceIndexCount; ++i) {
Uint32 value = 0;
switch (indexSize) {
case 1: value = source[i]; break;
case 1:
value = source[i];
break;
case 2: {
Uint16 narrow = 0;
std::memcpy(&narrow, source + i * 2, sizeof(narrow));
value = narrow;
break;
}
default: std::memcpy(&value, source + i * 4, sizeof(value)); break;
default:
std::memcpy(&value, source + i * 4, sizeof(value));
break;
}
// Unsigned wraparound is the defined behaviour for a negative base vertex.
out[i] = (restartActive && value == restartSentinel) ? 0xFFFFFFFFu : value + baseVertexBits;
@@ -525,9 +537,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// P5e (mv): `hasIndexBuffer` is a Bool and not the frontend object it used to be,
// because presence is the only thing this function ever asked of it - and on the record
// arm there is no such object on this side to hand it.
const Uint8* ResolveSubDrawIndices(Bool hasIndexBuffer, const Uint8* indexBufferBytes,
SizeT indexBufferSize, const void* indices, SizeT indexCount,
SizeT indexSize) {
const Uint8* ResolveSubDrawIndices(Bool hasIndexBuffer, const Uint8* indexBufferBytes, SizeT indexBufferSize,
const void* indices, SizeT indexCount, SizeT indexSize) {
if (!hasIndexBuffer) {
return static_cast<const Uint8*>(indices);
}
@@ -610,7 +621,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
for (GLsizei i = 0; i < drawcount; ++i) {
if (feedDrawID) SetCurrentDrawID(static_cast<Uint32>(i));
if (feedBaseVertex) SetCurrentBaseVertex(basevertex ? basevertex[i] : 0);
const SizeT commandOffset = commandBase + static_cast<SizeT>(i) * sizeof(DrawElementsIndirectCommand);
const SizeT commandOffset =
commandBase + static_cast<SizeT>(i) * sizeof(DrawElementsIndirectCommand);
ForEachViewportRoutingPass([&] {
g_GLESFuncs.glDrawElementsIndirect(mode, type, reinterpret_cast<const void*>(commandOffset));
});
@@ -650,8 +662,7 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
// ---------------------------------------------------------------------------
Bool RunRebasedDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID,
Bool feedBaseVertex) {
GLsizei drawcount, const GLint* basevertex, Bool feedDrawID, Bool feedBaseVertex) {
const SizeT indexSize = IndexTypeSize(type);
if (indexSize == 0) return false;
@@ -688,8 +699,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl {
indices[i], subDrawCount, indexSize);
if (!source) {
MGLOG_E_ONCE("DirectGLES multi-draw (drawelements tier): sub-draw %d reads outside the bound index "
"buffer; skipping the batch",
i);
"buffer; skipping the batch",
i);
return false;
}
RebaseIndices(source, subDrawCount, indexSize, basevertex ? basevertex[i] : 0, restartActive,
@@ -807,7 +818,8 @@ void main() {
if (status != GL_TRUE) {
char log[1024] = {};
g_GLESFuncs.glGetShaderInfoLog(shader, sizeof(log) - 1, nullptr, log);
MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): index-flattening shader failed to compile: %s", log);
MGLOG_E_ONCE("DirectGLES multi-draw (compute tier): index-flattening shader failed to compile: %s",
log);
g_GLESFuncs.glDeleteShader(shader);
return false;
}
@@ -1016,13 +1028,20 @@ void main() {
const char* TierName(GLESMultiDrawMode tier) {
switch (tier) {
case GLESMultiDrawMode::Auto: return "auto";
case GLESMultiDrawMode::Ext: return "ext";
case GLESMultiDrawMode::MultiIndirect: return "multiindirect";
case GLESMultiDrawMode::Indirect: return "indirect";
case GLESMultiDrawMode::BaseVertex: return "basevertex";
case GLESMultiDrawMode::DrawElements: return "drawelements";
case GLESMultiDrawMode::Compute: return "compute";
case GLESMultiDrawMode::Auto:
return "auto";
case GLESMultiDrawMode::Ext:
return "ext";
case GLESMultiDrawMode::MultiIndirect:
return "multiindirect";
case GLESMultiDrawMode::Indirect:
return "indirect";
case GLESMultiDrawMode::BaseVertex:
return "basevertex";
case GLESMultiDrawMode::DrawElements:
return "drawelements";
case GLESMultiDrawMode::Compute:
return "compute";
}
return "unknown";
}
@@ -1061,8 +1080,7 @@ void main() {
const Bool arbitraryRestart = restartKind == RestartSubstitutionKind::RewriteIndices;
const ScopedSuppressedPrimitiveRestart restartCapOverride(restartKind);
const Bool hasIndexBuffer =
ResolveBoundIndexBuffer(IndexBufferQuestion::Presence, "DrawElementsBatch").Present;
const Bool hasIndexBuffer = ResolveBoundIndexBuffer(IndexBufferQuestion::Presence, "DrawElementsBatch").Present;
// The compute tier dispatches BEFORE the draw state is established: doing it
// afterwards would mean unpicking the program, SSBO and index bindings
@@ -1116,8 +1134,8 @@ void main() {
drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
break;
case GLESMultiDrawMode::DrawElements:
drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID,
feedBaseVertex);
drawn =
RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
break;
case GLESMultiDrawMode::Compute:
// Its pre-pass ran above; reaching here means it declined this batch's shape.
@@ -1136,13 +1154,13 @@ void main() {
drawn = RunBaseVertexLoop(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
}
if (!drawn) {
drawn = RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID,
feedBaseVertex);
drawn =
RunRebasedDrawElements(mode, count, type, indices, drawcount, basevertex, feedDrawID, feedBaseVertex);
}
if (!drawn) {
MGLOG_E_ONCE("DirectGLES multi-draw: no usable tier for a %d sub-draw batch (mode 0x%x, type 0x%x); "
"the batch was dropped",
drawcount, mode, type);
"the batch was dropped",
drawcount, mode, type);
}
}
} // namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl
+30 -50
View File
@@ -70,7 +70,6 @@
// slot allocator (the link ratchet's P13 exit gate pins that).
namespace MobileGL::MG_Backend::DirectGLES {
// Declared in Managers.h as well; repeated here because this header is included from it
// before that declaration, and the table below is the arming site on this arm (D13: "the
// arming site moves to the slot table's first insertion").
@@ -218,7 +217,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<SharedPtr<Table>> Snapshot() const {
Vector<SharedPtr<Table>> all;
all.reserve(m_buckets.size());
for (const auto& bucket : m_buckets) all.push_back(bucket.second);
for (const auto& bucket : m_buckets)
all.push_back(bucket.second);
return all;
}
@@ -271,7 +271,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_buckets.clear();
m_memoKey = Key{};
m_memoTable = nullptr;
for (const SharedPtr<Table>& table : all) table->Reset();
for (const SharedPtr<Table>& table : all)
table->Reset();
}
// ---- the calling thread's bucket, member for member ---------------------------------
@@ -282,12 +283,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// - unchanged source - answer from the right context's object namespace. A caller that
// must name a bucket OTHER than its own (a death walk, a case) takes Shared()/Snapshot()
// and calls the table directly.
BackendPtr& GetOrCreate(MG_Pipe::MGPipeHandle handle) {
return ForCallingThread().GetOrCreate(handle);
}
BackendPtr* FindByHandle(MG_Pipe::MGPipeHandle handle) {
return ForCallingThread().FindByHandle(handle);
}
BackendPtr& GetOrCreate(MG_Pipe::MGPipeHandle handle) { return ForCallingThread().GetOrCreate(handle); }
BackendPtr* FindByHandle(MG_Pipe::MGPipeHandle handle) { return ForCallingThread().FindByHandle(handle); }
const BackendPtr* FindByHandle(MG_Pipe::MGPipeHandle handle) const {
return ForCallingThread().FindByHandle(handle);
}
@@ -321,17 +318,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
// The process-wide answer for the two statics: a notice is about an OBJECT, so it must
// reach the twin in EVERY bucket of EVERY holder. They forward to the table's own
// statics, which walk the holder list - each SHARED holder of it is one bucket.
static Bool ReleaseTwinByHandle(MG_Pipe::MGPipeHandle handle) {
return Table::ReleaseTwinByHandle(handle);
}
static Bool ReleaseTwinByHandle(MG_Pipe::MGPipeHandle handle) { return Table::ReleaseTwinByHandle(handle); }
// The instance spelling, and it HANDS THE TWIN OUT rather than answering a Bool: the two
// tables that ARE a bucket registry (buffers, sampler views) have always been called as
// `g_backendBufferResources.ReleaseByHandle(res)` and their callers still have to decide
// what happens to the driver id the twin owns. The bucket is the CALLING THREAD'S, which
// is the bucket that twin was created in.
BackendPtr ReleaseByHandle(MG_Pipe::MGPipeHandle handle) {
return ForCallingThread().ReleaseByHandle(handle);
}
BackendPtr ReleaseByHandle(MG_Pipe::MGPipeHandle handle) { return ForCallingThread().ReleaseByHandle(handle); }
static Uint32 HolderCount() { return Table::HolderCount(); }
Uint32 LiveCount() const { return ForCallingThread().LiveCount(); }
Uint32 CompositeLiveCount() const { return ForCallingThread().CompositeLiveCount(); }
@@ -405,18 +398,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// registry) is a holder for exactly as long as it exists. Copy and move carry the
// ENTRIES; the links are the table's own and are never copied.
BackendSlotTable() { LinkHolder(); }
BackendSlotTable(const BackendSlotTable& other):
m_ownerSession(other.m_ownerSession),
m_slots(other.m_slots),
m_band(other.m_band),
m_nullTwin(other.m_nullTwin) {
BackendSlotTable(const BackendSlotTable& other)
: m_ownerSession(other.m_ownerSession), m_slots(other.m_slots), m_band(other.m_band),
m_nullTwin(other.m_nullTwin) {
LinkHolder();
}
BackendSlotTable(BackendSlotTable&& other) noexcept:
m_ownerSession(other.m_ownerSession),
m_slots(std::move(other.m_slots)),
m_band(std::move(other.m_band)),
m_nullTwin(std::move(other.m_nullTwin)) {
BackendSlotTable(BackendSlotTable&& other) noexcept
: m_ownerSession(other.m_ownerSession), m_slots(std::move(other.m_slots)), m_band(std::move(other.m_band)),
m_nullTwin(std::move(other.m_nullTwin)) {
other.m_slots.clear();
other.m_band.clear();
LinkHolder();
@@ -457,8 +446,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// releases the twin by handle, and the slot is freed by the CLIENT after that returns.
//
BackendPtr& GetOrCreate(MG_Pipe::MGPipeHandle handle) {
MOBILEGL_ASSERT(!MG_Pipe::MGPipeHandleIsNull(handle),
"GetOrCreate(handle) named the reserved null handle");
MOBILEGL_ASSERT(!MG_Pipe::MGPipeHandleIsNull(handle), "GetOrCreate(handle) named the reserved null handle");
if (MG_Pipe::MGPipeHandleIsNull(handle)) return m_nullTwin;
// A slot index that ARRIVED in a payload indexes a vector this call would RESIZE,
@@ -479,8 +467,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
"GetOrCreate(handle) named slot %u, past this table's %u bound",
handle.Slot, kMaxHandleSlot);
#else
MOBILEGL_ASSERT(false, "GetOrCreate(handle) named slot %u, past this table's %u bound",
handle.Slot, kMaxHandleSlot);
MOBILEGL_ASSERT(false, "GetOrCreate(handle) named slot %u, past this table's %u bound", handle.Slot,
kMaxHandleSlot);
return m_nullTwin;
#endif
}
@@ -489,15 +477,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
EnsureProcessTeardownSentinel();
// THE TWO DIRECTIONS ARE NOT SYMMETRIC. The handle arrived in a payload, so
// `handle.Gen < entry.Gen` is a reachable input, and adopting it would destroy the INCUMBENT LIVE twin - a driver buffer id,
// a persistent map, a pooled store, released by a defaulted destructor that issues
// no glDeleteBuffers and no pool enrolment - and then stamp the slot back to the
// dead resource's generation, after which the incumbent's own FindByHandle refuses
// it and it is silently handed a fresh, empty twin. That is a leak AND a resource
// that loses its storage with no diagnostic, i.e. the shape commit d7655247 fixed
// and the thing MGPipeHandle::Gen exists to prevent. So: forward is a recycle and
// resets the twin, BACKWARD is refused - which is the same answer FindByHandle
// below already gives the same input.
// `handle.Gen < entry.Gen` is a reachable input, and adopting it would destroy the INCUMBENT LIVE twin - a
// driver buffer id, a persistent map, a pooled store, released by a defaulted destructor that issues no
// glDeleteBuffers and no pool enrolment - and then stamp the slot back to the dead resource's generation,
// after which the incumbent's own FindByHandle refuses it and it is silently handed a fresh, empty twin.
// That is a leak AND a resource that loses its storage with no diagnostic, i.e. the shape commit d7655247
// fixed and the thing MGPipeHandle::Gen exists to prevent. So: forward is a recycle and resets the twin,
// BACKWARD is refused - which is the same answer FindByHandle below already gives the same input.
Entry& entry = EntryAt(handle.Slot);
if (entry.Live && entry.Gen > handle.Gen) {
#if MOBILEGL_BUILD_DISAGGREGATED
@@ -599,8 +585,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// list; nothing on a shipping path asks.
static Uint32 HolderCount() {
Uint32 count = 0;
for (const BackendSlotTable* holder = s_firstHolder; holder != nullptr;
holder = holder->m_nextHolder) {
for (const BackendSlotTable* holder = s_firstHolder; holder != nullptr; holder = holder->m_nextHolder) {
++count;
}
return count;
@@ -632,9 +617,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
const Entry& entry = m_band[index];
if (!entry.Live || !entry.backend) continue;
const BackendPtr twin = entry.backend;
fn(MG_Pipe::MGPipeHandle{
static_cast<Uint32>(index) + MG_Pipe::kMGPipeShaderCsoCompositeSlotBase,
entry.Gen},
fn(MG_Pipe::MGPipeHandle{static_cast<Uint32>(index) + MG_Pipe::kMGPipeShaderCsoCompositeSlotBase,
entry.Gen},
twin);
}
}
@@ -714,9 +698,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return kHasCompositeBand && MG_Pipe::MGPipeIsCompositeShaderSlot(slot);
}
static constexpr SizeT IndexOfSlot(Uint32 slot) {
return SlotIsBanded(slot)
? static_cast<SizeT>(slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase)
: static_cast<SizeT>(slot);
return SlotIsBanded(slot) ? static_cast<SizeT>(slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase)
: static_cast<SizeT>(slot);
}
// The entry a slot names, or null when its space has never grown that far. NEVER grows:
@@ -728,9 +711,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (index >= table.size()) return nullptr;
return &table[index];
}
const Entry* EntryOrNull(Uint32 slot) const {
return const_cast<BackendSlotTable*>(this)->EntryOrNull(slot);
}
const Entry* EntryOrNull(Uint32 slot) const { return const_cast<BackendSlotTable*>(this)->EntryOrNull(slot); }
// Grows the right space to hold `slot`. Every caller bounds `slot` first - against
// kMaxHandleSlot - because this is the one place a client-supplied number decides an
@@ -779,7 +760,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<Entry> m_band;
// Handed back by GetOrCreate for a null state object. Never live, never handed a handle.
BackendPtr m_nullTwin;
};
} // namespace MobileGL::MG_Backend::DirectGLES
+103 -103
View File
@@ -82,22 +82,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
return options;
}
Flags<PixelFormatNormalizeOptionBit>
GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat,
Flags<PixelFormatNormalizeOptionBit> extraOptions) {
Flags<PixelFormatNormalizeOptionBit> GetRuntimeFallbackNormalizeOptions(
GLenum requestedInternalFormat, Flags<PixelFormatNormalizeOptionBit> extraOptions) {
using namespace MG_Util::TextureFormatProcessor;
const Flags<PixelFormatNormalizeOptionBit> forcedOptions = GetApplicablePixelFormatNormalizeOptions(
requestedInternalFormat, GetForcedPixelFormatNormalizeOptions() | extraOptions);
if (forcedOptions) {
return forcedOptions;
}
return GetApplicablePixelFormatNormalizeOptions(
requestedInternalFormat, GetDriverPixelFormatNormalizeOptions() | extraOptions);
return GetApplicablePixelFormatNormalizeOptions(requestedInternalFormat,
GetDriverPixelFormatNormalizeOptions() | extraOptions);
}
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat,
SizeT targetIndex,
Bool caveat,
Bool HasCachedFormatCapability(TextureInternalFormat internalFormat, SizeT targetIndex, Bool caveat,
FormatCapability capability) {
const FormatCapabilityCache* activeCaps = ActiveBackendFormatCaps();
if (activeCaps == nullptr || targetIndex >= kFormatCapabilityTargetCount) {
@@ -114,8 +111,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return HasFormatCapability(caps, capability);
}
Bool HasAnyCachedFormatCapability(TextureInternalFormat internalFormat,
Bool caveat,
Bool HasAnyCachedFormatCapability(TextureInternalFormat internalFormat, Bool caveat,
FormatCapability capability) {
for (SizeT targetIndex = 0; targetIndex < kFormatCapabilityTargetCount; ++targetIndex) {
if (HasCachedFormatCapability(internalFormat, targetIndex, caveat, capability)) {
@@ -131,10 +127,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::Creatable);
const Bool caveatCreatable =
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::Creatable);
const Bool fullRenderable =
HasCachedFormatCapability(internalFormat, targetIndex, false, FormatCapability::FramebufferRenderable);
const Bool caveatRenderable =
HasCachedFormatCapability(internalFormat, targetIndex, true, FormatCapability::FramebufferRenderable);
const Bool fullRenderable = HasCachedFormatCapability(internalFormat, targetIndex, false,
FormatCapability::FramebufferRenderable);
const Bool caveatRenderable = HasCachedFormatCapability(internalFormat, targetIndex, true,
FormatCapability::FramebufferRenderable);
return (!fullCreatable && caveatCreatable) || (!fullRenderable && caveatRenderable);
}
@@ -144,11 +140,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
return HasAnyCachedFormatCapability(internalFormat, true, FormatCapability::Creatable);
}
void GenerateFormatInfo(TextureInternalFormat internalFormat,
SizeT targetIndex,
GLenum* outInternalFormat,
GLenum* outFormat,
GLenum* outType) {
void GenerateFormatInfo(TextureInternalFormat internalFormat, SizeT targetIndex, GLenum* outInternalFormat,
GLenum* outFormat, GLenum* outType) {
using namespace MobileGL::MG_Util::TextureFormatProcessor;
const GLenum requestedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
Flags<PixelFormatNormalizeOptionBit> options;
@@ -257,8 +250,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
const SizeT targetIndex =
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
const SizeT targetIndex = target == TextureTarget::Unknown ? kFormatCapabilityTargetCount
: GetFormatCapabilityTargetIndex(target);
GenerateFormatInfo(internalFormat, targetIndex, outInternalFormat, outFormat, outType);
}
@@ -272,8 +265,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
Bool ShouldUseCaveatTextureFormat(TextureInternalFormat internalFormat, TextureTarget target) {
const SizeT targetIndex =
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
const SizeT targetIndex = target == TextureTarget::Unknown ? kFormatCapabilityTargetCount
: GetFormatCapabilityTargetIndex(target);
return ShouldUseCaveatFormat(internalFormat, targetIndex);
}
@@ -297,8 +290,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace
Bool BackendTextureFormatAddsAlpha(TextureInternalFormat internalFormat, TextureTarget target) {
const SizeT targetIndex =
target == TextureTarget::Unknown ? kFormatCapabilityTargetCount : GetFormatCapabilityTargetIndex(target);
const SizeT targetIndex = target == TextureTarget::Unknown ? kFormatCapabilityTargetCount
: GetFormatCapabilityTargetIndex(target);
return BackendFormatAddsAlpha(internalFormat, targetIndex);
}
@@ -308,8 +301,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
ImageBindableStorageWidening GetImageBindableStorageWidening(TextureInternalFormat internalFormat) {
const GLenum requested = MG_Util::ConvertTextureInternalFormatToGLEnum(internalFormat);
const auto carrier = static_cast<GLenum>(
MG_Util::ShaderTranspiler::ShaderCompiler::WidenedCoreEsslImageFormat(requested));
const auto carrier =
static_cast<GLenum>(MG_Util::ShaderTranspiler::ShaderCompiler::WidenedCoreEsslImageFormat(requested));
if (carrier == 0) {
return {};
}
@@ -329,8 +322,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
ImageBindableStorageWidening widening;
widening.InternalFormat = carrier;
widening.SourceChannels =
MG_Util::ShaderTranspiler::ShaderCompiler::ImageFormatChannelCount(requested);
widening.SourceChannels = MG_Util::ShaderTranspiler::ShaderCompiler::ImageFormatChannelCount(requested);
switch (carrier) {
case GL_RGBA32UI:
case GL_RGBA16UI:
@@ -496,7 +488,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (; outputIt != outputEnd; ++outputIt) {
const Uint location = static_cast<Uint>(std::stoul((*outputIt)[1].str()));
if (location < 32 && (outputMask & (1u << location))) {
outputClamps.push_back({(*outputIt)[2].str(), static_cast<Bool>(snormOutputMask & (1u << location))});
outputClamps.push_back(
{(*outputIt)[2].str(), static_cast<Bool>(snormOutputMask & (1u << location))});
}
}
if (outputClamps.empty()) {
@@ -520,8 +513,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
String clampLine;
for (const OutputClamp& outputClamp : outputClamps) {
const String minValue = outputClamp.Signed ? "-1.0" : "0.0";
clampLine += "\n " + outputClamp.Name + " = clamp(" + outputClamp.Name +
", vec4(" + minValue + "), vec4(1.0));";
clampLine += "\n " + outputClamp.Name + " = clamp(" + outputClamp.Name + ", vec4(" +
minValue + "), vec4(1.0));";
}
clampLine += "\n";
glslCode.insert(pos, clampLine);
@@ -557,8 +550,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
String replicaCopies;
for (Uint location = 1; location < drawBufferCount; ++location) {
const String name = String(kLoweredName) + "_" + std::to_string(location);
replicaDecls += "\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " +
name + ";";
replicaDecls +=
"\nlayout(location = " + std::to_string(location) + ") out " + precision + "vec4 " + name + ";";
replicaCopies += "\n " + name + " = " + kLoweredName + ";";
}
@@ -632,8 +625,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return result;
}
String RetargetTextureBufferExtension(String glslCode,
MG_External::GLESCapabilities::TextureBufferTier tier) {
String RetargetTextureBufferExtension(String glslCode, MG_External::GLESCapabilities::TextureBufferTier tier) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -789,12 +781,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
const char* PointSizeExtensionName(MG_External::GLESCapabilities::PointSizeTier tier, Bool tessellation) {
using Tier = MG_External::GLESCapabilities::PointSizeTier;
switch (tier) {
case Tier::ExtensionEXT:
return tessellation ? "GL_EXT_tessellation_point_size" : "GL_EXT_geometry_point_size";
case Tier::ExtensionOES:
return tessellation ? "GL_OES_tessellation_point_size" : "GL_OES_geometry_point_size";
default:
return nullptr;
case Tier::ExtensionEXT:
return tessellation ? "GL_EXT_tessellation_point_size" : "GL_EXT_geometry_point_size";
case Tier::ExtensionOES:
return tessellation ? "GL_OES_tessellation_point_size" : "GL_OES_geometry_point_size";
default:
return nullptr;
}
}
@@ -827,8 +819,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
return glslCode;
}
String BakeImageFormatQualifiers(String glslCode,
const UnorderedMap<String, String>& esslFormatByUniformName) {
String BakeImageFormatQualifiers(String glslCode, const UnorderedMap<String, String>& esslFormatByUniformName) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -931,11 +922,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
while ((pos = essl.find("gl_PerVertex", pos)) != String::npos) {
// Walk back over whitespace to the direction keyword.
SizeT before = pos;
while (before > 0 && std::isspace(static_cast<unsigned char>(essl[before - 1]))) --before;
while (before > 0 && std::isspace(static_cast<unsigned char>(essl[before - 1])))
--before;
const Bool matches = before >= keyword.size() &&
essl.compare(before - keyword.size(), keyword.size(), keyword) == 0 &&
(before == keyword.size() ||
!isIdentifierChar(essl[before - keyword.size() - 1]));
(before == keyword.size() || !isIdentifierChar(essl[before - keyword.size() - 1]));
if (!matches) {
pos += 1;
continue;
@@ -1010,7 +1001,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue;
}
SizeT before = pos;
while (before > 0 && std::isspace(static_cast<unsigned char>(glslCode[before - 1]))) --before;
while (before > 0 && std::isspace(static_cast<unsigned char>(glslCode[before - 1])))
--before;
if (before > 0 && (glslCode[before - 1] == '.' || isIdentifierChar(glslCode[before - 1]))) {
continue;
}
@@ -1070,16 +1062,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT pos = members.find("invariant "); pos != String::npos; pos = members.find("invariant ")) {
members.erase(pos, std::strlen("invariant "));
}
while (!members.empty() && std::isspace(static_cast<unsigned char>(members.back()))) members.pop_back();
while (!members.empty() && (members.front() == '\n' || members.front() == '\r')) members.erase(0, 1);
while (!members.empty() && std::isspace(static_cast<unsigned char>(members.back())))
members.pop_back();
while (!members.empty() && (members.front() == '\n' || members.front() == '\r'))
members.erase(0, 1);
String block = "in gl_PerVertex\n{\n" + members + "\n} gl_in[";
block += tessellation ? "gl_MaxPatchVertices" : "";
block += "];\n";
// After the header (#directives, precision statements and the stage's layout-only
// in/out declarations, which a geometry stage's unsized gl_in[] depends on).
static const std::regex headerRegex(
R"(^\s*(#.*|precision\s.*|layout\s*\(.*\)\s*(in|out)\s*;\s*)?$)");
static const std::regex headerRegex(R"(^\s*(#.*|precision\s.*|layout\s*\(.*\)\s*(in|out)\s*;\s*)?$)");
SizeT insertAt = 0;
SizeT lineStart = 0;
while (lineStart < glslCode.size()) {
@@ -1097,7 +1090,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
String RequestClipCullDistanceExtension(String glslCode) {
static constexpr const char* kDirective = "#extension GL_EXT_clip_cull_distance : require\n";
if (glslCode.find("GL_EXT_clip_cull_distance") != String::npos ||
(glslCode.find("gl_ClipDistance") == String::npos && glslCode.find("gl_CullDistance") == String::npos)) {
(glslCode.find("gl_ClipDistance") == String::npos &&
glslCode.find("gl_CullDistance") == String::npos)) {
return glslCode;
}
// Right after the #version line, like every other directive this backend adds.
@@ -1114,10 +1108,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
String BuildPassthroughTessControlEssl(const Uint esslVersion, const Uint patchVertices,
const String& inPerVertexMembers,
const String& outPerVertexMembers,
const FloatVec4& defaultOuterLevel,
const FloatVec2& defaultInnerLevel) {
const String& inPerVertexMembers, const String& outPerVertexMembers,
const FloatVec4& defaultOuterLevel, const FloatVec2& defaultInnerLevel) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
@@ -1169,8 +1161,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
SizeT CountIdentifierOccurrences(const String& code, const String& identifier) {
if (identifier.empty()) return 0;
SizeT count = 0;
for (SizeT pos = code.find(identifier); pos != String::npos;
pos = code.find(identifier, pos + 1)) {
for (SizeT pos = code.find(identifier); pos != String::npos; pos = code.find(identifier, pos + 1)) {
if (pos > 0 && IsImagePassIdentifierChar(code[pos - 1])) continue;
const SizeT after = pos + identifier.size();
if (after < code.size() && IsImagePassIdentifierChar(code[after])) continue;
@@ -1188,14 +1179,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// readonly/writeonly rule only applies to a declaration that carries one of them.
Bool IsImageFormatQualifier(const String& token) {
static constexpr StringView FORMATS[] = {
"rgba32f", "rgba16f", "rg32f", "rg16f", "r11f_g11f_b10f",
"r32f", "r16f", "rgba16", "rgb10_a2", "rgba8",
"rg16", "rg8", "r16", "r8", "rgba16_snorm",
"rgba8_snorm", "rg16_snorm", "rg8_snorm", "r16_snorm", "r8_snorm",
"rgba32i", "rgba16i", "rgba8i", "rg32i", "rg16i",
"rg8i", "r32i", "r16i", "r8i", "rgba32ui",
"rgba16ui", "rgb10_a2ui", "rgba8ui", "rg32ui", "rg16ui",
"rg8ui", "r32ui", "r16ui", "r8ui",
"rgba32f", "rgba16f", "rg32f", "rg16f", "r11f_g11f_b10f",
"r32f", "r16f", "rgba16", "rgb10_a2", "rgba8",
"rg16", "rg8", "r16", "r8", "rgba16_snorm",
"rgba8_snorm", "rg16_snorm", "rg8_snorm", "r16_snorm", "r8_snorm",
"rgba32i", "rgba16i", "rgba8i", "rg32i", "rg16i",
"rg8i", "r32i", "r16i", "r8i", "rgba32ui",
"rgba16ui", "rgb10_a2ui", "rgba8ui", "rg32ui", "rg16ui",
"rg8ui", "r32ui", "r16ui", "r8ui",
};
for (const StringView format : FORMATS) {
if (token == format) return true;
@@ -1251,7 +1242,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
// How an image builtin touches the image it is handed.
enum class ImageBuiltinAccess { None, Load, Store, Unknown };
enum class ImageBuiltinAccess {
None,
Load,
Store,
Unknown
};
ImageBuiltinAccess ClassifyImageBuiltin(const String& name) {
if (name == "imageStore") return ImageBuiltinAccess::Store;
@@ -1279,7 +1275,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
String arraySuffix; // "" or "[7]"
SizeT declStart = 0;
SizeT declLength = 0;
SizeT nameStart = 0; // the name token alone, for a rename that edits nothing else
SizeT nameStart = 0; // the name token alone, for a rename that edits nothing else
SizeT nameLength = 0;
SizeT referenceCount = 0; // uses this pass recognized and accounted for
Bool loaded = false;
@@ -1340,7 +1336,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
return false;
};
while (isTaken(candidate)) candidate += 'X';
while (isTaken(candidate))
candidate += 'X';
return candidate;
}
@@ -1498,8 +1495,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// stage that was never going to compile, and guessing which element it meant
// would only change which unit it addressed wrongly.
struct ElementUse {
SizeT start; // the first character of the name
SizeT length; // through the closing ']'
SizeT start; // the first character of the name
SizeT length; // through the closing ']'
SizeT element;
};
Vector<ElementUse> uses;
@@ -1559,9 +1556,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
Vector<String> elementNames;
String replacement;
for (SizeT element = 0; element < elementCount; ++element) {
const String elementName =
MakeImageAliasName(IMAGE_ARRAY_ELEMENT_PREFIX,
plan.name + "_" + std::to_string(element), glslCode, takenNames);
const String elementName = MakeImageAliasName(
IMAGE_ARRAY_ELEMENT_PREFIX, plan.name + "_" + std::to_string(element), glslCode, takenNames);
takenNames.push_back(elementName);
elementNames.push_back(elementName);
@@ -1687,7 +1683,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (SizeT pos = glslCode.find("image"); pos != String::npos; pos = glslCode.find("image", pos + 1)) {
if (pos > 0 && IsImagePassIdentifierChar(glslCode[pos - 1])) continue; // uimage2D, myimageFoo
SizeT tokenEnd = pos;
while (tokenEnd < glslCode.size() && IsImagePassIdentifierChar(glslCode[tokenEnd])) ++tokenEnd;
while (tokenEnd < glslCode.size() && IsImagePassIdentifierChar(glslCode[tokenEnd]))
++tokenEnd;
const String builtin = glslCode.substr(pos, tokenEnd - pos);
const SizeT openParen = glslCode.find_first_not_of(" \t\r\n", tokenEnd);
@@ -1699,7 +1696,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
continue; // an expression, not a bare variable - it names no image of ours
}
SizeT argEnd = argStart;
while (argEnd < glslCode.size() && IsImagePassIdentifierChar(glslCode[argEnd])) ++argEnd;
while (argEnd < glslCode.size() && IsImagePassIdentifierChar(glslCode[argEnd]))
++argEnd;
const SizeT declIndex = findDecl(glslCode.substr(argStart, argEnd - argStart));
if (declIndex == decls.size()) continue;
@@ -1713,11 +1711,12 @@ namespace MobileGL::MG_Backend::DirectGLES {
Int depth = 0;
SizeT scan = after;
for (; scan < glslCode.size(); ++scan) {
if (glslCode[scan] == '[') ++depth;
else if (glslCode[scan] == ']' && --depth == 0) break;
if (glslCode[scan] == '[')
++depth;
else if (glslCode[scan] == ']' && --depth == 0)
break;
}
after = scan >= glslCode.size() ? String::npos
: glslCode.find_first_not_of(" \t\r\n", scan + 1);
after = scan >= glslCode.size() ? String::npos : glslCode.find_first_not_of(" \t\r\n", scan + 1);
}
const char nextChar = after == String::npos ? '\0' : glslCode[after];
if (nextChar != ',' && nextChar != ')') {
@@ -1811,16 +1810,15 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
const char* aliasPrefix = decl.loaded && decl.stored ? IMAGE_SPLIT_READ_ALIAS_PREFIX
: decl.stored ? IMAGE_WRITEONLY_ALIAS_PREFIX
: IMAGE_READONLY_ALIAS_PREFIX;
: decl.stored ? IMAGE_WRITEONLY_ALIAS_PREFIX
: IMAGE_READONLY_ALIAS_PREFIX;
decl.aliasName = MakeImageAliasName(aliasPrefix, decl.name, glslCode, takenNames);
takenNames.push_back(decl.aliasName);
if (decl.loaded && decl.stored) {
// Minted from the ALREADY access-tagged name, so the write half of a split
// can never collide with the single declaration another stage's repair mints
// for the same image.
decl.writeName =
MakeImageAliasName(IMAGE_WRITE_ALIAS_PREFIX, decl.aliasName, glslCode, takenNames);
decl.writeName = MakeImageAliasName(IMAGE_WRITE_ALIAS_PREFIX, decl.aliasName, glslCode, takenNames);
takenNames.push_back(decl.writeName);
decl.split = true;
if (outSplitCount != nullptr) ++*outSplitCount;
@@ -1835,13 +1833,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
BuildImageDeclaration(decl, "writeonly", decl.writeName,
/*forceCoherent=*/true)});
} else if (decl.stored) {
edits.push_back({decl.declStart, decl.declLength,
BuildImageDeclaration(decl, "writeonly", decl.aliasName)});
edits.push_back(
{decl.declStart, decl.declLength, BuildImageDeclaration(decl, "writeonly", decl.aliasName)});
} else {
// Loaded only, or only ever handed to imageSize (or unused): readonly is
// the qualifier that keeps every one of those legal.
edits.push_back({decl.declStart, decl.declLength,
BuildImageDeclaration(decl, "readonly", decl.aliasName)});
edits.push_back(
{decl.declStart, decl.declLength, BuildImageDeclaration(decl, "readonly", decl.aliasName)});
}
}
for (const ImageUseSite& site : useSites) {
@@ -1849,8 +1847,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Empty exactly when the declaration was poisoned above and left untouched; its
// uses must keep naming the variable that is still called that.
if (decl.aliasName.empty()) continue;
edits.push_back(
{site.start, site.length, decl.split && site.stores ? decl.writeName : decl.aliasName});
edits.push_back({site.start, site.length, decl.split && site.stores ? decl.writeName : decl.aliasName});
if (!decl.split || !site.stores) continue;
// ...and an explicit barrier behind it. `coherent` on both halves is what makes
// the store VISIBLE to a load through the other variable, but it says nothing
@@ -1890,7 +1887,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// before the optional bias.
struct LodLookupForm {
const char* name;
Int requiredArgs; // arguments before the optional bias (implicit form)
Int requiredArgs; // arguments before the optional bias (implicit form)
Int explicitLodArg; // index of the explicit LOD argument, -1 for implicit
};
@@ -1907,15 +1904,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
// Sampler types with no mip chain, or whose GLSL lookups have no bias overload
// at all (the array-shadow forms), so nothing can or should be folded in.
Bool IsBiasableSamplerType(const String& samplerType) {
if (samplerType.find("MS") != String::npos) return false; // multisample
if (samplerType.find("Buffer") != String::npos) return false; // texture buffer
if (samplerType.find("Rect") != String::npos) return false; // rectangle: no mips
if (samplerType.find("MS") != String::npos) return false; // multisample
if (samplerType.find("Buffer") != String::npos) return false; // texture buffer
if (samplerType.find("Rect") != String::npos) return false; // rectangle: no mips
if (samplerType == "sampler2DArrayShadow") return false;
if (samplerType == "samplerCubeArrayShadow") return false;
return true;
}
Bool IsIdentifierChar(char c) { return std::isalnum(static_cast<unsigned char>(c)) || c == '_'; }
Bool IsIdentifierChar(char c) {
return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
}
// Byte offsets of the top-level argument separators and of the closing paren,
// starting from the '(' at openParen. Empty when the parentheses do not balance.
@@ -1994,7 +1993,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// First argument must be one of our samplers.
const SizeT firstArgStart = result.find_first_not_of(" \t", openParen + 1);
SizeT firstArgEnd = marks.front();
while (firstArgEnd > firstArgStart && (result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) {
while (firstArgEnd > firstArgStart &&
(result[firstArgEnd - 1] == ' ' || result[firstArgEnd - 1] == '\t')) {
--firstArgEnd;
}
if (firstArgStart == String::npos || firstArgEnd <= firstArgStart) continue;
@@ -2040,8 +2040,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
for (const auto& samplerName : usedSamplers) {
const String& biasName = samplerNames[samplerName];
if (result.find(String("float ") + biasName + ";") != String::npos) continue;
const std::regex declRegex(
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?[iu]?sampler[A-Za-z0-9]*\s+)" + samplerName + R"(\s*;)");
const std::regex declRegex(R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?[iu]?sampler[A-Za-z0-9]*\s+)" +
samplerName + R"(\s*;)");
std::smatch match;
if (!std::regex_search(result, match, declRegex)) continue;
const SizeT declEnd = static_cast<SizeT>(match.position(0)) + match[0].str().size();
@@ -2082,13 +2082,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (marks.size() != 5) continue;
SizeT nameStart = result.find_first_not_of(" \t", openParen + 1);
SizeT nameEnd = nameStart;
while (nameEnd < marks[0] && IsIdentifierChar(result[nameEnd])) ++nameEnd;
while (nameEnd < marks[0] && IsIdentifierChar(result[nameEnd]))
++nameEnd;
if (names.find(result.substr(nameStart, nameEnd - nameStart)) == names.end()) continue;
const auto argument = [&](SizeT index) {
const SizeT start = marks[index - 1] + 1;
String text = result.substr(start, marks[index] - start);
text.erase(std::remove_if(text.begin(), text.end(),
[](char c) { return c == ' ' || c == '\t'; }),
text.erase(std::remove_if(text.begin(), text.end(), [](char c) { return c == ' ' || c == '\t'; }),
text.end());
return text;
};
@@ -2112,7 +2112,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// deferred, and the only ones a lookup can name directly as its first argument.
static const std::regex declRegex(
R"(uniform\s+(?:(?:highp|mediump|lowp)\s+)?usampler2DMS(?:Array)?\s+([A-Za-z_][A-Za-z0-9_]*)\s*;)");
UnorderedMap<String, String> clampNames; // sampler name -> sample-max uniform name
UnorderedMap<String, String> clampNames; // sampler name -> sample-max uniform name
Vector<std::pair<SizeT, String>> declEnds; // where each declaration ends, and its uniform
for (std::sregex_iterator it(glslCode.begin(), glslCode.end(), declRegex), end; it != end; ++it) {
const String name = (*it)[1].str();
+3 -6
View File
@@ -211,8 +211,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// On a driver that advertises only GL_OES_texture_buffer that directive is a compile
// error, so the name is retargeted in the emitted source. A no-op on every other tier:
// ES 3.2 needs no directive at all and an EXT driver already has the right one.
String RetargetTextureBufferExtension(String glslCode,
MG_External::GLESCapabilities::TextureBufferTier tier);
String RetargetTextureBufferExtension(String glslCode, MG_External::GLESCapabilities::TextureBufferTier tier);
// Adds `#extension GL_NV_image_formats : require` when the shader carries an image
// format qualifier GLSL ES has no core spelling for. SPIRV-Cross prints the format and
// asks for nothing, so the request has to be made here. `needed` is the caller's answer,
@@ -384,10 +383,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// disagree on everything but the algorithm: desktop GLSL 450 against ESSL, a fixed
// gl_PerVertex shape that Vulkan matches structurally against a mirrored one, and a
// VkShaderModule against a driver shader object.
String BuildPassthroughTessControlEssl(Uint esslVersion, Uint patchVertices,
const String& inPerVertexMembers,
const String& outPerVertexMembers,
const FloatVec4& defaultOuterLevel,
String BuildPassthroughTessControlEssl(Uint esslVersion, Uint patchVertices, const String& inPerVertexMembers,
const String& outPerVertexMembers, const FloatVec4& defaultOuterLevel,
const FloatVec2& defaultInnerLevel);
// Prefix of the writeonly half a read+write image uniform is split into (see
// SplitReadWriteImageUniforms); the suffix is the image's own (already access-tagged) name.
@@ -528,8 +528,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
const Bool ok = BackendObject::MakeEGLCurrent(dpy, draw, read, ctx);
if (ok && UsesSurfaceTargets() && ctx != EGL_NO_CONTEXT && draw != EGL_NO_SURFACE) {
m_contextBindings[ctx] = EGLCurrentState{.Display = dpy, .DrawSurface = draw, .ReadSurface = read,
.Context = ctx};
m_contextBindings[ctx] =
EGLCurrentState{.Display = dpy, .DrawSurface = draw, .ReadSurface = read, .Context = ctx};
}
return ok;
}
@@ -791,26 +791,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported,
Bool nonZeroIndirectBaseInstanceSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features) {
Vector<GLExtension> extensions = {
// The version tokens have to reach the version the backend actually claims:
// TargetGLVersion is {4,6,0}, and a list that stopped at OpenGL40 told an
// application feature-detecting off these tokens the opposite of what
// GL_MAJOR_VERSION / GL_MINOR_VERSION told it.
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, V_OpenGL41, V_OpenGL42, V_OpenGL43,
V_OpenGL44, V_OpenGL45, V_OpenGL46,
E_GL_ARB_draw_buffers_blend,
V_OpenGL30, V_OpenGL31, V_OpenGL32, V_OpenGL33, V_OpenGL40, V_OpenGL41, V_OpenGL42, V_OpenGL43, V_OpenGL44,
V_OpenGL45, V_OpenGL46, E_GL_ARB_draw_buffers_blend,
// The GLSL 1.00/1.10-era ARB trio plus NPOT textures: all core since GL 2.0 and
// trivially true for every context this backend claims (4.6). KWin 6's
// EglContext::checkSupported() gates desktop-GL compositing on exactly these four
// strings, so withholding them fails its renderer bring-up. Same block as DirectGLES.
E_GL_ARB_shader_objects, E_GL_ARB_vertex_shader, E_GL_ARB_fragment_shader,
E_GL_ARB_texture_non_power_of_two,
E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object, E_GL_ARB_shader_image_load_store,
E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query, E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect,
E_GL_ARB_multi_draw_indirect,
E_GL_ARB_texture_non_power_of_two, E_GL_ARB_compute_shader, E_GL_ARB_shader_storage_buffer_object,
E_GL_ARB_shader_image_load_store, E_GL_ARB_clear_buffer_object, E_GL_ARB_program_interface_query,
E_GL_ARB_framebuffer_object, E_GL_ARB_draw_indirect, E_GL_ARB_multi_draw_indirect,
E_GL_ARB_indirect_parameters, E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
E_GL_ARB_texture_storage, E_GL_ARB_texture_storage_multisample, E_GL_ARB_texture_multisample,
E_GL_ARB_clear_texture, E_GL_ARB_direct_state_access, E_GL_ARB_shader_draw_parameters,
@@ -1116,8 +1113,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer && pVulkanRenderer->IsNonZeroIndirectBaseInstanceSupported(),
m_vulkanCaps.SupportsImageCubeArray,
AdvertisedFeatureSupport{.TessellationShader = m_vulkanCaps.SupportsTessellationShader,
.CullDistance = m_vulkanCaps.SupportsShaderCullDistance &&
m_vulkanCaps.MaxCullDistances > 0});
.CullDistance =
m_vulkanCaps.SupportsShaderCullDistance && m_vulkanCaps.MaxCullDistances > 0});
// GL_EXT_fragment_shading_rate rides on VK_KHR_fragment_shading_rate's pipeline rate, and
// its _primitive companion on the primitive rate; both only when the renderer enabled them.
// _attachment is not advertised: no framebuffer carries a rate attachment to this backend.
@@ -1214,9 +1211,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
0.3125f, 0.1875f, 0.1875f, 0.8125f, 0.0625f, 0.4375f,
0.6875f, 0.9375f, 0.9375f, 0.0625f};
static constexpr Float kStandard16[] = {
0.5625f, 0.5625f, 0.4375f, 0.3125f, 0.3125f, 0.625f, 0.75f, 0.4375f, 0.1875f, 0.375f, 0.625f,
0.8125f, 0.8125f, 0.6875f, 0.6875f, 0.1875f, 0.375f, 0.875f, 0.5f, 0.0625f, 0.25f, 0.125f,
0.125f, 0.75f, 0.0f, 0.5f, 0.9375f, 0.25f, 0.875f, 0.9375f, 0.0625f, 0.0f};
0.5625f, 0.5625f, 0.4375f, 0.3125f, 0.3125f, 0.625f, 0.75f, 0.4375f, 0.1875f, 0.375f, 0.625f,
0.8125f, 0.8125f, 0.6875f, 0.6875f, 0.1875f, 0.375f, 0.875f, 0.5f, 0.0625f, 0.25f, 0.125f,
0.125f, 0.75f, 0.0f, 0.5f, 0.9375f, 0.25f, 0.875f, 0.9375f, 0.0625f, 0.0f};
const Float* const tables[] = {kStandard2, kStandard4, kStandard8, kStandard16};
for (Int samples = 2, row = 0; samples <= decltype(m_dynamicParameters)::kMaxSamplePositionCount &&
samples <= m_vulkanCaps.MaxSamples;
@@ -1275,9 +1272,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_dynamicParameters.MaxCombinedShaderStorageBlocks =
clampLimit("GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS", m_vulkanCaps.MaxCombinedShaderStorageBlocks,
kMaxAdvertisedBufferBlocks);
m_dynamicParameters.MaxComputeUniformBlocks =
clampLimit("GL_MAX_COMPUTE_UNIFORM_BLOCKS", m_vulkanCaps.MaxComputeUniformBlocks,
kMaxAdvertisedBufferBlocks);
m_dynamicParameters.MaxComputeUniformBlocks = clampLimit(
"GL_MAX_COMPUTE_UNIFORM_BLOCKS", m_vulkanCaps.MaxComputeUniformBlocks, kMaxAdvertisedBufferBlocks);
m_dynamicParameters.MaxComputeWorkGroupInvocations = m_vulkanCaps.MaxComputeWorkGroupInvocations;
// The six per-axis compute limits, from the same VkPhysicalDeviceLimits fields
// GLFunctionsTable::GetIntegeri_v (DirectVulkan.cpp) reads live. Carried here so that
@@ -1544,8 +1540,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The attachment rate stays off (see UpdateAdvertisedExtensions), and with it every limit
// that only describes the attachment.
{
const FragmentShadingRateSupport fsr =
pVulkanRenderer != nullptr ? pVulkanRenderer->GetFragmentShadingRateSupport() : FragmentShadingRateSupport{};
const FragmentShadingRateSupport fsr = pVulkanRenderer != nullptr
? pVulkanRenderer->GetFragmentShadingRateSupport()
: FragmentShadingRateSupport{};
m_dynamicParameters.SupportsFragmentShadingRate = fsr.Pipeline;
m_dynamicParameters.SupportsPrimitiveFragmentShadingRate = fsr.Pipeline && fsr.Primitive;
m_dynamicParameters.SupportsAttachmentFragmentShadingRate = false;
@@ -18,8 +18,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// initialization; the physical device must remain valid for the duration of the call.
void PopulateFormatCapabilities(VkPhysicalDevice physicalDevice,
PFN_vkGetPhysicalDeviceFormatProperties getFormatProperties,
const MG_External::VulkanCapabilities& capabilities,
FormatCapabilityCache& cache);
const MG_External::VulkanCapabilities& capabilities, FormatCapabilityCache& cache);
class BackendObject_DirectVulkan : public BackendObject {
public:
@@ -115,8 +114,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL_KHR_shader_subgroup is offered at all, natively or emulated.
Vector<GLExtension> BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported,
Bool anisotropicFilteringSupported,
Bool nonZeroIndirectBaseInstanceSupported,
Bool cubeMapArraySupported,
Bool nonZeroIndirectBaseInstanceSupported, Bool cubeMapArraySupported,
const AdvertisedFeatureSupport& features = {});
// Format: <GPU Name>, Vulkan <Vulkan Version>, Driver <Driver Version> — the exact
@@ -16,13 +16,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// headers. They link the static library on desktop; the shipping shared library is built with
// hidden visibility, so on Android nothing outside the library can call GetDescriptorPoolCensus.
struct DescriptorPoolCensus {
bool available = false; // false when no Magma renderer is live (e.g. the DirectGLES backend)
bool available = false; // false when no Magma renderer is live (e.g. the DirectGLES backend)
uint32_t frameSlots = 0;
uint32_t pools = 0; // descriptor pools across every frame slot
uint32_t pools = 0; // descriptor pools across every frame slot
uint32_t maxPoolsInOneSlot = 0;
uint64_t capacitySets = 0; // sum of maxSets over those pools
uint64_t cachedSets = 0; // sets allocated from them and held in the per-layout caches
uint64_t slotTrims = 0; // whole-slot trims since the renderer came up
uint64_t capacitySets = 0; // sum of maxSets over those pools
uint64_t cachedSets = 0; // sets allocated from them and held in the per-layout caches
uint64_t slotTrims = 0; // whole-slot trims since the renderer came up
};
DescriptorPoolCensus GetDescriptorPoolCensus();
@@ -153,11 +153,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// or maxdrawcount > 1 without multiDrawIndirect: VulkanRenderer.cpp's
// MultiDrawElementsIndirectCount) - where ReadWireBuffer orders GPU-produced command/count
// bytes before the expansion.
void DrawWireIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount,
GLsizei stride, Bool indexed, Bool counted = false, GLintptr countOffset = 0) {
void DrawWireIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride,
Bool indexed, Bool counted = false, GLintptr countOffset = 0) {
if (drawcount <= 0 || countOffset < 0) return;
if (pVulkanRenderer->DrawWireIndirectNative(mode, type, reinterpret_cast<Uint64>(indirect), drawcount,
stride, indexed, counted, static_cast<Uint64>(countOffset)))
stride, indexed, counted, static_cast<Uint64>(countOffset)))
return;
auto& buffers = pVulkanRenderer->GetWireBufferManager();
const auto& state = MG_Pipe::MGPipeApplier();
@@ -174,8 +174,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} expansionCount{buffers};
if (counted) {
Uint32 count = 0;
if (!buffers.ReadWireBuffer(state.VerbIndirectParameterBuffer,
static_cast<Uint64>(countOffset), sizeof(count), &count)) return;
if (!buffers.ReadWireBuffer(state.VerbIndirectParameterBuffer, static_cast<Uint64>(countOffset),
sizeof(count), &count))
return;
drawcount = static_cast<GLsizei>(std::min<Uint32>(count, static_cast<Uint32>(drawcount)));
if (!drawcount) return;
}
@@ -185,8 +186,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint64 byteCount = static_cast<Uint64>(stride) * (drawcount - 1) + commandSize;
if (byteCount > std::numeric_limits<SizeT>::max()) return;
Vector<Uint8> bytes(static_cast<SizeT>(byteCount));
if (!buffers.ReadWireBuffer(state.VerbIndirectBuffer, reinterpret_cast<Uint64>(indirect),
byteCount, bytes.data())) return;
if (!buffers.ReadWireBuffer(state.VerbIndirectBuffer, reinterpret_cast<Uint64>(indirect), byteCount,
bytes.data()))
return;
if (indexed) {
const SizeT indexSize = MG_Util::GetGLTypeSize(type);
if (indexSize != 1 && indexSize != 2 && indexSize != 4) return;
@@ -199,12 +201,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (GLsizei i = 0; i < drawcount; ++i) {
DrawElementsIndirectCommand command{};
Memcpy(&command, bytes.data() + static_cast<SizeT>(i) * stride, sizeof(command));
params[i] = {command.count, command.instanceCount, command.firstIndex,
command.baseVertex, std::bit_cast<Int32>(command.baseInstance)};
params[i] = {command.count, command.instanceCount, command.firstIndex, command.baseVertex,
std::bit_cast<Int32>(command.baseInstance)};
const Uint64 end = (static_cast<Uint64>(command.firstIndex) + command.count) * indexSize;
if (end > std::numeric_limits<SizeT>::max()) return;
payload.indexBufferView.indexByteSize = std::max<SizeT>(payload.indexBufferView.indexByteSize,
static_cast<SizeT>(end));
payload.indexBufferView.indexByteSize =
std::max<SizeT>(payload.indexBufferView.indexByteSize, static_cast<SizeT>(end));
}
pVulkanRenderer->MultiDrawElements(payload);
} else {
@@ -255,7 +257,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name) {
const auto& blocks = program.GetLinkReflection().storageBlocks;
const auto find = [&blocks](const String& key) {
return std::find_if(blocks.begin(), blocks.end(),
return std::find_if(
blocks.begin(), blocks.end(),
[&](const MG_State::GLState::StorageBlockReflection& block) { return block.name == key; });
};
auto it = find(name);
@@ -324,8 +327,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DrawWireIndirect(mode, type, indirect, maxdrawcount, stride, true, true, drawcount);
return;
}
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride) {
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount,
GLsizei stride) {
DrawWireIndirect(mode, 0, indirect, maxdrawcount, stride, false, true, drawcount);
return;
}
@@ -342,8 +345,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLsizei instancecount, GLint basevertex, GLuint baseinstance) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsInstancedBaseVertexBaseInstance called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::DrawElementsInstancedBaseVertexBaseInstance called with null GL context");
MOBILEGL_ASSERT(pVulkanRenderer,
"DirectVulkan::DrawElementsInstancedBaseVertexBaseInstance called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(),
"DirectVulkan::DrawElementsInstancedBaseVertexBaseInstance called with null GL context");
DrawIndexedCmd payload{};
payload.mode = mode;
@@ -374,8 +379,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
GLuint baseinstance) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawArraysInstancedBaseInstance called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::DrawArraysInstancedBaseInstance called with null GL context");
MOBILEGL_ASSERT(pVulkanRenderer,
"DirectVulkan::DrawArraysInstancedBaseInstance called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(),
"DirectVulkan::DrawArraysInstancedBaseInstance called with null GL context");
DrawCmd payload{};
payload.mode = mode;
@@ -404,16 +411,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::CopyTexSubImage2D called with null GL context");
pVulkanRenderer->CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height);
}
void CopyImageSubData(const CopyImageEndpoint& src,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dst,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
void CopyImageSubData(const CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY,
GLint srcZ, const CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX,
GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::CopyImageSubData called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::CopyImageSubData called with null GL context");
pVulkanRenderer->CopyImageSubData(src, srcTarget, srcLevel, srcX, srcY, srcZ,
dst, dstTarget, dstLevel, dstX, dstY, dstZ,
srcWidth, srcHeight, srcDepth);
pVulkanRenderer->CopyImageSubData(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX,
dstY, dstZ, srcWidth, srcHeight, srcDepth);
}
void GenerateMipmap(GLenum target) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::GenerateMipmap called with null VulkanRenderer");
@@ -501,10 +505,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
auto& record = state.ShaderCsos[handle.Slot];
auto found = std::find_if(record.StorageOverrides.begin(), record.StorageOverrides.end(),
[&](const auto& entry) { return entry.Name == storageBlockName; });
[&](const auto& entry) { return entry.Name == storageBlockName; });
if (found == record.StorageOverrides.end())
record.StorageOverrides.push_back({storageBlockName, static_cast<Int32>(storageBlockBinding)});
else found->Binding = static_cast<Int32>(storageBlockBinding);
else
found->Binding = static_cast<Int32>(storageBlockBinding);
++record.BindingsSerial;
return;
// P7 wave 2 package C, OQ-8: NOTHING LEFT TO PATCH, and the hazard goes with it.
@@ -564,8 +569,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!MG_Pipe::MGPipeHandleIsNull(bound.Res)) {
owned.resize(static_cast<SizeT>(count) * width);
const Uint64 offset = bound.Offset + reinterpret_cast<Uint64>(indices);
if (offset < bound.Offset || !pVulkanRenderer->GetWireBufferManager().ReadWireBuffer(
bound.Res, offset, owned.size(), owned.data())) return false;
if (offset < bound.Offset ||
!pVulkanRenderer->GetWireBufferManager().ReadWireBuffer(bound.Res, offset, owned.size(), owned.data()))
return false;
bytes = owned.data();
}
if (!bytes) return false;
@@ -687,8 +693,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (count[i] <= 0) {
continue;
}
DrawElementsBaseVertex(mode, count[i], type, indices[i],
basevertex != nullptr ? basevertex[i] : 0);
DrawElementsBaseVertex(mode, count[i], type, indices[i], basevertex != nullptr ? basevertex[i] : 0);
}
return;
}
@@ -722,9 +727,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// TODO: this index view needs a redesign, now there's a lotta redundant uploads
payload.indexBufferView.indexByteOffset = 0;
payload.indexBufferView.indexByteSize =
std::max(reinterpret_cast<SizeT>(indices[i]) + count[i] * indexSize,
payload.indexBufferView.indexByteSize);
payload.indexBufferView.indexByteSize = std::max(reinterpret_cast<SizeT>(indices[i]) + count[i] * indexSize,
payload.indexBufferView.indexByteSize);
auto& param = params[i];
@@ -741,7 +745,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices, GLint basevertex) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::DrawElementsBaseVertex called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::DrawElementsBaseVertex called with null GL context");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(),
"DirectVulkan::DrawElementsBaseVertex called with null GL context");
if (mode == GL_LINE_LOOP) {
Vector<Uint32> closedIndices;
if (BuildClosedLineLoopIndices(count, type, indices, closedIndices)) {
@@ -765,7 +770,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices,
GLsizei drawcount, const GLint* basevertex) {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::MultiDrawElementsBaseVertex called with null VulkanRenderer");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(), "DirectVulkan::MultiDrawElementsBaseVertex called with null GL context");
MOBILEGL_ASSERT(MG_Pipe::gPipeInputs->IsLive(),
"DirectVulkan::MultiDrawElementsBaseVertex called with null GL context");
MultiDrawElementsImpl(mode, count, type, indices, drawcount, basevertex);
}
@@ -819,8 +825,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
pVulkanRenderer->FlushForSyncPoint(sync->submitIndex);
}
if (timeout == 0) {
return pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex) ? GL_ALREADY_SIGNALED
: GL_TIMEOUT_EXPIRED;
return pVulkanRenderer->IsSubmitIndexComplete(sync->submitIndex) ? GL_ALREADY_SIGNALED : GL_TIMEOUT_EXPIRED;
}
// Blocking wait: flush even without the flush bit - the sync's batch
// can only be submitted from this thread, so waiting on an unflushed
@@ -874,7 +879,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// records are shared (SharedPtr) with the owning pool's pending list,
// so deleting the query while results are still in flight is safe.
struct VulkanTimerQuery {
enum class Kind : Uint8 { Timer, Occlusion, XfbWritten, XfbGenerated };
enum class Kind : Uint8 {
Timer,
Occlusion,
XfbWritten,
XfbGenerated
};
Kind kind = Kind::Timer;
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
SharedPtr<VkTimerQueryManager::TimestampRecord> end;
@@ -1003,22 +1013,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
*outNanoseconds = samples;
return true;
}
if (query->kind == VulkanTimerQuery::Kind::XfbWritten ||
query->kind == VulkanTimerQuery::Kind::XfbGenerated) {
if (query->kind == VulkanTimerQuery::Kind::XfbWritten || query->kind == VulkanTimerQuery::Kind::XfbGenerated) {
Uint64 primitives = 0;
if (!pVulkanRenderer->ResolveXfbQueryResult(query->occlusionSlots, query->rerouteSlots,
query->kind == VulkanTimerQuery::Kind::XfbGenerated,
primitives)) {
return false;
}
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated &&
!query->pausedPrimitivesCountedByGpu &&
if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && !query->pausedPrimitivesCountedByGpu &&
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport == MG_Config::TransportMode::Monolith &&
#endif
MG_Pipe::gPipeInputs->IsLive()) {
primitives += MG_Pipe::gPipeInputs->GetTransformFeedbackPausedPrimitiveCounter() -
query->pausedPrimitiveSnapshot;
primitives +=
MG_Pipe::gPipeInputs->GetTransformFeedbackPausedPrimitiveCounter() - query->pausedPrimitiveSnapshot;
}
*outNanoseconds = primitives;
return true;
@@ -1067,7 +1075,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
#if MOBILEGL_BUILD_DISAGGREGATED
MG_Config::Transport == MG_Config::TransportMode::Monolith &&
#endif
MG_Pipe::gPipeInputs->IsLive() ? MG_Pipe::gPipeInputs->GetTransformFeedbackPausedPrimitiveCounter() : 0;
MG_Pipe::gPipeInputs->IsLive()
? MG_Pipe::gPipeInputs->GetTransformFeedbackPausedPrimitiveCounter()
: 0;
// 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.
query->pausedPrimitivesCountedByGpu = generated && pVulkanRenderer->ArePausedDrawsGpuCounted();
@@ -1080,9 +1090,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (query == nullptr || query->rendererGeneration != GetRendererGeneration()) {
return;
}
pVulkanRenderer->StopXfbQueryCapture(
query->kind == VulkanTimerQuery::Kind::XfbGenerated ? 1u : 0u, query->occlusionSlots,
query->rerouteSlots);
pVulkanRenderer->StopXfbQueryCapture(query->kind == VulkanTimerQuery::Kind::XfbGenerated ? 1u : 0u,
query->occlusionSlots, query->rerouteSlots);
}
BackendQueryHandle BeginOcclusionQuery() {
@@ -76,8 +76,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride);
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount,
GLsizei maxdrawcount, GLsizei stride);
void MultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount,
GLsizei stride);
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
const void* indices, GLint basevertex);
void DrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices);
@@ -99,11 +99,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GLsizei height, GLint border);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
GLsizei height);
void CopyImageSubData(const CopyImageEndpoint& src,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dst,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void CopyImageSubData(const CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY,
GLint srcZ, const CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX,
GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void GenerateMipmap(GLenum target);
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
void DispatchComputeIndirect(GLintptr indirect);
@@ -12,9 +12,9 @@
namespace MobileGL::MG_State::GLState {
class ProgramObject;
}
} // namespace MobileGL::MG_State::GLState
namespace MobileGL::MG_Backend::DirectVulkan {
GLuint GetShaderStorageBlockIndex(const MG_State::GLState::ProgramObject& program, const String& name);
GLuint GetShaderStorageBlockBinding(const MG_State::GLState::ProgramObject& program, GLuint blockIndex);
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -40,7 +40,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return static_cast<Uint32>(registry.size() - 1);
}
MagmaSession& Detail::ProcessMagmaSession() { return ProcessSessionInstance(); }
MagmaSession& Detail::ProcessMagmaSession() {
return ProcessSessionInstance();
}
void* Detail::SessionLocalSlotSlow(MagmaSession& session, Uint32 index) {
if (index >= session.m_slots.size()) session.m_slots.resize(Registry().size(), nullptr);
@@ -54,7 +56,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the app renders on, and a lazily built slot would be a first-touch race between two.
const auto& registry = Registry();
m_slots.resize(registry.size(), nullptr);
for (SizeT i = 0; i < registry.size(); ++i) m_slots[i] = registry[i].construct();
for (SizeT i = 0; i < registry.size(); ++i)
m_slots[i] = registry[i].construct();
}
MagmaSession::~MagmaSession() {
@@ -73,10 +76,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Detail::t_boundMagmaSession = session;
}
MagmaSessionScope::~MagmaSessionScope() { Detail::t_boundMagmaSession = m_previous; }
MagmaSessionScope::~MagmaSessionScope() {
Detail::t_boundMagmaSession = m_previous;
}
void BindMagmaSessionToThisThread(MagmaSession* session) { Detail::t_boundMagmaSession = session; }
void BindMagmaSessionToThisThread(MagmaSession* session) {
Detail::t_boundMagmaSession = session;
}
MagmaSession* BoundMagmaSession() { return Detail::t_boundMagmaSession; }
MagmaSession* BoundMagmaSession() {
return Detail::t_boundMagmaSession;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -104,9 +104,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
SessionLocal(const SessionLocal&) = delete;
SessionLocal& operator=(const SessionLocal&) = delete;
T& Get() const {
return *static_cast<T*>(Detail::SessionLocalSlot(Detail::CurrentMagmaSession(), m_index));
}
T& Get() const { return *static_cast<T*>(Detail::SessionLocalSlot(Detail::CurrentMagmaSession(), m_index)); }
T& operator*() const { return Get(); }
T* operator->() const { return &Get(); }
@@ -35,7 +35,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void CollectDeferredReleases(Uint32 frameIndex);
Bool Allocate(Uint32 frameIndex, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice);
Bool Upload(Uint32 frameIndex, const void* data, VkDeviceSize size, VkDeviceSize alignment,
BufferSlice& outSlice);
VkDeviceSize GetWriteCursor(Uint32 frameIndex) const;
Uint32 GetFrameCount() const;
@@ -101,7 +101,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags,
const VkCommandBufferInheritanceInfo* pInheritanceInfo) {
auto& frame = GetCurrent();
MOBILEGL_ASSERT(!frame.isCommandRecording, "BeginCommandRecording called while command buffer is already recording");
MOBILEGL_ASSERT(!frame.isCommandRecording,
"BeginCommandRecording called while command buffer is already recording");
frame.hasCommandBufferRecorded = false;
VK_VERIFY(vkResetCommandBuffer(frame.commandBuffer, 0), "BeginCommandRecording, vkResetCommandBuffer");
@@ -174,8 +175,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_swapchainImageRenderFinishedSemaphores.assign(swapchainImageCount, VK_NULL_HANDLE);
VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
for (Uint32 imageIndex = 0; imageIndex < swapchainImageCount; ++imageIndex) {
VkResult result =
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &m_swapchainImageRenderFinishedSemaphores[imageIndex]);
VkResult result = vkCreateSemaphore(device, &semaphoreInfo, nullptr,
&m_swapchainImageRenderFinishedSemaphores[imageIndex]);
if (result != VK_SUCCESS) {
DestroySwapchainSemaphores(device);
return result;
@@ -240,10 +241,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
presentBarrier.subresourceRange.levelCount = 1;
presentBarrier.subresourceRange.baseArrayLayer = 0;
presentBarrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(commandBuffer,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0,
nullptr, 0, nullptr, 1, &presentBarrier);
vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0,
0, nullptr, 0, nullptr, 1, &presentBarrier);
if (openedRecording) {
EndCommandRecording();
@@ -254,7 +253,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
FrameContext::SubmitInfoPacket FrameContext::GetSubmitInfo(Bool shouldSubmitCommandBuffer,
Uint32 swapchainImageIndex) const {
const auto& frame = GetCurrent();
MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active");
MOBILEGL_ASSERT(!frame.isCommandRecording,
"GetSubmitInfo called while command buffer recording is still active");
MOBILEGL_ASSERT(!frame.isPreCommandRecording,
"GetSubmitInfo called while the pre-pass stream is still recording");
AssertValidSwapchainImageIndex(swapchainImageIndex);
@@ -273,7 +273,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
packet.submitInfo.waitSemaphoreCount = frame.imageAvailableSemaphoreConsumed ? 0U : 1U;
packet.submitInfo.pWaitSemaphores = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitSemaphore;
packet.submitInfo.pWaitDstStageMask = frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
packet.submitInfo.pWaitDstStageMask =
frame.imageAvailableSemaphoreConsumed ? nullptr : &packet.waitDstStageMask;
packet.submitInfo.commandBufferCount = commandBufferCount;
packet.submitInfo.pCommandBuffers = commandBufferCount > 0 ? packet.commandBuffers : nullptr;
packet.submitInfo.signalSemaphoreCount = 1;
@@ -464,8 +465,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DestroySyncObjectsForFrame(device, frameIndex);
auto& frame = m_frames[frameIndex];
VkResult result =
vkCreateSemaphore(device, &semaphoreInfo, nullptr, &frame.imageAvailableSemaphore);
VkResult result = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &frame.imageAvailableSemaphore);
if (result != VK_SUCCESS) {
return result;
}
@@ -120,7 +120,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkResult AcquireNextImageInCurrentSlot(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex);
// The per-swapchain-image render-finished semaphores belong to one swapchain; a renderer
// that parks a surface parks them with it.
void ExchangeSwapchainSemaphores(Vector<VkSemaphore>& other) { std::swap(m_swapchainImageRenderFinishedSemaphores, other); }
void ExchangeSwapchainSemaphores(Vector<VkSemaphore>& other) {
std::swap(m_swapchainImageRenderFinishedSemaphores, other);
}
VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex,
Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE);
@@ -189,7 +189,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandBuffer buffers[kMaxOwn + 2];
Uint32 count = 0;
buffers[count++] = bracket.Begin;
for (Uint32 i = 0; i < info.commandBufferCount; ++i) buffers[count++] = info.pCommandBuffers[i];
for (Uint32 i = 0; i < info.commandBufferCount; ++i)
buffers[count++] = info.pCommandBuffers[i];
buffers[count++] = bracket.End;
const VkCommandBuffer* own = info.pCommandBuffers;
const Uint32 ownCount = info.commandBufferCount;
@@ -80,9 +80,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandBuffer Begin = VK_NULL_HANDLE;
VkCommandBuffer End = VK_NULL_HANDLE;
Uint64 Id = 0;
Bool InFlight = false; // submitted and not yet seen finished
Bool Claimed = false; // handed out by Acquire, not yet submitted or abandoned
Bool Dirty = false; // its events were set by a past submission: reset before reuse
Bool InFlight = false; // submitted and not yet seen finished
Bool Claimed = false; // handed out by Acquire, not yet submitted or abandoned
Bool Dirty = false; // its events were set by a past submission: reset before reuse
Uint64 SubmitIndex = 0; // the renderer submit index of the submission it last bracketed
};
static constexpr Uint32 kMaxSlots = 16;
@@ -81,26 +81,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// longer does it SILENTLY: the combination is named once, at startup, right here.
inline void MagmaPipeValidateSubsystemConfiguration() {
if (!MagmaPipeSubsystemOn(MG_Pipe::kMGPipeSubsystemRenderState)) {
MGLOG_W("MGPipe: MOBILEGL_PIPE_LEGACY_MEMOS=0 with kMGPipeSubsystemRenderState (bit 0 "
"of MOBILEGL_PIPE_PUSH) clear - Magma's pipeline memo has no CSO handle to key "
"on, so every draw whose pipeline-state version moved runs the pre-handle STATE "
"HASH instead. That is not a Track-H subsystem and not fatal, but it is not the "
"handle arm either: set bit 0 (MOBILEGL_PIPE_PUSH=0x%llx) if this run was meant "
"to measure it.",
static_cast<unsigned long long>(MG_Config::Features.PipePush |
MG_Pipe::kMGPipeSubsystemRenderState));
MGLOG_W(
"MGPipe: MOBILEGL_PIPE_LEGACY_MEMOS=0 with kMGPipeSubsystemRenderState (bit 0 "
"of MOBILEGL_PIPE_PUSH) clear - Magma's pipeline memo has no CSO handle to key "
"on, so every draw whose pipeline-state version moved runs the pre-handle STATE "
"HASH instead. That is not a Track-H subsystem and not fatal, but it is not the "
"handle arm either: set bit 0 (MOBILEGL_PIPE_PUSH=0x%llx) if this run was meant "
"to measure it.",
static_cast<unsigned long long>(MG_Config::Features.PipePush | MG_Pipe::kMGPipeSubsystemRenderState));
}
if (MagmaPipeSubsystemOn(MG_Pipe::kMGPipeSubsystemMagmaVertexInput)) return;
const char* const why =
"this build has cmake -DMOBILEGL_PIPE_LEGACY_MEMOS=OFF, which compiles no such arm";
MGLOG_F("MGPipe: Fatal{PipeLegacyMemosDisabled} Magma's Track-H subsystem "
"(kMGPipeSubsystemMagmaVertexInput, bit 6 of MOBILEGL_PIPE_PUSH) is clear, so the "
"vertex-input cache and the VAO draw memo want the pre-handle arm - but %s. Set "
"bit 6 (MOBILEGL_PIPE_PUSH=0x%llx, or the default 0x%llx), or allow the legacy arm.",
why,
static_cast<unsigned long long>(MG_Config::Features.PipePush |
MG_Pipe::kMGPipeSubsystemMagmaVertexInput),
static_cast<unsigned long long>(MG_Pipe::kMGPipeSubsystemsMigratedAtP2));
const char* const why = "this build has cmake -DMOBILEGL_PIPE_LEGACY_MEMOS=OFF, which compiles no such arm";
MGLOG_F(
"MGPipe: Fatal{PipeLegacyMemosDisabled} Magma's Track-H subsystem "
"(kMGPipeSubsystemMagmaVertexInput, bit 6 of MOBILEGL_PIPE_PUSH) is clear, so the "
"vertex-input cache and the VAO draw memo want the pre-handle arm - but %s. Set "
"bit 6 (MOBILEGL_PIPE_PUSH=0x%llx, or the default 0x%llx), or allow the legacy arm.",
why,
static_cast<unsigned long long>(MG_Config::Features.PipePush | MG_Pipe::kMGPipeSubsystemMagmaVertexInput),
static_cast<unsigned long long>(MG_Pipe::kMGPipeSubsystemsMigratedAtP2));
std::abort();
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -34,21 +34,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MG_Pipe::MGPipeHandle Handle() const { return m_handle; }
Uint GetExternalIndex() const { return m_handle.Slot; }
Uint64 GetLifetimeId() const {
return (Uint64(m_handle.Gen) << 32) | m_handle.Slot;
}
Uint64 GetLifetimeId() const { return (Uint64(m_handle.Gen) << 32) | m_handle.Slot; }
Bool GetLinkStatus() const { return m_record->Desc.LinkStatus != 0; }
Bool GetSpirvStatus() const { return m_record->Desc.SpirvStatus != 0; }
Bool PointSizeDemoted() const { return m_record->Desc.PointSizeDemoted != 0; }
Bool GetSpirvValidationEnabled() const {
return m_record->Desc.EnableSpirvValidation != 0;
}
const Vector<Vector<Uint>>& GetGeneratedSpirv() const {
return m_record->Archive->Spirv.generatedSpirv;
}
Bool GetSpirvValidationEnabled() const { return m_record->Desc.EnableSpirvValidation != 0; }
const Vector<Vector<Uint>>& GetGeneratedSpirv() const { return m_record->Archive->Spirv.generatedSpirv; }
Vector<ShaderStage> GetLinkedShaderStages() const {
Vector<ShaderStage> result;
for (const auto stage : m_record->Archive->LinkedStages) result.push_back(static_cast<ShaderStage>(stage));
for (const auto stage : m_record->Archive->LinkedStages)
result.push_back(static_cast<ShaderStage>(stage));
return result;
}
Bool HasLinkedShaderStage(ShaderStage stage) const {
@@ -85,12 +80,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const void* GetUBOData() const { return m_record->GlobalConstants.data(); }
Uint GetUBOSize() const { return static_cast<Uint>(m_record->GlobalConstants.size()); }
Uint32 GetUBOContentVersion() const { return m_record->GlobalConstantsVersion; }
Int GetActiveUniformBlocksCount() const {
return static_cast<Int>(Link().glBlockIndexToTProgram.size());
}
Int GetActiveUniformBlocksCount() const { return static_cast<Int>(Link().glBlockIndexToTProgram.size()); }
Uint GetUniformBlockIndex(const char* name) const {
auto found = Link().uniformBlockIndexByName.find(name);
if (found == Link().uniformBlockIndexByName.end()) found = Link().uniformBlockIndexByName.find(String(name) + "[0]");
if (found == Link().uniformBlockIndexByName.end())
found = Link().uniformBlockIndexByName.find(String(name) + "[0]");
return found == Link().uniformBlockIndexByName.end() ? GL_INVALID_INDEX : found->second;
}
Uint GetUniformBlockBinding(Uint index) const {
@@ -105,20 +99,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto* block = Block(index);
return block ? (static_cast<Uint>(block->size) + 15u) & ~15u : 0;
}
Bool IsValidUniformLocation(Int location) const {
return Program::IsValidUniformLocation(Link(), location);
}
Bool IsValidUniformLocation(Int location) const { return Program::IsValidUniformLocation(Link(), location); }
Bool UniformLocationsAliasSameUniform(Int a, Int b) const {
return IsValidUniformLocation(a) && IsValidUniformLocation(b) &&
Link().uniformIndexInTProgram[a] == Link().uniformIndexInTProgram[b];
Link().uniformIndexInTProgram[a] == Link().uniformIndexInTProgram[b];
}
GLenum GetUniformType(Uint location) const {
return IsValidUniformLocation(static_cast<Int>(location))
? Program::UniformAtIn(Link(), Link().uniformIndexInTProgram[location]).glDefineType : 0;
? Program::UniformAtIn(Link(), Link().uniformIndexInTProgram[location]).glDefineType
: 0;
}
Int GetUniformSamplerOrImageUnitIndex(Uint location) const {
const auto& units = m_record->SamplerUnits;
const auto found = std::lower_bound(units.begin(), units.end(), location,
const auto found = std::lower_bound(
units.begin(), units.end(), location,
[](const MG_Pipe::MGPProgramSamplerUnit& unit, Uint value) { return unit.Location < value; });
return found != units.end() && found->Location == location ? found->Unit : -1;
}
@@ -145,22 +139,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!IsValidUniformLocation(base)) return -1;
const Int tIndex = Link().uniformIndexInTProgram[base];
if (!Program::UniformAtIn(Link(), tIndex).type.isArray ||
static_cast<GLint>(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex)) return -1;
static_cast<GLint>(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex))
return -1;
const Int location = base + static_cast<Int>(element);
return UniformLocationsAliasSameUniform(base, location) ? location : -1;
}
SizeT GetTransformFeedbackVaryingCount() const { return Link().xfbVaryings.size(); }
const Vector<MG_State::GLState::XfbVarying>& GetTransformFeedbackVaryings() const {
return Link().xfbVaryings;
}
const Vector<MG_State::GLState::XfbVarying>& GetTransformFeedbackVaryings() const { return Link().xfbVaryings; }
SizeT GetTransformFeedbackBufferCount() const { return Link().xfbStrides.size(); }
Uint32 GetTransformFeedbackStride(Uint index) const {
return index < Link().xfbStrides.size() ? Link().xfbStrides[index] : 0;
}
GLenum GetTransformFeedbackBufferMode() const {
return Link().xfbBufferMode;
}
GLenum GetTransformFeedbackBufferMode() const { return Link().xfbBufferMode; }
// P7 wave 2 package C, OQ-8 (CONTRACT-P7 §5.3): READ OUT OF THE ARCHIVE.
//
// What stood here re-ran spvReflectCreateShaderModule over EVERY stage module of the
@@ -177,18 +168,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ProgramFactory::ReflectLayout looks up. A caller that still holds a GL-style
// "Foo[2]" gets the same second chance it always had.
const auto find = [&blocks](const String& key) {
return std::find_if(blocks.begin(), blocks.end(),
[&](const MG_State::GLState::StorageBlockReflection& block) {
return block.name == key;
});
return std::find_if(
blocks.begin(), blocks.end(),
[&](const MG_State::GLState::StorageBlockReflection& block) { return block.name == key; });
};
auto found = find(name);
if (found == blocks.end() && !name.empty() && name.back() == ']') {
const auto bracket = name.rfind('[');
if (bracket != String::npos) found = find(name.substr(0, bracket));
}
return found == blocks.end() ? GL_INVALID_INDEX
: static_cast<Uint>(found - blocks.begin());
return found == blocks.end() ? GL_INVALID_INDEX : static_cast<Uint>(found - blocks.begin());
}
Uint GetShaderStorageBlockBinding(Uint index) const {
const auto& blocks = Link().storageBlocks;
@@ -210,7 +199,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (index >= Link().glBlockIndexToTProgram.size()) return nullptr;
const Int tIndex = Link().glBlockIndexToTProgram[index];
return tIndex >= 0 && static_cast<SizeT>(tIndex) < Link().blockReflection.size()
? &Link().blockReflection[tIndex] : nullptr;
? &Link().blockReflection[tIndex]
: nullptr;
}
MG_Pipe::MGPipeHandle m_handle = MG_Pipe::kMGPipeNullHandle;
const MG_Pipe::MGPipeShaderCsoRecord* m_record = nullptr;
@@ -219,4 +209,4 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// also why they never helped: a view lives for one draw, so the latch was cold every
// time it was read.
};
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -13,17 +13,17 @@
namespace MobileGL::MG_Backend::DirectVulkan {
static const char* PrimitiveTopologyToString(VkPrimitiveTopology topology) {
switch (topology) {
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_POINT_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY)
ENUM_STR_CASE(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST)
default:
return "VK_PRIMITIVE_TOPOLOGY_UNKNOWN";
}
@@ -31,13 +31,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static const char* SampleCountToString(VkSampleCountFlagBits sampleCount) {
switch (sampleCount) {
ENUM_STR_CASE(VK_SAMPLE_COUNT_1_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_2_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_4_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_8_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_16_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_32_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_64_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_1_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_2_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_4_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_8_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_16_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_32_BIT)
ENUM_STR_CASE(VK_SAMPLE_COUNT_64_BIT)
default:
return "VK_SAMPLE_COUNT_UNKNOWN";
}
@@ -60,14 +60,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static const char* CompareOpToString(VkCompareOp compareOp) {
switch (compareOp) {
ENUM_STR_CASE(VK_COMPARE_OP_NEVER)
ENUM_STR_CASE(VK_COMPARE_OP_LESS)
ENUM_STR_CASE(VK_COMPARE_OP_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_LESS_OR_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_GREATER)
ENUM_STR_CASE(VK_COMPARE_OP_NOT_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_GREATER_OR_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_ALWAYS)
ENUM_STR_CASE(VK_COMPARE_OP_NEVER)
ENUM_STR_CASE(VK_COMPARE_OP_LESS)
ENUM_STR_CASE(VK_COMPARE_OP_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_LESS_OR_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_GREATER)
ENUM_STR_CASE(VK_COMPARE_OP_NOT_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_GREATER_OR_EQUAL)
ENUM_STR_CASE(VK_COMPARE_OP_ALWAYS)
default:
return "VK_COMPARE_OP_UNKNOWN";
}
@@ -75,29 +75,29 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static const char* LogicOpToString(VkLogicOp logicOp) {
switch (logicOp) {
ENUM_STR_CASE(VK_LOGIC_OP_CLEAR)
ENUM_STR_CASE(VK_LOGIC_OP_AND)
ENUM_STR_CASE(VK_LOGIC_OP_AND_REVERSE)
ENUM_STR_CASE(VK_LOGIC_OP_COPY)
ENUM_STR_CASE(VK_LOGIC_OP_AND_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_NO_OP)
ENUM_STR_CASE(VK_LOGIC_OP_XOR)
ENUM_STR_CASE(VK_LOGIC_OP_OR)
ENUM_STR_CASE(VK_LOGIC_OP_NOR)
ENUM_STR_CASE(VK_LOGIC_OP_EQUIVALENT)
ENUM_STR_CASE(VK_LOGIC_OP_INVERT)
ENUM_STR_CASE(VK_LOGIC_OP_OR_REVERSE)
ENUM_STR_CASE(VK_LOGIC_OP_COPY_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_OR_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_NAND)
ENUM_STR_CASE(VK_LOGIC_OP_SET)
ENUM_STR_CASE(VK_LOGIC_OP_CLEAR)
ENUM_STR_CASE(VK_LOGIC_OP_AND)
ENUM_STR_CASE(VK_LOGIC_OP_AND_REVERSE)
ENUM_STR_CASE(VK_LOGIC_OP_COPY)
ENUM_STR_CASE(VK_LOGIC_OP_AND_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_NO_OP)
ENUM_STR_CASE(VK_LOGIC_OP_XOR)
ENUM_STR_CASE(VK_LOGIC_OP_OR)
ENUM_STR_CASE(VK_LOGIC_OP_NOR)
ENUM_STR_CASE(VK_LOGIC_OP_EQUIVALENT)
ENUM_STR_CASE(VK_LOGIC_OP_INVERT)
ENUM_STR_CASE(VK_LOGIC_OP_OR_REVERSE)
ENUM_STR_CASE(VK_LOGIC_OP_COPY_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_OR_INVERTED)
ENUM_STR_CASE(VK_LOGIC_OP_NAND)
ENUM_STR_CASE(VK_LOGIC_OP_SET)
default:
return "VK_LOGIC_OP_UNKNOWN";
}
}
PipelineFactory::PipelineFactory(VkDevice device, const VulkanRendererConfig& config):
m_device(device), m_config(config) {
PipelineFactory::PipelineFactory(VkDevice device, const VulkanRendererConfig& config)
: m_device(device), m_config(config) {
MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "PipelineFactory: device is null");
if (m_config.DisablePipelineCache) {
@@ -149,8 +149,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// If a future workload pairs another blend shape with an equality chain, widen
// this with that evidence in hand rather than pre-emptively.
Bool IsAccumulationBlend(const VkPipelineColorBlendAttachmentState& attachment) {
return attachment.colorBlendOp == VK_BLEND_OP_MIN ||
attachment.colorBlendOp == VK_BLEND_OP_MAX;
return attachment.colorBlendOp == VK_BLEND_OP_MIN || attachment.colorBlendOp == VK_BLEND_OP_MAX;
}
} // namespace
@@ -204,9 +203,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
constexpr Uint64 wireDomain = 0x5749524552504b59ull;
XXHASH_VERIFY(XXH64_update(hashState, &wireDomain, sizeof(wireDomain)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.wireRenderPassCompatibilityId,
sizeof(payload.wireRenderPassCompatibilityId)));
} else
{
sizeof(payload.wireRenderPassCompatibilityId)));
} else {
XXHASH_VERIFY(XXH64_update(hashState, &payload.renderPass, sizeof(payload.renderPass)));
}
XXHASH_VERIFY(XXH64_update(hashState, &payload.colorAttachmentCount, sizeof(payload.colorAttachmentCount)));
@@ -216,11 +214,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(hashState, &payload.sampleMask, sizeof(payload.sampleMask)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.subpass, sizeof(payload.subpass)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.topology, sizeof(payload.topology)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.primitiveRestartEnable, sizeof(payload.primitiveRestartEnable)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.patchControlPoints, sizeof(payload.patchControlPoints)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.passthroughTessControlKey,
sizeof(payload.passthroughTessControlKey)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.passthroughTessControlKey, sizeof(payload.passthroughTessControlKey)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.viewportCount, sizeof(payload.viewportCount)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.nonLayeredStages, sizeof(payload.nonLayeredStages)));
const Bool fullCoverageSampleMask = payload.fullCoverageFragmentModule != VK_NULL_HANDLE;
@@ -228,8 +225,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(hashState, &payload.polygonMode, sizeof(payload.polygonMode)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.cullMode, sizeof(payload.cullMode)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.frontFace, sizeof(payload.frontFace)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.provokingVertexMode, sizeof(payload.provokingVertexMode)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.depthTestEnable, sizeof(payload.depthTestEnable)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.depthWriteEnable, sizeof(payload.depthWriteEnable)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.depthBiasEnable, sizeof(payload.depthBiasEnable)));
@@ -243,21 +239,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(hashState, &payload.frontStencilPassOp, sizeof(payload.frontStencilPassOp)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.frontStencilDepthFailOp, sizeof(payload.frontStencilDepthFailOp)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.frontStencilCompareOp, sizeof(payload.frontStencilCompareOp)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilFailOp, sizeof(payload.backStencilFailOp)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilPassOp, sizeof(payload.backStencilPassOp)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
XXHASH_VERIFY(
XXH64_update(hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilDepthFailOp, sizeof(payload.backStencilDepthFailOp)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.backStencilCompareOp, sizeof(payload.backStencilCompareOp)));
XXHASH_VERIFY(XXH64_update(hashState, &payload.fragmentReplacesDepth, sizeof(payload.fragmentReplacesDepth)));
if (payload.colorAttachmentCount > 0) {
XXHASH_VERIFY(XXH64_update(
hashState,
payload.colorBlendAttachments.data(),
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
XXHASH_VERIFY(XXH64_update(hashState, payload.colorBlendAttachments.data(),
sizeof(payload.colorBlendAttachments[0]) * payload.colorAttachmentCount));
}
return XXH64_digest(hashState);
}
@@ -284,17 +274,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Log.h ordering fix made MGLOG_E live in INFO builds.
MGLOG_E("PipelineFactory::GetOrCreatePipeline: creation failed for hash=0x%llx "
"programHash=0x%llx; not caching the failure",
static_cast<unsigned long long>(hash),
static_cast<unsigned long long>(payload.programHash));
static_cast<unsigned long long>(hash), static_cast<unsigned long long>(payload.programHash));
return VK_NULL_HANDLE;
}
m_cache.emplace(hash, PipelineCacheEntry{pipeline, payload.programHash,
// Wire pipelines outlive their creator pass and
// may serve another compatible pass in flight.
// Never evict them by a recycled native handle.
payload.wireRenderPassCompatibilityId != 0 ? VK_NULL_HANDLE :
payload.renderPass,
m_frameCounter});
m_cache.emplace(
hash, PipelineCacheEntry{pipeline, payload.programHash,
// Wire pipelines outlive their creator pass and
// may serve another compatible pass in flight.
// Never evict them by a recycled native handle.
payload.wireRenderPassCompatibilityId != 0 ? VK_NULL_HANDLE : payload.renderPass,
m_frameCounter});
return pipeline;
}
@@ -356,8 +345,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
if (evicted > 0) {
MGLOG_D("PipelineFactory::EvictByProgramHash: evicted %u pipelines for program hash 0x%llx",
evicted, static_cast<unsigned long long>(programHash));
MGLOG_D("PipelineFactory::EvictByProgramHash: evicted %u pipelines for program hash 0x%llx", evicted,
static_cast<unsigned long long>(programHash));
}
return evicted;
}
@@ -377,27 +366,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(payload.pipelineLayout != VK_NULL_HANDLE, "PipelineFactory: pipelineLayout is null");
MOBILEGL_ASSERT(payload.renderPass != VK_NULL_HANDLE, "PipelineFactory: renderPass is null");
MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineCreatePayload::kMaxColorAttachments,
"PipelineFactory: colorAttachmentCount=%u is unexpectedly large",
payload.colorAttachmentCount);
MGLOG_D("PipelineFactory::CreatePipeline: programHash=0x%llx vertexInputHash=0x%llx colorAttachmentCount=%u subpass=%u",
static_cast<unsigned long long>(payload.programHash),
static_cast<unsigned long long>(payload.vertexInputHash),
payload.colorAttachmentCount,
payload.subpass);
"PipelineFactory: colorAttachmentCount=%u is unexpectedly large", payload.colorAttachmentCount);
MGLOG_D("PipelineFactory::CreatePipeline: programHash=0x%llx vertexInputHash=0x%llx colorAttachmentCount=%u "
"subpass=%u",
static_cast<unsigned long long>(payload.programHash),
static_cast<unsigned long long>(payload.vertexInputHash), payload.colorAttachmentCount,
payload.subpass);
// The fragment shading rate goes last so the common prefix is the eight-state list every
// pipeline shares; it is only present when the device enabled the pipeline rate.
static constexpr VkDynamicState kDynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_LINE_WIDTH,
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR
};
static constexpr VkDynamicState kDynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR,
VK_DYNAMIC_STATE_BLEND_CONSTANTS,
VK_DYNAMIC_STATE_DEPTH_BIAS,
VK_DYNAMIC_STATE_LINE_WIDTH,
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK,
VK_DYNAMIC_STATE_STENCIL_REFERENCE,
VK_DYNAMIC_STATE_FRAGMENT_SHADING_RATE_KHR};
VkPipelineDynamicStateCreateInfo dynamicState{};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
@@ -552,12 +538,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!s_warnedHalfTessellatedPipeline) {
s_warnedHalfTessellatedPipeline = true;
MGLOG_E_ONCE("PipelineFactory::CreatePipeline: refusing a pipeline with %s tessellation stage and "
"no %s stage (VUID-VkGraphicsPipelineCreateInfo-pStages-00730). programHash=0x%llx "
"patchControlPoints=%u. Its draws are skipped; logged once.",
hasTessEval ? "an evaluation" : "a control",
hasTessEval ? "control" : "evaluation",
static_cast<unsigned long long>(payload.programHash),
payload.patchControlPoints);
"no %s stage (VUID-VkGraphicsPipelineCreateInfo-pStages-00730). programHash=0x%llx "
"patchControlPoints=%u. Its draws are skipped; logged once.",
hasTessEval ? "an evaluation" : "a control", hasTessEval ? "control" : "evaluation",
static_cast<unsigned long long>(payload.programHash), payload.patchControlPoints);
}
return VK_NULL_HANDLE;
}
@@ -568,8 +552,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
gpi.pStages = effectiveStages->data();
gpi.pVertexInputState = payload.vertexInputState;
gpi.pInputAssemblyState = &ia;
gpi.pTessellationState =
payload.topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST ? &tessellation : nullptr;
gpi.pTessellationState = payload.topology == VK_PRIMITIVE_TOPOLOGY_PATCH_LIST ? &tessellation : nullptr;
gpi.pViewportState = &vpci;
gpi.pRasterizationState = &raster;
gpi.pMultisampleState = &ms;
@@ -590,32 +573,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GetOrCreatePipeline deliberately does not cache the failure, which is what makes that
// repetition happen; if the repetition ever needs to stop, fix the pipeline, not the log.
if (result != VK_SUCCESS) {
MGLOG_F("PipelineFactory::CreatePipeline failed: result=%s (%d) programHash=0x%llx vertexInputHash=0x%llx stageCount=%u topology=%s(%d) colorAttachmentCount=%u samples=%s(%d) subpass=%u",
VkResultToString(result),
result,
static_cast<unsigned long long>(payload.programHash),
static_cast<unsigned long long>(payload.vertexInputHash),
gpi.stageCount,
PrimitiveTopologyToString(payload.topology),
payload.topology,
payload.colorAttachmentCount,
SampleCountToString(payload.rasterizationSamples),
payload.rasterizationSamples,
payload.subpass);
MGLOG_F("PipelineFactory::CreatePipeline state: cullMode=%s(0x%x) frontFace=%d depthTest=%d depthWrite=%d depthCompare=%s(%d) depthBias=%d rasterizerDiscard=%d stencilTest=%d logicOpEnable=%d logicOp=%s(%d)",
CullModeToString(payload.cullMode),
static_cast<Uint32>(payload.cullMode),
payload.frontFace,
payload.depthTestEnable ? 1 : 0,
payload.depthWriteEnable ? 1 : 0,
CompareOpToString(payload.depthCompareOp),
payload.depthCompareOp,
payload.depthBiasEnable ? 1 : 0,
payload.rasterizerDiscardEnable ? 1 : 0,
payload.stencilTestEnable ? 1 : 0,
payload.logicOpEnable ? 1 : 0,
LogicOpToString(payload.logicOp),
payload.logicOp);
MGLOG_F("PipelineFactory::CreatePipeline failed: result=%s (%d) programHash=0x%llx vertexInputHash=0x%llx "
"stageCount=%u topology=%s(%d) colorAttachmentCount=%u samples=%s(%d) subpass=%u",
VkResultToString(result), result, static_cast<unsigned long long>(payload.programHash),
static_cast<unsigned long long>(payload.vertexInputHash), gpi.stageCount,
PrimitiveTopologyToString(payload.topology), payload.topology, payload.colorAttachmentCount,
SampleCountToString(payload.rasterizationSamples), payload.rasterizationSamples, payload.subpass);
MGLOG_F(
"PipelineFactory::CreatePipeline state: cullMode=%s(0x%x) frontFace=%d depthTest=%d depthWrite=%d "
"depthCompare=%s(%d) depthBias=%d rasterizerDiscard=%d stencilTest=%d logicOpEnable=%d logicOp=%s(%d)",
CullModeToString(payload.cullMode), static_cast<Uint32>(payload.cullMode), payload.frontFace,
payload.depthTestEnable ? 1 : 0, payload.depthWriteEnable ? 1 : 0,
CompareOpToString(payload.depthCompareOp), payload.depthCompareOp, payload.depthBiasEnable ? 1 : 0,
payload.rasterizerDiscardEnable ? 1 : 0, payload.stencilTestEnable ? 1 : 0,
payload.logicOpEnable ? 1 : 0, LogicOpToString(payload.logicOp), payload.logicOp);
MGLOG_F("PipelineFactory::CreatePipeline vertex input: bindingCount=%u attributeCount=%u",
payload.vertexInputState->vertexBindingDescriptionCount,
payload.vertexInputState->vertexAttributeDescriptionCount);
@@ -629,8 +600,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto& digest = (*payload.stageSpirvDigests)[i];
MGLOG_I("PipelineFactory::CreatePipeline spirv[%zu]: stage=0x%x words=%u bytes=%zu "
"hash=0x%llx",
i, digest.stage, digest.wordCount,
static_cast<SizeT>(digest.wordCount) * sizeof(Uint32),
i, digest.stage, digest.wordCount, static_cast<SizeT>(digest.wordCount) * sizeof(Uint32),
static_cast<unsigned long long>(digest.hash));
}
} else {
@@ -651,16 +621,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
const auto& attachment = payload.colorBlendAttachments[i];
MGLOG_F("PipelineFactory::CreatePipeline colorAttachment[%u]: blend=%d colorWriteMask=0x%x srcColor=%d dstColor=%d colorOp=%d srcAlpha=%d dstAlpha=%d alphaOp=%d",
i,
attachment.blendEnable == VK_TRUE ? 1 : 0,
static_cast<Uint32>(attachment.colorWriteMask),
attachment.srcColorBlendFactor,
attachment.dstColorBlendFactor,
attachment.colorBlendOp,
attachment.srcAlphaBlendFactor,
attachment.dstAlphaBlendFactor,
attachment.alphaBlendOp);
MGLOG_F("PipelineFactory::CreatePipeline colorAttachment[%u]: blend=%d colorWriteMask=0x%x srcColor=%d "
"dstColor=%d colorOp=%d srcAlpha=%d dstAlpha=%d alphaOp=%d",
i, attachment.blendEnable == VK_TRUE ? 1 : 0, static_cast<Uint32>(attachment.colorWriteMask),
attachment.srcColorBlendFactor, attachment.dstColorBlendFactor, attachment.colorBlendOp,
attachment.srcAlphaBlendFactor, attachment.dstAlphaBlendFactor, attachment.alphaBlendOp);
}
}
VK_VERIFY(result, "vkCreateGraphicsPipelines");
@@ -174,8 +174,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static Bool IsSuppressBlendedDepthWriteEnabled() { return s_suppressBlendedDepthWrite; }
// Device gate for the quirk: ForceOn/ForceOff bypass detection, Auto enables it on
// the known-affected vendor (Qualcomm).
static Bool ShouldSuppressBlendedDepthWriteForDevice(MG_Config::QuirkOverride quirkOverride,
Uint32 vendorId);
static Bool ShouldSuppressBlendedDepthWriteForDevice(MG_Config::QuirkOverride quirkOverride, Uint32 vendorId);
// Pure per-pipeline strip decision (exempts gl_FragDepth writers, masked-out and
// non-accumulation blends); combined with the device flag in CreatePipeline. Static
// and payload-only so tests can pin the contract without a VkDevice.
File diff suppressed because it is too large Load Diff
@@ -102,9 +102,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
// What a program parsed below #version 450 carries, and the fallback when a module's
// block cannot be read.
static constexpr Uint32 kDefaultPerVertexMembers =
static_cast<Uint32>(PerVertexMemberBit::Position) | static_cast<Uint32>(PerVertexMemberBit::PointSize) |
static_cast<Uint32>(PerVertexMemberBit::ClipDistance);
static constexpr Uint32 kDefaultPerVertexMembers = static_cast<Uint32>(PerVertexMemberBit::Position) |
static_cast<Uint32>(PerVertexMemberBit::PointSize) |
static_cast<Uint32>(PerVertexMemberBit::ClipDistance);
struct UpdateAfterBindLimits {
Bool enabled = false;
@@ -281,7 +281,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// entry pointer re-stamps use through a const reference (StampProgramUse).
mutable Uint64 lastUsedFrame = 0;
static inline SessionLocal<VkDevice> s_device; // per Magma session (MagmaSession.h)
static inline SessionLocal<VkDevice> s_device; // per Magma session (MagmaSession.h)
VkProgramObject() = default;
VkProgramObject(const VkProgramObject&) = delete;
@@ -315,8 +315,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
storageImageUsesBindingFormatByBinding =
std::move(other.storageImageUsesBindingFormatByBinding);
storageImageUsesBindingFormatByBinding = std::move(other.storageImageUsesBindingFormatByBinding);
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
hasStorageImages = other.hasStorageImages;
@@ -390,8 +389,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
samplerNumericDomainByBinding = std::move(other.samplerNumericDomainByBinding);
storageImageFormatByBinding = std::move(other.storageImageFormatByBinding);
storageImageUsesBindingFormatByBinding =
std::move(other.storageImageUsesBindingFormatByBinding);
storageImageUsesBindingFormatByBinding = std::move(other.storageImageUsesBindingFormatByBinding);
storageBlockNameByBinding = std::move(other.storageBlockNameByBinding);
storageBlockIndexByBinding = std::move(other.storageBlockIndexByBinding);
hasStorageImages = other.hasStorageImages;
@@ -441,9 +439,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return *this;
}
~VkProgramObject() {
Destroy();
}
~VkProgramObject() { Destroy(); }
private:
void Destroy() {
@@ -489,11 +485,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// repair passes patch modules AROUND them, and the emulation only replaces them
// on opted-in devices with no subgroup support at all.
struct SubgroupLoweringPolicy {
Bool emulateSubgroups = false; // no-native-support devices, or MOBILEGL_SUBGROUP_EMULATION
Bool emulateSubgroups = false; // no-native-support devices, or MOBILEGL_SUBGROUP_EMULATION
Bool fixIterationRPSubgroupScratch = false; // patch iterationRP's under-declared scratch
Bool fixIterationRPBarrier = false; // repair Program 203's shared-scratch race
Bool deriveNumSubgroups = false; // repair the NumSubgroups builtin
Bool requireFullSubgroups = false; // computeFullSubgroups enabled on the device
Bool fixIterationRPBarrier = false; // repair Program 203's shared-scratch race
Bool deriveNumSubgroups = false; // repair the NumSubgroups builtin
Bool requireFullSubgroups = false; // computeFullSubgroups enabled on the device
Uint32 nativeSubgroupSize = 0;
// Full-subgroup launches are bounded by this device limit; a dispatch whose
// workgroup needs more subgroups than this cannot request the flag.
@@ -504,18 +500,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
explicit ProgramFactory(VkDevice device, const VulkanRendererConfig& config, Uint32 maxBindings,
Bool shaderDrawParametersEnabled,
Bool unformattedFloatStorageImagesEnabled,
Bool tessellationAndGeometryPointSizeEnabled,
Bool enableSpirvValidation,
UpdateAfterBindLimits updateAfterBindLimits,
SubgroupLoweringPolicy subgroupPolicy)
Bool shaderDrawParametersEnabled, Bool unformattedFloatStorageImagesEnabled,
Bool tessellationAndGeometryPointSizeEnabled, Bool enableSpirvValidation,
UpdateAfterBindLimits updateAfterBindLimits, SubgroupLoweringPolicy subgroupPolicy)
: m_device(device), m_maxBindings(maxBindings), m_config(config),
m_shaderDrawParametersEnabled(shaderDrawParametersEnabled),
m_unformattedFloatStorageImagesEnabled(unformattedFloatStorageImagesEnabled),
m_tessellationAndGeometryPointSizeEnabled(tessellationAndGeometryPointSizeEnabled),
m_enableSpirvValidation(enableSpirvValidation),
m_updateAfterBindLimits(updateAfterBindLimits),
m_enableSpirvValidation(enableSpirvValidation), m_updateAfterBindLimits(updateAfterBindLimits),
m_subgroupPolicy(subgroupPolicy) {
VkProgramObject::s_device.Get() = device;
}
@@ -526,8 +518,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ProgramFactory(const ProgramFactory&) = delete;
HashType ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const;
const VkProgramObject& GetOrCreateProgram(
const MagmaProgramSource& program, CompileOptionFlags flags);
const VkProgramObject& GetOrCreateProgram(const MagmaProgramSource& program, CompileOptionFlags flags);
// The default framebuffer's current image height, baked as a literal into every
// FragCoordYFlip variant (there is no push-constant or specialization channel here, and
@@ -631,9 +622,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (size, levels, members) combinations a program draws with - one or two in practice -
// and only ever happens for the rare program that has no control stage at all.
VkPipelineShaderStageCreateInfo GetOrCreatePassthroughTessControlStage(Uint32 patchVertices,
const FloatVec4& defaultOuterLevel,
const FloatVec2& defaultInnerLevel,
Uint32 perVertexMembers);
const FloatVec4& defaultOuterLevel,
const FloatVec2& defaultInnerLevel,
Uint32 perVertexMembers);
// Source of the module above. Exposed for tests: the generated GLSL is the whole
// contract with the evaluation stage, so it is worth pinning independently of a device.
@@ -668,22 +659,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// `spirv`, at the same index. Taking the stages rather than the shader objects is what
// keeps the program's live attach list, which is a longer and differently-indexed list
// the moment a glAttachShader lands after the link, from being passed here by mistake.
void ReflectVertexInputs(const Vector<ShaderStage>& stages,
const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
void ReflectViewportIndexUsage(const Vector<ShaderStage>& stages,
const Vector<Vector<Uint>>& spirv,
void ReflectVertexInputs(const Vector<ShaderStage>& stages, const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
void ReflectViewportIndexUsage(const Vector<ShaderStage>& stages, const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
void ReflectFragmentOutputs(const Vector<ShaderStage>& stages,
const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
void ReflectFragmentOutputs(const Vector<ShaderStage>& stages, const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
void ReflectLayout(const MagmaProgramSource& program, const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
// Fills needsPassthroughTessControl / passthroughTessControlEmulatable off the linked
// modules. Const and reflection-only: it decides nothing about the pipeline, it only
// records what the evaluation stage's input interface is made of.
void ReflectPassthroughTessControlNeed(const Vector<ShaderStage>& stages,
const Vector<Vector<Uint>>& spirv,
void ReflectPassthroughTessControlNeed(const Vector<ShaderStage>& stages, const Vector<Vector<Uint>>& spirv,
VkProgramObject& entry) const;
VkDevice m_device = VK_NULL_HANDLE;
@@ -13,29 +13,29 @@
namespace MobileGL::MG_Backend::DirectVulkan {
// Collect every fence in the ordered submission prefix. Waiting on only the last matching
// frame-serial record is insufficient: the same serial may have an earlier in-flight submit.
template <typename Records, typename Fence>
bool CollectSubmitFencePrefix(const Records& records, std::uint64_t submitIndex,
Fence nullFence, std::vector<Fence>& fences) {
fences.clear();
bool foundTarget = false;
for (const auto& record : records) {
if (record.submitIndex > submitIndex) {
break;
// Collect every fence in the ordered submission prefix. Waiting on only the last matching
// frame-serial record is insufficient: the same serial may have an earlier in-flight submit.
template <typename Records, typename Fence>
bool CollectSubmitFencePrefix(const Records& records, std::uint64_t submitIndex, Fence nullFence,
std::vector<Fence>& fences) {
fences.clear();
bool foundTarget = false;
for (const auto& record : records) {
if (record.submitIndex > submitIndex) {
break;
}
if (record.fence == nullFence) {
fences.clear();
return false;
}
fences.push_back(record.fence);
foundTarget = foundTarget || record.submitIndex == submitIndex;
}
if (record.fence == nullFence) {
if (!foundTarget) {
fences.clear();
return false;
}
fences.push_back(record.fence);
foundTarget = foundTarget || record.submitIndex == submitIndex;
return true;
}
if (!foundTarget) {
fences.clear();
return false;
}
return true;
}
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -92,7 +92,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} // namespace
SwapchainObject::SwapchainCapabilities SwapchainObject::GetSwapchainCapabilities(VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface) {
VkSurfaceKHR surface) {
SwapchainCapabilities swapchainCapabilities{};
VK_VERIFY(
@@ -102,17 +102,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr));
if (formatCount != 0) {
swapchainCapabilities.surfaceFormats.resize(formatCount);
VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(
physicalDevice, surface, &formatCount, swapchainCapabilities.surfaceFormats.data()));
VK_VERIFY(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount,
swapchainCapabilities.surfaceFormats.data()));
}
Uint32 presentModeCount = 0;
VK_VERIFY(
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr));
VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr));
if (presentModeCount != 0) {
swapchainCapabilities.presentModes.resize(presentModeCount);
VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, surface, &presentModeCount, swapchainCapabilities.presentModes.data()));
VK_VERIFY(vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount,
swapchainCapabilities.presentModes.data()));
}
return swapchainCapabilities;
@@ -139,13 +138,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return availableFormats[0];
}
VkPresentModeKHR SwapchainObject::ChooseSwapchainPresentMode(
const Vector<VkPresentModeKHR>& availablePresentModes, Optional<Int> swapInterval) {
VkPresentModeKHR SwapchainObject::ChooseSwapchainPresentMode(const Vector<VkPresentModeKHR>& availablePresentModes,
Optional<Int> swapInterval) {
static constexpr VkPresentModeKHR s_unthrottledPresentModes[]{
VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR};
static constexpr VkPresentModeKHR s_vsyncPresentModes[]{VK_PRESENT_MODE_FIFO_KHR};
static constexpr VkPresentModeKHR s_adaptiveVsyncPresentModes[]{VK_PRESENT_MODE_FIFO_RELAXED_KHR,
VK_PRESENT_MODE_FIFO_KHR};
VK_PRESENT_MODE_FIFO_KHR};
std::span<const VkPresentModeKHR> ranked = s_desiredPresentModes;
if (swapInterval) {
if (*swapInterval == 0) {
@@ -200,8 +199,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto& swapchainCaps = swapchainCapabilities.capabilities;
// The extra images count from the surface's own minimum: that is how many the platform
// keeps for itself and the application, so only images beyond it delay a buffer's reuse.
Uint32 targetImageCount =
std::max<Uint32>(minImageCountHint, swapchainCaps.minImageCount + extraImageCount);
Uint32 targetImageCount = std::max<Uint32>(minImageCountHint, swapchainCaps.minImageCount + extraImageCount);
if (swapchainCaps.maxImageCount != 0) {
targetImageCount = std::min(targetImageCount, swapchainCaps.maxImageCount);
}
@@ -217,11 +215,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
createInfo.imageColorSpace = pickedSurfaceFormat.colorSpace;
createInfo.imageExtent = swapchainCaps.currentExtent;
if (createInfo.imageExtent.width == UINT32_MAX || createInfo.imageExtent.height == UINT32_MAX) {
createInfo.imageExtent.width = std::clamp(desiredExtent.width,
swapchainCaps.minImageExtent.width,
swapchainCaps.maxImageExtent.width);
createInfo.imageExtent.height = std::clamp(desiredExtent.height,
swapchainCaps.minImageExtent.height,
createInfo.imageExtent.width =
std::clamp(desiredExtent.width, swapchainCaps.minImageExtent.width, swapchainCaps.maxImageExtent.width);
createInfo.imageExtent.height = std::clamp(desiredExtent.height, swapchainCaps.minImageExtent.height,
swapchainCaps.maxImageExtent.height);
}
const VkExtent2D defaultFramebufferExtent = createInfo.imageExtent;
@@ -258,11 +254,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
createInfo.preTransform = swapchainCaps.currentTransform;
MGLOG_I("Set swapchain preTransform = %s", string_VkSurfaceTransformFlagBitsKHR(createInfo.preTransform));
const VkCompositeAlphaFlagBitsKHR compositeAlphaCandidates[] = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR
};
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR};
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
for (auto candidate : compositeAlphaCandidates) {
if ((swapchainCaps.supportedCompositeAlpha & candidate) != 0) {
@@ -312,24 +305,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo;
const Int extentWidth = static_cast<Int>(defaultFramebufferExtent.width);
const Int extentHeight = static_cast<Int>(defaultFramebufferExtent.height);
const SizeT defaultAttachmentByteSize =
static_cast<SizeT>(defaultFramebufferExtent.width) *
static_cast<SizeT>(defaultFramebufferExtent.height) * 4;
const SizeT defaultAttachmentByteSize = static_cast<SizeT>(defaultFramebufferExtent.width) *
static_cast<SizeT>(defaultFramebufferExtent.height) * 4;
TextureInternalFormat depthFormat = TextureInternalFormat::Depth24Stencil8;
switch (m_depthStencilFormat) {
case VK_FORMAT_D24_UNORM_S8_UINT:
depthFormat = TextureInternalFormat::Depth24Stencil8;
break;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
depthFormat = TextureInternalFormat::Depth32FStencil8;
break;
case VK_FORMAT_D32_SFLOAT:
depthFormat = TextureInternalFormat::DepthComponent32F;
break;
default:
depthFormat = TextureInternalFormat::Depth24Stencil8;
break;
case VK_FORMAT_D24_UNORM_S8_UINT:
depthFormat = TextureInternalFormat::Depth24Stencil8;
break;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
depthFormat = TextureInternalFormat::Depth32FStencil8;
break;
case VK_FORMAT_D32_SFLOAT:
depthFormat = TextureInternalFormat::DepthComponent32F;
break;
default:
depthFormat = TextureInternalFormat::Depth24Stencil8;
break;
}
// The default FBO's stencil attachment must track the swapchain extent:
@@ -339,16 +331,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL_INVALID_FRAMEBUFFER_OPERATION on DirectVulkan.
TextureInternalFormat stencilFormat = TextureInternalFormat::Depth24Stencil8;
switch (m_depthStencilFormat) {
case VK_FORMAT_D32_SFLOAT_S8_UINT:
stencilFormat = TextureInternalFormat::Depth32FStencil8;
break;
case VK_FORMAT_D24_UNORM_S8_UINT:
stencilFormat = TextureInternalFormat::Depth24Stencil8;
break;
default:
// No stencil plane; mirror the depth format for consistency.
stencilFormat = depthFormat;
break;
case VK_FORMAT_D32_SFLOAT_S8_UINT:
stencilFormat = TextureInternalFormat::Depth32FStencil8;
break;
case VK_FORMAT_D24_UNORM_S8_UINT:
stencilFormat = TextureInternalFormat::Depth24Stencil8;
break;
default:
// No stencil plane; mirror the depth format for consistency.
stencilFormat = depthFormat;
break;
}
if (MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged != nullptr) {
@@ -367,26 +359,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
info.Layers = 1;
info.IsDefault = 1;
MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged(&info);
} else
{
} else {
auto* colorTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->colorAttachment.get());
colorTex->AllocateStorage(
TextureUploadTarget::Texture2D, 0, {
{extentWidth, extentHeight, 1},
defaultAttachmentByteSize}); // TODO: 4 is format size
TextureUploadTarget::Texture2D, 0,
{{extentWidth, extentHeight, 1}, defaultAttachmentByteSize}); // TODO: 4 is format size
auto* depthTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->depthAttachment.get());
depthTex->SetInternalFormat(depthFormat);
depthTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
{extentWidth, extentHeight, 1},
defaultAttachmentByteSize}); // TODO: 4 is format size
depthTex->AllocateStorage(
TextureUploadTarget::Texture2D, 0,
{{extentWidth, extentHeight, 1}, defaultAttachmentByteSize}); // TODO: 4 is format size
auto* stencilTex = static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->stencilAttachment.get());
auto* stencilTex =
static_cast<MG_State::GLState::TextureObject2D*>(defaultFBOInfo->stencilAttachment.get());
stencilTex->SetInternalFormat(stencilFormat);
stencilTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {
{extentWidth, extentHeight, 1},
defaultAttachmentByteSize}); // TODO: 4 is format size
stencilTex->AllocateStorage(
TextureUploadTarget::Texture2D, 0,
{{extentWidth, extentHeight, 1}, defaultAttachmentByteSize}); // TODO: 4 is format size
}
}
void SwapchainObject::CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice) {
@@ -20,9 +20,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<VkSurfaceFormatKHR> surfaceFormats;
Vector<VkPresentModeKHR> presentModes;
Bool IsComplete() const {
return !surfaceFormats.empty() && !presentModes.empty();
}
Bool IsComplete() const { return !surfaceFormats.empty() && !presentModes.empty(); }
};
static SwapchainCapabilities GetSwapchainCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface);
@@ -97,12 +95,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void CreateImageViews(VkDevice device);
void CreateDepthStencilResources(VkDevice device, VkPhysicalDevice physicalDevice);
void DestroyDepthStencilResources(VkDevice device);
static constexpr VkPresentModeKHR s_desiredPresentModes[] {
VK_PRESENT_MODE_MAILBOX_KHR,
VK_PRESENT_MODE_IMMEDIATE_KHR,
VK_PRESENT_MODE_FIFO_RELAXED_KHR,
VK_PRESENT_MODE_FIFO_KHR
};
static constexpr VkPresentModeKHR s_desiredPresentModes[]{
VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR,
VK_PRESENT_MODE_FIFO_KHR};
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
VkPresentModeKHR m_presentMode = VK_PRESENT_MODE_FIFO_KHR;
@@ -44,8 +44,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
SizeT UniformManager::WireImageViewKeyHash::operator()(const WireImageViewKey& key) const {
SizeT hash = std::hash<VkImage>{}(key.image);
const auto mix = [&hash](Uint64 value) {
hash ^= std::hash<Uint64>{}(value) + static_cast<SizeT>(0x9e3779b97f4a7c15ULL) +
(hash << 6) + (hash >> 2);
hash ^= std::hash<Uint64>{}(value) + static_cast<SizeT>(0x9e3779b97f4a7c15ULL) + (hash << 6) + (hash >> 2);
};
mix(key.root.Slot);
mix(key.root.Gen);
@@ -72,26 +71,34 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", detail);
}
static Bool ResolveWireRange(Uint64 offset, Uint64 declaredSize, VkDeviceSize bufferSize,
VkDeviceSize& start, VkDeviceSize& size) {
static Bool ResolveWireRange(Uint64 offset, Uint64 declaredSize, VkDeviceSize bufferSize, VkDeviceSize& start,
VkDeviceSize& size) {
if (offset >= bufferSize) return false;
start = static_cast<VkDeviceSize>(offset);
const VkDeviceSize remaining = bufferSize - start;
size = declaredSize == MG_Pipe::kMGPipeWholeBuffer
? remaining : std::min<VkDeviceSize>(declaredSize, remaining);
size =
declaredSize == MG_Pipe::kMGPipeWholeBuffer ? remaining : std::min<VkDeviceSize>(declaredSize, remaining);
return size != 0;
}
static VkImageViewType WireViewType(MG_Pipe::MGPipeResourceTarget target) {
using T = MG_Pipe::MGPipeResourceTarget;
switch (target) {
case T::Tex1D: return VK_IMAGE_VIEW_TYPE_1D;
case T::Tex1DArray: return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case T::Tex3D: return VK_IMAGE_VIEW_TYPE_3D;
case T::TexCube: return VK_IMAGE_VIEW_TYPE_CUBE;
case T::TexCubeArray: return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case T::Tex2DArray: case T::Tex2DMSArray: return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
default: return VK_IMAGE_VIEW_TYPE_2D;
case T::Tex1D:
return VK_IMAGE_VIEW_TYPE_1D;
case T::Tex1DArray:
return VK_IMAGE_VIEW_TYPE_1D_ARRAY;
case T::Tex3D:
return VK_IMAGE_VIEW_TYPE_3D;
case T::TexCube:
return VK_IMAGE_VIEW_TYPE_CUBE;
case T::TexCubeArray:
return VK_IMAGE_VIEW_TYPE_CUBE_ARRAY;
case T::Tex2DArray:
case T::Tex2DMSArray:
return VK_IMAGE_VIEW_TYPE_2D_ARRAY;
default:
return VK_IMAGE_VIEW_TYPE_2D;
}
}
@@ -99,8 +106,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// view, and an ignored nonzero Layer must not address another slice.
static Bool WireTargetHasLayers(MG_Pipe::MGPipeResourceTarget target) {
using T = MG_Pipe::MGPipeResourceTarget;
return target == T::Tex1DArray || target == T::Tex2DArray || target == T::Tex2DMSArray ||
target == T::Tex3D || target == T::TexCube || target == T::TexCubeArray;
return target == T::Tex1DArray || target == T::Tex2DArray || target == T::Tex2DMSArray || target == T::Tex3D ||
target == T::TexCube || target == T::TexCubeArray;
}
// GL 4.6 core 8.26: an access through an image unit whose texture does not HAVE the
@@ -140,20 +147,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static VkComponentSwizzle WireSwizzle(Uint8 value) {
switch (static_cast<TextureSwizzleParam>(value)) {
case TextureSwizzleParam::Red: return VK_COMPONENT_SWIZZLE_R;
case TextureSwizzleParam::Green: return VK_COMPONENT_SWIZZLE_G;
case TextureSwizzleParam::Blue: return VK_COMPONENT_SWIZZLE_B;
case TextureSwizzleParam::Alpha: return VK_COMPONENT_SWIZZLE_A;
case TextureSwizzleParam::Zero: return VK_COMPONENT_SWIZZLE_ZERO;
case TextureSwizzleParam::One: return VK_COMPONENT_SWIZZLE_ONE;
default: WireDescriptorFatal("sampler-swizzle");
case TextureSwizzleParam::Red:
return VK_COMPONENT_SWIZZLE_R;
case TextureSwizzleParam::Green:
return VK_COMPONENT_SWIZZLE_G;
case TextureSwizzleParam::Blue:
return VK_COMPONENT_SWIZZLE_B;
case TextureSwizzleParam::Alpha:
return VK_COMPONENT_SWIZZLE_A;
case TextureSwizzleParam::Zero:
return VK_COMPONENT_SWIZZLE_ZERO;
case TextureSwizzleParam::One:
return VK_COMPONENT_SWIZZLE_ONE;
default:
WireDescriptorFatal("sampler-swizzle");
}
}
Bool UniformManager::ResolveWireImageDescriptor(
VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 element,
Bool storage, VkDescriptorImageInfo& out, VkTextureManager::TextureResource** outResource) const {
Bool UniformManager::ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, Bool storage, VkDescriptorImageInfo& out,
VkTextureManager::TextureResource** outResource) const {
out = {};
if (outResource != nullptr) *outResource = nullptr;
auto& state = MG_Pipe::MGPipeApplier();
@@ -161,8 +175,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Int location = baseLocation + static_cast<Int>(element);
if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, location)) 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;
if (unit < 0 ||
static_cast<Uint32>(unit) >= (storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits))
return false;
const auto handle = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
// An EMPTY image unit is the same 8.26 invalid access as the named-but-missing texel
// below, so it takes the same answer: a null storage descriptor where the device has
@@ -190,8 +205,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool layerable = WireTargetHasLayers(target);
Uint32 level = storage ? state.BoundShaderImages[unit].Level : record.Params.BaseLevel;
// GL ignores Layer/Layered for non-layerable targets (WireTargetHasLayers).
Uint32 layer = storage && layerable && !state.BoundShaderImages[unit].Layered
? state.BoundShaderImages[unit].Layer : 0;
Uint32 layer =
storage && layerable && !state.BoundShaderImages[unit].Layered ? state.BoundShaderImages[unit].Layer : 0;
const Uint32 localLevel = level;
const Uint32 localLayer = layer;
Uint32 layers = 0;
@@ -207,8 +222,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 levels = 1;
if (!storage) {
if (record.Params.MaxLevel < localLevel || record.Desc.Levels <= localLevel) return false;
levels = std::min<Uint32>(record.Desc.Levels - localLevel,
record.Params.MaxLevel - localLevel + 1);
levels = std::min<Uint32>(record.Desc.Levels - localLevel, record.Params.MaxLevel - localLevel + 1);
const auto viewHandle = state.BoundSamplerViews[unit].View;
if (!MG_Pipe::MGPipeHandleIsNull(viewHandle) && viewHandle.Slot < state.SamplerViewCsos.size()) {
const auto& view = state.SamplerViewCsos[viewHandle.Slot];
@@ -229,8 +243,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto domain = programObj.samplerNumericDomainByBinding[binding];
if (storage) {
format = ResolveStorageImageViewFormat(programObj.storageImageFormatByBinding[binding],
state.BoundShaderImages[unit].InternalFormat, format,
programObj.storageImageUsesBindingFormatByBinding[binding]);
state.BoundShaderImages[unit].InternalFormat, format,
programObj.storageImageUsesBindingFormatByBinding[binding]);
} else if (resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT) {
format = VkTextureManager::ResolveSampledImageViewFormat(format, domain);
}
@@ -238,14 +252,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageAspectFlags aspect = resource->aspect;
if (!storage && (aspect & VK_IMAGE_ASPECT_DEPTH_BIT))
aspect = record.Params.DepthStencilMode == MG_Pipe::kMGPipeDepthStencilModeStencil
? VK_IMAGE_ASPECT_STENCIL_BIT : VK_IMAGE_ASPECT_DEPTH_BIT;
? VK_IMAGE_ASPECT_STENCIL_BIT
: VK_IMAGE_ASPECT_DEPTH_BIT;
// All wire image descriptors use GENERAL. A texture sampled and image-bound in
// the same shader then has one truthful layout, including views of the same owner.
if (!VkTextureManager::TransitionImageLayout(commandBuffer, resource->image, resource->layout,
VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
resource->layout == VK_IMAGE_LAYOUT_UNDEFINED ? 0 : VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, resource->aspect, 0, resource->mipLevels))
if (!VkTextureManager::TransitionImageLayout(
commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_GENERAL,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
resource->layout == VK_IMAGE_LAYOUT_UNDEFINED ? 0
: VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, resource->aspect, 0, resource->mipLevels))
WireDescriptorFatal("image-descriptor-transition");
// A second descriptor/draw can keep GENERAL after a shader write. A layout
// equality fast return is not a memory dependency for that prior write.
@@ -268,18 +285,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
info.subresourceRange = {aspect, level, levels, layer, layers};
if (!storage) {
// An X-format shared image carries undefined alpha bytes, exactly like an expanded RGB.
const Bool alphaIsOne = resource->sharedImageAlphaOne || ResolveTextureFormatInfo(
static_cast<TextureInternalFormat>(record.Desc.InternalFormat)).expandRgbToRgba;
const Bool alphaIsOne =
resource->sharedImageAlphaOne ||
ResolveTextureFormatInfo(static_cast<TextureInternalFormat>(record.Desc.InternalFormat))
.expandRgbToRgba;
const auto swizzle = [&](Uint32 channel) {
const auto value = record.Params.Swizzle[channel];
return alphaIsOne && static_cast<TextureSwizzleParam>(value) == TextureSwizzleParam::Alpha
? VK_COMPONENT_SWIZZLE_ONE : WireSwizzle(value);
? VK_COMPONENT_SWIZZLE_ONE
: WireSwizzle(value);
};
info.components = {swizzle(0), swizzle(1), swizzle(2), swizzle(3)};
}
VkImageView view = VK_NULL_HANDLE;
const WireImageViewKey key{root, info.image, m_textureManager->GetTextureImageEpoch(),
info.flags, info.viewType, info.format, info.components, info.subresourceRange};
const WireImageViewKey key{root,
info.image,
m_textureManager->GetTextureImageEpoch(),
info.flags,
info.viewType,
info.format,
info.components,
info.subresourceRange};
const auto& frame = m_frames[m_wireFrameIndex];
const auto cached = frame.wireImageViewCache.find(key);
if (cached != frame.wireImageViewCache.end()) {
@@ -306,26 +332,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// session - the DirectGLES arm declines the push in both cases.
using SamplerCsoRecord = std::decay_t<decltype(state.SamplerCsos[0])>;
static const decltype(SamplerCsoRecord::Params) kDefaultSamplerParams{};
const Bool known = !MG_Pipe::MGPipeHandleIsNull(samplerHandle) &&
samplerHandle.Slot < state.SamplerCsos.size();
const Bool known =
!MG_Pipe::MGPipeHandleIsNull(samplerHandle) && samplerHandle.Slot < state.SamplerCsos.size();
const SamplerCsoRecord* sampler = known ? &state.SamplerCsos[samplerHandle.Slot] : nullptr;
const Bool stale = sampler == nullptr || !sampler->Live || sampler->Gen != samplerHandle.Gen;
if (stale) {
MGLOG_E_ONCE("Magma wire: sampler CSO {%u, %u} named by texture unit %u is missing or no longer "
"live; sampling with default parameters", samplerHandle.Slot, samplerHandle.Gen, unit);
"live; sampling with default parameters",
samplerHandle.Slot, samplerHandle.Gen, unit);
}
const Bool forceNearest =
domain == SamplerNumericDomain::SignedInteger || domain == SamplerNumericDomain::UnsignedInteger;
static_assert(kWireSamplerMemoUnits == MG_Pipe::kMGPipeMaxTextureUnits,
"the wire sampler memo has one entry per texture unit");
auto& memo = m_wireSamplerMemo[static_cast<SizeT>(unit)];
const Bool memoHit = !stale && memo.valid && memo.slot == samplerHandle.Slot &&
memo.gen == samplerHandle.Gen && memo.serial == sampler->Serial &&
memo.contextSerial == state.ContextSerial &&
memo.format == record.Desc.InternalFormat && memo.viewLevelCount == levels &&
memo.forceNearest == forceNearest &&
memo.frameBoundary == m_samplerManager->FrameBoundaryCount() &&
memo.destroyEpoch == m_samplerManager->DestroyEpoch();
const Bool memoHit =
!stale && memo.valid && memo.slot == samplerHandle.Slot && memo.gen == samplerHandle.Gen &&
memo.serial == sampler->Serial && memo.contextSerial == state.ContextSerial &&
memo.format == record.Desc.InternalFormat && memo.viewLevelCount == levels &&
memo.forceNearest == forceNearest && memo.frameBoundary == m_samplerManager->FrameBoundaryCount() &&
memo.destroyEpoch == m_samplerManager->DestroyEpoch();
if (memoHit) {
out.sampler = memo.sampler;
} else {
@@ -379,7 +405,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return (properties.bufferFeatures & requiredFeature) == requiredFeature;
}
}
} // namespace
#include "WirePlaceholderImages.inc"
@@ -402,12 +428,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return reflectedFormat != VK_FORMAT_UNDEFINED ? reflectedFormat : resourceFormat;
}
Bool UniformManager::Initialize(VkDevice device, VkPhysicalDevice physicalDevice,
VkBufferManager* bufferManager,
ProgramFactory* programFactory,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings, Uint32 setsPerFrame,
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Bool UniformManager::Initialize(VkDevice device, VkPhysicalDevice physicalDevice, VkBufferManager* bufferManager,
ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment,
Uint32 frameCount, Uint32 maxBindings, Uint32 setsPerFrame,
VkTextureManager* textureManager, VkSamplerManager* samplerManager) {
Shutdown();
MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "UniformDescriptorBinder::Initialize requires valid VkDevice");
@@ -465,7 +489,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkDescriptorPool initialPool = VK_NULL_HANDLE;
if (!CreateDescriptorPool(m_setsPerFrame, false, initialPool)) {
MGLOG_E_ONCE("UniformDescriptorBinder::Initialize failed: cannot create frame descriptor pool %u",
frameIndex);
frameIndex);
Shutdown();
return false;
}
@@ -491,7 +515,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (auto& frame : m_frames) {
if (m_device != VK_NULL_HANDLE) {
frame.wireImageViewCache.clear();
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
for (const auto view : frame.wireImageViews)
vkDestroyImageView(m_device, view, nullptr);
frame.wireImageViews.clear();
for (auto& view : frame.texelBufferViews) {
if (view != VK_NULL_HANDLE) {
@@ -515,7 +540,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.quietEpochs = 0;
}
m_frames.clear();
for (auto& entry : m_wirePlaceholderImages) DestroyWirePlaceholderImage(entry.second);
for (auto& entry : m_wirePlaceholderImages)
DestroyWirePlaceholderImage(entry.second);
m_wirePlaceholderImages.clear();
m_wirePrivateStoragePlaceholders = 0;
m_wireInvalidStorageImagesBindNull = false;
@@ -542,7 +568,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& frame = m_frames[frameIndex];
m_wireFrameIndex = frameIndex;
frame.wireImageViewCache.clear();
for (const auto view : frame.wireImageViews) vkDestroyImageView(m_device, view, nullptr);
for (const auto view : frame.wireImageViews)
vkDestroyImageView(m_device, view, nullptr);
frame.wireImageViews.clear();
for (auto& view : frame.texelBufferViews) {
if (view != VK_NULL_HANDLE) {
@@ -552,9 +579,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
frame.texelBufferViews.clear();
if (frame.peakAllocatedSetsThisFrame > m_peakDescriptorSetsObserved) {
m_peakDescriptorSetsObserved = frame.peakAllocatedSetsThisFrame;
MGLOG_D(
"UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, pools=%zu)",
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
MGLOG_D("UniformDescriptorBinder: new descriptor set peak observed=%u (base setsPerFrame=%u, frame=%u, "
"pools=%zu)",
m_peakDescriptorSetsObserved, m_setsPerFrame, frameIndex, frame.descriptorPools.size());
}
// The epoch that just ended is the one of the slot begun last - this slot again on the
// drain path, the previous frame's slot on the Present path. Its peak is still unreset.
@@ -725,22 +752,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Uint32 frameIndex,
Bool storage, VkBufferView& out) {
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 frameIndex, Bool storage,
VkBufferView& out) {
out = VK_NULL_HANDLE;
if (binding >= programObj.samplerUniformLocationByBinding.size() ||
binding >= programObj.samplerNumericDomainByBinding.size() || frameIndex >= m_frames.size()) return false;
binding >= programObj.samplerNumericDomainByBinding.size() || frameIndex >= m_frames.size())
return false;
const Int location = programObj.samplerUniformLocationByBinding[binding];
if (location < 0) return false;
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
const auto& state = MG_Pipe::MGPipeApplier();
if (unit < 0 || static_cast<Uint32>(unit) >=
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false;
if (unit < 0 ||
static_cast<Uint32>(unit) >= (storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits))
return false;
if (storage && binding >= programObj.storageImageFormatByBinding.size()) return false;
const VkFormat declaredFormat = storage ? programObj.storageImageFormatByBinding[binding] : VK_FORMAT_UNDEFINED;
const auto placeholder = [&] {
out = AcquireUnboundTexelBufferView(declaredFormat,
programObj.samplerNumericDomainByBinding[binding], storage);
out = AcquireUnboundTexelBufferView(declaredFormat, programObj.samplerNumericDomainByBinding[binding],
storage);
return out != VK_NULL_HANDLE;
};
const auto texture = storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
@@ -754,14 +784,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (MG_Pipe::MGPipeHandleIsNull(buffer)) return placeholder();
BufferSlice slice{};
if (!m_bufferManager->AcquireWireSlice(BufferKind::TextureBuffer, buffer, slice) || !slice.IsValid()) return false;
if (!m_bufferManager->AcquireWireSlice(BufferKind::TextureBuffer, buffer, slice) || !slice.IsValid())
return false;
const auto internalFormat = static_cast<TextureInternalFormat>(record.Desc.InternalFormat);
VkFormat format = declaredFormat;
if (storage && format == VK_FORMAT_UNDEFINED && state.BoundShaderImages[unit].InternalFormat != 0)
format = MG_Util::ConvertTextureInternalFormatToVkEnum(MG_Util::ConvertGLEnumToTextureInternalFormat(
state.BoundShaderImages[unit].InternalFormat));
format = MG_Util::ConvertTextureInternalFormatToVkEnum(
MG_Util::ConvertGLEnumToTextureInternalFormat(state.BoundShaderImages[unit].InternalFormat));
if (format == VK_FORMAT_UNDEFINED) format = MG_Util::ConvertTextureInternalFormatToVkEnum(internalFormat);
const auto feature = storage ? VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT : VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
const auto feature =
storage ? VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT : VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
if (format == VK_FORMAT_UNDEFINED || !BufferFormatSupportsFeature(m_physicalDevice, format, feature)) {
// P7 A.5, a decline (rule I (a)). GL's buffer-texture format list is wider than what
// Vulkan mandates for texel buffers - GL_RGB32{F,I,UI} and the 16-bit UNORM members
@@ -785,7 +817,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
const VkDeviceSize texelSize = MG_Util::GetSizedInternalFormatSizeInBytes(internalFormat);
VkDeviceSize start = 0, size = 0;
if (texelSize == 0 || !ResolveWireRange(record.Desc.BufOffset, record.Desc.BufSize, slice.size, start, size)) return false;
if (texelSize == 0 || !ResolveWireRange(record.Desc.BufOffset, record.Desc.BufSize, slice.size, start, size))
return false;
size = std::min(size / texelSize, static_cast<VkDeviceSize>(m_wireMaxTexelElements)) * texelSize;
if (size == 0) return false;
if (start > std::numeric_limits<VkDeviceSize>::max() - slice.offset)
@@ -799,8 +832,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool writable = storage && state.BoundShaderImages[unit].Access != kMGPipeImageAccessReadOnly;
MGLOG_E_ONCE("ResolveWireTexelBufferDescriptor: binding %u is bound at offset %llu, which is not a "
"multiple of this device's minTexelBufferOffsetAlignment (%llu); the %s view is DECLINED "
"(unaligned-texel-buffer-range)", binding,
static_cast<unsigned long long>(slice.offset + start),
"(unaligned-texel-buffer-range)",
binding, static_cast<unsigned long long>(slice.offset + start),
static_cast<unsigned long long>(m_wireTexelOffsetAlignment),
writable ? "writable" : "read-only");
// A read-only fetch can still be served the way an unbound buffer texture is -
@@ -827,9 +860,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::ResolveTexelBufferDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 frameIndex,
VkBufferView& outBufferView) {
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 frameIndex, VkBufferView& outBufferView) {
return ResolveWireTexelBufferDescriptor(program, programObj, binding, frameIndex, false, outBufferView);
}
@@ -862,12 +894,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(binding < programObj.storageBlockIndexByBinding.size(),
"ResolveStorageBufferDescriptor: binding %u out of range", binding);
if ((binding >= programObj.storageBlockIndexByBinding.size() ||
binding >= programObj.storageBlockNameByBinding.size()))
binding >= programObj.storageBlockNameByBinding.size()))
WireDescriptorFatal("storage-buffer-reflection-binding");
const Int blockIndex = programObj.storageBlockIndexByBinding[binding];
MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u",
binding);
MOBILEGL_ASSERT(blockIndex >= 0, "ResolveStorageBufferDescriptor: no SSBO block mapped to binding %u", binding);
if (blockIndex < 0) WireDescriptorFatal("storage-buffer-reflection-block");
// An atomic counter is not an SSBO the application ever declared: glslang lowers every
// atomic_uint onto a synthesized gl_AtomicCounterBlock_<N> storage block, where N is the
@@ -895,12 +926,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// synthesizes one counter block per GL binding, so a counter block is never an instance
// array and `element` is always 0 there; the +element rule stays with the SSBO case.
const GLuint frontendBinding =
isAtomicCounterBlock
? static_cast<GLuint>(atomicCounterBinding)
: program.GetShaderStorageBlockBinding(static_cast<GLuint>(blockIndex)) + element;
isAtomicCounterBlock ? static_cast<GLuint>(atomicCounterBinding)
: program.GetShaderStorageBlockBinding(static_cast<GLuint>(blockIndex)) + element;
const auto& state = MG_Pipe::MGPipeApplier();
const Uint32 cls = isAtomicCounterBlock ? MG_Pipe::kMGPipeShaderBufferClassAtomicCounter
: MG_Pipe::kMGPipeShaderBufferClassShaderStorage;
: MG_Pipe::kMGPipeShaderBufferClassShaderStorage;
if (frontendBinding >= MG_Pipe::kMGPipeMaxBufferBindingPoints)
WireDescriptorFatal("storage-buffer-binding-point");
// The applier retains entries outside the last partial update window. Count
@@ -913,7 +943,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
BufferSlice slice{};
if (!m_bufferManager->AcquireWireSlice(BufferKind::ShaderStorage, range.Res, slice) || !slice.IsValid()) return false;
if (!m_bufferManager->AcquireWireSlice(BufferKind::ShaderStorage, range.Res, slice) || !slice.IsValid())
return false;
VkDeviceSize start = 0, size = 0;
if (!ResolveWireRange(range.Offset, range.Size, slice.size, start, size)) return false;
if (start > std::numeric_limits<VkDeviceSize>::max() - slice.offset)
@@ -973,8 +1004,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool UniformManager::ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
const MagmaProgramSource& program,
Bool UniformManager::ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 element,
VkDescriptorImageInfo& outImageInfo) const {
@@ -984,8 +1014,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferView UniformManager::AcquireUnboundTexelBufferView(VkFormat declaredFormat,
SamplerNumericDomain numericDomain, Bool storage) {
MOBILEGL_ASSERT(m_bufferManager != nullptr, "AcquireUnboundTexelBufferView: buffer manager is null");
const VkFormatFeatureFlags requiredFeature = storage ? VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT
: VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
const VkFormatFeatureFlags requiredFeature =
storage ? VK_FORMAT_FEATURE_STORAGE_TEXEL_BUFFER_BIT : VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT;
const VkFormat fallbackFormat = PlaceholderFormatForNumericDomain(numericDomain);
VkFormat format = declaredFormat;
@@ -1054,10 +1084,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return imageAccess != GL_READ_ONLY && imageLevel >= samplerBaseLevel && imageLevel <= samplerMaxLevel;
}
Bool UniformManager::ResolveWireUniformBufferPayload(const MagmaProgramSource& program,
Uint32 blockIndex, Uint32 bindingPoint, UboBindResult& out) const {
if (bindingPoint >= MG_Pipe::kMGPipeMaxBufferBindingPoints)
WireDescriptorFatal("uniform-buffer-binding-point");
Bool UniformManager::ResolveWireUniformBufferPayload(const MagmaProgramSource& program, Uint32 blockIndex,
Uint32 bindingPoint, UboBindResult& out) const {
if (bindingPoint >= MG_Pipe::kMGPipeMaxBufferBindingPoints) WireDescriptorFatal("uniform-buffer-binding-point");
VkDeviceSize blockSize = program.GetUBOSizeAt(blockIndex);
if (blockSize == 0) return false;
if (blockSize > m_wireMaxUniformRange) {
@@ -1077,13 +1106,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static_cast<unsigned long long>(m_wireMaxUniformRange));
blockSize = m_wireMaxUniformRange;
}
const auto& range = MG_Pipe::MGPipeApplier().BoundShaderBuffers[
MG_Pipe::kMGPipeShaderBufferClassUniform][bindingPoint];
const auto& range =
MG_Pipe::MGPipeApplier().BoundShaderBuffers[MG_Pipe::kMGPipeShaderBufferClassUniform][bindingPoint];
BufferSlice source{};
VkDeviceSize start = 0, available = 0;
const Bool bound = !MG_Pipe::MGPipeHandleIsNull(range.Res);
if (bound) {
if (!m_bufferManager->AcquireWireSlice(BufferKind::Uniform, range.Res, source) || !source.IsValid()) return false;
if (!m_bufferManager->AcquireWireSlice(BufferKind::Uniform, range.Res, source) || !source.IsValid())
return false;
if (!ResolveWireRange(range.Offset, range.Size, source.size, start, available)) return false;
if (start > std::numeric_limits<VkDeviceSize>::max() - source.offset)
WireDescriptorFatal("uniform-buffer-offset-overflow");
@@ -1107,7 +1137,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
zero.assign(static_cast<SizeT>(blockSize), 0);
BufferSlice padded{};
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, m_wireFrameIndex, zero.data(), blockSize,
std::max<VkDeviceSize>(4, m_minDynamicOffsetAlignment), padded) || !padded.IsValid()) return false;
std::max<VkDeviceSize>(4, m_minDynamicOffsetAlignment), padded) ||
!padded.IsValid())
return false;
if (bound && copied != 0) {
// P7 A.1 retires `uniform-buffer-byte-tail`. The window is word-aligned at both
// ends on nearly every bind - glBindBufferRange already forces the offset onto
@@ -1121,10 +1153,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// slice and one extra region copy, and nothing outside [start, start + copied)
// reaches the block, which is what makes the padded zeros the visible tail.
const Bool wordWindow = ((start | copied) & 3u) == 0;
if (!(wordWindow
? m_bufferManager->CopyWireBufferRangeToSlice(range.Res, start, copied, padded)
: m_bufferManager->CopyWireBufferSubWordRangeToSlice(range.Res, start, copied,
m_wireFrameIndex, padded, 0)))
if (!(wordWindow ? m_bufferManager->CopyWireBufferRangeToSlice(range.Res, start, copied, padded)
: m_bufferManager->CopyWireBufferSubWordRangeToSlice(range.Res, start, copied,
m_wireFrameIndex, padded, 0)))
return false;
}
if (padded.offset > std::numeric_limits<Uint32>::max()) {
@@ -1184,9 +1215,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const auto arrayIt = programObj.arrayedUniformBlockIndicesByBinding.find(binding);
const Bool elementValid = arrayIt != programObj.arrayedUniformBlockIndicesByBinding.end() &&
arrayElement < arrayIt->second.size();
MOBILEGL_ASSERT(elementValid,
"ResolveUniformBufferPayload: UBO binding %u has no array element %u", binding,
arrayElement);
MOBILEGL_ASSERT(elementValid, "ResolveUniformBufferPayload: UBO binding %u has no array element %u",
binding, arrayElement);
if (!elementValid) {
return false;
}
@@ -1258,7 +1288,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkResult result = vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &outPool);
if (result != VK_SUCCESS) {
MGLOG_E_ONCE("UniformDescriptorBinder::CreateDescriptorPool failed: vkCreateDescriptorPool returned %d",
result);
result);
return false;
}
return true;
@@ -1292,8 +1322,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// rather than failing the draw - halving down to the base size, where the old policy lived.
while (!CreateDescriptorPool(grownMaxSets, updateAfterBind, grownPool)) {
if (grownMaxSets <= m_setsPerFrame) {
MGLOG_E_ONCE("UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u sets)",
grownMaxSets);
MGLOG_E_ONCE(
"UniformDescriptorBinder::GrowFrameDescriptorPool failed: cannot create grown pool (%u sets)",
grownMaxSets);
return false;
}
grownMaxSets = std::max(m_setsPerFrame, grownMaxSets / 2);
@@ -1310,18 +1341,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet) {
VkResult UniformManager::AllocateDescriptorSetsFromActivePool(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
const Bool updateAfterBind = programObj.usesUpdateAfterBind;
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size() ||
frame.descriptorPools[frame.activeDescriptorPoolIndex].updateAfterBind != updateAfterBind ||
frame.descriptorPools[frame.activeDescriptorPoolIndex].allocatedSets >=
frame.descriptorPools[frame.activeDescriptorPoolIndex].maxSets) {
const auto availableBucket = std::find_if(
frame.descriptorPools.begin(), frame.descriptorPools.end(),
[updateAfterBind](const DescriptorPoolBucket& candidate) {
return candidate.updateAfterBind == updateAfterBind && candidate.allocatedSets < candidate.maxSets;
});
const auto availableBucket = std::find_if(frame.descriptorPools.begin(), frame.descriptorPools.end(),
[updateAfterBind](const DescriptorPoolBucket& candidate) {
return candidate.updateAfterBind == updateAfterBind &&
candidate.allocatedSets < candidate.maxSets;
});
if (availableBucket == frame.descriptorPools.end()) {
outDescriptorSet = VK_NULL_HANDLE;
return VK_ERROR_OUT_OF_POOL_MEMORY;
@@ -1347,8 +1380,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return result;
}
VkResult UniformManager::AcquireDescriptorSet(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkResult UniformManager::AcquireDescriptorSet(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet) {
auto& frame = m_frames[frameIndex];
auto& cache = frame.descriptorSetCacheByLayout[programObj.descriptorSetLayout];
@@ -1376,22 +1408,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
++frame.allocatedSetsThisFrame;
frame.peakAllocatedSetsThisFrame =
std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
frame.peakAllocatedSetsThisFrame = std::max(frame.peakAllocatedSetsThisFrame, frame.allocatedSetsThisFrame);
return VK_SUCCESS;
}
Bool UniformManager::ResolveDynamicUboDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 binding, Uint32 arrayElement, Uint32 frameIndex,
VkBuffer& outBuffer, VkDeviceSize& outRange,
Uint32& outDynamicOffset) {
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 arrayElement, Uint32 frameIndex, VkBuffer& outBuffer,
VkDeviceSize& outRange, Uint32& outDynamicOffset) {
UboBindResult ubo{};
const Bool hasPayload = ResolveUniformBufferPayload(program, programObj, binding, arrayElement, ubo);
if (!hasPayload) return false;
MOBILEGL_ASSERT(hasPayload && (ubo.directBindable || (ubo.payload != nullptr && ubo.payloadSize > 0)),
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: missing UBO payload on binding %u element %u",
binding, arrayElement);
MOBILEGL_ASSERT(
hasPayload && (ubo.directBindable || (ubo.payload != nullptr && ubo.payloadSize > 0)),
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: missing UBO payload on binding %u element %u",
binding, arrayElement);
if (ubo.directBindable) {
// Zero-copy: bind the app's resident VkBuffer directly, no per-draw memcpy.
outBuffer = ubo.buffer;
@@ -1421,7 +1452,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!m_bufferManager->UploadTransient(BufferKind::Uniform, frameIndex, ubo.payload, ubo.payloadSize,
m_minDynamicOffsetAlignment, slice)) {
MOBILEGL_ASSERT(false,
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: UBO upload failed on binding %u element %u",
"UniformDescriptorBinder::ResolveDynamicUboDescriptor failed: UBO upload failed on binding "
"%u element %u",
binding, arrayElement);
return false;
}
@@ -1438,7 +1470,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (isGlobalUbo) {
m_globalUboMemo[m_globalUboMemoNext] =
GlobalUboSliceMemo{uboProgramLifetimeId, uboFrameSerial, uboContentVersion,
slice.buffer, slice.offset, static_cast<VkDeviceSize>(ubo.payloadSize)};
slice.buffer, slice.offset, static_cast<VkDeviceSize>(ubo.payloadSize)};
m_globalUboMemoNext = (m_globalUboMemoNext + 1) % kGlobalUboMemoSize;
}
return true;
@@ -1450,9 +1482,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Skip the driver call when this exact binding is already live on the
// command buffer (see the bind-dedup shadow in the header).
const Uint32 offsetCount = static_cast<Uint32>(dynamicOffsets.size());
Bool identicalBind = m_lastBindValid && m_lastBindSet == descriptorSet &&
m_lastBindLayout == pipelineLayout && m_lastBindPoint == bindPoint &&
m_lastBindOffsetCount == offsetCount && offsetCount <= kMaxShadowedDynamicOffsets;
Bool identicalBind = m_lastBindValid && m_lastBindSet == descriptorSet && m_lastBindLayout == pipelineLayout &&
m_lastBindPoint == bindPoint && m_lastBindOffsetCount == offsetCount &&
offsetCount <= kMaxShadowedDynamicOffsets;
if (identicalBind) {
for (Uint32 i = 0; i < offsetCount; ++i) {
if (m_lastBindOffsets[i] != dynamicOffsets[i]) {
@@ -1462,8 +1494,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
if (!identicalBind) {
vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1,
&descriptorSet, offsetCount, dynamicOffsets.data());
vkCmdBindDescriptorSets(commandBuffer, bindPoint, pipelineLayout, 0, 1, &descriptorSet, offsetCount,
dynamicOffsets.data());
if (offsetCount <= kMaxShadowedDynamicOffsets) {
m_lastBindValid = true;
m_lastBindSet = descriptorSet;
@@ -1478,7 +1510,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool UniformManager::PrepareWireTextureResources(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj) {
const ProgramFactory::VkProgramObject& programObj) {
if (programObj.declinedDescriptors || m_textureManager == nullptr) {
MGL_WIRE_DECLINE_AT(DescriptorDeclined, "the program carries a descriptor MobileGL could not resolve");
return false;
@@ -1504,12 +1536,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast<Uint>(location));
if (unit < 0 || static_cast<Uint32>(unit) >=
(storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) {
(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;
const auto handle =
storage ? state.BoundShaderImages[unit].Res : state.BoundSamplerViews[unit].Texture;
// Null is the wire's unbound/incomplete binding. Its native
// placeholder needs no resource upload or frontend allocation.
if (MG_Pipe::MGPipeHandleIsNull(handle)) continue;
@@ -1530,11 +1562,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 frameIndex,
VkPipelineBindPoint bindPoint) {
Bool UniformManager::BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex,
VkPipelineBindPoint bindPoint) {
// This program has a descriptor MobileGL could not resolve (see
// VkProgramObject::declinedDescriptors). Refusing here is the whole of the decline: the
// binding is still declared in the layout, so the pipeline is consistent with the shader
@@ -1548,7 +1578,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
auto& frame = m_frames[frameIndex];
if (frame.descriptorPools.empty()) {
MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
MGLOG_E_ONCE(
"UniformDescriptorBinder::BindProgramUniformBuffers failed: frame descriptor pools are invalid");
return false;
}
if (frame.activeDescriptorPoolIndex >= frame.descriptorPools.size()) {
@@ -1610,7 +1641,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
texelBufferViews.reserve(m_maxBindings);
dynamicOffsets.reserve(programObj.dynamicBindings.size() + uboArrayExtra);
// Iterate only the bindings this program declares. The old walk covered all 256 slots of
// bindingKinds on every draw to find the 1-8 a real program uses.
for (const Uint32 binding : programObj.activeBindings) {
@@ -1657,9 +1687,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferView bufferView = VK_NULL_HANDLE;
if (!ResolveTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
bufferView == VK_NULL_HANDLE) {
MGLOG_E_ONCE(
"UniformDescriptorBinder::BindProgramUniformBuffers failed: texture buffer binding %u has no valid descriptor",
binding);
MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: texture buffer binding %u "
"has no valid descriptor",
binding);
return false;
}
@@ -1676,8 +1706,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!ResolveStorageTexelBufferDescriptor(program, programObj, binding, frameIndex, bufferView) ||
bufferView == VK_NULL_HANDLE) {
MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: image buffer binding %u "
"has no valid descriptor",
binding);
"has no valid descriptor",
binding);
return false;
}
@@ -1746,8 +1776,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool hasImage = ResolveWireImageDescriptor(commandBuffer, program, programObj, binding,
element, false, imageInfo, &sampledResource);
// A placeholder (an empty unit) has no resource to re-check: full resolve.
if (sampledResource == nullptr) rebindEligible = false;
else rebindResources.push_back(sampledResource);
if (sampledResource == nullptr)
rebindEligible = false;
else
rebindResources.push_back(sampledResource);
if (!hasImage) {
MGLOG_E_ONCE(
"UniformDescriptorBinder::BindProgramUniformBuffers failed: sampler binding %u element %u "
@@ -1783,9 +1815,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// bypass and invalidate the cache.
Uint64 signature = 0xcbf29ce484222325ULL;
{
const auto mix64 = [&signature](Uint64 word) {
signature = (signature ^ word) * 0x100000001b3ULL;
};
const auto mix64 = [&signature](Uint64 word) { signature = (signature ^ word) * 0x100000001b3ULL; };
// The hashed descriptor payloads (VkDescriptorBufferInfo=24B,
// VkDescriptorImageInfo=24B, VkBufferView=8B) are all 8-byte-multiple sized
// and value-initialized (padding is zero), so hashing 64-bit words at a time
@@ -1807,8 +1837,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the memo miss on every draw.
mix64(m_bufferManager->GetWireStoreDestroyEpoch());
for (const auto& write : writes) {
mix64((static_cast<Uint64>(write.dstBinding) << 40) ^
(static_cast<Uint64>(write.descriptorType) << 8) ^
mix64((static_cast<Uint64>(write.dstBinding) << 40) ^ (static_cast<Uint64>(write.descriptorType) << 8) ^
static_cast<Uint64>(write.descriptorCount));
}
mixWords(bufferInfos.data(), bufferInfos.size() * sizeof(VkDescriptorBufferInfo));
@@ -1828,8 +1857,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} else {
VkResult allocResult = AcquireDescriptorSet(frameIndex, programObj, descriptorSet);
if (allocResult != VK_SUCCESS || descriptorSet == VK_NULL_HANDLE) {
MGLOG_E_ONCE("UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d",
allocResult);
MGLOG_E_ONCE(
"UniformDescriptorBinder::BindProgramUniformBuffers failed: descriptor set acquire returned %d",
allocResult);
return false;
}
for (auto& write : writes) {
@@ -1838,19 +1868,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!writes.empty()) {
vkUpdateDescriptorSets(m_device, static_cast<Uint32>(writes.size()), writes.data(), 0, nullptr);
}
m_descriptorReuseMemo[m_descriptorReuseMemoNext] =
DescriptorReuseEntry{signature, descriptorSet, true};
m_descriptorReuseMemo[m_descriptorReuseMemoNext] = DescriptorReuseEntry{signature, descriptorSet, true};
m_descriptorReuseMemoNext = (m_descriptorReuseMemoNext + 1) % kDescriptorReuseMemoSize;
}
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet,
dynamicOffsets);
BindDescriptorSetDeduped(commandBuffer, bindPoint, programObj.pipelineLayout, descriptorSet, dynamicOffsets);
// Stamp what this full resolve depended on, for TryRebindWireDescriptorSet. Taken after
// the walk: the walk's own syncs and barriers are part of what it proved.
auto& rebind = m_wireDescriptorRebind;
rebind.valid = rebindEligible && descriptorSet != VK_NULL_HANDLE &&
WireImageWriteEpoch() == m_wireImageBarrierEpoch;
rebind.valid =
rebindEligible && descriptorSet != VK_NULL_HANDLE && WireImageWriteEpoch() == m_wireImageBarrierEpoch;
if (rebind.valid) {
const auto& st = MG_Pipe::MGPipeApplier();
rebind.frameIndex = frameIndex;
@@ -1891,8 +1919,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
const auto& st = MG_Pipe::MGPipeApplier();
if (rebind.contextSerial != st.ContextSerial || rebind.samplerViewsSerial != st.SamplerViewsSerial ||
rebind.samplerStatesSerial != st.SamplerStatesSerial || rebind.shaderImagesSerial != st.ShaderImagesSerial ||
rebind.shaderBuffersSerial != st.ShaderBuffersSerial || rebind.textureShutterSerial != st.TextureShutterSerial)
rebind.samplerStatesSerial != st.SamplerStatesSerial ||
rebind.shaderImagesSerial != st.ShaderImagesSerial ||
rebind.shaderBuffersSerial != st.ShaderBuffersSerial ||
rebind.textureShutterSerial != st.TextureShutterSerial)
return false;
if (rebind.storeDestroyEpoch != m_bufferManager->GetWireStoreDestroyEpoch() ||
rebind.textureImageEpoch != m_textureManager->GetTextureImageEpoch() ||
@@ -23,7 +23,7 @@ namespace MobileGL::MG_State::GLState {
class ITextureObject;
class ProgramObject;
class SamplerObject;
}
} // namespace MobileGL::MG_State::GLState
namespace MobileGL::MG_Backend::DirectVulkan {
class UniformManager {
@@ -40,10 +40,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// an unbound texel-buffer binding has to be built from a format the DEVICE accepts as a
// texel buffer, and there is no other route to that answer from here.
Bool Initialize(VkDevice device, VkPhysicalDevice physicalDevice, VkBufferManager* bufferManager,
ProgramFactory* programFactory,
VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64,
VkTextureManager* textureManager = nullptr, VkSamplerManager* samplerManager = nullptr);
ProgramFactory* programFactory, VkDeviceSize minUniformBufferOffsetAlignment, Uint32 frameCount,
Uint32 maxBindings = 16, Uint32 setsPerFrame = 64, VkTextureManager* textureManager = nullptr,
VkSamplerManager* samplerManager = nullptr);
void Shutdown();
void BeginFrame(Uint32 frameIndex);
@@ -95,17 +94,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 textureLifetimeId = 0;
Uint64 samplerLifetimeId = 0;
};
static Bool SamplerOverlapsWritableImageSubresource(Int samplerBaseLevel, Int samplerMaxLevel,
GLint imageLevel, GLenum imageAccess);
static Bool SamplerOverlapsWritableImageSubresource(Int samplerBaseLevel, Int samplerMaxLevel, GLint imageLevel,
GLenum imageAccess);
// Resolve lazy texture uploads/promotions before the caller captures the
// command buffer passed to BindProgramUniformBuffers. Preparation may
// submit older work, but descriptor recording must never rotate it.
Bool PrepareWireTextureResources(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj);
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer,
const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj,
Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj);
Bool BindProgramUniformBuffers(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex,
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS);
// Pure format-policy helper kept public for host regression tests. Formatted storage
@@ -154,7 +151,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DescriptorPoolCensus GetDescriptorPoolCensus() const;
private:
// A cached descriptor set together with the pool it was allocated from, so a
// layout-destroyed purge can vkFreeDescriptorSets it back and credit the
// owning bucket's accounting.
@@ -188,8 +184,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
components.r == other.components.r && components.g == other.components.g &&
components.b == other.components.b && components.a == other.components.a &&
range.aspectMask == other.range.aspectMask && range.baseMipLevel == other.range.baseMipLevel &&
range.levelCount == other.range.levelCount && range.baseArrayLayer == other.range.baseArrayLayer &&
range.layerCount == other.range.layerCount;
range.levelCount == other.range.levelCount &&
range.baseArrayLayer == other.range.baseArrayLayer && range.layerCount == other.range.layerCount;
}
};
@@ -222,8 +218,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (GL 4.6 core 8.26); UNDEFINED keeps the numeric-domain R32 default. `unit` is the image
// unit of a storage binding (what its private placeholder is private to); unused for a sampler.
Bool ResolveWirePlaceholderImage(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding, Bool storage,
VkDescriptorImageInfo& out, VkFormat boundStorageFormat = VK_FORMAT_UNDEFINED, Uint32 unit = ~0u) const;
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Bool storage, VkDescriptorImageInfo& out,
VkFormat boundStorageFormat = VK_FORMAT_UNDEFINED, Uint32 unit = ~0u) const;
void DestroyWirePlaceholderImage(WirePlaceholderImage& image) const;
// descriptorSetCacheByLayout is the per-frame set cache: BeginFrame rewinds every cursor, so
@@ -264,9 +261,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferView AcquireUnboundTexelBufferView(VkFormat declaredFormat, SamplerNumericDomain numericDomain,
Bool storage);
Bool ResolveWireImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, Bool storage, VkDescriptorImageInfo& out,
VkTextureManager::TextureResource** outResource = nullptr) const;
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, Bool storage, VkDescriptorImageInfo& out,
VkTextureManager::TextureResource** outResource = nullptr) const;
// P14: THE WIRE DESCRIPTOR REBIND. A draw whose image descriptors cannot have changed since
// the last full resolve of the same program in the same frame - same applier context, same
// unit / sampler / image / buffer-binding serials, same texture shutter, no texture image
@@ -301,8 +298,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ProgramFactory::VkProgramObject& programObj, Uint32 frameIndex,
VkPipelineBindPoint bindPoint);
Bool ResolveWireTexelBufferDescriptor(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 frameIndex, Bool storage, VkBufferView& out);
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 frameIndex, Bool storage, VkBufferView& out);
Uint32 m_wireFrameIndex = 0;
VkDeviceSize m_wireStorageOffsetAlignment = 1;
VkDeviceSize m_wireTexelOffsetAlignment = 1;
@@ -328,8 +325,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 element, VkDescriptorBufferInfo& outBufferInfo) const;
// `element` indexes an image ARRAY inside one binding; each element carries its own
// independently assigned GL image unit.
Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer,
const MagmaProgramSource& program,
Bool ResolveStorageImageDescriptor(VkCommandBuffer commandBuffer, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 element, VkDescriptorImageInfo& outImageInfo) const;
// Result of resolving a UBO binding: either a zero-copy direct bind to the app's resident
@@ -342,8 +338,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const void* payload = nullptr; // fallback UploadTransient path
VkDeviceSize payloadSize = 0;
};
Bool ResolveWireUniformBufferPayload(const MagmaProgramSource& program, Uint32 blockIndex,
Uint32 bindingPoint, UboBindResult& out) const;
Bool ResolveWireUniformBufferPayload(const MagmaProgramSource& program, Uint32 blockIndex, Uint32 bindingPoint,
UboBindResult& out) const;
Bool ResolveUniformBufferPayload(const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, Uint32 binding,
Uint32 arrayElement, UboBindResult& out) const;
@@ -386,10 +382,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void TrimFrameDescriptorPools(FrameResources& frame, Uint32 frameIndex);
// Drops every handle this class holds to a descriptor set outside the per-layout caches.
void InvalidateDescriptorSetMemos();
VkResult AllocateDescriptorSetsFromActivePool(
Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj, VkDescriptorSet& outDescriptorSet);
VkResult AcquireDescriptorSet(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkResult AllocateDescriptorSetsFromActivePool(Uint32 frameIndex,
const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet);
VkResult AcquireDescriptorSet(Uint32 frameIndex, const ProgramFactory::VkProgramObject& programObj,
VkDescriptorSet& outDescriptorSet);
VkDevice m_device = VK_NULL_HANDLE;
@@ -25,8 +25,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_state.pVertexAttributeDescriptions = nullptr;
}
VertexInputStateBuilder& VertexInputStateBuilder::AddBinding(
Uint32 binding, Uint32 stride, VkVertexInputRate inputRate) {
VertexInputStateBuilder& VertexInputStateBuilder::AddBinding(Uint32 binding, Uint32 stride,
VkVertexInputRate inputRate) {
VkVertexInputBindingDescription desc{};
desc.binding = binding;
desc.stride = stride;
@@ -35,8 +35,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return *this;
}
VertexInputStateBuilder& VertexInputStateBuilder::AddAttribute(
Uint32 location, Uint32 binding, VkFormat format, Uint32 offset) {
VertexInputStateBuilder& VertexInputStateBuilder::AddAttribute(Uint32 location, Uint32 binding, VkFormat format,
Uint32 offset) {
VkVertexInputAttributeDescription desc{};
desc.location = location;
desc.binding = binding;
@@ -15,8 +15,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VertexInputStateBuilder();
void Reset();
VertexInputStateBuilder& AddBinding(
Uint32 binding, Uint32 stride, VkVertexInputRate inputRate = VK_VERTEX_INPUT_RATE_VERTEX);
VertexInputStateBuilder& AddBinding(Uint32 binding, Uint32 stride,
VkVertexInputRate inputRate = VK_VERTEX_INPUT_RATE_VERTEX);
VertexInputStateBuilder& AddAttribute(Uint32 location, Uint32 binding, VkFormat format, Uint32 offset);
const VkPipelineVertexInputStateCreateInfo& Build();
@@ -37,8 +37,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} // namespace
const VertexInputStateFactory::BackendVertexInputState* VertexInputStateFactory::ResolveWireVertexInput(
const MG_Pipe::MGPipeVertexElementsRecord& elements, const MG_Pipe::MGPipeApplierState& state,
Uint32 activeMask) const {
const MG_Pipe::MGPipeVertexElementsRecord& elements, const MG_Pipe::MGPipeApplierState& state,
Uint32 activeMask) const {
auto* memo = static_cast<WireVertexInputMemo*>(elements.BackendMemo.get());
if (memo == nullptr) {
auto created = std::make_shared<WireVertexInputMemo>();
@@ -77,7 +77,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool VertexInputStateFactory::BuildWireVertexInput(const MG_Pipe::MGPipeVertexElementsRecord& elements,
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask, BackendVertexInputState& out) const {
const MG_Pipe::MGPipeApplierState& state, Uint32 activeMask,
BackendVertexInputState& out) const {
// The caller's state is a per-draw scratch (WireDraw.cpp): reset it without freeing.
out.ResetKeepingCapacity();
for (Uint32 location = 0; location < elements.AttributeCount; ++location) {
@@ -117,11 +118,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// a Fatal for a number no application can reach on purpose. Recorded in
// docs/archive/notes/p7/magma-c.md rather than silently reconciled.
const auto describe = [&](const char* reason, VkFormat format) {
MGLOG_E_ONCE("Magma wire vertex layout: %s location=%u type=%u size=%u normalized=%u integer=%u long=%u bgra=%u stride=%d offset=%llu format=%d bufferWindow=%u+%u activeMask=0x%x",
reason, location, attr.Type, static_cast<Uint32>(attr.Size), static_cast<Uint32>(attr.Normalized),
static_cast<Uint32>(attr.IsInteger), static_cast<Uint32>(attr.IsLong), static_cast<Uint32>(attr.IsBgra),
attr.Stride, static_cast<unsigned long long>(attr.Offset), static_cast<Int>(format),
state.VertexBufferStart, state.VertexBufferCount, activeMask);
MGLOG_E_ONCE("Magma wire vertex layout: %s location=%u type=%u size=%u normalized=%u integer=%u "
"long=%u bgra=%u stride=%d offset=%llu format=%d bufferWindow=%u+%u activeMask=0x%x",
reason, location, attr.Type, static_cast<Uint32>(attr.Size),
static_cast<Uint32>(attr.Normalized), static_cast<Uint32>(attr.IsInteger),
static_cast<Uint32>(attr.IsLong), static_cast<Uint32>(attr.IsBgra), attr.Stride,
static_cast<unsigned long long>(attr.Offset), static_cast<Int>(format),
state.VertexBufferStart, state.VertexBufferCount, activeMask);
};
// The protocol verdict: the caller turns a false into the named Fatal.
const auto reject = [&](const char* reason, VkFormat format = VK_FORMAT_UNDEFINED) {
@@ -138,13 +141,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
// set_vertex_buffers is flattened PER ATTRIBUTE (VertexInputEmit.h), not
// indexed by the original ARB binding point in attr.BindingIndex.
if (location < state.VertexBufferStart ||
location - state.VertexBufferStart >= state.VertexBufferCount) return reject("buffer-window");
if (location < state.VertexBufferStart || location - state.VertexBufferStart >= state.VertexBufferCount)
return reject("buffer-window");
const auto& buffer = state.VertexBuffers[location];
const auto type = static_cast<DataType>(attr.Type);
if (attr.Stride < 0 || attr.Size < 1 || attr.Size > 4) { maskOut("attribute-shape"); continue; }
VkFormat format = ToVkVertexFormat(type, attr.Size, attr.Normalized, attr.IsInteger,
attr.IsBgra, attr.IsLong);
if (attr.Stride < 0 || attr.Size < 1 || attr.Size > 4) {
maskOut("attribute-shape");
continue;
}
VkFormat format =
ToVkVertexFormat(type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra, attr.IsLong);
auto conversion = VertexStreamConversion::None;
if (format == VK_FORMAT_UNDEFINED && type == DataType::Float64) {
const auto* backend = MG_Backend::ApplyRoleBackend();
@@ -168,29 +174,45 @@ namespace MobileGL::MG_Backend::DirectVulkan {
format = ToFloat32VertexFormat(attr.Size);
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
}
if (format == VK_FORMAT_UNDEFINED) { maskOut("format-map"); continue; }
if (format == VK_FORMAT_UNDEFINED) {
maskOut("format-map");
continue;
}
if (!SupportsVertexBufferFormat(format)) {
if (!IsScaledIntegerVertexFormat(format)) { maskOut("native-format-feature", format); continue; }
if (!IsScaledIntegerVertexFormat(format)) {
maskOut("native-format-feature", format);
continue;
}
format = ToFloat32VertexFormat(attr.Size);
conversion = VertexStreamConversion::ScaledIntegerToFloat32;
if (!SupportsVertexBufferFormat(format)) { maskOut("converted-format-feature", format); continue; }
if (!SupportsVertexBufferFormat(format)) {
maskOut("converted-format-feature", format);
continue;
}
}
const SizeT elementSize = GetAttributeByteSize(type, attr.Size, attr.IsBgra);
if (!elementSize) { maskOut("element-size", format); continue; }
if (!elementSize) {
maskOut("element-size", format);
continue;
}
const Uint64 offset = attr.Offset + buffer.Offset;
if (offset < attr.Offset) { maskOut("offset-overflow", format); continue; }
if (offset < attr.Offset) {
maskOut("offset-overflow", format);
continue;
}
const SizeT alignment = (type == DataType::Int2101010Rev || type == DataType::Uint2101010Rev)
? elementSize : GetComponentSize(type);
? elementSize
: GetComponentSize(type);
if (conversion == VertexStreamConversion::None && alignment > 1 &&
(offset % alignment || static_cast<Uint32>(attr.Stride) % alignment))
conversion = VertexStreamConversion::Repack;
Uint32 stride = static_cast<Uint32>(attr.Stride);
if (stride && conversion != VertexStreamConversion::None)
stride = conversion == VertexStreamConversion::Repack ? static_cast<Uint32>(elementSize)
: static_cast<Uint32>(attr.Size) * sizeof(Float);
: static_cast<Uint32>(attr.Size) * sizeof(Float);
const Uint32 binding = static_cast<Uint32>(out.bindings.size());
out.bindings.push_back({binding, stride,
buffer.Divisor ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX});
out.bindings.push_back(
{binding, stride, buffer.Divisor ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX});
out.attributes.push_back({location, binding, format, 0});
out.bindingAttributeLocations.push_back(location);
out.bindingBaseOffsets.push_back(static_cast<SizeT>(offset));
@@ -202,8 +224,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// resolved binding/attribute/divisor values, including a legal zero stride.
Uint64 hash = XXH64(out.bindings.data(), out.bindings.size() * sizeof(out.bindings[0]), 0);
hash = XXH64(out.attributes.data(), out.attributes.size() * sizeof(out.attributes[0]), hash);
hash = XXH64(out.bindingDivisors.data(),
out.bindingDivisors.size() * sizeof(out.bindingDivisors[0]), hash);
hash = XXH64(out.bindingDivisors.data(), out.bindingDivisors.size() * sizeof(out.bindingDivisors[0]), hash);
// THE MASK IS PART OF THE LAYOUT NOW, for the same reason the monolith entry's hash
// carries it (the XXH64_update over unsupportedAttribMask in GetOrCreateVertexInputState
// above). Before this package the mask was always zero here, so leaving it out of the
@@ -271,60 +292,84 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P5c (hd, CONTRACT-P5C §3.7): see the narrowFloat64Arrays site above.
MG_Config::Transport != MG_Config::TransportMode::Monolith
? (MG_Remote::Server::ServerLoopInstance().Backend() == nullptr ||
!MG_Remote::Server::ServerLoopInstance().Backend()
!MG_Remote::Server::ServerLoopInstance()
.Backend()
->GetDynamicParameters()
.SupportsFloat64VertexAttributes)
:
#endif
MG_Backend::pActiveBackendObject == nullptr ||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
MG_Backend::pActiveBackendObject == nullptr ||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
return VK_FORMAT_UNDEFINED;
}
if (!isLong || isInteger || normalized) return VK_FORMAT_UNDEFINED;
switch (size) {
case 1: return VK_FORMAT_R32G32_UINT;
case 2: return VK_FORMAT_R32G32B32A32_UINT;
case 1:
return VK_FORMAT_R32G32_UINT;
case 2:
return VK_FORMAT_R32G32B32A32_UINT;
// A dvec3/dvec4 input is 6/8 uint32 components: no single VkFormat, and GL spreads it
// over two attribute locations, which the location-per-VAO-index model here does not
// express. Declined rather than fetched wrong.
default: return VK_FORMAT_UNDEFINED;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Float32:
switch (size) {
case 1: return VK_FORMAT_R32_SFLOAT;
case 2: return VK_FORMAT_R32G32_SFLOAT;
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
default: return VK_FORMAT_UNDEFINED;
case 1:
return VK_FORMAT_R32_SFLOAT;
case 2:
return VK_FORMAT_R32G32_SFLOAT;
case 3:
return VK_FORMAT_R32G32B32_SFLOAT;
case 4:
return VK_FORMAT_R32G32B32A32_SFLOAT;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Float16:
// GL_HALF_FLOAT is a floating-point array type: it is never an integer attribute, and
// GL_TRUE for `normalized` is ignored for float types rather than selecting a *NORM format.
if (isInteger) return VK_FORMAT_UNDEFINED;
switch (size) {
case 1: return VK_FORMAT_R16_SFLOAT;
case 2: return VK_FORMAT_R16G16_SFLOAT;
case 3: return VK_FORMAT_R16G16B16_SFLOAT;
case 4: return VK_FORMAT_R16G16B16A16_SFLOAT;
default: return VK_FORMAT_UNDEFINED;
case 1:
return VK_FORMAT_R16_SFLOAT;
case 2:
return VK_FORMAT_R16G16_SFLOAT;
case 3:
return VK_FORMAT_R16G16B16_SFLOAT;
case 4:
return VK_FORMAT_R16G16B16A16_SFLOAT;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Int32:
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
switch (size) {
case 1: return VK_FORMAT_R32_SINT;
case 2: return VK_FORMAT_R32G32_SINT;
case 3: return VK_FORMAT_R32G32B32_SINT;
case 4: return VK_FORMAT_R32G32B32A32_SINT;
default: return VK_FORMAT_UNDEFINED;
case 1:
return VK_FORMAT_R32_SINT;
case 2:
return VK_FORMAT_R32G32_SINT;
case 3:
return VK_FORMAT_R32G32B32_SINT;
case 4:
return VK_FORMAT_R32G32B32A32_SINT;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Uint32:
if (!isInteger || normalized) return VK_FORMAT_UNDEFINED;
switch (size) {
case 1: return VK_FORMAT_R32_UINT;
case 2: return VK_FORMAT_R32G32_UINT;
case 3: return VK_FORMAT_R32G32B32_UINT;
case 4: return VK_FORMAT_R32G32B32A32_UINT;
default: return VK_FORMAT_UNDEFINED;
case 1:
return VK_FORMAT_R32_UINT;
case 2:
return VK_FORMAT_R32G32_UINT;
case 3:
return VK_FORMAT_R32G32B32_UINT;
case 4:
return VK_FORMAT_R32G32B32A32_UINT;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Int16:
switch (size) {
@@ -339,7 +384,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
case 4:
return isInteger ? VK_FORMAT_R16G16B16A16_SINT
: (normalized ? VK_FORMAT_R16G16B16A16_SNORM : VK_FORMAT_R16G16B16A16_SSCALED);
default: return VK_FORMAT_UNDEFINED;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Uint16:
switch (size) {
@@ -354,37 +400,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
case 4:
return isInteger ? VK_FORMAT_R16G16B16A16_UINT
: (normalized ? VK_FORMAT_R16G16B16A16_UNORM : VK_FORMAT_R16G16B16A16_USCALED);
default: return VK_FORMAT_UNDEFINED;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Int8:
switch (size) {
case 1:
return isInteger ? VK_FORMAT_R8_SINT : (normalized ? VK_FORMAT_R8_SNORM : VK_FORMAT_R8_SSCALED);
case 2:
return isInteger ? VK_FORMAT_R8G8_SINT
: (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
return isInteger ? VK_FORMAT_R8G8_SINT : (normalized ? VK_FORMAT_R8G8_SNORM : VK_FORMAT_R8G8_SSCALED);
case 3:
return isInteger ? VK_FORMAT_R8G8B8_SINT
: (normalized ? VK_FORMAT_R8G8B8_SNORM : VK_FORMAT_R8G8B8_SSCALED);
case 4:
return isInteger ? VK_FORMAT_R8G8B8A8_SINT
: (normalized ? VK_FORMAT_R8G8B8A8_SNORM : VK_FORMAT_R8G8B8A8_SSCALED);
default: return VK_FORMAT_UNDEFINED;
default:
return VK_FORMAT_UNDEFINED;
}
case DataType::Uint8:
switch (size) {
case 1:
return isInteger ? VK_FORMAT_R8_UINT : (normalized ? VK_FORMAT_R8_UNORM : VK_FORMAT_R8_USCALED);
case 2:
return isInteger ? VK_FORMAT_R8G8_UINT
: (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
return isInteger ? VK_FORMAT_R8G8_UINT : (normalized ? VK_FORMAT_R8G8_UNORM : VK_FORMAT_R8G8_USCALED);
case 3:
return isInteger ? VK_FORMAT_R8G8B8_UINT
: (normalized ? VK_FORMAT_R8G8B8_UNORM : VK_FORMAT_R8G8B8_USCALED);
case 4:
return isInteger ? VK_FORMAT_R8G8B8A8_UINT
: (normalized ? VK_FORMAT_R8G8B8A8_UNORM : VK_FORMAT_R8G8B8A8_USCALED);
default: return VK_FORMAT_UNDEFINED;
default:
return VK_FORMAT_UNDEFINED;
}
default:
return VK_FORMAT_UNDEFINED;
@@ -454,11 +501,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFormat VertexInputStateFactory::ToFloat32VertexFormat(Int componentCount) {
switch (componentCount) {
case 1: return VK_FORMAT_R32_SFLOAT;
case 2: return VK_FORMAT_R32G32_SFLOAT;
case 3: return VK_FORMAT_R32G32B32_SFLOAT;
case 4: return VK_FORMAT_R32G32B32A32_SFLOAT;
default: return VK_FORMAT_UNDEFINED;
case 1:
return VK_FORMAT_R32_SFLOAT;
case 2:
return VK_FORMAT_R32G32_SFLOAT;
case 3:
return VK_FORMAT_R32G32B32_SFLOAT;
case 4:
return VK_FORMAT_R32G32B32A32_SFLOAT;
default:
return VK_FORMAT_UNDEFINED;
}
}
@@ -69,11 +69,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// binding uses divisor 1, which is what the plain input rate already means.
Vector<VkVertexInputBindingDivisorDescriptionEXT> bindingDivisors;
VkPipelineVertexInputDivisorStateCreateInfoEXT divisorState{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT
};
VkPipelineVertexInputStateCreateInfo state{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
};
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_DIVISOR_STATE_CREATE_INFO_EXT};
VkPipelineVertexInputStateCreateInfo state{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
// Back to a default-constructed state while keeping the vectors' storage, so a
// per-draw scratch rebuilds without touching the heap. Every member is reset by the
@@ -104,8 +101,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
};
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice):
m_config(config), m_physicalDevice(physicalDevice) {}
VertexInputStateFactory(const VulkanRendererConfig& config, VkPhysicalDevice physicalDevice)
: m_config(config), m_physicalDevice(physicalDevice) {}
~VertexInputStateFactory() = default;
VertexInputStateFactory(const VertexInputStateFactory&) = delete;
@@ -88,9 +88,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// its mapped pointer), and may be bound to any role, so it carries every usage.
// TRANSFER_DST is added by CreateResidentStorage.
constexpr VkBufferUsageFlags kPersistentBackedUsage =
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
// "Every usage" has to mean every usage: a buffer texture reached through an IMAGE
// unit takes a VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER descriptor, and the write is
// invalid unless the buffer was created with this bit. Nothing asked for it until
@@ -98,8 +98,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
// Appended to kPersistentBackedUsage when VK_EXT_transform_feedback is enabled
// (see VkBufferManagerInitInfo::transformFeedbackUsageEnabled).
constexpr VkBufferUsageFlags kTransformFeedbackUsage =
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
constexpr VkBufferUsageFlags kTransformFeedbackUsage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_BUFFER_BIT_EXT;
// The app writes into the persistent map with no explicit flush, so its memory must
// be host-coherent (Adreno host-visible memory is; requiring it keeps us portable).
constexpr VkMemoryPropertyFlags kPersistentBackedRequiredFlags =
@@ -108,7 +107,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
using MG_State::GLState::BufferObject;
// The manager owned by the active VulkanRenderer; immediate ops route here.
SessionLocal<VkBufferManager*> g_activeBufferManager; // per Magma session (MagmaSession.h)
SessionLocal<VkBufferManager*> g_activeBufferManager; // per Magma session (MagmaSession.h)
// The session's manager, or null once its renderer is gone - a client's records can still
// arrive after its eglTerminate released it (or, surfaceless, before its first surface built
@@ -122,27 +121,38 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
const MG_Pipe::MGPipeResourceOps g_vulkanWireResourceOps = {
.Create = [](auto res, const auto& desc) { if (auto* m = WireManager("create")) m->CreateWireBuffer(res, desc); },
.Respecify = [](auto res, const auto& desc, const void* bytes) {
if (auto* m = WireManager("respecify")) m->RespecifyWireBuffer(res, desc, bytes);
},
.SubData = [](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.SubDataResident = [](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.FlushRange = [](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("flush")) m->FlushWireBuffer(res, record.Offset, record.Size, bytes);
},
.Readback = [](auto res, const auto& record) {
if (auto* m = WireManager("readback")) m->ReadbackWireBuffer(res, record.Offset, record.Size);
},
.Destroy = [](auto res) { if (auto* m = WireManager("destroy")) m->DestroyWireBuffer(res); },
.Create =
[](auto res, const auto& desc) {
if (auto* m = WireManager("create")) m->CreateWireBuffer(res, desc);
},
.Respecify =
[](auto res, const auto& desc, const void* bytes) {
if (auto* m = WireManager("respecify")) m->RespecifyWireBuffer(res, desc, bytes);
},
.SubData =
[](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.SubDataResident =
[](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("write"))
m->WriteWireBuffer(res, MG_Pipe::MGPipeSubDataBufferOffset(record),
MG_Pipe::MGPipeSubDataBufferSize(record), bytes);
},
.FlushRange =
[](auto res, const auto& record, const void* bytes) {
if (auto* m = WireManager("flush")) m->FlushWireBuffer(res, record.Offset, record.Size, bytes);
},
.Readback =
[](auto res, const auto& record) {
if (auto* m = WireManager("readback")) m->ReadbackWireBuffer(res, record.Offset, record.Size);
},
.Destroy =
[](auto res) {
if (auto* m = WireManager("destroy")) m->DestroyWireBuffer(res);
},
// Split mapping remains T2: the client owns its map and pushes exact
// modified ranges. A server pointer is never donated across the wire. Monolith's record
// arm (P13 W4) is one process: there the store IS handed over, as the legacy arm's
@@ -206,8 +216,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkBufferManager::CreateWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc) {
if (FindWireBuffer(res) != nullptr) {
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"duplicate-buffer-create\"} {slot=%u, gen=%u}",
res.Slot, res.Gen);
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"duplicate-buffer-create\"} {slot=%u, gen=%u}", res.Slot,
res.Gen);
std::abort();
}
m_wireBuffers.try_emplace(WireBufferKey(res));
@@ -219,11 +229,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
void VkBufferManager::RespecifyWireBuffer(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc,
const void* initialBytes) {
const void* initialBytes) {
auto* resource = FindWireBuffer(res);
if (!resource) {
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"unknown-buffer-respecify\"} {slot=%u, gen=%u}",
res.Slot, res.Gen);
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"unknown-buffer-respecify\"} {slot=%u, gen=%u}", res.Slot,
res.Gen);
std::abort();
}
// THE SERIAL IS READ BEFORE THE RESET BELOW ZEROES IT. Whether it is zero - whether any
@@ -240,7 +250,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
resource->imported = false;
resource->size = desc.Width;
resource->lastUseSerial = 0;
resource->lastUseSubmitIndex = 0; // M2 r2: the new store carries no submission yet (B3's stamp is per store)
resource->lastUseSubmitIndex = 0; // M2 r2: the new store carries no submission yet (B3's stamp is per store)
resource->gpuWritesPending = false;
resource->indirectReadBarrierPending = false;
resource->stagedCoverage.clear();
@@ -249,8 +259,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (resource->size == 0) return;
if (!CreateWireStoreBuffer(*resource)) {
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-storage\"} {slot=%u, gen=%u, size=%llu}",
res.Slot, res.Gen, static_cast<unsigned long long>(resource->size));
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-storage\"} {slot=%u, gen=%u, size=%llu}", res.Slot,
res.Gen, static_cast<unsigned long long>(resource->size));
std::abort();
}
++m_wireStoreCount;
@@ -378,8 +388,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT;
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0, 1, &barrier, 0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &barrier,
0, nullptr, 0, nullptr);
// P8-D: counted here, past the idle early-out, so `whw` is waits TAKEN, not calls.
++m_wireHostWaits;
const Bool timed = MG_Util::PipeStats::Enabled();
@@ -389,8 +399,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (timed) {
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::WireHostWaits, 1);
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::WireHostWaitMicros,
static_cast<Uint64>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - waitStart).count()));
static_cast<Uint64>(std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - waitStart)
.count()));
}
if (!waited) {
return false;
@@ -403,18 +414,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
void VkBufferManager::WriteWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
const void* bytes) {
void VkBufferManager::WriteWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, const void* bytes) {
auto* resource = FindWireBuffer(res);
if (!resource || offset > resource->size || size > resource->size - offset || (size && !bytes)) {
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"buffer-write-range\"} {slot=%u, gen=%u}",
res.Slot, res.Gen);
MGLOG_F("Magma: Fatal{ProtocolCorruption, \"buffer-write-range\"} {slot=%u, gen=%u}", res.Slot, res.Gen);
std::abort();
}
if (size == 0) return;
resource->contentSerial = ++m_wireContentSerial;
if (!resource->shadow.empty())
Memcpy(resource->shadow.data() + offset, bytes, static_cast<SizeT>(size));
if (!resource->shadow.empty()) Memcpy(resource->shadow.data() + offset, bytes, static_cast<SizeT>(size));
Bool uploaded = false;
const Uint64 probeLastUse = resource->lastUseSerial;
const Uint64 probeCompleted = GetCompletedSerial();
@@ -468,8 +476,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// byte update waits, then writes only the exact range; rounding it
// from a CPU shadow could overwrite neighbouring GPU-written bytes.
if (((offset | size) & 3u) == 0) {
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset),
static_cast<SizeT>(size));
uploaded = StagedWireRangeCopy(*resource, bytes, static_cast<SizeT>(offset), static_cast<SizeT>(size));
}
if (!uploaded && !WaitForWireBufferHostAccess(*resource)) {
if (LatchedOnDeviceLoss("buffer-host-write")) return;
@@ -490,32 +497,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (WireBufProbeEnabled()) {
MGLOG_I("WBUF write slot=%u gen=%u off=%llu size=%llu branch=%s bySerial=%d bySubmit=%d "
"lastUse=%llu completed=%llu frameSerial=%llu floor=%llu lastUseSubmit=%llu",
res.Slot, res.Gen, static_cast<unsigned long long>(offset),
static_cast<unsigned long long>(size),
(busyBySerial || busyBySubmission) ? "STAGED-or-WAIT" : "HOST-IMMEDIATE",
busyBySerial ? 1 : 0, busyBySubmission ? 1 : 0,
static_cast<unsigned long long>(probeLastUse), static_cast<unsigned long long>(probeCompleted),
static_cast<unsigned long long>(m_frameSerial),
res.Slot, res.Gen, static_cast<unsigned long long>(offset), static_cast<unsigned long long>(size),
(busyBySerial || busyBySubmission) ? "STAGED-or-WAIT" : "HOST-IMMEDIATE", busyBySerial ? 1 : 0,
busyBySubmission ? 1 : 0, static_cast<unsigned long long>(probeLastUse),
static_cast<unsigned long long>(probeCompleted), static_cast<unsigned long long>(m_frameSerial),
static_cast<unsigned long long>(m_completedSerialFloor),
static_cast<unsigned long long>(resource->lastUseSubmitIndex));
}
MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage,
static_cast<SizeT>(offset),
static_cast<SizeT>(offset + size));
MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage, static_cast<SizeT>(offset),
static_cast<SizeT>(offset + size));
}
void VkBufferManager::FlushWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
const void* bytes) {
void VkBufferManager::FlushWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, const void* bytes) {
if (bytes != nullptr) {
WriteWireBuffer(res, offset, size, bytes);
return;
}
const auto* resource = FindWireBuffer(res);
if (!resource || offset > resource->size || size > resource->size - offset ||
!MG_Record::StagedShadowStore::CoverageHas(resource->stagedCoverage,
static_cast<SizeT>(offset), static_cast<SizeT>(offset + size))) {
MGLOG_F("Magma: Fatal{StageSnapshotTooNarrow, \"buffer-flush\"} {slot=%u, gen=%u}",
res.Slot, res.Gen);
!MG_Record::StagedShadowStore::CoverageHas(resource->stagedCoverage, static_cast<SizeT>(offset),
static_cast<SizeT>(offset + size))) {
MGLOG_F("Magma: Fatal{StageSnapshotTooNarrow, \"buffer-flush\"} {slot=%u, gen=%u}", res.Slot, res.Gen);
std::abort();
}
// SubData has already copied the owned record bytes into GPU storage,
@@ -567,7 +569,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
while (!m_convertedStreams.empty() && m_convertedStreamBytes + size > kConvertedStreamBudget) {
SizeT oldest = 0;
for (SizeT i = 1; i < m_convertedStreams.size(); ++i)
if (m_convertedStreams[i].lastUseFrameSerial < m_convertedStreams[oldest].lastUseFrameSerial) oldest = i;
if (m_convertedStreams[i].lastUseFrameSerial < m_convertedStreams[oldest].lastUseFrameSerial)
oldest = i;
RetireConvertedStream(oldest);
}
ConvertedStream entry;
@@ -582,7 +585,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT,
.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
}) || entry.buffer.Map() == nullptr || !entry.buffer.Upload(bytes, static_cast<VkDeviceSize>(size), 0))
}) ||
entry.buffer.Map() == nullptr || !entry.buffer.Upload(bytes, static_cast<VkDeviceSize>(size), 0))
return false;
m_convertedStreamBytes += size;
outSlice = entry.buffer.GetSlice();
@@ -614,8 +618,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// path) can submit this index without the draw. This stamp is the defence term's input
// (see WriteWireBuffer); the floor behind lastUseSerial is the guarantee. Read off the
// owning renderer's counter (its GetWireNextSubmitIndex()) when it was handed one.
if (m_submitCounter != nullptr) resource->lastUseSubmitIndex = *m_submitCounter + 1;
else if (pVulkanRenderer) resource->lastUseSubmitIndex = pVulkanRenderer->GetWireNextSubmitIndex();
if (m_submitCounter != nullptr)
resource->lastUseSubmitIndex = *m_submitCounter + 1;
else if (pVulkanRenderer)
resource->lastUseSubmitIndex = pVulkanRenderer->GetWireNextSubmitIndex();
outSlice = resource->buffer.GetSlice(0, resource->size);
outSlice.mapped = nullptr;
if (WireBufProbeEnabled()) {
@@ -674,15 +680,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
after.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &after, 0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &after,
0, nullptr, 0, nullptr);
resource->lastUseSerial = m_frameSerial;
return true;
}
Bool VkBufferManager::CopyWireBufferSubWordRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset,
Uint64 size, Uint32 frameIndex,
const BufferSlice& dst, Uint64 dstSkip) {
Bool VkBufferManager::CopyWireBufferSubWordRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
Uint32 frameIndex, const BufferSlice& dst, Uint64 dstSkip) {
auto* resource = FindWireBuffer(res);
if (!resource || offset > resource->size || size > resource->size - offset) return false;
if (dstSkip > dst.size || size > dst.size - dstSkip) return false;
@@ -703,8 +708,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// already name - see BufferArena::EnsureCapacity - so the two slices are allowed to
// sit in different VkBuffers and the copy names each slice's own handle.
BufferSlice staging{};
if (!m_transientUploadArena.Allocate(frameIndex, stagedSize, 4, staging) || !staging.IsValid())
return false;
if (!m_transientUploadArena.Allocate(frameIndex, stagedSize, 4, staging) || !staging.IsValid()) return false;
const VkCommandBuffer commands = m_copyProvider->AcquireBufferCopyCommandBuffer();
if (commands == VK_NULL_HANDLE) return false;
@@ -723,8 +727,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
staged.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
staged.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
0, 1, &staged, 0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &staged, 0,
nullptr, 0, nullptr);
const VkBufferCopy shift{staging.offset + headPad, dst.offset + dstSkip, size};
EndActiveRenderPassOn(commands);
vkCmdCopyBuffer(commands, staging.buffer, dst.buffer, 1, &shift);
@@ -732,8 +736,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
after.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, 1, &after, 0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &after,
0, nullptr, 0, nullptr);
resource->lastUseSerial = m_frameSerial;
return true;
}
@@ -763,8 +767,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
// The reverse producer synchronously copies into SEG_EVENT before
// this owned vector is released; no mapped Vulkan pointer crosses roles.
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(res, offset,
{reinterpret_cast<Uint64>(bytes.data()), size, MG_Pipe::kMGHostSpanSegNone, 0});
MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(
res, offset, {reinterpret_cast<Uint64>(bytes.data()), size, MG_Pipe::kMGHostSpanSegNone, 0});
}
void VkBufferManager::MarkWireBufferGpuWritten(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size) {
@@ -809,8 +813,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
#if defined(__ANDROID__)
namespace {
Int32 T0PickMemoryType(const VkPhysicalDeviceMemoryProperties& props, Uint32 bits,
VkMemoryPropertyFlags want) {
Int32 T0PickMemoryType(const VkPhysicalDeviceMemoryProperties& props, Uint32 bits, VkMemoryPropertyFlags want) {
for (Uint32 i = 0; i < props.memoryTypeCount; ++i) {
if ((bits & (1u << i)) != 0 && (props.memoryTypes[i].propertyFlags & want) == want)
return static_cast<Int32>(i);
@@ -874,7 +877,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkMemoryDedicatedAllocateInfo dedicated{VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO};
dedicated.buffer = out.buffer;
VkImportAndroidHardwareBufferInfoANDROID import{VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID};
VkImportAndroidHardwareBufferInfoANDROID import{
VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID};
import.buffer = static_cast<AHardwareBuffer*>(ahb);
import.pNext = &dedicated;
VkMemoryAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
@@ -991,11 +995,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (pVulkanRenderer == nullptr || !pVulkanRenderer->GetWireAhbImport(ctx))
return answer(false, "the device did not take VK_ANDROID_external_memory_android_hardware_buffer");
constexpr Uint64 kBytes = 64 * 1024;
constexpr Uint32 kWords = 1024; // one 4 KiB region
constexpr Uint64 kFillAt = 4 * 4096; // region 4
constexpr Uint32 kWords = 1024; // one 4 KiB region
constexpr Uint64 kFillAt = 4 * 4096; // region 4
constexpr Uint32 kFillWord = 0x6C0FFEE0u;
Vector<Uint32> pattern(kBytes / 4);
for (SizeT i = 0; i < pattern.size(); ++i) pattern[i] = 0x5A000000u ^ static_cast<Uint32>(i * 2654435761u);
for (SizeT i = 0; i < pattern.size(); ++i)
pattern[i] = 0x5A000000u ^ static_cast<Uint32>(i * 2654435761u);
MG_Remote::Transport::AdoptT0::HeldStore held;
String why;
if (!MG_Remote::Transport::AdoptT0::AllocateHeld(kBytes, pattern.data(), held, why))
@@ -1035,12 +1040,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkMemoryRequirements requirements{};
vkGetBufferMemoryRequirements(ctx.device, staging.buffer, &requirements);
const Int32 stagingType = T0PickMemoryType(ctx.memory, requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
const Int32 stagingType =
T0PickMemoryType(ctx.memory, requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
VkMemoryAllocateInfo stagingAlloc{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
stagingAlloc.allocationSize = requirements.size;
stagingAlloc.memoryTypeIndex = static_cast<Uint32>(stagingType < 0 ? 0 : stagingType);
if (stagingType < 0 || vkAllocateMemory(ctx.device, &stagingAlloc, nullptr, &staging.memory) != VK_SUCCESS ||
if (stagingType < 0 ||
vkAllocateMemory(ctx.device, &stagingAlloc, nullptr, &staging.memory) != VK_SUCCESS ||
vkBindBufferMemory(ctx.device, staging.buffer, staging.memory, 0) != VK_SUCCESS ||
vkMapMemory(ctx.device, staging.memory, 0, VK_WHOLE_SIZE, 0, &staging.mapped) != VK_SUCCESS) {
verdict = "staging memory";
@@ -1084,8 +1091,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
toHost.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
toHost.dstAccessMask = VK_ACCESS_HOST_READ_BIT;
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost,
0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost, 0,
nullptr, 0, nullptr);
vkEndCommandBuffer(commands);
VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO};
submit.commandBufferCount = 1;
@@ -1105,8 +1112,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
ok = gpuRead == kWords && heldSaw == kWords;
verdict = "64 KiB AHB, lock held throughout: host map read ok (" +
String(imported.coherent ? "coherent" : "non-coherent") + " memory), GPU read " +
std::to_string(gpuRead) + "/1024 words of the CPU's pattern, GPU write seen through the "
"held lock " + std::to_string(heldSaw) + "/1024 words";
std::to_string(gpuRead) +
"/1024 words of the CPU's pattern, GPU write seen through the "
"held lock " +
std::to_string(heldSaw) + "/1024 words";
if (!ok) break;
// STEADY STATE. The round above reads bytes the CPU wrote BEFORE the import, and an import
@@ -1142,8 +1151,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
EndActiveRenderPassOn(commands);
vkCmdFillBuffer(commands, imported.buffer, kFillAt, kWords * 4, kSteadyFillWord);
EndActiveRenderPassOn(commands);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost,
0, nullptr, 0, nullptr);
vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost, 0,
nullptr, 0, nullptr);
vkEndCommandBuffer(commands);
if (vkQueueSubmit(ctx.queue, 1, &submit, fence) != VK_SUCCESS ||
vkWaitForFences(ctx.device, 1, &fence, VK_TRUE, 2'000'000'000ull) != VK_SUCCESS) {
@@ -1172,17 +1181,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
#endif
}
void VkBufferManager::DescribeWireBufferHistory(MG_Pipe::MGPipeHandle res) const {
const auto& history = *g_wireBufferHistory;
Int createdAgo = -1, destroyedAgo = -1;
for (SizeT i = 0; i < std::min(history.createdCount, WireBufferHistory::kDepth); ++i) {
const auto& h = history.created[(history.createdCount - 1 - i) % WireBufferHistory::kDepth];
if (h.Slot == res.Slot && h.Gen == res.Gen) { createdAgo = static_cast<Int>(i); break; }
if (h.Slot == res.Slot && h.Gen == res.Gen) {
createdAgo = static_cast<Int>(i);
break;
}
}
for (SizeT i = 0; i < std::min(history.destroyedCount, WireBufferHistory::kDepth); ++i) {
const auto& h = history.destroyed[(history.destroyedCount - 1 - i) % WireBufferHistory::kDepth];
if (h.Slot == res.Slot && h.Gen == res.Gen) { destroyedAgo = static_cast<Int>(i); break; }
if (h.Slot == res.Slot && h.Gen == res.Gen) {
destroyedAgo = static_cast<Int>(i);
break;
}
}
MGLOG_E("Magma wire buffer {slot=%u, gen=%u}: live stores %zu; created %d creates ago, destroyed %d "
"destroys ago (-1 = not in the last %zu); %zu creates, %zu destroys in this session",
@@ -1466,8 +1480,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return std::max(completed, m_completedSerialFloor);
}
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data,
VkDeviceSize size, VkDeviceSize alignment, BufferSlice& outSlice) {
Bool VkBufferManager::UploadTransient(BufferKind kind, Uint32 frameIndex, const void* data, VkDeviceSize size,
VkDeviceSize alignment, BufferSlice& outSlice) {
if (!m_transientUploadArena.Upload(frameIndex, data, size, alignment, outSlice)) {
return false;
}
@@ -1492,8 +1506,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break;
case BufferKind::TextureBuffer:
case BufferKind::ShaderStorage:
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer,
static_cast<Uint64>(size));
MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast<Uint64>(size));
break;
case BufferKind::Uniform:
break;
@@ -1508,8 +1521,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.frameCount = m_initInfo.frameCount,
.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT |
(VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT}) |
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
(VkBufferUsageFlags{VK_BUFFER_USAGE_TRANSFER_DST_BIT}) | VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
.memoryUsage = m_initInfo.transientMemoryUsage,
.allocationFlags = m_initInfo.transientAllocationFlags,
.minBufferSize = m_initInfo.minUploadBytes,
@@ -1517,14 +1529,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
});
}
Bool VkBufferManager::StagedWireRangeCopy(WireBufferResource& resource, const void* data,
SizeT offset, SizeT size) {
Bool VkBufferManager::StagedWireRangeCopy(WireBufferResource& resource, const void* data, SizeT offset,
SizeT size) {
if (!m_copyProvider) {
return false;
}
BufferSlice staging{};
if (!m_transientUploadArena.Upload(m_currentFrameIndex, data,
static_cast<VkDeviceSize>(size), 16, staging)) {
if (!m_transientUploadArena.Upload(m_currentFrameIndex, data, static_cast<VkDeviceSize>(size), 16, staging)) {
return false;
}
if (MG_Util::PipeStats::Enabled()) {
@@ -1569,7 +1580,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
void VkBufferManager::CollectDeferredReleases(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredBufferReleases.size(),
"VkBufferManager::CollectDeferredReleases frame index out of range");
@@ -1595,8 +1605,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.size = kUnboundStorageDescriptorBytes,
.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT,
.allocationFlags =
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
});
if (!created) {
@@ -1628,8 +1638,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
.allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT,
.allocationFlags =
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT,
.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
});
if (!created) {
@@ -1647,7 +1657,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VkBufferManager::DestroyAllDeferredReleases() {
// Both callers (Shutdown, RecreateTransientArenas) have proven the device idle.
DestroyAllDeferredWireReleases();
for (auto& stream : m_convertedStreams) stream.buffer.Destroy();
for (auto& stream : m_convertedStreams)
stream.buffer.Destroy();
m_convertedStreams.clear();
m_convertedStreamBytes = 0;
for (auto& releases : m_deferredBufferReleases) {
@@ -62,8 +62,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Names, for a Fatal about an unknown handle, whether this manager ever saw it.
void DescribeWireBufferHistory(MG_Pipe::MGPipeHandle res) const;
Bool ReadWireBuffer(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, void* dst);
Bool CopyWireBufferRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
const BufferSlice& dst);
Bool CopyWireBufferRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, const BufferSlice& dst);
// P7 A.1, the sub-word half of the one above: the same copy for a window whose start
// or end does not land on a four-byte boundary. The read of the APPLICATION's store is
// rounded OUT to whole words and clamped to the store's end, so the widened window can
@@ -73,8 +72,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// on a region's offsets or size (unlike vkCmdUpdateBuffer / vkCmdFillBuffer), so the
// shift is a plain legal copy. `dstSkip` is the byte inside `dst` the window starts at.
// This retires `uniform-buffer-byte-tail`.
Bool CopyWireBufferSubWordRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size,
Uint32 frameIndex, const BufferSlice& dst, Uint64 dstSkip);
Bool CopyWireBufferSubWordRangeToSlice(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size, Uint32 frameIndex,
const BufferSlice& dst, Uint64 dstSkip);
void MarkWireBufferGpuWritten(MG_Pipe::MGPipeHandle res, Uint64 offset, Uint64 size);
// P8-D: whether a shader write was marked on this store since the last indirect draw
// read it natively, clearing the mark. The caller records the INDIRECT_COMMAND_READ
@@ -186,6 +185,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// below it may be considered signaled. Drives IsResourceBusy and the
// backend GL fence objects.
Uint64 GetCompletedSerial() const;
private:
struct WireBufferResource {
VkBufferObject buffer;
@@ -36,9 +36,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferObject& operator=(VkBufferObject&& other) noexcept;
Bool Create(const VkBufferObjectDesc& desc);
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage,
VmaMemoryUsage memoryUsage, VmaAllocationCreateFlags allocationFlags = 0,
VkMemoryPropertyFlags requiredFlags = 0);
Bool Create(VmaAllocator allocator, VkDeviceSize size, VkBufferUsageFlags usage, VmaMemoryUsage memoryUsage,
VmaAllocationCreateFlags allocationFlags = 0, VkMemoryPropertyFlags requiredFlags = 0);
void Destroy();
void* Map();
@@ -76,8 +75,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the memory and drops `ahb` - which is why the store goes through the wire arm's
// serial-gated deferred release like every other: the AHardwareBuffer is let go only once
// no submission can still name it. `coherent` = the memory type is HOST_COHERENT.
void AdoptExternal(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize size,
void* mapped, Bool coherent, void* ahb);
void AdoptExternal(VkDevice device, VkBuffer buffer, VkDeviceMemory memory, VkDeviceSize size, void* mapped,
Bool coherent, void* ahb);
Bool IsExternal() const { return m_externalMemory != VK_NULL_HANDLE; }
private:
@@ -22,8 +22,7 @@
namespace MobileGL::MG_Backend::DirectVulkan {
static Bool IsCubeMapFaceUploadTarget(TextureUploadTarget target) {
return target >= TextureUploadTarget::CubeMapPositiveX &&
target <= TextureUploadTarget::CubeMapNegativeZ;
return target >= TextureUploadTarget::CubeMapPositiveX && target <= TextureUploadTarget::CubeMapNegativeZ;
}
VkClearColorValue MakeVkClearColorValue(const ClearAttachmentPayload& payload, Bool formatLacksAlpha) {
@@ -64,8 +63,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Float value = std::clamp(encoded, 0.0f, 1.0f);
return value <= 0.04045f ? value / 12.92f : std::pow((value + 0.055f) / 1.055f, 2.4f);
};
payload.color = FloatVec4(toLinear(payload.color.x()), toLinear(payload.color.y()),
toLinear(payload.color.z()), payload.color.w());
payload.color = FloatVec4(toLinear(payload.color.x()), toLinear(payload.color.y()), toLinear(payload.color.z()),
payload.color.w());
}
// ResolveAttachmentLayerCount used to be duplicated here, reading attachment.GetSize().z()
@@ -29,7 +29,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// image's format rather than converting between the members - a float written where an
// integer format is expected is reinterpreted bit for bit, not rounded. Remember which entry
// point supplied the value so the member written when the clear is materialized matches.
enum class ClearColorEncoding : Uint8 { Float, Int, Uint };
enum class ClearColorEncoding : Uint8 {
Float,
Int,
Uint
};
struct ClearAttachmentPayload {
GLbitfield mask = 0;
@@ -18,7 +18,8 @@
#include <MG_Backend/MGPipe/PipeInputs.h>
namespace MobileGL::MG_Backend::DirectVulkan {
[[maybe_unused]] static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture, Float requestedAlpha) {
[[maybe_unused]] static Float ResolveColorClearAlpha(const MG_State::GLState::ITextureObject* texture,
Float requestedAlpha) {
if (texture != nullptr && MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()) == 3) {
return 1.0f;
}
@@ -29,20 +30,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VkClearManager needs the SAME answer: its pending-clear key's layerCount becomes a real
// VkImageSubresourceRange when a clear is materialised outside a render pass. See the header.
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil) {
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(VkImageLayout trackedLayout, Bool clearDepth,
Bool clearStencil) {
DepthStencilAttachmentLoadInfo info{};
info.depthLoadOp = clearDepth
? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.depthLoadOp = clearDepth ? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.stencilLoadOp = clearStencil
? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.initialLayout =
(trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED || (clearDepth && clearStencil)) ? VK_IMAGE_LAYOUT_UNDEFINED
: trackedLayout;
? VK_ATTACHMENT_LOAD_OP_CLEAR
: (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_ATTACHMENT_LOAD_OP_DONT_CARE
: VK_ATTACHMENT_LOAD_OP_LOAD);
info.initialLayout = (trackedLayout == VK_IMAGE_LAYOUT_UNDEFINED || (clearDepth && clearStencil))
? VK_IMAGE_LAYOUT_UNDEFINED
: trackedLayout;
return info;
}
@@ -54,11 +54,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return {attachmentExtent.x(), attachmentExtent.y()};
}
VkRenderPassManager::VkRenderPassManager(VkDevice device,
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
VkTextureManager& textureManager, SwapchainObject& swapchainObject):
m_device(device), m_physicalDevice(physicalDevice), m_allocator(allocator), m_config(config),
m_textureManager(textureManager), m_swapchainObject(swapchainObject) {
VkRenderPassManager::VkRenderPassManager(VkDevice device, VkPhysicalDevice physicalDevice, VmaAllocator allocator,
const VulkanRendererConfig& config, VkTextureManager& textureManager,
SwapchainObject& swapchainObject)
: m_device(device), m_physicalDevice(physicalDevice), m_allocator(allocator), m_config(config),
m_textureManager(textureManager), m_swapchainObject(swapchainObject) {
RenderPassEntry::s_device.Get() = m_device;
s_textureManager.Get() = &m_textureManager;
s_swapchainObject.Get() = &m_swapchainObject;
@@ -83,12 +83,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
renderPassBeginInfo.pNext = nullptr;
renderPassBeginInfo.renderPass = renderPassEntry.renderPass;
renderPassBeginInfo.framebuffer = renderPassEntry.framebuffer;
renderPassBeginInfo.renderArea.offset = { 0, 0 };
renderPassBeginInfo.renderArea.extent = {
(Uint32)renderPassEntry.extent.x(), (Uint32)renderPassEntry.extent.y() };
renderPassBeginInfo.renderArea.offset = {0, 0};
renderPassBeginInfo.renderArea.extent = {(Uint32)renderPassEntry.extent.x(),
(Uint32)renderPassEntry.extent.y()};
Vector<VkClearValue> clearValues(renderPassEntry.attachmentCount);
for (auto& clearValue: clearValues) {
for (auto& clearValue : clearValues) {
clearValue.color = {0.0f, 0.0f, 0.0f, 1.0f};
clearValue.depthStencil = {1.0f, 0};
}
@@ -111,8 +111,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = renderPass;
begin.framebuffer = renderPassEntry.framebuffer;
begin.renderArea = {{0, 0}, {static_cast<Uint32>(renderPassEntry.extent.x()),
static_cast<Uint32>(renderPassEntry.extent.y())}};
begin.renderArea = {
{0, 0}, {static_cast<Uint32>(renderPassEntry.extent.x()), static_cast<Uint32>(renderPassEntry.extent.y())}};
begin.clearValueCount = clearValueCount;
begin.pClearValues = clearValues;
vkCmdBeginRenderPass(commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
@@ -137,9 +137,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
namespace {
std::atomic<Uint64> g_wireImageWriteEpoch{1};
} // namespace
Uint64 WireImageWriteEpoch() {
return g_wireImageWriteEpoch.load(std::memory_order_relaxed);
}
void BumpWireImageWriteEpoch() {
g_wireImageWriteEpoch.fetch_add(1, std::memory_order_relaxed);
}
Uint64 WireImageWriteEpoch() { return g_wireImageWriteEpoch.load(std::memory_order_relaxed); }
void BumpWireImageWriteEpoch() { g_wireImageWriteEpoch.fetch_add(1, std::memory_order_relaxed); }
void EndActiveRenderPassOn(VkCommandBuffer commandBuffer) {
BumpWireImageWriteEpoch();
@@ -26,8 +26,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkImageLayout initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
};
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(
VkImageLayout trackedLayout, Bool clearDepth, Bool clearStencil);
DepthStencilAttachmentLoadInfo ResolveDepthStencilAttachmentLoadInfo(VkImageLayout trackedLayout, Bool clearDepth,
Bool clearStencil);
IntVec2 ResolveRenderPassFramebufferExtent(Bool isDefaultFbo, const TextureSize& attachmentExtent,
VkExtent2D swapchainExtent);
@@ -128,9 +128,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return this->compatibilityHash == that.compatibilityHash;
}
Bool CompatibleWith(Uint64 compatibilityHash) const {
return this->compatibilityHash == compatibilityHash;
}
Bool CompatibleWith(Uint64 compatibilityHash) const { return this->compatibilityHash == compatibilityHash; }
};
struct ActiveRenderPassInfo {
@@ -139,31 +137,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
IntVec2 extent = {0, 0};
Bool CompatibleWith(const RenderPassEntry& that) const {
return compatibilityHash == that.compatibilityHash;
}
Bool CompatibleWith(const RenderPassEntry& that) const { return compatibilityHash == that.compatibilityHash; }
Bool CompatibleWith(Uint64 thatCompatibilityHash) const {
return compatibilityHash == thatCompatibilityHash;
}
Bool CompatibleWith(Uint64 thatCompatibilityHash) const { return compatibilityHash == thatCompatibilityHash; }
};
class VkRenderPassManager {
public:
using HashType = Uint64;
VkRenderPassManager(VkDevice device,
VkPhysicalDevice physicalDevice, VmaAllocator allocator, const VulkanRendererConfig& config,
VkTextureManager& textureManager, SwapchainObject& swapchainObject);
VkRenderPassManager(VkDevice device, VkPhysicalDevice physicalDevice, VmaAllocator allocator,
const VulkanRendererConfig& config, VkTextureManager& textureManager,
SwapchainObject& swapchainObject);
~VkRenderPassManager();
Bool Initialize();
void Shutdown();
// The surface target the default framebuffer currently names (VulkanRenderer's surface
// targets): a default-framebuffer pass of one surface is not another's.
void SetDefaultFramebufferTarget(Uint64 serial) {
m_defaultFramebufferTarget = serial;
}
void SetDefaultFramebufferTarget(Uint64 serial) { m_defaultFramebufferTarget = serial; }
static Bool BeginRenderPass(VkCommandBuffer commandBuffer, RenderPassEntry& renderPassEntry);
// P15 S0: begins `renderPassEntry` through `renderPass` (one of its clear-load variants)
// with these clear values; it is active as the entry itself, so a draw continues it.
@@ -171,6 +163,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkRenderPass renderPass, const VkClearValue* clearValues, Uint32 clearValueCount);
static Bool EndRenderPass(VkCommandBuffer commandBuffer);
static ActiveRenderPassInfo* GetActiveRenderPass();
private:
VkDevice m_device = VK_NULL_HANDLE;
VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE;
@@ -190,14 +183,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint64 GetRenderbufferImageEpoch() const { return m_renderbufferImageEpoch; }
private:
Uint64 m_defaultFramebufferTarget = 0;
// Supported sample counts per attachment format, so per-draw resource lookups
// do not repeat vkGetPhysicalDeviceImageFormatProperties.
UnorderedMap<VkFormat, VkSampleCountFlags> m_attachmentSampleCountsByFormat;
static inline thread_local XXH64_state_t* m_hashState = XXH64_createState();
// Per Magma session (MagmaSession.h): one session's open render pass and managers are not
// another's.
@@ -261,7 +261,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
template <class SamplerSource>
Float VkSamplerManager::ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
Bool forceNearestFiltering) const {
Bool forceNearestFiltering) const {
if (!m_samplerAnisotropySupported) return 1.0f;
if (forceNearestFiltering) return 1.0f;
// VUID-VkSamplerCreateInfo-anisotropyEnable-01071/01072: anisotropy requires both filters to
@@ -322,9 +322,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
template <class SamplerSource>
Uint64 VkSamplerManager::BuildSamplerKey(const SamplerSource& sampler,
Bool forceNearestFiltering, Bool singleLevelView,
const ResolvedBorderColor& borderColor) const {
Uint64 VkSamplerManager::BuildSamplerKey(const SamplerSource& sampler, Bool forceNearestFiltering,
Bool singleLevelView, const ResolvedBorderColor& borderColor) const {
MOBILEGL_ASSERT(m_config != nullptr, "VkSamplerManager::BuildSamplerKey: m_config is null");
// P15: one read of the thread_local per hash (emulated TLS: every read is a call).
XXH64_state_t* const hashState = m_hashState;
@@ -371,9 +370,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
template <class SamplerSource>
VkSampler VkSamplerManager::GetOrCreateSamplerImpl(const SamplerSource& sampler,
TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount) {
VkSampler VkSamplerManager::GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount) {
// A view that exposes a single mip level has no second level to blend with, so GL's
// *_MIPMAP_* minification filters degenerate to plain filtering on the base level -
// sampling is unchanged by pinning the Vulkan sampler to NEAREST mip mode at LOD 0.
@@ -396,9 +394,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
samplerInfo.magFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMagFilter());
samplerInfo.minFilter = forceNearestFiltering ? VK_FILTER_NEAREST : ToVkFilter(sampler.GetMinFilter());
samplerInfo.mipmapMode = (forceNearestFiltering || singleLevelView)
? VK_SAMPLER_MIPMAP_MODE_NEAREST
: ToVkMipmapMode(sampler.GetMipmapMode());
samplerInfo.mipmapMode = (forceNearestFiltering || singleLevelView) ? VK_SAMPLER_MIPMAP_MODE_NEAREST
: ToVkMipmapMode(sampler.GetMipmapMode());
samplerInfo.addressModeU = ToVkAddressMode(sampler.GetWrapS());
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
@@ -408,7 +405,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Float maxAnisotropy = ResolveEffectiveMaxAnisotropy(sampler, forceNearestFiltering);
samplerInfo.anisotropyEnable = maxAnisotropy > 1.0f ? VK_TRUE : VK_FALSE;
samplerInfo.maxAnisotropy = maxAnisotropy;
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
samplerInfo.compareEnable =
sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
samplerInfo.compareOp = ToVkCompareOp(sampler.GetSamplerCompareFunc());
// Must match BuildSamplerKey's resolution exactly.
samplerInfo.maxLod = ResolveSingleLevelMaxLod(sampler, singleLevelView);
@@ -445,9 +443,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkSampler VkSamplerManager::GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
TextureInternalFormat format, Bool forceNearestFiltering, Uint32 viewLevelCount) {
return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format,
forceNearestFiltering, viewLevelCount);
TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount) {
return GetOrCreateSamplerImpl(SamplerParametersSource{parameters}, format, forceNearestFiltering,
viewLevelCount);
}
VkFilter VkSamplerManager::ToVkFilter(SamplerFilterMode mode) {
@@ -506,8 +505,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
template <class SamplerSource>
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(
const SamplerSource& sampler, TextureInternalFormat format) const {
VkSamplerManager::ResolvedBorderColor VkSamplerManager::ResolveBorderColor(const SamplerSource& sampler,
TextureInternalFormat format) const {
ResolvedBorderColor resolved{};
if (!UsesBorderColor(sampler)) {
return resolved; // FLOAT_TRANSPARENT_BLACK, never sampled
@@ -14,117 +14,116 @@
#include <MG_State/GLState/SamplerState/SamplerObject.h>
namespace MobileGL::MG_State::GLState {
class SamplerObject;
class ITextureObject;
}
class SamplerObject;
class ITextureObject;
} // namespace MobileGL::MG_State::GLState
namespace MobileGL::MG_Backend::DirectVulkan {
class VkSamplerManager {
public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
const VulkanRendererConfig* config = nullptr;
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
Bool samplerAnisotropySupported = false;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
Float maxSamplerAnisotropy = 1.0f;
// VK_EXT_custom_border_color was enabled with BOTH customBorderColors and
// customBorderColorWithoutFormat; see VulkanRenderer::m_customBorderColorFeatureEnabled.
Bool customBorderColorSupported = false;
// VkPhysicalDeviceCustomBorderColorPropertiesEXT::maxCustomBorderColorSamplers. A hard device
// limit on how many LIVE samplers may carry a custom border colour, so the cache counts them
// and falls back to the snapped predefined value once it is reached.
Uint32 maxCustomBorderColorSamplers = 0;
class VkSamplerManager {
public:
struct InitInfo {
VkDevice device = VK_NULL_HANDLE;
const VulkanRendererConfig* config = nullptr;
// The samplerAnisotropy device feature was requested and granted at vkCreateDevice.
Bool samplerAnisotropySupported = false;
// VkPhysicalDeviceLimits::maxSamplerAnisotropy.
Float maxSamplerAnisotropy = 1.0f;
// VK_EXT_custom_border_color was enabled with BOTH customBorderColors and
// customBorderColorWithoutFormat; see VulkanRenderer::m_customBorderColorFeatureEnabled.
Bool customBorderColorSupported = false;
// VkPhysicalDeviceCustomBorderColorPropertiesEXT::maxCustomBorderColorSamplers. A hard device
// limit on how many LIVE samplers may carry a custom border colour, so the cache counts them
// and falls back to the snapped predefined value once it is reached.
Uint32 maxCustomBorderColorSamplers = 0;
};
Bool Initialize(const InitInfo& initInfo);
void Shutdown();
// The applier owns these values: effective bound/builtin sampler parameters and
// the sampled texture view's format/range. No frontend object is constructed.
VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters, TextureInternalFormat format,
Bool forceNearestFiltering = false, Uint32 viewLevelCount = 0);
// Frame boundary hook: ages the sampler cache and destroys samplers not used
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
// anisotropy), so an app animating those would otherwise mint an unbounded
// stream of never-destroyed VkSamplers and eventually exhaust the device's
// maxSamplerAllocationCount. A sampler idle for over a thousand frame
// boundaries cannot be referenced by any in-flight command buffer (frames in
// flight are single digits), and every descriptor set the GPU consumes is
// written that same frame with live handles (the per-binding resolve memo and
// descriptor-set reuse are both frame-reset), so destruction here needs no
// fence wait. Self-gated: one counter bump and compare except on sweep
// boundaries.
void OnFrameBoundary();
// For a caller that memoises a returned VkSampler (UniformManager's wire sampler memo): the
// frame-boundary count a GetOrCreate call stamps, and a counter that moves whenever any cached
// sampler is destroyed. A memo may reuse a handle only while the destroy epoch is unchanged,
// and must still call GetOrCreate once per frame boundary so the entry stays stamped as used.
Uint64 FrameBoundaryCount() const { return m_frameBoundaryCounter; }
Uint64 DestroyEpoch() const { return m_destroyEpoch; }
// What GL_TEXTURE_BORDER_COLOR resolves to for one (sampler, texture) pair. `color` is always a
// legal VkBorderColor; when `isCustom` it is one of the *_CUSTOM_EXT values and `customValue`
// carries the actual components in a VkSamplerCustomBorderColorCreateInfoEXT.
//
// Resolved ONCE per GetOrCreateSampler call and threaded into both the cache key and the
// create-info, so the two cannot disagree - the same discipline the resolved anisotropy needs,
// and here it also makes the maxCustomBorderColorSamplers fallback deterministic: whether a
// custom colour was affordable is decided before the key is built, not twice with a budget
// change in between.
struct ResolvedBorderColor {
VkBorderColor color = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
VkClearColorValue customValue{};
Bool isCustom = false;
};
private:
struct SamplerCacheEntry {
VkSampler handle = VK_NULL_HANDLE;
Uint externalIndex = 0;
Uint16 version = 0;
// Frame boundary of the last cache hit; entries idle past the
// OnFrameBoundary retirement age have their VkSampler destroyed.
Uint64 lastUsedFrameBoundary = 0;
// Counted against maxCustomBorderColorSamplers for as long as this entry lives.
Bool usesCustomBorderColor = false;
};
template <class SamplerSource>
VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount);
template <class SamplerSource>
Uint64 BuildSamplerKey(const SamplerSource& sampler, Bool forceNearestFiltering, Bool singleLevelView,
const ResolvedBorderColor& borderColor) const;
static VkFilter ToVkFilter(SamplerFilterMode mode);
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
template <class SamplerSource>
ResolvedBorderColor ResolveBorderColor(const SamplerSource& sampler, TextureInternalFormat format) const;
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
template <class SamplerSource>
Float ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler, Bool forceNearestFiltering) const;
VkDevice m_device = VK_NULL_HANDLE;
const VulkanRendererConfig* m_config = nullptr;
Bool m_samplerAnisotropySupported = false;
Float m_maxSamplerAnisotropy = 1.0f;
Bool m_customBorderColorSupported = false;
Uint32 m_maxCustomBorderColorSamplers = 0;
// Live cache entries carrying a custom border colour. Kept in step with the entries themselves
// in exactly the three places one can appear or disappear: creation, the OnFrameBoundary sweep,
// and Shutdown.
Uint32 m_customBorderColorSamplerCount = 0;
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
Uint64 m_frameBoundaryCounter = 0;
// Moves on every vkDestroySampler of a cached entry (sweep and Shutdown).
Uint64 m_destroyEpoch = 0;
static inline thread_local XXH64_state_t* m_hashState = XXH64_createState();
};
Bool Initialize(const InitInfo& initInfo);
void Shutdown();
// The applier owns these values: effective bound/builtin sampler parameters and
// the sampled texture view's format/range. No frontend object is constructed.
VkSampler GetOrCreateSamplerFromParameters(const SamplerParameters& parameters,
TextureInternalFormat format, Bool forceNearestFiltering = false, Uint32 viewLevelCount = 0);
// Frame boundary hook: ages the sampler cache and destroys samplers not used
// for many frames. The key hashes continuous float state (lodBias, LOD clamps,
// anisotropy), so an app animating those would otherwise mint an unbounded
// stream of never-destroyed VkSamplers and eventually exhaust the device's
// maxSamplerAllocationCount. A sampler idle for over a thousand frame
// boundaries cannot be referenced by any in-flight command buffer (frames in
// flight are single digits), and every descriptor set the GPU consumes is
// written that same frame with live handles (the per-binding resolve memo and
// descriptor-set reuse are both frame-reset), so destruction here needs no
// fence wait. Self-gated: one counter bump and compare except on sweep
// boundaries.
void OnFrameBoundary();
// For a caller that memoises a returned VkSampler (UniformManager's wire sampler memo): the
// frame-boundary count a GetOrCreate call stamps, and a counter that moves whenever any cached
// sampler is destroyed. A memo may reuse a handle only while the destroy epoch is unchanged,
// and must still call GetOrCreate once per frame boundary so the entry stays stamped as used.
Uint64 FrameBoundaryCount() const { return m_frameBoundaryCounter; }
Uint64 DestroyEpoch() const { return m_destroyEpoch; }
// What GL_TEXTURE_BORDER_COLOR resolves to for one (sampler, texture) pair. `color` is always a
// legal VkBorderColor; when `isCustom` it is one of the *_CUSTOM_EXT values and `customValue`
// carries the actual components in a VkSamplerCustomBorderColorCreateInfoEXT.
//
// Resolved ONCE per GetOrCreateSampler call and threaded into both the cache key and the
// create-info, so the two cannot disagree - the same discipline the resolved anisotropy needs,
// and here it also makes the maxCustomBorderColorSamplers fallback deterministic: whether a
// custom colour was affordable is decided before the key is built, not twice with a budget
// change in between.
struct ResolvedBorderColor {
VkBorderColor color = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
VkClearColorValue customValue{};
Bool isCustom = false;
};
private:
struct SamplerCacheEntry {
VkSampler handle = VK_NULL_HANDLE;
Uint externalIndex = 0;
Uint16 version = 0;
// Frame boundary of the last cache hit; entries idle past the
// OnFrameBoundary retirement age have their VkSampler destroyed.
Uint64 lastUsedFrameBoundary = 0;
// Counted against maxCustomBorderColorSamplers for as long as this entry lives.
Bool usesCustomBorderColor = false;
};
template <class SamplerSource>
VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format,
Bool forceNearestFiltering, Uint32 viewLevelCount);
template <class SamplerSource>
Uint64 BuildSamplerKey(const SamplerSource& sampler, Bool forceNearestFiltering,
Bool singleLevelView, const ResolvedBorderColor& borderColor) const;
static VkFilter ToVkFilter(SamplerFilterMode mode);
static VkSamplerMipmapMode ToVkMipmapMode(SamplerMipmapMode mode);
static VkSamplerAddressMode ToVkAddressMode(SamplerWrapMode mode);
static VkCompareOp ToVkCompareOp(SamplerCompareFunc func);
template <class SamplerSource>
ResolvedBorderColor ResolveBorderColor(const SamplerSource& sampler, TextureInternalFormat format) const;
// The anisotropy Vulkan will actually apply: 1.0 (i.e. disabled) unless the feature is on and
// the sampler filters linearly both ways, otherwise the GL request clamped to the device limit.
// GL happily carries GL_TEXTURE_MAX_ANISOTROPY on a NEAREST sampler (Blaze3D's blocks do exactly
// that) while Vulkan forbids anisotropyEnable there, so the GL value must never be forwarded raw.
template <class SamplerSource>
Float ResolveEffectiveMaxAnisotropy(const SamplerSource& sampler,
Bool forceNearestFiltering) const;
VkDevice m_device = VK_NULL_HANDLE;
const VulkanRendererConfig* m_config = nullptr;
Bool m_samplerAnisotropySupported = false;
Float m_maxSamplerAnisotropy = 1.0f;
Bool m_customBorderColorSupported = false;
Uint32 m_maxCustomBorderColorSamplers = 0;
// Live cache entries carrying a custom border colour. Kept in step with the entries themselves
// in exactly the three places one can appear or disappear: creation, the OnFrameBoundary sweep,
// and Shutdown.
Uint32 m_customBorderColorSamplerCount = 0;
UnorderedMap<Uint64, SamplerCacheEntry> m_samplers;
// Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging.
Uint64 m_frameBoundaryCounter = 0;
// Moves on every vkDestroySampler of a cached entry (sweep and Shutdown).
Uint64 m_destroyEpoch = 0;
static inline thread_local XXH64_state_t* m_hashState = XXH64_createState();
};
} // namespace MobileGL::MG_Backend::DirectVulkan
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -16,15 +16,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTimerQueryManager::Initialize requires non-zero frame count");
if (initInfo.timestampValidBits == 0 || initInfo.timestampPeriodNs <= 0.0f || initInfo.slotsPerPool == 0) {
MGLOG_W_ONCE("VkTimerQueryManager: timestamps unsupported (validBits=%u, period=%f, slots=%u)",
initInfo.timestampValidBits, initInfo.timestampPeriodNs, initInfo.slotsPerPool);
initInfo.timestampValidBits, initInfo.timestampPeriodNs, initInfo.slotsPerPool);
return false;
}
m_device = initInfo.device;
m_timestampPeriodNs = initInfo.timestampPeriodNs;
m_validBitsMask = initInfo.timestampValidBits >= 64
? ~0ull
: ((1ull << initInfo.timestampValidBits) - 1ull);
m_validBitsMask = initInfo.timestampValidBits >= 64 ? ~0ull : ((1ull << initInfo.timestampValidBits) - 1ull);
m_slotsPerPool = initInfo.slotsPerPool;
m_pools.resize(initInfo.frameCount);
@@ -91,8 +89,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (poolState.cursor >= m_slotsPerPool) {
if (!poolState.exhaustionWarned) {
MGLOG_W_ONCE("VkTimerQueryManager: frame %u timestamp pool exhausted (%u slots); further timer queries "
"this frame fall back to the frontend path",
frameIndex, m_slotsPerPool);
"this frame fall back to the frontend path",
frameIndex, m_slotsPerPool);
poolState.exhaustionWarned = true;
}
return nullptr;
@@ -64,8 +64,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// vkCmdWriteTimestamp (valid both inside and outside a render pass).
// Returns null on pool exhaustion, with one warning per pool cycle; the
// frontend falls back gracefully on a null handle.
SharedPtr<TimestampRecord> WriteTimestamp(VkCommandBuffer commandBuffer, Uint32 frameIndex,
Uint64 frameSerial);
SharedPtr<TimestampRecord> WriteTimestamp(VkCommandBuffer commandBuffer, Uint32 frameIndex, Uint64 frameSerial);
// Non-blocking single-slot read (WITH_AVAILABILITY, no WAIT). Returns
// true once the record holds its raw ticks. Callers gate this on the
@@ -61,8 +61,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
using MagmaTailRsp = RenderStateParameters;
#define MAGMA_TAIL_INPUT_IS_DYNAMIC(Member) \
static_assert(MagmaRenderStateRangeIsDynamic(offsetof(MagmaTailRsp, Member), \
sizeof(MagmaTailRsp::Member)), \
static_assert(MagmaRenderStateRangeIsDynamic(offsetof(MagmaTailRsp, Member), sizeof(MagmaTailRsp::Member)), \
"ApplyDynamicDrawStateTail reads " #Member \
", which the P2 chunk table no longer calls dynamic state: a change to it would " \
"move the pipeline version, not the parameters version, and the tail would stop " \
@@ -74,14 +73,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// is dynamic entirely, and asserting the whole of it says so. If a later phase ever
// splits a per-viewport chunk out, this is a build break by design - narrow the assert
// to element 0 then, and say why in the same commit.
MAGMA_TAIL_INPUT_IS_DYNAMIC(Viewports); // ApplyGLViewportState: Viewports[0]
MAGMA_TAIL_INPUT_IS_DYNAMIC(DepthRanges); // ApplyGLViewportState: DepthRanges[0]
MAGMA_TAIL_INPUT_IS_DYNAMIC(BlendColor); // ApplyBlendConstants
MAGMA_TAIL_INPUT_IS_DYNAMIC(Viewports); // ApplyGLViewportState: Viewports[0]
MAGMA_TAIL_INPUT_IS_DYNAMIC(DepthRanges); // ApplyGLViewportState: DepthRanges[0]
MAGMA_TAIL_INPUT_IS_DYNAMIC(BlendColor); // ApplyBlendConstants
MAGMA_TAIL_INPUT_IS_DYNAMIC(PolygonOffsetFactor); // ApplyPolygonOffsetState
MAGMA_TAIL_INPUT_IS_DYNAMIC(PolygonOffsetUnits); // ApplyPolygonOffsetState
MAGMA_TAIL_INPUT_IS_DYNAMIC(PolygonOffsetClamp); // ApplyPolygonOffsetState
MAGMA_TAIL_INPUT_IS_DYNAMIC(LineWidth); // ApplyLineWidthState
MAGMA_TAIL_INPUT_IS_DYNAMIC(ScissorBoxes); // the scissor rect: ScissorBoxes[0]
MAGMA_TAIL_INPUT_IS_DYNAMIC(LineWidth); // ApplyLineWidthState
MAGMA_TAIL_INPUT_IS_DYNAMIC(ScissorBoxes); // the scissor rect: ScissorBoxes[0]
#undef MAGMA_TAIL_INPUT_IS_DYNAMIC
// ApplyStencilState reads three of the seven members of each face, and D6 splits
@@ -91,11 +90,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// because the split runs THROUGH the struct rather than around it.
constexpr SizeT kMagmaStencilFace1 = offsetof(MagmaTailRsp, StencilStates) + sizeof(StencilFaceState);
#define MAGMA_TAIL_STENCIL_IS_DYNAMIC(Member) \
static_assert(MagmaRenderStateRangeIsDynamic(offsetof(MagmaTailRsp, StencilStates) + \
offsetof(StencilFaceState, Member), \
sizeof(StencilFaceState::Member)), \
"ApplyStencilState reads the FRONT face's " #Member " as dynamic state"); \
static_assert(MagmaRenderStateRangeIsDynamic(kMagmaStencilFace1 + offsetof(StencilFaceState, Member), \
static_assert( \
MagmaRenderStateRangeIsDynamic(offsetof(MagmaTailRsp, StencilStates) + offsetof(StencilFaceState, Member), \
sizeof(StencilFaceState::Member)), \
"ApplyStencilState reads the FRONT face's " #Member " as dynamic state"); \
static_assert(MagmaRenderStateRangeIsDynamic(kMagmaStencilFace1 + offsetof(StencilFaceState, Member), \
sizeof(StencilFaceState::Member)), \
"ApplyStencilState reads the BACK face's " #Member " as dynamic state")
@@ -158,17 +157,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
maxBindings = std::min(maxBindings, maxSampledImages + maxDynamicUniformBuffers);
if (updateAfterBindLimits.enabled) {
const Uint32 updateAfterBindSamplers = std::min(updateAfterBindLimits.maxPerStageSamplers,
updateAfterBindLimits.maxSetSamplers);
const Uint32 updateAfterBindSampledImages = std::min(updateAfterBindLimits.maxPerStageSampledImages,
updateAfterBindLimits.maxSetSampledImages);
const Uint32 updateAfterBindDynamicUniformBuffers =
std::min(updateAfterBindLimits.maxPerStageUniformBuffers,
updateAfterBindLimits.maxSetUniformBuffersDynamic);
const Uint32 updateAfterBindSamplers =
std::min(updateAfterBindLimits.maxPerStageSamplers, updateAfterBindLimits.maxSetSamplers);
const Uint32 updateAfterBindSampledImages =
std::min(updateAfterBindLimits.maxPerStageSampledImages, updateAfterBindLimits.maxSetSampledImages);
const Uint32 updateAfterBindDynamicUniformBuffers = std::min(
updateAfterBindLimits.maxPerStageUniformBuffers, updateAfterBindLimits.maxSetUniformBuffersDynamic);
Uint32 updateAfterBindBindings = updateAfterBindLimits.maxPerStageResources;
updateAfterBindBindings = std::min(updateAfterBindBindings, updateAfterBindSamplers);
updateAfterBindBindings =
std::min(updateAfterBindBindings, updateAfterBindSampledImages + updateAfterBindDynamicUniformBuffers);
updateAfterBindBindings = std::min(updateAfterBindBindings,
updateAfterBindSampledImages + updateAfterBindDynamicUniformBuffers);
maxBindings = std::max(maxBindings, updateAfterBindBindings);
}
@@ -257,11 +255,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_I("MaxFramesInFlight: configured request=%u", requestedFramesInFlight);
VkSurfaceCapabilitiesKHR surfaceCaps{};
const VkResult capsResult = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
m_physicalDevice.handle, m_surface, &surfaceCaps);
const VkResult capsResult =
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice.handle, m_surface, &surfaceCaps);
if (capsResult != VK_SUCCESS) {
MGLOG_W("MaxFramesInFlight: vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed (VkResult=%d); "
"keeping requested %u", static_cast<Int>(capsResult), requestedFramesInFlight);
"keeping requested %u",
static_cast<Int>(capsResult), requestedFramesInFlight);
} else {
// Frames-in-flight is the CPU pipeline depth; it only needs to stay <= the number
// of swapchain images the surface can provide (maxImageCount), so the extra slots
@@ -270,16 +269,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the count), and inflating the CPU depth would only add latency + memory.
Uint32 chosenFramesInFlight = requestedFramesInFlight;
if (surfaceCaps.maxImageCount != 0 && chosenFramesInFlight > surfaceCaps.maxImageCount) {
chosenFramesInFlight = surfaceCaps.maxImageCount; // 0 == no upper bound
chosenFramesInFlight = surfaceCaps.maxImageCount; // 0 == no upper bound
}
if (chosenFramesInFlight < 2) {
chosenFramesInFlight = 2; // never drop below double buffering
chosenFramesInFlight = 2; // never drop below double buffering
}
m_config.MaxFramesInFlight = chosenFramesInFlight;
if (chosenFramesInFlight != requestedFramesInFlight) {
MGLOG_W("MaxFramesInFlight: requested %u unsupported by surface (minImageCount=%u, "
"maxImageCount=%u); using %u", requestedFramesInFlight, surfaceCaps.minImageCount,
surfaceCaps.maxImageCount, chosenFramesInFlight);
"maxImageCount=%u); using %u",
requestedFramesInFlight, surfaceCaps.minImageCount, surfaceCaps.maxImageCount,
chosenFramesInFlight);
} else {
MGLOG_I("MaxFramesInFlight: using %u (surface minImageCount=%u, maxImageCount=%u)",
chosenFramesInFlight, surfaceCaps.minImageCount, surfaceCaps.maxImageCount);
@@ -318,21 +318,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
m_textureManager = MakeUnique<VkTextureManager>();
MOBILEGL_ASSERT(m_textureManager != nullptr, "VkTextureManager creation failed.");
succeeded = m_textureManager->Initialize(
{m_device, m_physicalDevice.handle, m_allocator, m_commandPool, m_graphicsQueue,
m_frameContext.GetFrameCount(), m_imageFormatListExtensionEnabled,
m_sampledReadStageMask,
static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily)});
succeeded = m_textureManager->Initialize({m_device, m_physicalDevice.handle, m_allocator, m_commandPool,
m_graphicsQueue, m_frameContext.GetFrameCount(),
m_imageFormatListExtensionEnabled, m_sampledReadStageMask,
static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily)});
MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed.");
m_textureManager->SetWireE5RawCopyKit(GetWireE5RawCopyKit());
m_renderPassManager =
MakeUnique<VkRenderPassManager>(m_device, m_physicalDevice.handle, m_allocator, m_config,
*m_textureManager, m_swapchainObject);
m_renderPassManager = MakeUnique<VkRenderPassManager>(m_device, m_physicalDevice.handle, m_allocator, m_config,
*m_textureManager, m_swapchainObject);
MOBILEGL_ASSERT(m_renderPassManager != nullptr, "VkRenderPassManager creation failed.");
succeeded = m_renderPassManager->Initialize();
MOBILEGL_ASSERT(succeeded, "VkRenderPassManager initialization failed.");
const Uint32 maxProgramBindings = ComputeMaxProgramBindings(m_physicalDevice.properties, m_updateAfterBindLimits);
const Uint32 maxProgramBindings =
ComputeMaxProgramBindings(m_physicalDevice.properties, m_updateAfterBindLimits);
MGLOG_I("DirectVulkan: using %u program descriptor bindings", maxProgramBindings);
if (IsPowerVRDevice(m_physicalDevice.properties)) {
m_config.DisablePipelineCache = true;
@@ -353,8 +352,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// there (see PipelineFactory::ShouldSuppressDepthWrite for the exact scope);
// MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE forces the quirk on or off on any
// driver.
const MG_Config::QuirkOverride quirkOverride =
MG_Config::Features.MagmaDisableBlendedDepthWriteQuirk;
const MG_Config::QuirkOverride quirkOverride = MG_Config::Features.MagmaDisableBlendedDepthWriteQuirk;
const Bool suppressBlendedDepthWrite = PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(
quirkOverride, m_physicalDevice.properties.vendorID);
if (suppressBlendedDepthWrite) {
@@ -373,14 +371,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
subgroupPolicy.requireFullSubgroups = m_computeFullSubgroupsFeatureEnabled;
subgroupPolicy.nativeSubgroupSize = m_nativeSubgroupSize;
subgroupPolicy.maxComputeWorkgroupSubgroups = m_maxComputeWorkgroupSubgroups;
subgroupPolicy.maxComputeSharedMemoryBytes =
m_physicalDevice.properties.limits.maxComputeSharedMemorySize;
m_programFactory = MakeUnique<ProgramFactory>(m_device, m_config, maxProgramBindings,
m_shaderDrawParametersFeatureEnabled,
m_unformattedFloatStorageImagesEnabled,
m_tessellationAndGeometryPointSizeFeatureEnabled,
MG_Config::Features.EnableSpirvValidation,
m_updateAfterBindLimits, subgroupPolicy);
subgroupPolicy.maxComputeSharedMemoryBytes = m_physicalDevice.properties.limits.maxComputeSharedMemorySize;
m_programFactory = MakeUnique<ProgramFactory>(
m_device, m_config, maxProgramBindings, m_shaderDrawParametersFeatureEnabled,
m_unformattedFloatStorageImagesEnabled, m_tessellationAndGeometryPointSizeFeatureEnabled,
MG_Config::Features.EnableSpirvValidation, m_updateAfterBindLimits, subgroupPolicy);
MOBILEGL_ASSERT(m_programFactory != nullptr, "ProgramFactory creation failed.");
// The swapchain already exists at this point (Initialize creates it first), so seed the
// height the factory could not be told about from CreateSwapchain.
@@ -394,8 +389,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(m_samplerManager != nullptr, "VkSamplerManager creation failed.");
succeeded = m_samplerManager->Initialize({m_device, &m_config, m_samplerAnisotropyFeatureEnabled,
m_physicalDevice.properties.limits.maxSamplerAnisotropy,
m_customBorderColorFeatureEnabled,
m_maxCustomBorderColorSamplers});
m_customBorderColorFeatureEnabled, m_maxCustomBorderColorSamplers});
MOBILEGL_ASSERT(succeeded, "VkSamplerManager initialization failed.");
succeeded = InitializeBlitResources();
MOBILEGL_ASSERT(succeeded, "Blit pipeline resource initialization failed.");
@@ -410,8 +404,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
maxProgramBindings, kDescriptorSetsPerFrame, m_textureManager.get(), m_samplerManager.get());
MOBILEGL_ASSERT(succeeded, "UniformDescriptorBinder initialization failed.");
m_uniformManager->SetWireInvalidStorageImageArm(m_wireNullDescriptor);
m_vertexInputStateFactory =
MakeUnique<VertexInputStateFactory>(m_config, m_physicalDevice.handle);
m_vertexInputStateFactory = MakeUnique<VertexInputStateFactory>(m_config, m_physicalDevice.handle);
MOBILEGL_ASSERT(m_vertexInputStateFactory != nullptr, "VertexInputStateFactory creation failed.");
// Prime the first frame so Render() always targets an acquired swapchain image.
@@ -427,8 +420,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// rebuilding and re-acquiring on the same semaphore is safe.
MGLOG_D("Initialize, vkAcquireNextImageKHR got %d, recreating swapchain", acquireResult);
RecreateSwapchain();
acquireResult =
m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
acquireResult = m_frameContext.WaitAndAcquireNextImage(m_device, m_swapchainObject.GetHandle(),
m_imageIndexAcquired);
} else if (acquireResult == VK_SUBOPTIMAL_KHR) {
// The image is usable, and its acquire signal is already armed on
// imageAvailableSemaphore. Re-acquiring here would arm a second signal on a
@@ -461,7 +454,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
m_progressMarkers.Shutdown();
// Parked surface targets (ActivateSurfaceTarget) go before the device and instance.
for (auto& [_, target] : m_parkedTargets) DestroyParkedTarget(target);
for (auto& [_, target] : m_parkedTargets)
DestroyParkedTarget(target);
m_parkedTargets.clear();
// 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).
@@ -499,8 +493,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// No sweep runs during teardown, but the observers point at this renderer
// and the factories die at different times below; disconnect them first.
if (m_renderPassManager) {
}
if (m_renderPassManager) {}
if (m_programFactory) {
m_programFactory->SetEvictionObserver(nullptr);
}
@@ -708,8 +701,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VulkanRenderer::CollectDeferredDepthMipmapCleanup(Uint32 frameIndex) {
MOBILEGL_ASSERT(frameIndex < m_deferredDepthMipmapCleanup.size(),
"CollectDeferredDepthMipmapCleanup: frame index %u out of range (size=%zu)",
frameIndex, m_deferredDepthMipmapCleanup.size());
"CollectDeferredDepthMipmapCleanup: frame index %u out of range (size=%zu)", frameIndex,
m_deferredDepthMipmapCleanup.size());
if (m_device == VK_NULL_HANDLE) {
return;
}
@@ -48,15 +48,15 @@ namespace MobileGL::MG_State::GLState {
} // namespace MobileGL::MG_State::GLState
namespace MobileGL::MG_Backend::DirectVulkan {
enum class DrawSetupAspect: Uint8 {
FramebufferObject = 1 << 0,
VertexArrayObject = 1 << 1,
UniformBuffer = 1 << 2,
VertexBuffer = 1 << 3,
IndexBuffer = 1 << 4,
enum class DrawSetupAspect : Uint8 {
FramebufferObject = 1 << 0,
VertexArrayObject = 1 << 1,
UniformBuffer = 1 << 2,
VertexBuffer = 1 << 3,
IndexBuffer = 1 << 4,
IndirectDrawBuffer = 1 << 5,
Viewport = 1 << 6,
Scissor = 1 << 7,
Viewport = 1 << 6,
Scissor = 1 << 7,
};
struct DrawCmdParam {
@@ -187,9 +187,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// (rd12 Magma: the wait was ~1.8 ms/frame of fence stall, one per lightmap update.)
// P15 (WireFramebuffer.cpp): runs an image's deferred clears now, outside any pass.
void MaterializeWirePendingClears(VkTextureManager::TextureResource& resource);
Bool FlushWirePendingCommandsForTextureUpload() {
return !HasPendingRecordedWork() || FlushPendingCommands();
}
Bool FlushWirePendingCommandsForTextureUpload() { return !HasPendingRecordedWork() || FlushPendingCommands(); }
// P7 wave 2 package B3: the submission that will carry whatever is recorded NEXT.
// RetireWireObjects (WireDraw.cpp) already tags future objects with exactly this, and
// for the same reason: a draw being set up now is not in any submission yet, so the
@@ -265,32 +263,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// idleness guarantee - they are only bound through draws/dispatches that
// stamp the program entry) and purge the descriptor-set cache entries
// keyed by its now-recyclable VkDescriptorSetLayout handle.
void OnProgramEvicted(ProgramFactory::HashType programHash,
VkDescriptorSetLayout descriptorSetLayout) override;
void OnProgramEvicted(ProgramFactory::HashType programHash, VkDescriptorSetLayout descriptorSetLayout) override;
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const DrawCmdParam& drawParams,
const IndexBufferView* pIndexBufferView = nullptr);
const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView = nullptr);
enum class ScissoredClearPrep {
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
NoOp, // nothing to clear (degenerate target or empty scissor rect)
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
NotNeeded, // scissor covers the whole target — take the deferred whole-surface path instead
NoOp, // nothing to clear (degenerate target or empty scissor rect)
Ready, // a render pass is active; record vkCmdClearAttachments with the returned rect
};
void Clear(GLbitfield mask);
void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
void ClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value);
void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value);
void ClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height);
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dstEndpoint,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth);
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
GLint dstY1, GLbitfield mask, GLenum filter);
void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
void CopyImageSubData(const CopyImageEndpoint& srcEndpoint, GLenum srcTarget, GLint srcLevel, GLint srcX,
GLint srcY, GLint srcZ, const CopyImageEndpoint& dstEndpoint, GLenum dstTarget,
GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight,
GLsizei srcDepth);
void GenerateMipmap(GLenum target);
void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
// Server resource handle in, tightly packed owned bytes out. No frontend
@@ -329,14 +323,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkOffset2D* imageOffset, VkExtent2D* imageCopyExtent);
// Reorder a tightly packed block copied with MapDefaultFramebufferReadbackRect back into
// GL row order. The input block has swapped dimensions for 90/270 degree transforms.
static Bool RemapDefaultFramebufferReadback(const Uint8* rawPixels, Uint32 logicalWidth,
Uint32 logicalHeight,
VkSurfaceTransformFlagBitsKHR preTransform,
SizeT texelSize, Uint8* outPixels);
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
GLsizei width, GLsizei height, GLenum destinationFormat,
GLenum destinationType, SizeT destinationRowStride,
Uint8* destinationPixels);
static Bool RemapDefaultFramebufferReadback(const Uint8* rawPixels, Uint32 logicalWidth, Uint32 logicalHeight,
VkSurfaceTransformFlagBitsKHR preTransform, SizeT texelSize,
Uint8* outPixels);
static Bool ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat, GLsizei width,
GLsizei height, GLenum destinationFormat, GLenum destinationType,
SizeT destinationRowStride, Uint8* destinationPixels);
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ);
void DispatchComputeIndirect(GLintptr indirect);
void MemoryBarrier(GLbitfield barriers);
@@ -548,11 +540,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
//
// The structs stay, empty, so ShutdownBlitResources and the two `= {}` assignments read
// the same on both builds.
struct BlitResources {
};
struct BlitResources {};
struct DepthMipmapResources {
};
struct DepthMipmapResources {};
// A single-sample staging image for multisample-resolve blits that also have to change
// orientation. vkCmdResolveImage cannot flip (it takes one offset per side, not the
@@ -610,9 +600,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// `isWriteTarget` selects which side of the default framebuffer this resolves: a write
// re-points it at the image Present() acquired, a read keeps the current one (see
// m_defaultFramebufferImageIndex). Reads must pass false.
WireImage ResolveWireImage(const MG_Pipe::MGPFramebufferState& fbo,
const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect,
Bool isWriteTarget = false);
WireImage ResolveWireImage(const MG_Pipe::MGPFramebufferState& fbo, const MG_Pipe::MGPSurface& surface,
VkImageAspectFlags aspect, Bool isWriteTarget = false);
void TransitionWireImage(WireImage& image, VkImageLayout layout, Bool coalesceWithLastFullBarrier = false);
// P15: WIRE DEFERRED CLEARS (WireFramebuffer.cpp). A clear of images the open pass
// holds is recorded inside it; a whole-image clear of others waits on the image
@@ -637,26 +626,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool BeginWireDrawPassFolding(FrameContext::FrameData& frame, RenderPassEntry& begun, Uint64 variantKey,
const VkClearValue* values);
void ApplyWireFoldClearsInPass(FrameContext::FrameData& frame, RenderPassEntry& pass);
void ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo,
const ClearAttachmentPayload& payload, GLint drawbuffer = -1);
void BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter);
void ResolveWireDepthStencil(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1);
void ClearWireFramebuffer(const MG_Pipe::MGPFramebufferState& fbo, const ClearAttachmentPayload& payload,
GLint drawbuffer = -1);
void BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1,
GLint dy1, GLbitfield mask, GLenum filter);
void ResolveWireDepthStencil(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1,
GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1);
// One aspect of a depth/stencil image through a buffer (source in TRANSFER_SRC_OPTIMAL,
// destination in TRANSFER_DST_OPTIMAL, one format). The returned buffer is named by the
// recorded commands and must be retired with them.
UniquePtr<VkBufferObject> CopyWireAspectThroughBuffer(VkImage srcImage, Uint32 srcLevel, Uint32 srcLayer,
GLint sx, GLint sy, const WireImage& destination,
GLint dx, GLint dy, Uint32 width, Uint32 height,
VkImageAspectFlags aspect, VkFormat format,
Bool mirrorY, const char* fatalDetail);
VkImageAspectFlags aspect, VkFormat format, Bool mirrorY,
const char* fatalDetail);
// A single-sample depth/stencil blit whose rectangles make it a plain copy, performed
// without vkCmdBlitImage (P7 gate 5). False when the shape is not a plain copy.
Bool CopyWireDepthStencilBlit(WireImage& source, WireImage& destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, VkImageAspectFlags aspect);
Bool CopyWireDepthStencilBlit(WireImage& source, WireImage& destination, GLint sx0, GLint sy0, GLint sx1,
GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, VkImageAspectFlags aspect);
// True when the rectangles give a multisample depth/stencil resolve a shape it declines
// (a scale, an X mirror, a Y mirror across two formats); the decline has then been
// logged and its INVALID_OPERATION recorded. BlitWireFramebuffers asks it BEFORE any
@@ -665,9 +652,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ResolveWireDepthStencil (a stencil-only resolve without VK_EXT_shader_stencil_export
// on a device without VK_KHR_depth_stencil_resolve) is still decided per aspect, after
// colour ran; rule I (a) accepts that (docs/archive/notes/p7/magma-b2.md §2.10).
Bool DeclineWireDepthStencilResolveShape(const WireImage& source, const WireImage& destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1);
Bool DeclineWireDepthStencilResolveShape(const WireImage& source, const WireImage& destination, GLint sx0,
GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1,
GLint dy1);
PFN_vkCreateRenderPass2 m_wireCreateRenderPass2 = nullptr;
VkResolveModeFlags m_wireDepthResolveModes = 0;
VkResolveModeFlags m_wireStencilResolveModes = 0;
@@ -676,9 +663,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VK_KHR_depth_stencil_resolve. One baked pass per aspect, sample zero, no frontend
// object - see WireMultisampleResolve.cpp. `resolved` must be a single-sample image of
// the source's format and extent; the rect is expressed by the scissor.
Bool ResolveWireDepthStencilWithShader(WireImage source, WireImage resolved,
VkImageAspectFlags aspect, GLint sx0, GLint sy0,
Uint32 width, Uint32 height);
Bool ResolveWireDepthStencilWithShader(WireImage source, WireImage resolved, VkImageAspectFlags aspect,
GLint sx0, GLint sy0, Uint32 width, Uint32 height);
struct WireMultisampleResolveResources;
WireMultisampleResolveResources* m_wireMultisampleResolveResources = nullptr;
void DestroyWireMultisampleResolveResources();
@@ -806,26 +792,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void ArmWireDiscardStateReemit();
void ForgetDynamicStateAfterDiscardDraw();
void ArmWireE5RawReads();
public:
// The kit when the route is armed, else nullptr. VkTextureManager asks before a level copy.
const WireE5RawCopyKit* GetWireE5RawCopyKit() const {
return m_wireE5RawReads ? &m_wireE5RawCopyKit : nullptr;
}
const WireE5RawCopyKit* GetWireE5RawCopyKit() const { return m_wireE5RawReads ? &m_wireE5RawCopyKit : nullptr; }
private:
Bool BlitWireColorToDefault(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter);
Bool BlitWireColorImage(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap);
Bool BlitWireColorToDefault(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter);
Bool BlitWireColorImage(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap);
// P7 wave 2-B, CONTRACT-P7 §3.2 (`default-color-blit-shape`): the rotated arm. Moves the
// source region into an owned single-sample 2D image of a sampled-float format in the
// same size-compatibility class, then shader-blits THAT to the rotated default
// framebuffer - the shapes BlitWireColorImage itself cannot sample (an integer format, a
// 3D or array source) become shapes it can.
Bool BlitWireColorToDefaultThroughScratch(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1,
Bool BlitWireColorToDefaultThroughScratch(WireImage source, WireImage destination, GLint sx0, GLint sy0,
GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1,
GLenum filter);
struct WireColorBlitResources;
WireColorBlitResources* m_wireColorBlitResources = nullptr;
@@ -843,8 +826,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// P7 wave 2-B (exit gate 3): the readback's own write-visibility barrier, recorded before
// the copy instead of inferred from the image's tracked layout. See WireFramebuffer.cpp.
void RecordWireReadbackWriteBarrier();
void ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height,
GLenum format, GLenum type, void* pixels);
void ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels);
void GenerateWireMipmap();
Bool SetupWireDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const DrawCmdParam& drawParams, const IndexBufferView* indices);
@@ -996,12 +978,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<RenderPassEntry> m_wireDrawPass;
Vector<VkImageView> m_wireDrawViews;
VkPipeline GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
Bool primitiveRestartEnable, Bool layeredFramebuffer, Uint64 wireRenderPassCompatibilityId = 0);
const ProgramFactory::VkProgramObject& programObj,
ProgramFactory::CompileOptionFlags transformFlags,
const VertexInputStateFactory::BackendVertexInputState& vis,
const RenderPassEntry& renderPassEntry, Bool primitiveRestartEnable,
Bool layeredFramebuffer, Uint64 wireRenderPassCompatibilityId = 0);
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height);
void CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height);
// ---- Submission fence tracking (GL sync objects) ----
// One record per vkQueueSubmit still in flight, in ascending submit
// order. Present/readback submissions reference the frame slot's
@@ -1276,7 +1260,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the pipeline-state value hash, neither of which moves when glBeginTransformFeedback is
// called, so a dynamic input here would hand back a stale VkPipeline.
VkProvokingVertexModeEXT SelectProvokingVertexMode(VkPrimitiveTopology topology,
Bool capturesXfbFromGeometryStage) const;
Bool capturesXfbFromGeometryStage) const;
// VK_EXT_vertex_attribute_divisor: without it every non-zero glVertexAttribDivisor
// behaves as 1, because that is all Vulkan's instance input rate can express.
Bool m_vertexAttributeDivisorEnabled = false;
@@ -1310,9 +1294,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT
// when GL transform feedback is active; binds capture buffers on demand.
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
Bool BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 count,
const VkBuffer* buffers, const VkDeviceSize* offsets,
const VkDeviceSize* sizes);
Bool BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 count, const VkBuffer* buffers,
const VkDeviceSize* offsets, const VkDeviceSize* sizes);
Bool BeginWireXfbCaptureForDraw(FrameContext::FrameData& frame);
Uint32 m_currentDrawXfbBufferCount = 0;
Uint32 m_currentDrawXfbBufferMask = 0;
@@ -1378,8 +1361,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// tessellationShader was enabled at device creation (it is taken whenever the
// device advertises it); gates the probe's PATCHES shape.
Bool m_tessellationShaderFeatureEnabled = false;
MG_Util::SelfTest::PrimGenRerouteKind m_primGenRerouteKind =
MG_Util::SelfTest::PrimGenRerouteKind::None;
MG_Util::SelfTest::PrimGenRerouteKind m_primGenRerouteKind = MG_Util::SelfTest::PrimGenRerouteKind::None;
// The bring-up probe measured this device's stream query as counting draws made
// with no capture span open (the StreamCounts verdict) - so it counts the
// PAUSED-span ones too, through the stream slot they take when nothing is
@@ -1411,8 +1393,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// kind: 0 = PRIMITIVES_WRITTEN, 1 = PRIMITIVES_GENERATED.
Bool StartXfbQueryCapture(Uint32 kind);
void StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots, Vector<Uint32>& outRerouteSlots);
Bool ResolveXfbQueryResult(const Vector<Uint32>& slots, const Vector<Uint32>& rerouteSlots,
Bool wantGenerated, Uint64& outPrimitives);
Bool ResolveXfbQueryResult(const Vector<Uint32>& slots, const Vector<Uint32>& rerouteSlots, Bool wantGenerated,
Uint64& outPrimitives);
private:
void BeginXfbQueryForDraw(VkCommandBuffer commandBuffer, Bool xfbActive);
@@ -1632,8 +1614,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// pass renders unflipped). Keyed on BOTH inputs; missing the FBO bit shipped
// an upside-down default-framebuffer pass after any render-to-texture
// (minecraft-1.17-main-menu retrace, whole frame flipped).
VkSurfaceTransformFlagBitsKHR m_baseTransformFlagsPreTransform =
VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR;
VkSurfaceTransformFlagBitsKHR m_baseTransformFlagsPreTransform = VK_SURFACE_TRANSFORM_FLAG_BITS_MAX_ENUM_KHR;
Bool m_baseTransformFlagsIsDefaultFbo = false;
Bool m_baseTransformFlagsKeyValid = false;
Uint32 m_baseTransformFlagsCache = 0;
@@ -1671,7 +1652,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// must not allocate.
Vector<VkVertexInputAttributeDescription> m_patchedAttributesScratch;
void CreateInstance();
VkResult SetupDebugMessenger();
VkResult DestroyDebugMessenger();
@@ -1708,8 +1688,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 viewportCount = 1);
void ApplyMultiViewportDynamicState(VkCommandBuffer commandBuffer, Uint32 viewportCount, const IntVec2& extent,
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFbo);
VkRect2D ComputeGLScissorRect(Uint32 index, const IntVec2& extent,
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFbo) const;
VkRect2D ComputeGLScissorRect(Uint32 index, const IntVec2& extent, VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFbo) const;
// How many viewports a draw with this program rasterizes into: 1 unless the program
// assigns gl_ViewportIndex AND the device enabled multiViewport. Both the pipeline's
// baked viewportCount and the dynamic arrays come from this one answer, so they cannot
@@ -1728,7 +1708,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
public:
private:
void ShutdownSwapchain();
// Static functions
@@ -13,138 +13,138 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static void GetImageTransitionDestinationState(VkImageLayout newLayout, VkPipelineStageFlags& outDstStageMask,
VkAccessFlags& outDstAccessMask) {
switch (newLayout) {
case VK_IMAGE_LAYOUT_UNDEFINED:
outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
outDstAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
outDstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outDstAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
outDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
break;
default:
outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_UNDEFINED:
outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
outDstAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
outDstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
outDstStageMask =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
outDstAccessMask =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outDstAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outDstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
outDstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
outDstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
break;
default:
outDstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
outDstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
break;
}
}
static Bool DecodeReadbackPixel(const Uint8* source, VkFormat sourceFormat, Float* rgba) {
switch (sourceFormat) {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
rgba[0] = static_cast<Float>(source[0]) / 255.0f;
rgba[1] = static_cast<Float>(source[1]) / 255.0f;
rgba[2] = static_cast<Float>(source[2]) / 255.0f;
rgba[3] = static_cast<Float>(source[3]) / 255.0f;
return true;
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
rgba[0] = static_cast<Float>(source[2]) / 255.0f;
rgba[1] = static_cast<Float>(source[1]) / 255.0f;
rgba[2] = static_cast<Float>(source[0]) / 255.0f;
rgba[3] = static_cast<Float>(source[3]) / 255.0f;
return true;
case VK_FORMAT_R16G16B16A16_UNORM:
for (SizeT component = 0; component < 4; ++component) {
Uint16 value = 0;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = static_cast<Float>(value) / 65535.0f;
}
return true;
case VK_FORMAT_R16G16B16A16_SFLOAT:
for (SizeT component = 0; component < 4; ++component) {
Uint16 value = 0;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = MG_Util::DecodeHalfBitsToFloat(value);
}
return true;
case VK_FORMAT_R32G32B32A32_SFLOAT:
Memcpy(rgba, source, sizeof(Float) * 4);
return true;
// Single- and dual-channel formats the reinterpretation feature makes common
// as readback sources (iterationRP custom images are R32F/R32UI-class).
// Missing channels take GL's defaults: 0 for GB, 1 for alpha.
case VK_FORMAT_R32_SFLOAT: {
Float value = 0.0f;
Memcpy(&value, source, sizeof(value));
rgba[0] = value;
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32G32_SFLOAT: {
Float values[2] = {0.0f, 0.0f};
Memcpy(values, source, sizeof(values));
rgba[0] = values[0];
rgba[1] = values[1];
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32_UINT: {
Uint32 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = static_cast<Float>(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32_SINT: {
Int32 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = static_cast<Float>(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R16_SFLOAT: {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
rgba[0] = static_cast<Float>(source[0]) / 255.0f;
rgba[1] = static_cast<Float>(source[1]) / 255.0f;
rgba[2] = static_cast<Float>(source[2]) / 255.0f;
rgba[3] = static_cast<Float>(source[3]) / 255.0f;
return true;
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SRGB:
rgba[0] = static_cast<Float>(source[2]) / 255.0f;
rgba[1] = static_cast<Float>(source[1]) / 255.0f;
rgba[2] = static_cast<Float>(source[0]) / 255.0f;
rgba[3] = static_cast<Float>(source[3]) / 255.0f;
return true;
case VK_FORMAT_R16G16B16A16_UNORM:
for (SizeT component = 0; component < 4; ++component) {
Uint16 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = MG_Util::DecodeHalfBitsToFloat(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = static_cast<Float>(value) / 65535.0f;
}
case VK_FORMAT_R16G16_SFLOAT:
for (SizeT component = 0; component < 2; ++component) {
Uint16 value = 0;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = MG_Util::DecodeHalfBitsToFloat(value);
}
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
default:
return false;
return true;
case VK_FORMAT_R16G16B16A16_SFLOAT:
for (SizeT component = 0; component < 4; ++component) {
Uint16 value = 0;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = MG_Util::DecodeHalfBitsToFloat(value);
}
return true;
case VK_FORMAT_R32G32B32A32_SFLOAT:
Memcpy(rgba, source, sizeof(Float) * 4);
return true;
// Single- and dual-channel formats the reinterpretation feature makes common
// as readback sources (iterationRP custom images are R32F/R32UI-class).
// Missing channels take GL's defaults: 0 for GB, 1 for alpha.
case VK_FORMAT_R32_SFLOAT: {
Float value = 0.0f;
Memcpy(&value, source, sizeof(value));
rgba[0] = value;
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32G32_SFLOAT: {
Float values[2] = {0.0f, 0.0f};
Memcpy(values, source, sizeof(values));
rgba[0] = values[0];
rgba[1] = values[1];
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32_UINT: {
Uint32 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = static_cast<Float>(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R32_SINT: {
Int32 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = static_cast<Float>(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R16_SFLOAT: {
Uint16 value = 0;
Memcpy(&value, source, sizeof(value));
rgba[0] = MG_Util::DecodeHalfBitsToFloat(value);
rgba[1] = 0.0f;
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
}
case VK_FORMAT_R16G16_SFLOAT:
for (SizeT component = 0; component < 2; ++component) {
Uint16 value = 0;
Memcpy(&value, source + component * sizeof(value), sizeof(value));
rgba[component] = MG_Util::DecodeHalfBitsToFloat(value);
}
rgba[2] = 0.0f;
rgba[3] = 1.0f;
return true;
default:
return false;
}
}
@@ -160,14 +160,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static Int GetReadbackChannelCount(GLenum format) {
switch (format) {
case GL_RGB:
case GL_BGR:
return 3;
case GL_RGBA:
case GL_BGRA:
return 4;
default:
return 0;
case GL_RGB:
case GL_BGR:
return 3;
case GL_RGBA:
case GL_BGRA:
return 4;
default:
return 0;
}
}
@@ -177,30 +177,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint8 b = EncodeReadbackUnorm8(rgba[2]);
const Uint8 a = EncodeReadbackUnorm8(rgba[3]);
switch (dstFormat) {
case GL_RGB:
dst[0] = r;
dst[1] = g;
dst[2] = b;
break;
case GL_BGR:
dst[0] = b;
dst[1] = g;
dst[2] = r;
break;
case GL_RGBA:
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
break;
case GL_BGRA:
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
break;
default:
break;
case GL_RGB:
dst[0] = r;
dst[1] = g;
dst[2] = b;
break;
case GL_BGR:
dst[0] = b;
dst[1] = g;
dst[2] = r;
break;
case GL_RGBA:
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
break;
case GL_BGRA:
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
break;
default:
break;
}
}
@@ -210,30 +210,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Float b = rgba[2];
const Float a = rgba[3];
switch (dstFormat) {
case GL_RGB:
dst[0] = r;
dst[1] = g;
dst[2] = b;
break;
case GL_BGR:
dst[0] = b;
dst[1] = g;
dst[2] = r;
break;
case GL_RGBA:
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
break;
case GL_BGRA:
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
break;
default:
break;
case GL_RGB:
dst[0] = r;
dst[1] = g;
dst[2] = b;
break;
case GL_BGR:
dst[0] = b;
dst[1] = g;
dst[2] = r;
break;
case GL_RGBA:
dst[0] = r;
dst[1] = g;
dst[2] = b;
dst[3] = a;
break;
case GL_BGRA:
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
break;
default:
break;
}
}
} // namespace
@@ -246,10 +246,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return formatInfo.texel_block_size;
}
Bool VulkanRenderer::MapDefaultFramebufferReadbackRect(
GLint x, GLint y, GLsizei width, GLsizei height, VkExtent2D imageExtent,
VkSurfaceTransformFlagBitsKHR preTransform, VkOffset2D* imageOffset,
VkExtent2D* imageCopyExtent) {
Bool VulkanRenderer::MapDefaultFramebufferReadbackRect(GLint x, GLint y, GLsizei width, GLsizei height,
VkExtent2D imageExtent,
VkSurfaceTransformFlagBitsKHR preTransform,
VkOffset2D* imageOffset, VkExtent2D* imageCopyExtent) {
if (width <= 0 || height <= 0 || imageOffset == nullptr || imageCopyExtent == nullptr) {
return false;
}
@@ -287,8 +287,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break;
}
if (mappedX < 0 || mappedY < 0 || mappedWidth > imageExtent.width ||
mappedHeight > imageExtent.height ||
if (mappedX < 0 || mappedY < 0 || mappedWidth > imageExtent.width || mappedHeight > imageExtent.height ||
static_cast<Uint64>(mappedX) + mappedWidth > imageExtent.width ||
static_cast<Uint64>(mappedY) + mappedHeight > imageExtent.height) {
return false;
@@ -298,11 +297,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool VulkanRenderer::RemapDefaultFramebufferReadback(
const Uint8* rawPixels, Uint32 logicalWidth, Uint32 logicalHeight,
VkSurfaceTransformFlagBitsKHR preTransform, SizeT texelSize, Uint8* outPixels) {
if (rawPixels == nullptr || outPixels == nullptr || logicalWidth == 0 || logicalHeight == 0 ||
texelSize == 0) {
Bool VulkanRenderer::RemapDefaultFramebufferReadback(const Uint8* rawPixels, Uint32 logicalWidth,
Uint32 logicalHeight,
VkSurfaceTransformFlagBitsKHR preTransform, SizeT texelSize,
Uint8* outPixels) {
if (rawPixels == nullptr || outPixels == nullptr || logicalWidth == 0 || logicalHeight == 0 || texelSize == 0) {
return false;
}
@@ -328,17 +327,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break;
}
Memcpy(outPixels + (static_cast<SizeT>(outY) * logicalWidth + outX) * texelSize,
rawPixels + (static_cast<SizeT>(srcY) * rawWidth + srcX) * texelSize,
texelSize);
rawPixels + (static_cast<SizeT>(srcY) * rawWidth + srcX) * texelSize, texelSize);
}
}
return true;
}
Bool VulkanRenderer::ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat,
GLsizei width, GLsizei height, GLenum destinationFormat,
GLenum destinationType, SizeT destinationRowStride,
Uint8* destinationPixels) {
Bool VulkanRenderer::ConvertReadbackPixels(const Uint8* sourcePixels, VkFormat sourceFormat, GLsizei width,
GLsizei height, GLenum destinationFormat, GLenum destinationType,
SizeT destinationRowStride, Uint8* destinationPixels) {
if (width <= 0 || height <= 0) {
return true;
}
@@ -359,8 +356,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
for (GLsizei row = 0; row < height; ++row) {
const Uint8* sourceRow = sourcePixels +
static_cast<SizeT>(row) * static_cast<SizeT>(width) * sourceTexelSize;
const Uint8* sourceRow =
sourcePixels + static_cast<SizeT>(row) * static_cast<SizeT>(width) * sourceTexelSize;
Uint8* destinationRow = destinationPixels + static_cast<SizeT>(row) * destinationRowStride;
for (GLsizei column = 0; column < width; ++column) {
const Uint8* source = sourceRow + static_cast<SizeT>(column) * sourceTexelSize;
@@ -397,7 +394,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const ClearAttachmentPayload& clearPayload) {
const auto* fbo = MG_Pipe::MGPipeApplier().DrawFramebuffer();
if (!fbo) MagmaWireFatal("clear-buffer-framebuffer-record");
ClearWireFramebuffer(*fbo,clearPayload,buffer == GL_COLOR ? drawbuffer : -1);
ClearWireFramebuffer(*fbo, clearPayload, buffer == GL_COLOR ? drawbuffer : -1);
return;
}
@@ -416,16 +413,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
ClearAttachmentPayload payload{};
switch (buffer) {
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.color = FloatVec4(value[0], value[1], value[2], value[3]);
break;
case GL_DEPTH:
payload.mask = GL_DEPTH_BUFFER_BIT;
payload.depth = std::clamp(value[0], 0.0f, 1.0f);
break;
default:
break;
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.color = FloatVec4(value[0], value[1], value[2], value[3]);
break;
case GL_DEPTH:
payload.mask = GL_DEPTH_BUFFER_BIT;
payload.depth = std::clamp(value[0], 0.0f, 1.0f);
break;
default:
break;
}
QueueClearBufferPayload(buffer, drawbuffer, payload);
}
@@ -436,17 +433,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
ClearAttachmentPayload payload{};
switch (buffer) {
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.colorEncoding = ClearColorEncoding::Uint;
payload.colorUint = UintVec4(value[0], value[1], value[2], value[3]);
break;
case GL_STENCIL:
payload.mask = GL_STENCIL_BUFFER_BIT;
payload.stencil = value[0];
break;
default:
break;
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.colorEncoding = ClearColorEncoding::Uint;
payload.colorUint = UintVec4(value[0], value[1], value[2], value[3]);
break;
case GL_STENCIL:
payload.mask = GL_STENCIL_BUFFER_BIT;
payload.stencil = value[0];
break;
default:
break;
}
QueueClearBufferPayload(buffer, drawbuffer, payload);
}
@@ -457,17 +454,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
ClearAttachmentPayload payload{};
switch (buffer) {
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.colorEncoding = ClearColorEncoding::Int;
payload.colorInt = IntVec4(value[0], value[1], value[2], value[3]);
break;
case GL_STENCIL:
payload.mask = GL_STENCIL_BUFFER_BIT;
payload.stencil = static_cast<Uint32>(std::max(value[0], 0));
break;
default:
break;
case GL_COLOR:
payload.mask = GL_COLOR_BUFFER_BIT;
payload.colorEncoding = ClearColorEncoding::Int;
payload.colorInt = IntVec4(value[0], value[1], value[2], value[3]);
break;
case GL_STENCIL:
payload.mask = GL_STENCIL_BUFFER_BIT;
payload.stencil = static_cast<Uint32>(std::max(value[0], 0));
break;
default:
break;
}
QueueClearBufferPayload(buffer, drawbuffer, payload);
}
@@ -479,16 +476,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_msResolveScratch = {};
}
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
GLbitfield mask, GLenum filter) {
BlitWireFramebuffers(srcX0,srcY0,srcX1,srcY1,dstX0,dstY0,dstX1,dstY1,mask,filter);
void VulkanRenderer::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0,
GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) {
BlitWireFramebuffers(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter);
return;
}
void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset,
GLint x, GLint y, GLsizei width, GLsizei height) {
CopyWireFramebufferToTexture(target,level,xoffset,yoffset,x,y,width,height);
void VulkanRenderer::CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y,
GLsizei width, GLsizei height) {
CopyWireFramebufferToTexture(target, level, xoffset, yoffset, x, y, width, height);
return;
}
@@ -603,9 +599,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
} // namespace
void VulkanRenderer::CopyImageSubData(const CopyImageEndpoint& srcEndpoint,
GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ,
const CopyImageEndpoint& dstEndpoint,
void VulkanRenderer::CopyImageSubData(const CopyImageEndpoint& srcEndpoint, GLenum srcTarget, GLint srcLevel,
GLint srcX, GLint srcY, GLint srcZ, const CopyImageEndpoint& dstEndpoint,
GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ,
GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) {
const Bool wire = true; // P13 W6: the record arm is the only arm
@@ -671,8 +666,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
//
// `inPlace` is constexpr false in the pull build so that every branch it guards folds
// away: this function is in the monolith image too and G1 pins that image's .text.
const Bool inPlace = wire && srcImage.image != VK_NULL_HANDLE &&
srcImage.image == dstImage.image;
const Bool inPlace = wire && srcImage.image != VK_NULL_HANDLE && srcImage.image == dstImage.image;
if (wire) m_textureManager->FlushPendingUploads();
// Real checks, not MOBILEGL_ASSERT: the assertions this replaces compile to nothing in
// a release build, which is where both observed failures happened - a null resource
@@ -705,10 +699,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return endpoint.RenderbufferHandle;
}
Uint32 mappedMip = static_cast<Uint32>(mip), mappedLayer = static_cast<Uint32>(layer);
const auto storage = m_textureManager->ResolveWireTextureStorage(
endpoint.TextureHandle, mappedMip, mappedLayer);
const auto storage =
m_textureManager->ResolveWireTextureStorage(endpoint.TextureHandle, mappedMip, mappedLayer);
if (MG_Pipe::MGPipeHandleIsNull(storage)) MagmaWireFatal("copy-image-view-record");
mip = static_cast<GLint>(mappedMip); layer = static_cast<GLint>(mappedLayer);
mip = static_cast<GLint>(mappedMip);
layer = static_cast<GLint>(mappedLayer);
return storage;
};
(void)mapSubresource(srcEndpoint, srcLevel, srcZ);
@@ -763,9 +758,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkImageAspectFlags copyAspectMask =
srcImage.aspect & dstImage.aspect &
(VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
MOBILEGL_ASSERT(copyAspectMask != 0 &&
(srcImage.aspect & copyAspectMask) == srcImage.aspect &&
(dstImage.aspect & copyAspectMask) == dstImage.aspect,
MOBILEGL_ASSERT(copyAspectMask != 0 && (srcImage.aspect & copyAspectMask) == srcImage.aspect &&
(dstImage.aspect & copyAspectMask) == dstImage.aspect,
"CopyImageSubData source and destination aspects are incompatible.");
const Uint32 srcMipLevel = static_cast<Uint32>(srcLevel);
const Uint32 dstMipLevel = static_cast<Uint32>(dstLevel);
@@ -779,15 +773,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// copy_image.exceeding_boundaries, that asks for exactly this input). Nothing legal is
// lost by declining - a copy that reads or writes outside the image was never going to
// produce a correct result, it was going to produce whatever the driver did next.
if (srcX < 0 || srcY < 0 || dstX < 0 || dstY < 0 ||
static_cast<Uint32>(srcX + srcWidth) > srcMipWidth ||
if (srcX < 0 || srcY < 0 || dstX < 0 || dstY < 0 || static_cast<Uint32>(srcX + srcWidth) > srcMipWidth ||
static_cast<Uint32>(srcY + srcHeight) > srcMipHeight ||
static_cast<Uint32>(dstX + srcWidth) > dstMipWidth ||
static_cast<Uint32>(dstY + srcHeight) > dstMipHeight) {
MGLOG_E_ONCE("%s: region outside image bounds (src %dx%d+%d+%d of %ux%u, dst +%d+%d of %ux%u); "
"declining the copy",
__func__, srcWidth, srcHeight, srcX, srcY, srcMipWidth, srcMipHeight, dstX, dstY,
dstMipWidth, dstMipHeight);
__func__, srcWidth, srcHeight, srcX, srcY, srcMipWidth, srcMipHeight, dstX, dstY, dstMipWidth,
dstMipHeight);
return;
}
@@ -828,17 +821,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// between disjoint rectangles of one level, is perfectly defined and is performed.
if (inPlace && srcMipLevel == dstMipLevel) {
const Uint32 srcSliceBegin = srcSlices.baseSlice, dstSliceBegin = dstSlices.baseSlice;
const Bool slicesOverlap = srcSliceBegin < dstSliceBegin + copySliceCount &&
dstSliceBegin < srcSliceBegin + copySliceCount;
const Bool rectOverlaps = srcX < dstX + srcWidth && dstX < srcX + srcWidth &&
srcY < dstY + srcHeight && dstY < srcY + srcHeight;
const Bool slicesOverlap =
srcSliceBegin < dstSliceBegin + copySliceCount && dstSliceBegin < srcSliceBegin + copySliceCount;
const Bool rectOverlaps =
srcX < dstX + srcWidth && dstX < srcX + srcWidth && srcY < dstY + srcHeight && dstY < srcY + srcHeight;
if (slicesOverlap && rectOverlaps) {
MGLOG_E_ONCE("%s: in-place copy on objectId=%u overlaps itself at level %u "
"(src %dx%d+%d+%d slice %u, dst +%d+%d slice %u, %u slice(s)); GL leaves "
"the result undefined, so the copy is declined rather than recorded",
__func__, CopyImageEndpointName(srcEndpoint), srcMipLevel,
srcWidth, srcHeight, srcX, srcY, srcSliceBegin, dstX, dstY,
dstSliceBegin, copySliceCount);
__func__, CopyImageEndpointName(srcEndpoint), srcMipLevel, srcWidth, srcHeight, srcX, srcY,
srcSliceBegin, dstX, dstY, dstSliceBegin, copySliceCount);
return;
}
}
@@ -853,17 +845,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
}
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) {
return true;
};
const auto materializeClear = [this, &frame](const CopyImageEndpoint& endpoint) { return true; };
const Bool clearReady = materializeClear(srcEndpoint);
MOBILEGL_ASSERT(clearReady, "%s: failed to materialize pending clear for source objectId=%u",
__func__, CopyImageEndpointName(srcEndpoint));
MOBILEGL_ASSERT(clearReady, "%s: failed to materialize pending clear for source objectId=%u", __func__,
CopyImageEndpointName(srcEndpoint));
// A clear still parked on the destination would otherwise materialize AFTER this copy and
// wipe the texels it just wrote.
const Bool dstClearReady = materializeClear(dstEndpoint);
MOBILEGL_ASSERT(dstClearReady, "%s: failed to materialize pending clear for destination objectId=%u",
__func__, CopyImageEndpointName(dstEndpoint));
MOBILEGL_ASSERT(dstClearReady, "%s: failed to materialize pending clear for destination objectId=%u", __func__,
CopyImageEndpointName(dstEndpoint));
const VkImageLayout srcOriginalLayout = *srcImage.trackedLayout;
const VkImageLayout dstOriginalLayout = *dstImage.trackedLayout;
@@ -878,8 +868,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (originalLayout != VK_IMAGE_LAYOUT_UNDEFINED) {
return originalLayout;
}
const Bool depthStencil =
(copyAspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0;
const Bool depthStencil = (copyAspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0;
if (isRenderbuffer) {
return depthStencil ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
: VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
@@ -902,8 +891,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const VkImageLayout copyDestinationLayout =
inPlace ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
const VkAccessFlags copySourceAccess =
inPlace ? (VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT)
: VK_ACCESS_TRANSFER_READ_BIT;
inPlace ? (VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT) : VK_ACCESS_TRANSFER_READ_BIT;
VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcAccessMask = 0;
GetImageTransitionSourceState(srcOriginalLayout, srcStageMask, srcAccessMask);
@@ -913,19 +901,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// [baseSlice, baseSlice + depth) the slice mapping above hands the copy.
if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
Bool srcReady = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, copySourceLayout,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, copySourceAccess,
srcImage.aspect, 0, srcImage.mipLevels);
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, copySourceLayout, srcStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, copySourceAccess, srcImage.aspect, 0,
srcImage.mipLevels);
MOBILEGL_ASSERT(srcReady, "%s: failed to transition undefined source image", __func__);
srcCopyLayout = *srcImage.trackedLayout;
} else {
Bool srcReady = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage.image, srcCopyLayout, copySourceLayout,
srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcAccessMask, copySourceAccess,
inPlace ? srcImage.aspect : copyAspectMask,
inPlace ? 0u : srcMipLevel, inPlace ? srcImage.mipLevels : 1u);
frame.commandBuffer, srcImage.image, srcCopyLayout, copySourceLayout, srcStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, copySourceAccess,
inPlace ? srcImage.aspect : copyAspectMask, inPlace ? 0u : srcMipLevel,
inPlace ? srcImage.mipLevels : 1u);
MOBILEGL_ASSERT(srcReady, "%s: failed to transition source image", __func__);
}
@@ -939,16 +925,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} else if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
Bool dstReady = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage.image, *dstImage.trackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
dstImage.aspect, 0, dstImage.mipLevels);
MOBILEGL_ASSERT(dstReady, "%s: failed to transition undefined destination image", __func__);
dstCopyLayout = *dstImage.trackedLayout;
} else {
Bool dstReady = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage.image, dstCopyLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
dstStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, copyAspectMask, dstMipLevel, 1);
frame.commandBuffer, dstImage.image, dstCopyLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dstStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, copyAspectMask,
dstMipLevel, 1);
MOBILEGL_ASSERT(dstReady, "%s: failed to transition destination image", __func__);
}
@@ -973,10 +958,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MGLOG_D("CopyImageSubData: src(target=%s level=%u layer=%u+%u z=%d) -> dst(target=%s level=%u layer=%u+%u "
"z=%d) extent=[%d x %d x %u]",
MG_Util::ConvertTextureTargetToString(srcTextureTarget).c_str(), srcMipLevel,
copyRegion.srcSubresource.baseArrayLayer, copyRegion.srcSubresource.layerCount,
copyRegion.srcOffset.z, MG_Util::ConvertTextureTargetToString(dstTextureTarget).c_str(), dstMipLevel,
copyRegion.dstSubresource.baseArrayLayer, copyRegion.dstSubresource.layerCount,
copyRegion.dstOffset.z, srcWidth, srcHeight, copyRegion.extent.depth);
copyRegion.srcSubresource.baseArrayLayer, copyRegion.srcSubresource.layerCount, copyRegion.srcOffset.z,
MG_Util::ConvertTextureTargetToString(dstTextureTarget).c_str(), dstMipLevel,
copyRegion.dstSubresource.baseArrayLayer, copyRegion.dstSubresource.layerCount, copyRegion.dstOffset.z,
srcWidth, srcHeight, copyRegion.extent.depth);
VkImage copySourceImage = srcImage.image;
VkImageLayout copySourceImageLayout = copySourceLayout;
if (srcImage.rawE5) {
@@ -1014,25 +999,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (Uint32 slice = 0; slice < copySliceCount; ++slice) {
VkImageCopy& one = slices[slice];
one.extent.depth = 1;
if (srcSlices.slicesAreDepth) one.srcOffset.z += static_cast<Int32>(slice);
else one.srcSubresource = {copyRegion.srcSubresource.aspectMask, copyRegion.srcSubresource.mipLevel,
copyRegion.srcSubresource.baseArrayLayer + slice, 1};
if (dstSlices.slicesAreDepth) one.dstOffset.z += static_cast<Int32>(slice);
else one.dstSubresource = {copyRegion.dstSubresource.aspectMask, copyRegion.dstSubresource.mipLevel,
copyRegion.dstSubresource.baseArrayLayer + slice, 1};
if (srcSlices.slicesAreDepth)
one.srcOffset.z += static_cast<Int32>(slice);
else
one.srcSubresource = {copyRegion.srcSubresource.aspectMask, copyRegion.srcSubresource.mipLevel,
copyRegion.srcSubresource.baseArrayLayer + slice, 1};
if (dstSlices.slicesAreDepth)
one.dstOffset.z += static_cast<Int32>(slice);
else
one.dstSubresource = {copyRegion.dstSubresource.aspectMask, copyRegion.dstSubresource.mipLevel,
copyRegion.dstSubresource.baseArrayLayer + slice, 1};
}
vkCmdCopyImage(frame.commandBuffer, copySourceImage, copySourceImageLayout, dstImage.image,
copyDestinationLayout, copySliceCount, slices.data());
} else {
vkCmdCopyImage(frame.commandBuffer,
copySourceImage, copySourceImageLayout,
dstImage.image, copyDestinationLayout,
1, &copyRegion);
vkCmdCopyImage(frame.commandBuffer, copySourceImage, copySourceImageLayout, dstImage.image,
copyDestinationLayout, 1, &copyRegion);
}
// The copy coordinates above are already storage-relative. Name that
// storage here too, so a texture view's offsets are not applied twice.
if (wire && !dstImage.isRenderbuffer) m_textureManager->MarkWireTextureGpuWritten(dstStorageHandle,
dstMipLevel, dstSlices.BaseArrayLayer(), dstSlices.slicesAreDepth ? 1u : copySliceCount);
if (wire && !dstImage.isRenderbuffer)
m_textureManager->MarkWireTextureGpuWritten(dstStorageHandle, dstMipLevel, dstSlices.BaseArrayLayer(),
dstSlices.slicesAreDepth ? 1u : copySliceCount);
VkPipelineStageFlags srcRestoreStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcRestoreAccessMask = 0;
@@ -1040,16 +1028,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (srcOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
Bool srcRestored = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage.image, *srcImage.trackedLayout, srcRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask,
copySourceAccess, srcRestoreAccessMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask, copySourceAccess, srcRestoreAccessMask,
srcImage.aspect, 0, srcImage.mipLevels);
MOBILEGL_ASSERT(srcRestored, "%s: failed to restore undefined source image layout", __func__);
} else {
Bool srcRestored = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage.image, srcCopyLayout, srcRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStageMask,
copySourceAccess, srcRestoreAccessMask,
inPlace ? srcImage.aspect : copyAspectMask,
frame.commandBuffer, srcImage.image, srcCopyLayout, srcRestoreLayout, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcRestoreStageMask, copySourceAccess, srcRestoreAccessMask, inPlace ? srcImage.aspect : copyAspectMask,
inPlace ? 0u : srcMipLevel, inPlace ? srcImage.mipLevels : 1u);
MOBILEGL_ASSERT(srcRestored, "%s: failed to restore source image layout", __func__);
}
@@ -1064,18 +1049,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
} else if (dstOriginalLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
Bool dstRestored = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage.image, *dstImage.trackedLayout, dstRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask,
VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask,
dstImage.aspect, 0, dstImage.mipLevels);
MOBILEGL_ASSERT(dstRestored, "%s: failed to restore undefined destination image layout", __func__);
} else {
Bool dstRestored = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage.image, dstCopyLayout, dstRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStageMask,
VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, copyAspectMask, dstMipLevel, 1);
frame.commandBuffer, dstImage.image, dstCopyLayout, dstRestoreLayout, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstRestoreStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccessMask, copyAspectMask, dstMipLevel,
1);
MOBILEGL_ASSERT(dstRestored, "%s: failed to restore destination image layout", __func__);
}
}
Bool VulkanRenderer::SubmitReadbackCommandsAndWait(FrameContext::FrameData& frame) {
@@ -1152,8 +1135,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// fences=2 upTo=73 submitCounter=73 completed=71. Two fences, because submission 72 -
// the frame's draws, flushed by ReadWirePixels under a pooled fence - was still
// outstanding; the code this replaces waited on one.
VkResult result = vkWaitForFences(m_device, static_cast<Uint32>(readbackFences.size()),
readbackFences.data(), VK_TRUE, UINT64_MAX);
VkResult result = vkWaitForFences(m_device, static_cast<Uint32>(readbackFences.size()), readbackFences.data(),
VK_TRUE, UINT64_MAX);
if (result != VK_SUCCESS) {
NoteDeviceLoss(result, "readback vkWaitForFences");
MGLOG_E_ONCE("DirectVulkan readback: vkWaitForFences returned %d", result);
@@ -1181,7 +1164,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VulkanRenderer::ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type,
void* pixels) {
ReadWirePixels(x,y,width,height,format,type,pixels);
ReadWirePixels(x, y, width, height, format, type, pixels);
return;
}
@@ -1190,9 +1173,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 srcBaseArrayLayer, VkImage dstImage, VkFormat dstFormat,
VkImageLayout* dstTrackedLayout, Uint32 dstMipLevel,
Uint32 dstBaseArrayLayer, GLint srcX, GLint srcY, GLint dstX,
GLint dstY, GLint width, GLint height,
VkImageLayout srcRestoreLayout, VkImageLayout dstRestoreLayout,
Bool stencilAspect) {
GLint dstY, GLint width, GLint height, VkImageLayout srcRestoreLayout,
VkImageLayout dstRestoreLayout, Bool stencilAspect) {
const auto aspectMaskForFormat = [](VkFormat format) -> VkImageAspectFlags {
switch (format) {
case VK_FORMAT_D16_UNORM:
@@ -1225,7 +1207,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const SizeT dstTexel = stencilAspect ? 1 : depthTexelSize(dstFormat);
if (srcTexel == 0 || dstTexel == 0 || width <= 0 || height <= 0) {
MGLOG_E_ONCE("BlitDepthAcrossFormats skipped: unsupported formats src=%d dst=%d",
static_cast<Int>(srcFormat), static_cast<Int>(dstFormat));
static_cast<Int>(srcFormat), static_cast<Int>(dstFormat));
return false;
}
const SizeT pixelCount = static_cast<SizeT>(width) * static_cast<SizeT>(height);
@@ -1246,8 +1228,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GetImageTransitionSourceState(*srcTrackedLayout, srcStageMask, srcAccessMask);
Bool ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage, *srcTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT,
aspectMaskForFormat(srcFormat), srcMipLevel, 1);
VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_READ_BIT, aspectMaskForFormat(srcFormat),
srcMipLevel, 1);
MOBILEGL_ASSERT(ok, "BlitDepthAcrossFormats: source transition failed");
VkBufferImageCopy readRegion{};
@@ -1264,10 +1246,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineStageFlags srcRestoreStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags srcRestoreAccess = 0;
GetImageTransitionDestinationState(srcRestoreLayout, srcRestoreStage, srcRestoreAccess);
ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, srcImage, *srcTrackedLayout, srcRestoreLayout, VK_PIPELINE_STAGE_TRANSFER_BIT,
srcRestoreStage, VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccess,
aspectMaskForFormat(srcFormat), srcMipLevel, 1);
ok = VkTextureManager::TransitionImageLayout(frame.commandBuffer, srcImage, *srcTrackedLayout, srcRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, srcRestoreStage,
VK_ACCESS_TRANSFER_READ_BIT, srcRestoreAccess,
aspectMaskForFormat(srcFormat), srcMipLevel, 1);
MOBILEGL_ASSERT(ok, "BlitDepthAcrossFormats: source restore failed");
if (!SubmitReadbackCommandsAndWait(frame)) {
@@ -1341,8 +1323,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
GetImageTransitionSourceState(*dstTrackedLayout, dstStageMask, dstAccessMask);
ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage, *dstTrackedLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dstStageMask,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT,
aspectMaskForFormat(dstFormat), dstMipLevel, 1);
VK_PIPELINE_STAGE_TRANSFER_BIT, dstAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, aspectMaskForFormat(dstFormat),
dstMipLevel, 1);
MOBILEGL_ASSERT(ok, "BlitDepthAcrossFormats: destination transition failed");
VkBufferImageCopy writeRegion{};
@@ -1360,10 +1342,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineStageFlags dstRestoreStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
VkAccessFlags dstRestoreAccess = 0;
GetImageTransitionDestinationState(dstRestoreLayout, dstRestoreStage, dstRestoreAccess);
ok = VkTextureManager::TransitionImageLayout(
frame.commandBuffer, dstImage, *dstTrackedLayout, dstRestoreLayout, VK_PIPELINE_STAGE_TRANSFER_BIT,
dstRestoreStage, VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccess,
aspectMaskForFormat(dstFormat), dstMipLevel, 1);
ok = VkTextureManager::TransitionImageLayout(frame.commandBuffer, dstImage, *dstTrackedLayout, dstRestoreLayout,
VK_PIPELINE_STAGE_TRANSFER_BIT, dstRestoreStage,
VK_ACCESS_TRANSFER_WRITE_BIT, dstRestoreAccess,
aspectMaskForFormat(dstFormat), dstMipLevel, 1);
MOBILEGL_ASSERT(ok, "BlitDepthAcrossFormats: destination restore failed");
return true;
}
@@ -1407,7 +1389,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break;
default:
MGLOG_E_ONCE("DirectVulkan::ReadDepthStencilPixels skipped: unsupported source format=%d",
static_cast<Int>(vkFormat));
static_cast<Int>(vkFormat));
return;
}
@@ -1445,8 +1427,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (defaultFramebufferOrientation) {
const VkExtent2D defaultFboExtent = m_swapchainObject.GetExtent();
const Bool mapped = MapDefaultFramebufferReadbackRect(
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), &copyOffset,
&copyExtent);
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), &copyOffset, &copyExtent);
MOBILEGL_ASSERT(mapped, "ReadDepthStencilPixels: default framebuffer read rectangle is out of bounds");
if (!mapped) return;
}
@@ -1509,15 +1490,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool remapped = true;
if (wantDepth && depthCopyBytes > 0) {
remappedDepth.resize(pixelCount * depthCopyBytes);
remapped = RemapDefaultFramebufferReadback(depthSrc, static_cast<Uint32>(width),
static_cast<Uint32>(height), preTransform,
depthCopyBytes, remappedDepth.data());
remapped =
RemapDefaultFramebufferReadback(depthSrc, static_cast<Uint32>(width), static_cast<Uint32>(height),
preTransform, depthCopyBytes, remappedDepth.data());
}
if (remapped && wantStencil) {
remappedStencil.resize(pixelCount);
remapped = RemapDefaultFramebufferReadback(stencilSrc, static_cast<Uint32>(width),
static_cast<Uint32>(height), preTransform, 1,
remappedStencil.data());
remapped =
RemapDefaultFramebufferReadback(stencilSrc, static_cast<Uint32>(width), static_cast<Uint32>(height),
preTransform, 1, remappedStencil.data());
}
if (remapped) {
if (!remappedDepth.empty()) depthSrc = remappedDepth.data();
@@ -1543,14 +1524,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Store honoring the client pack state (single slice).
const SharedPtr<MG_State::GLState::BufferObject> wireReplyHasNoPackBuffer;
const auto& pixelPackBufferObject =
wireReplyHasNoPackBuffer;
const auto& pixelPackBufferObject = wireReplyHasNoPackBuffer;
const auto packParams = MG_Pipe::gPipeInputs->GetPixelStoreParameters(false);
const SizeT rowPixels = static_cast<SizeT>(packParams.RowLength > 0 ? packParams.RowLength : width);
const SizeT packAlignment = packParams.Alignment > 0 ? static_cast<SizeT>(packParams.Alignment) : 1;
const SizeT dstRowStride = ((rowPixels * dstPixelBytes) + packAlignment - 1) / packAlignment * packAlignment;
const SizeT dstSkipOffset = static_cast<SizeT>(std::max(packParams.SkipRows, 0)) * dstRowStride +
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
static_cast<SizeT>(std::max(packParams.SkipPixels, 0)) * dstPixelBytes;
const SizeT dstRowBytes = static_cast<SizeT>(width) * dstPixelBytes;
const SizeT pboBaseOffset = reinterpret_cast<SizeT>(pixels);
if (pixelPackBufferObject != nullptr) {
@@ -49,8 +49,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
CGRect frame = {{0.0, 0.0}, {surfaceWidth, surfaceHeight}};
id window = SendId(windowClass, "alloc");
window = ObjcMsgSend<id (*)(id, SEL, CGRect, unsigned long, unsigned long, bool)>()(
window, sel_registerName("initWithContentRect:styleMask:backing:defer:"),
frame, kNSWindowStyleMaskBorderless, kNSBackingStoreBuffered, true);
window, sel_registerName("initWithContentRect:styleMask:backing:defer:"), frame,
kNSWindowStyleMaskBorderless, kNSBackingStoreBuffered, true);
MOBILEGL_ASSERT(window, "Failed to create hidden NSWindow for DirectVulkan pbuffer");
id contentView = SendId(window, "contentView");
@@ -248,8 +248,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
static VKAPI_ATTR VkBool32 VKAPI_CALL DebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT,
Uint64, size_t, Int32 messageCode, const char* pLayerPrefix,
const char* pMessage, void*) {
Uint64, size_t, Int32 messageCode,
const char* pLayerPrefix, const char* pMessage, void*) {
if ((flags & (VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT |
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)) != 0) {
// MGLOG_F, unlatched, on purpose: a validation-layer report means MobileGL fed
@@ -275,8 +275,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
void VulkanRenderer::DestroyDebugReportCallback() {
if (m_debugReportCallback == VK_NULL_HANDLE) return;
auto func = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance,
"vkDestroyDebugReportCallbackEXT");
auto func =
(PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(m_instance, "vkDestroyDebugReportCallbackEXT");
if (func != nullptr) func(m_instance, m_debugReportCallback, nullptr);
m_debugReportCallback = VK_NULL_HANDLE;
}
@@ -512,9 +512,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// out-of-range fetches; arbitrary GL vertex strides/offsets still need the explicit tight
// repack in VertexInputStateFactory when they violate Vulkan's address-alignment rules.
// MOBILEGL_MAGMA_DISABLE_ROBUST_BUFFER_ACCESS leaves it off to measure or dodge its GPU cost.
deviceFeatures.robustBufferAccess = MG_Config::Features.MagmaDisableRobustBufferAccess
? VK_FALSE
: supportedDeviceFeatures.robustBufferAccess;
deviceFeatures.robustBufferAccess =
MG_Config::Features.MagmaDisableRobustBufferAccess ? VK_FALSE : supportedDeviceFeatures.robustBufferAccess;
deviceFeatures.geometryShader = supportedDeviceFeatures.geometryShader;
deviceFeatures.tessellationShader = supportedDeviceFeatures.tessellationShader;
// gl_PointSize is an ORDINARY per-vertex output in desktop GL - a tessellation
@@ -532,8 +531,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Sampled-read barriers may only name the shader stages whose device feature is
// actually enabled (VUID-vkCmdPipelineBarrier-srcStageMask-04090/-04091), so the
// mask is assembled here, next to the feature decision, and handed to consumers.
m_sampledReadStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT |
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
m_sampledReadStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT |
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT;
if (deviceFeatures.geometryShader == VK_TRUE) {
m_sampledReadStageMask |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
@@ -598,11 +596,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Required for desktop GL image load/store semantics. iterationRP writes storage
// images from vertex and fragment stages and uses formats outside Vulkan's small
// mandatory storage-image set.
deviceFeatures.vertexPipelineStoresAndAtomics =
supportedDeviceFeatures.vertexPipelineStoresAndAtomics;
deviceFeatures.vertexPipelineStoresAndAtomics = supportedDeviceFeatures.vertexPipelineStoresAndAtomics;
deviceFeatures.fragmentStoresAndAtomics = supportedDeviceFeatures.fragmentStoresAndAtomics;
deviceFeatures.shaderStorageImageExtendedFormats =
supportedDeviceFeatures.shaderStorageImageExtendedFormats;
deviceFeatures.shaderStorageImageExtendedFormats = supportedDeviceFeatures.shaderStorageImageExtendedFormats;
// The formatless float-storage compatibility path must be all-or-nothing: transformed
// modules declare both capabilities and image bindings may be read, written, or both.
m_unformattedFloatStorageImagesEnabled =
@@ -672,8 +668,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_imageFormatListExtensionEnabled) {
enabledDeviceExtensions.push_back(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME);
}
MGLOG_I("VK_KHR_image_format_list enabled: %s",
m_imageFormatListExtensionEnabled ? "true" : "false");
MGLOG_I("VK_KHR_image_format_list enabled: %s", m_imageFormatListExtensionEnabled ? "true" : "false");
MGLOG_I("VK_KHR_draw_indirect_count enabled: %s", m_drawIndirectCountExtensionEnabled ? "true" : "false");
m_indexTypeUint8ExtensionEnabled = false;
@@ -725,7 +720,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
IsExtensionSupported(availableExtensions, VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) &&
IsExtensionSupported(availableExtensions, VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
const Bool wireDepthResolveEnabled = getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions);
const Bool wireDepthResolveEnabled =
getPhysicalDeviceProperties2 && (wireDepthResolveCore || wireDepthResolveExtensions);
m_wireCreateRenderPass2 = nullptr;
m_wireDepthResolveModes = m_wireStencilResolveModes = 0;
if (wireDepthResolveEnabled) {
@@ -748,7 +744,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// write the stencil aspect without it. Enabled on the same terms as the resolve
// extensions above - wire arms only - and the arm declines stencil where it is absent
// rather than producing an undefined aspect.
m_wireShaderStencilExport = IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
m_wireShaderStencilExport =
IsExtensionSupported(availableExtensions, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
if (m_wireShaderStencilExport)
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions,
VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
@@ -765,15 +762,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_sharedImageSyncFd = false;
#if defined(__ANDROID__)
if (m_physicalDevice.properties.apiVersion >= VK_API_VERSION_1_1 &&
IsExtensionSupported(availableExtensions, VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) &&
IsExtensionSupported(availableExtensions,
VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME) &&
IsExtensionSupported(availableExtensions, VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME)) {
m_wireAhbImport = true;
for (const char* name : {VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, VK_KHR_MAINTENANCE_1_EXTENSION_NAME}) {
for (const char* name :
{VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME,
VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_MAINTENANCE_1_EXTENSION_NAME}) {
if (IsExtensionSupported(availableExtensions, name))
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, name);
}
@@ -781,11 +779,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// device waits on and signals through SYNC_FD binary semaphores - imported for a wait,
// exported for a signal. Both directions, or the CPU fallback.
if (IsExtensionSupported(availableExtensions, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME)) {
auto getExternalSemaphoreProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphoreProperties"));
auto getExternalSemaphoreProperties =
reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphoreProperties"));
if (getExternalSemaphoreProperties == nullptr) {
getExternalSemaphoreProperties = reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR"));
getExternalSemaphoreProperties =
reinterpret_cast<PFN_vkGetPhysicalDeviceExternalSemaphoreProperties>(
vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR"));
}
VkExternalSemaphoreProperties properties{VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES};
if (getExternalSemaphoreProperties != nullptr) {
@@ -798,8 +798,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT | VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT;
if ((properties.externalSemaphoreFeatures & both) == both) {
m_sharedImageSyncFd = true;
for (const char* name : {VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME,
VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME}) {
for (const char* name :
{VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME, VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME}) {
if (IsExtensionSupported(availableExtensions, name))
EnableOptionalDeviceExtension(availableExtensions, enabledDeviceExtensions, name);
}
@@ -834,9 +834,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
descriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind == VK_TRUE &&
(!deviceFeatures.robustBufferAccess || descriptorIndexingProperties.robustBufferAccessUpdateAfterBind);
if (allUpdateAfterBindFeatures) {
if (!descriptorIndexingCore && !IsExtensionAlreadyEnabled(
enabledDeviceExtensions,
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)) {
if (!descriptorIndexingCore &&
!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME)) {
enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
}
descriptorIndexingFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
@@ -883,11 +882,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
deviceCreateInfo.pNext = &indexTypeUint8Features;
MGLOG_I("Enabled optional device extension: %s", indexTypeUint8ExtensionName);
} else {
MGLOG_W("%s is advertised, but indexTypeUint8 feature is unavailable; uint8 index buffers will stay disabled",
MGLOG_W("%s is advertised, but indexTypeUint8 feature is unavailable; uint8 index buffers will stay "
"disabled",
indexTypeUint8ExtensionName);
}
} else {
MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay disabled");
MGLOG_W("VK_KHR_index_type_uint8 / VK_EXT_index_type_uint8 not supported; uint8 index buffers will stay "
"disabled");
}
m_shaderDrawParametersFeatureEnabled = false;
@@ -931,8 +932,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
listRestartFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
deviceCreateInfo.pNext = &listRestartFeatures;
m_primitiveTopologyListRestartFeatureEnabled = true;
MGLOG_I("Enabled optional device extension: %s",
VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
MGLOG_I("Enabled optional device extension: %s", VK_EXT_PRIMITIVE_TOPOLOGY_LIST_RESTART_EXTENSION_NAME);
}
}
@@ -958,8 +958,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
if (customBorderColorFeatures.customBorderColors == VK_TRUE &&
customBorderColorFeatures.customBorderColorWithoutFormat == VK_TRUE) {
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME)) {
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME)) {
enabledDeviceExtensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
}
customBorderColorFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
@@ -1004,18 +1003,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
getPhysicalDeviceProperties2(m_physicalDevice.handle, &subgroupPropertyQuery);
// Mirrors the loader's HasUsableShaderSubgroupSupport gate, so the module lowerings
// can never disagree with the advertised capabilities.
const Bool usableSubgroups =
subgroupProperties.subgroupSize > 0 &&
(subgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 &&
(subgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0;
const Bool usableSubgroups = subgroupProperties.subgroupSize > 0 &&
(subgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 &&
(subgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0;
if (usableSubgroups) {
m_nativeSubgroupSize = subgroupProperties.subgroupSize;
m_nativeSubgroupSupported = true;
}
}
VkPhysicalDeviceSubgroupSizeControlFeaturesEXT subgroupSizeControlFeatures{};
subgroupSizeControlFeatures.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT;
subgroupSizeControlFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_SIZE_CONTROL_FEATURES_EXT;
m_maxComputeWorkgroupSubgroups = 0;
if (m_nativeSubgroupSupported &&
IsExtensionSupported(availableExtensions, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME) &&
@@ -1032,12 +1029,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
propertyQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
propertyQuery.pNext = &subgroupSizeControlProperties;
getPhysicalDeviceProperties2(m_physicalDevice.handle, &propertyQuery);
m_maxComputeWorkgroupSubgroups =
subgroupSizeControlProperties.maxComputeWorkgroupSubgroups;
m_maxComputeWorkgroupSubgroups = subgroupSizeControlProperties.maxComputeWorkgroupSubgroups;
}
if (subgroupSizeControlFeatures.computeFullSubgroups == VK_TRUE) {
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions,
VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME)) {
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME)) {
enabledDeviceExtensions.push_back(VK_EXT_SUBGROUP_SIZE_CONTROL_EXTENSION_NAME);
}
// Only the full-subgroups guarantee is wanted; required/varying subgroup
@@ -1100,8 +1095,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
deviceCreateInfo.pNext = &primitivesGeneratedQueryFeatures;
m_primitivesGeneratedQueryFeatureEnabled = true;
m_primitivesGeneratedQueryDiscardFeatureEnabled = true;
MGLOG_I("Enabled optional device extension: %s",
VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME);
MGLOG_I("Enabled optional device extension: %s", VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME);
}
}
// VK_EXT_provoking_vertex. Two independent features live behind one extension:
@@ -1186,8 +1180,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// collapses to 1 and an attribute meant to change every N instances changes every one.
m_vertexAttributeDivisorEnabled = false;
VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT vertexAttributeDivisorFeatures{};
vertexAttributeDivisorFeatures.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT;
vertexAttributeDivisorFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT;
if (IsExtensionSupported(availableExtensions, VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) &&
getPhysicalDeviceFeatures2 != nullptr) {
VkPhysicalDeviceFeatures2 featureQuery{};
@@ -1288,9 +1281,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
// Spec minimum is 1024; a driver reporting 0 through a failed query must not
// zero out every batch, so fall back to the spec minimum.
m_maxMultiDrawCount = multiDrawProperties.maxMultiDrawCount != 0
? multiDrawProperties.maxMultiDrawCount
: 1024;
m_maxMultiDrawCount =
multiDrawProperties.maxMultiDrawCount != 0 ? multiDrawProperties.maxMultiDrawCount : 1024;
MGLOG_I("Enabled optional device extension: %s (maxMultiDrawCount=%u)",
VK_EXT_MULTI_DRAW_EXTENSION_NAME, m_maxMultiDrawCount);
} else {
@@ -1360,7 +1352,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
rateCount, VkPhysicalDeviceFragmentShadingRateKHR{
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_KHR});
getFragmentShadingRates(m_physicalDevice.handle, &rateCount, rates.data());
const auto log2Of = [](Uint32 size) -> Int { return size == 1 ? 0 : size == 2 ? 1 : size == 4 ? 2 : -1; };
const auto log2Of = [](Uint32 size) -> Int {
return size == 1 ? 0 : size == 2 ? 1 : size == 4 ? 2 : -1;
};
for (Uint32 r = 0; r < rateCount; ++r) {
const Int w = log2Of(rates[r].fragmentSize.width);
const Int h = log2Of(rates[r].fragmentSize.height);
@@ -1387,12 +1381,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_wireNullDescriptor = false;
VkPhysicalDeviceRobustness2FeaturesEXT robustness2Features{};
robustness2Features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT;
const char* robustness2Name =
IsExtensionSupported(availableExtensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME) ? VK_EXT_ROBUSTNESS_2_EXTENSION_NAME
: IsExtensionSupported(availableExtensions, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME) ? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME
: nullptr;
if (robustness2Name != nullptr &&
getPhysicalDeviceFeatures2 != nullptr) {
const char* robustness2Name = IsExtensionSupported(availableExtensions, VK_EXT_ROBUSTNESS_2_EXTENSION_NAME)
? VK_EXT_ROBUSTNESS_2_EXTENSION_NAME
: IsExtensionSupported(availableExtensions, VK_KHR_ROBUSTNESS_2_EXTENSION_NAME)
? VK_KHR_ROBUSTNESS_2_EXTENSION_NAME
: nullptr;
if (robustness2Name != nullptr && getPhysicalDeviceFeatures2 != nullptr) {
VkPhysicalDeviceFeatures2 featureQuery{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2};
featureQuery.pNext = &robustness2Features;
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
@@ -1410,47 +1404,46 @@ namespace MobileGL::MG_Backend::DirectVulkan {
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s "
"shaderClipDistance=%s "
"shaderCullDistance=%s wideLines=%s shaderInt64=%s vertexStoresAtomics=%s "
"fragmentStoresAtomics=%s storageImageExtendedFormats=%s storageImageReadWithoutFormat=%s "
"storageImageWriteWithoutFormat=%s drawIndirectFirstInstance=%s "
"multiDrawIndirect=%s",
supportedDeviceFeatures.robustBufferAccess ? "true" : "false",
supportedDeviceFeatures.geometryShader ? "true" : "false",
supportedDeviceFeatures.independentBlend ? "true" : "false",
supportedDeviceFeatures.logicOp ? "true" : "false",
supportedDeviceFeatures.shaderClipDistance ? "true" : "false",
supportedDeviceFeatures.shaderCullDistance ? "true" : "false",
supportedDeviceFeatures.wideLines ? "true" : "false",
supportedDeviceFeatures.shaderInt64 ? "true" : "false",
supportedDeviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
supportedDeviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false",
supportedDeviceFeatures.multiDrawIndirect ? "true" : "false");
MGLOG_I("Device feature enabled: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
supportedDeviceFeatures.robustBufferAccess ? "true" : "false",
supportedDeviceFeatures.geometryShader ? "true" : "false",
supportedDeviceFeatures.independentBlend ? "true" : "false",
supportedDeviceFeatures.logicOp ? "true" : "false",
supportedDeviceFeatures.shaderClipDistance ? "true" : "false",
supportedDeviceFeatures.shaderCullDistance ? "true" : "false",
supportedDeviceFeatures.wideLines ? "true" : "false",
supportedDeviceFeatures.shaderInt64 ? "true" : "false",
supportedDeviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
supportedDeviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
supportedDeviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
supportedDeviceFeatures.drawIndirectFirstInstance ? "true" : "false",
supportedDeviceFeatures.multiDrawIndirect ? "true" : "false");
MGLOG_I("Device feature enabled: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s "
"shaderClipDistance=%s "
"shaderCullDistance=%s wideLines=%s shaderInt64=%s vertexStoresAtomics=%s "
"fragmentStoresAtomics=%s storageImageExtendedFormats=%s storageImageReadWithoutFormat=%s "
"storageImageWriteWithoutFormat=%s drawIndirectFirstInstance=%s "
"multiDrawIndirect=%s shaderDrawParameters=%s",
deviceFeatures.robustBufferAccess ? "true" : "false",
deviceFeatures.geometryShader ? "true" : "false",
deviceFeatures.independentBlend ? "true" : "false",
deviceFeatures.logicOp ? "true" : "false",
deviceFeatures.shaderClipDistance ? "true" : "false",
deviceFeatures.shaderCullDistance ? "true" : "false",
deviceFeatures.wideLines ? "true" : "false",
deviceFeatures.shaderInt64 ? "true" : "false",
deviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
deviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
deviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
deviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
deviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
deviceFeatures.drawIndirectFirstInstance ? "true" : "false",
deviceFeatures.multiDrawIndirect ? "true" : "false",
m_shaderDrawParametersFeatureEnabled ? "true" : "false");
deviceFeatures.robustBufferAccess ? "true" : "false", deviceFeatures.geometryShader ? "true" : "false",
deviceFeatures.independentBlend ? "true" : "false", deviceFeatures.logicOp ? "true" : "false",
deviceFeatures.shaderClipDistance ? "true" : "false",
deviceFeatures.shaderCullDistance ? "true" : "false", deviceFeatures.wideLines ? "true" : "false",
deviceFeatures.shaderInt64 ? "true" : "false",
deviceFeatures.vertexPipelineStoresAndAtomics ? "true" : "false",
deviceFeatures.fragmentStoresAndAtomics ? "true" : "false",
deviceFeatures.shaderStorageImageExtendedFormats ? "true" : "false",
deviceFeatures.shaderStorageImageReadWithoutFormat ? "true" : "false",
deviceFeatures.shaderStorageImageWriteWithoutFormat ? "true" : "false",
deviceFeatures.drawIndirectFirstInstance ? "true" : "false",
deviceFeatures.multiDrawIndirect ? "true" : "false",
m_shaderDrawParametersFeatureEnabled ? "true" : "false");
VK_VERIFY(vkCreateDevice(m_physicalDevice.handle, &deviceCreateInfo, nullptr, &m_device), "vkCreateDevice");
if (wireDepthResolveEnabled) {
@@ -1483,7 +1476,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkGetDeviceProcAddr(m_device, "vkCmdDrawIndexedIndirectCount"));
}
if (m_drawIndirectCountExtensionEnabled && s_vkCmdDrawIndexedIndirectCount == nullptr) {
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will continue as if VK_KHR_draw_indirect_count is not supported!");
MGLOG_W("VK_KHR_draw_indirect_count enabled but vkCmdDrawIndexedIndirectCount entry point is missing, will "
"continue as if VK_KHR_draw_indirect_count is not supported!");
m_drawIndirectCountExtensionEnabled = false;
}
// P8-D: the wire arm's glMultiDrawArraysIndirectCount. Null = that verb reads its count on
@@ -1578,8 +1572,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (m_transformFeedbackFeatureEnabled) {
s_vkCmdBeginQueryIndexedEXT = reinterpret_cast<PFN_vkCmdBeginQueryIndexedEXT>(
vkGetDeviceProcAddr(m_device, "vkCmdBeginQueryIndexedEXT"));
s_vkCmdEndQueryIndexedEXT = reinterpret_cast<PFN_vkCmdEndQueryIndexedEXT>(
vkGetDeviceProcAddr(m_device, "vkCmdEndQueryIndexedEXT"));
s_vkCmdEndQueryIndexedEXT =
reinterpret_cast<PFN_vkCmdEndQueryIndexedEXT>(vkGetDeviceProcAddr(m_device, "vkCmdEndQueryIndexedEXT"));
VkPhysicalDeviceTransformFeedbackPropertiesEXT xfbProperties{};
xfbProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TRANSFORM_FEEDBACK_PROPERTIES_EXT;
VkPhysicalDeviceProperties2 properties2{};
@@ -1597,7 +1591,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
getPhysicalDeviceProperties2(m_physicalDevice.handle, &properties2);
}
m_xfbQueriesSupported = xfbProperties.transformFeedbackQueries == VK_TRUE &&
s_vkCmdBeginQueryIndexedEXT != nullptr && s_vkCmdEndQueryIndexedEXT != nullptr;
s_vkCmdBeginQueryIndexedEXT != nullptr && s_vkCmdEndQueryIndexedEXT != nullptr;
}
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
MGLOG_I("Logical device created.");
@@ -1654,7 +1648,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ResolveWireDepthStencil's own test for the render-pass arm, less its per-format half
// (the probe asks that per format): without it the shader pass is the only arm.
const Bool renderPassArmAvailable = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 &&
(m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) != 0;
(m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT) != 0;
const char* knobValue = std::getenv("MGITEST_MAGMA_DEPTH_RESOLVE_PROBE");
const WireDepthResolveProbeKnob knob = ParseWireDepthResolveProbeKnob(knobValue);
// Memoized per DEVICE IDENTITY, not per process (codex closeout finding 7; see
@@ -1665,7 +1659,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const WireDepthResolveArmChoice resolved = s_choices.Resolve(identity, [&]() {
if (knob == WireDepthResolveProbeKnob::Unrecognised)
MGLOG_W("DirectVulkan: MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=%s is not `bug`, `clean` or "
"`elide-subject`; the resolve probe runs as if it were unset", knobValue);
"`elide-subject`; the resolve probe runs as if it were unset",
knobValue);
WireDepthResolveArmChoice choice = ChooseWireDepthResolveArm(knob, renderPassArmAvailable, [&]() {
WireDepthResolveProbeContext context;
context.physicalDevice = m_physicalDevice.handle;
@@ -1702,8 +1697,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
: (renderPassArmAvailable || choice.measurement.fromKnob)
? "the VK_KHR_depth_stencil_resolve render pass resolves first"
: "the shader pass is the only arm",
choice.measurement.failureReason.empty() ? "" : "; ",
choice.measurement.failureReason.c_str());
choice.measurement.failureReason.empty() ? "" : "; ", choice.measurement.failureReason.c_str());
for (const WireDepthResolveFormatReading& reading : choice.measurement.formats)
MGLOG_I("DirectVulkan: depth/stencil resolve probe %s",
DescribeWireDepthResolveFormat(reading).c_str());
@@ -1767,8 +1761,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
m_wireE5RawReads = armed;
MGLOG_I("DirectVulkan: E5B9G9R9 reads %s",
armed ? "go through the raw shader route" : "use plain transfers");
MGLOG_I("DirectVulkan: E5B9G9R9 reads %s", armed ? "go through the raw shader route" : "use plain transfers");
}
// THE PER-SLICE SPLIT IS ARMED BY MEASUREMENT (WireCopySliceProbe.h), once per device identity,
@@ -1873,8 +1866,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
PrimitivesGeneratedNoXfbProbeContext probeContext;
probeContext.device = m_device;
probeContext.queue = m_graphicsQueue;
probeContext.queueFamilyIndex =
static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
probeContext.queueFamilyIndex = static_cast<Uint32>(m_physicalDevice.queueFamilies.graphicsFamily);
probeContext.transformFeedbackQueriesUsable = m_xfbQueriesSupported;
probeContext.primitivesGeneratedQueryUsable = primitivesGeneratedQueryUsable;
probeContext.pipelineStatisticsEnabled = m_pipelineStatisticsQueryFeatureEnabled;
@@ -1925,18 +1917,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the process's life; a device this sick has bigger problems.
MGLOG_W("PRIMITIVES_GENERATED probe timed out waiting on its own submission (%s); its "
"Vulkan objects are deliberately leaked and XFB-inactive draws keep the stream "
"query", s_measurement.failureReason.c_str());
"query",
s_measurement.failureReason.c_str());
} else if (!s_measurement.ran) {
MGLOG_W("PRIMITIVES_GENERATED probe did not run (%s); XFB-inactive draws keep the "
"stream query", s_measurement.failureReason.c_str());
"stream query",
s_measurement.failureReason.c_str());
} else {
const auto logShape = [](const char* name,
const MG_Util::SelfTest::PrimitivesGeneratedNoXfbShapeMeasurement&
shape) {
const MG_Util::SelfTest::PrimitivesGeneratedNoXfbShapeMeasurement& shape) {
MGLOG_I("PRIMITIVES_GENERATED probe %s: drawn=%d stream=%llu/%llu pgq=%llu(%d) "
"stat=%llu(%d)",
name, shape.drawn ? 1 : 0,
static_cast<unsigned long long>(shape.streamGenerated),
name, shape.drawn ? 1 : 0, static_cast<unsigned long long>(shape.streamGenerated),
static_cast<unsigned long long>(shape.expectedPrimitives),
static_cast<unsigned long long>(shape.primitivesGeneratedExt),
shape.primitivesGeneratedExtMeasured ? 1 : 0,
@@ -2038,10 +2030,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// and a renderer created for a WINDOW never enabled the headless extension - asking its
// instance for vkCreateHeadlessSurfaceEXT answers null, and calling that crashed the server
// the first time such a renderer needed an offscreen target.
auto* createHeadlessSurface =
m_headlessSurfaceSupported ? reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"))
: nullptr;
auto* createHeadlessSurface = m_headlessSurfaceSupported
? reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"))
: nullptr;
const OffscreenSurfaceRoute route = ChooseOffscreenSurfaceRoute(
m_headlessSurfaceSupported, createHeadlessSurface != nullptr, m_androidSurfaceEnabled);
if (route == OffscreenSurfaceRoute::Headless) {
@@ -2113,9 +2105,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// system. CreateInstance() has already refused the bring-up if the loader
// lacks the extension, so reaching here without it is a broken invariant
// rather than a platform limitation - report it and fail, do not continue.
auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
auto* createHeadlessSurface = reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
if (!m_headlessSurfaceSupported || createHeadlessSurface == nullptr) {
MGLOG_F("vkCreateHeadlessSurfaceEXT is unavailable (%s reported as %s) while creating an "
"offscreen DirectVulkan surface",
@@ -2128,9 +2119,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
"vkCreateHeadlessSurfaceEXT failed");
return true;
#else
auto* createHeadlessSurface =
reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
auto* createHeadlessSurface = reinterpret_cast<PFN_vkCreateHeadlessSurfaceEXT>(
vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT"));
if (createHeadlessSurface == nullptr) {
// Same class as the Xlib branch above: a null entry point behind
// MOBILEGL_ASSERT is a segv on the next line in every INFO-level build.
@@ -2191,8 +2181,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
x11Lib = dlopen("libX11.so", RTLD_LOCAL | RTLD_NOW);
}
if (x11Lib == nullptr) {
MGLOG_F("Failed to open libX11 (.so.6 and .so) while creating a Vulkan Xlib window surface: %s",
dlerror());
MGLOG_F("Failed to open libX11 (.so.6 and .so) while creating a Vulkan Xlib window surface: %s", dlerror());
throw RuntimeError("libX11 is unavailable for the Vulkan Xlib window surface");
}
using XOpenDisplayFn = Display* (*)(const char*);
@@ -2290,8 +2279,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (extensionCount == 0) {
return extensions;
}
const VkResult result =
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
const VkResult result = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data());
if (result == VK_SUCCESS) {
extensions.resize(extensionCount);
return extensions;
@@ -2351,9 +2339,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Vector<const char*>& inOutEnabledExtensions) {
m_drawIndirectCountExtensionEnabled = EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME);
m_shaderDrawParametersExtensionEnabled =
EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME);
m_shaderDrawParametersExtensionEnabled = EnableOptionalDeviceExtension(
availableExtensions, inOutEnabledExtensions, VK_KHR_SHADER_DRAW_PARAMETERS_EXTENSION_NAME);
#ifdef VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
EnableOptionalDeviceExtension(availableExtensions, inOutEnabledExtensions,
VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
@@ -101,13 +101,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool VulkanRenderer::BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 bufferCount,
const VkBuffer* buffers, const VkDeviceSize* offsets, const VkDeviceSize* sizes) {
const VkBuffer* buffers, const VkDeviceSize* offsets,
const VkDeviceSize* sizes) {
if (!m_xfbCounterBuffer.IsValid()) {
if (!m_xfbCounterBuffer.Create({
.allocator = m_allocator,
.size = 16 * kXfbCounterObjectSlots,
.usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.usage =
VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.memoryUsage = VMA_MEMORY_USAGE_AUTO,
})) {
MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to create the counter buffer");
@@ -119,8 +120,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (Uint32 i = 0; i < bufferCount; ++i) {
if (buffers[i] == VK_NULL_HANDLE) continue;
m_currentDrawXfbBufferMask |= 1u << i;
s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, i, 1, buffers + i,
offsets + i, sizes + i);
s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, i, 1, buffers + i, offsets + i, sizes + i);
}
const Uint32 counterSlot = CurrentXfbCounterSlot();
@@ -136,8 +136,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
counterOffsets[i] = static_cast<VkDeviceSize>(counterSlot) * 16 + static_cast<VkDeviceSize>(i) * 4;
}
if (resume) {
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount),
counterBuffers, counterOffsets);
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast<Uint32>(bufferCount), counterBuffers,
counterOffsets);
} else {
s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, 0, nullptr, nullptr);
}
@@ -186,15 +186,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkMemoryBarrier memoryBarrier{};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
memoryBarrier.srcAccessMask =
VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT | VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT;
VK_ACCESS_TRANSFORM_FEEDBACK_WRITE_BIT_EXT | VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_WRITE_BIT_EXT;
// Every way a captured buffer can be read back: replayed as vertex attributes or indices
// by glDrawTransformFeedback, sampled through a uniform or storage binding, sourced as an
// indirect command, copied out, or mapped.
memoryBarrier.dstAccessMask =
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT |
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT |
VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT;
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT |
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INDIRECT_COMMAND_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_TRANSFORM_FEEDBACK_COUNTER_READ_BIT_EXT;
vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFORM_FEEDBACK_BIT_EXT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
}
@@ -213,11 +212,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
BeginXfbQueryForDraw(commandBuffer, xfbActive);
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
vkCmdDraw(commandBuffer,
payload.params.vertexCount,
payload.params.instanceCount,
payload.params.firstVertex,
payload.params.firstInstance);
vkCmdDraw(commandBuffer, payload.params.vertexCount, payload.params.instanceCount, payload.params.firstVertex,
payload.params.firstInstance);
EndOcclusionForDraw(commandBuffer, occlusionActive);
EndXfbCaptureForDraw(frame, xfbActive);
EndXfbQueryForDraw(commandBuffer);
@@ -348,8 +344,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return rerouteArmed || m_primGenStreamCountsXfbInactiveDraws;
}
void VulkanRenderer::StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots,
Vector<Uint32>& outRerouteSlots) {
void VulkanRenderer::StopXfbQueryCapture(Uint32 kind, Vector<Uint32>& outSlots, Vector<Uint32>& outRerouteSlots) {
if (kind > 1) {
return;
}
@@ -369,8 +364,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool haveStreamSlots = !slots.empty() && m_xfbQueryPool != VK_NULL_HANDLE;
// Reroute slots only ever accumulate the GENERATED target (see
// BeginXfbQueryForDraw); WRITTEN never opens one.
const Bool haveRerouteSlots =
wantGenerated && !rerouteSlots.empty() && m_primGenReroutePool != VK_NULL_HANDLE;
const Bool haveRerouteSlots = wantGenerated && !rerouteSlots.empty() && m_primGenReroutePool != VK_NULL_HANDLE;
if (!haveStreamSlots && !haveRerouteSlots) {
return true;
}
@@ -398,9 +392,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (const Uint32 slot : rerouteSlots) {
// Both reroute pool kinds answer one 64-bit primitive count per slot.
Uint64 generated = 0;
const VkResult result = vkGetQueryPoolResults(
m_device, m_primGenReroutePool, slot, 1, sizeof(generated), &generated,
sizeof(generated), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
const VkResult result =
vkGetQueryPoolResults(m_device, m_primGenReroutePool, slot, 1, sizeof(generated), &generated,
sizeof(generated), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
if (result == VK_SUCCESS) {
outPrimitives += generated;
}
@@ -435,8 +429,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// and for GENERATED wherever this draw is not rerouted - so one GL query span
// may accumulate stream slots (XFB-active draws) and reroute slots
// (XFB-inactive draws) side by side.
const Bool wantStreamSlot =
m_xfbQueryCaptureActive[0] || (m_xfbQueryCaptureActive[1] && !rerouteGenerated);
const Bool wantStreamSlot = m_xfbQueryCaptureActive[0] || (m_xfbQueryCaptureActive[1] && !rerouteGenerated);
if (wantStreamSlot) {
const Uint32 slot = m_xfbQuerySlotCursor;
m_xfbQuerySlotCursor = (m_xfbQuerySlotCursor + 1) % kXfbQuerySlots;
@@ -459,8 +452,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// CI runs compile INFO in.
MGLOG_I_ONCE("PRIMITIVES_GENERATED reroute engaged: an XFB-inactive draw accumulates "
"through the %s pool",
m_primGenRerouteKind ==
MG_Util::SelfTest::PrimGenRerouteKind::PrimitivesGeneratedExt
m_primGenRerouteKind == MG_Util::SelfTest::PrimGenRerouteKind::PrimitivesGeneratedExt
? "VK_QUERY_TYPE_PRIMITIVES_GENERATED_EXT"
: "clipping-invocations statistics");
const Uint32 slot = m_primGenRerouteSlotCursor;
@@ -513,9 +505,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& frame = m_frameContext.GetCurrent();
DrawCmdParam vertexRange{};
vertexRange.vertexCount = payload.params.indexCount + (payload.params.vertexOffset > 0
? static_cast<Uint32>(payload.params.vertexOffset)
: 0);
vertexRange.vertexCount =
payload.params.indexCount +
(payload.params.vertexOffset > 0 ? static_cast<Uint32>(payload.params.vertexOffset) : 0);
vertexRange.instanceCount = payload.params.instanceCount;
vertexRange.firstVertex = 0;
vertexRange.firstInstance = static_cast<Uint32>(payload.params.firstInstance);
@@ -524,8 +516,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// conversion may bound its work by scanning them.
vertexRange.indexRangeIsExactView = true;
if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange,
&payload.indexBufferView)) {
if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange, &payload.indexBufferView)) {
return;
}
@@ -536,12 +527,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
BeginXfbQueryForDraw(commandBuffer, xfbActive);
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
vkCmdDrawIndexed(commandBuffer,
payload.params.indexCount,
payload.params.instanceCount,
payload.params.firstIndex,
payload.params.vertexOffset,
payload.params.firstInstance);
vkCmdDrawIndexed(commandBuffer, payload.params.indexCount, payload.params.instanceCount,
payload.params.firstIndex, payload.params.vertexOffset, payload.params.firstInstance);
EndOcclusionForDraw(commandBuffer, occlusionActive);
EndXfbCaptureForDraw(frame, xfbActive);
EndXfbQueryForDraw(commandBuffer);
@@ -573,16 +560,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// renderer's draw-parameter structs are exactly Vulkan's indirect command layouts, and
// vkCmdDraw(Indexed)Indirect accepts any 4-aligned stride >= the command size, so the
// trailing CPU-side metadata rides along unread instead of forcing a repack.
static_assert(sizeof(DrawIndexedCmdParam) == sizeof(VkDrawIndexedIndirectCommand) &&
offsetof(DrawIndexedCmdParam, indexCount) == offsetof(VkDrawIndexedIndirectCommand, indexCount) &&
offsetof(DrawIndexedCmdParam, instanceCount) ==
offsetof(VkDrawIndexedIndirectCommand, instanceCount) &&
offsetof(DrawIndexedCmdParam, firstIndex) == offsetof(VkDrawIndexedIndirectCommand, firstIndex) &&
offsetof(DrawIndexedCmdParam, vertexOffset) ==
offsetof(VkDrawIndexedIndirectCommand, vertexOffset) &&
offsetof(DrawIndexedCmdParam, firstInstance) ==
offsetof(VkDrawIndexedIndirectCommand, firstInstance),
"DrawIndexedCmdParam must alias VkDrawIndexedIndirectCommand for the tier-2 multi-draw upload");
static_assert(
sizeof(DrawIndexedCmdParam) == sizeof(VkDrawIndexedIndirectCommand) &&
offsetof(DrawIndexedCmdParam, indexCount) == offsetof(VkDrawIndexedIndirectCommand, indexCount) &&
offsetof(DrawIndexedCmdParam, instanceCount) == offsetof(VkDrawIndexedIndirectCommand, instanceCount) &&
offsetof(DrawIndexedCmdParam, firstIndex) == offsetof(VkDrawIndexedIndirectCommand, firstIndex) &&
offsetof(DrawIndexedCmdParam, vertexOffset) == offsetof(VkDrawIndexedIndirectCommand, vertexOffset) &&
offsetof(DrawIndexedCmdParam, firstInstance) == offsetof(VkDrawIndexedIndirectCommand, firstInstance),
"DrawIndexedCmdParam must alias VkDrawIndexedIndirectCommand for the tier-2 multi-draw upload");
static_assert(sizeof(DrawCmdParam) % 4 == 0 && sizeof(DrawCmdParam) >= sizeof(VkDrawIndirectCommand) &&
offsetof(DrawCmdParam, vertexCount) == offsetof(VkDrawIndirectCommand, vertexCount) &&
offsetof(DrawCmdParam, instanceCount) == offsetof(VkDrawIndirectCommand, instanceCount) &&
@@ -645,15 +630,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint32 maxIndirectCount = m_physicalDevice.properties.limits.maxDrawIndirectCount;
if (firstInstanceLegal && maxIndirectCount > 0) {
BufferSlice commandSlice{};
if (m_bufferManager.UploadTransient(BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(),
pParams,
static_cast<VkDeviceSize>(drawCount) * sizeof(DrawCmdParam),
sizeof(Uint32), commandSlice)) {
if (m_bufferManager.UploadTransient(
BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(), pParams,
static_cast<VkDeviceSize>(drawCount) * sizeof(DrawCmdParam), sizeof(Uint32), commandSlice)) {
for (Uint32 base = 0; base < drawCount; base += maxIndirectCount) {
const Uint32 chunk = std::min(drawCount - base, maxIndirectCount);
vkCmdDrawIndirect(commandBuffer, commandSlice.buffer,
commandSlice.offset +
static_cast<VkDeviceSize>(base) * sizeof(DrawCmdParam),
commandSlice.offset + static_cast<VkDeviceSize>(base) * sizeof(DrawCmdParam),
chunk, sizeof(DrawCmdParam));
}
return;
@@ -704,8 +687,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
for (Uint32 base = 0; base < drawCount; base += m_maxMultiDrawCount) {
const Uint32 chunk = std::min(drawCount - base, m_maxMultiDrawCount);
s_vkCmdDrawMultiIndexedEXT(commandBuffer, chunk, infos.data() + base,
pParams[0].instanceCount,
s_vkCmdDrawMultiIndexedEXT(commandBuffer, chunk, infos.data() + base, pParams[0].instanceCount,
static_cast<Uint32>(pParams[0].firstInstance),
sizeof(VkMultiDrawIndexedInfoEXT), nullptr);
}
@@ -731,8 +713,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
BufferSlice commandSlice{};
if (m_bufferManager.UploadTransient(BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(),
pParams,
static_cast<VkDeviceSize>(drawCount) *
sizeof(DrawIndexedCmdParam),
static_cast<VkDeviceSize>(drawCount) * sizeof(DrawIndexedCmdParam),
sizeof(Uint32), commandSlice)) {
for (Uint32 base = 0; base < drawCount; base += maxIndirectCount) {
const Uint32 chunk = std::min(drawCount - base, maxIndirectCount);
@@ -761,12 +742,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (Uint32 idraw = 0; idraw < payload.drawCount; ++idraw) {
vertexRange.vertexCount = std::max(vertexRange.vertexCount, payload.pParams[idraw].indexCount);
vertexRange.instanceCount = std::max(vertexRange.instanceCount, payload.pParams[idraw].instanceCount);
vertexRange.firstInstance = std::max(vertexRange.firstInstance,
static_cast<Uint32>(payload.pParams[idraw].firstInstance));
vertexRange.firstInstance =
std::max(vertexRange.firstInstance, static_cast<Uint32>(payload.pParams[idraw].firstInstance));
}
if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange,
&payload.indexBufferView)) {
if (!SetupDraw(frame, payload.mode, DrawSetupAspect::IndexBuffer, vertexRange, &payload.indexBufferView)) {
return;
}
@@ -792,10 +772,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// merged range fetches exactly the two sub-draws' indices in order.
Uint32 mergeGranularity = 0;
switch (payload.mode) {
case GL_POINTS: mergeGranularity = 1; break;
case GL_LINES: mergeGranularity = 2; break;
case GL_TRIANGLES: mergeGranularity = 3; break;
default: break;
case GL_POINTS:
mergeGranularity = 1;
break;
case GL_LINES:
mergeGranularity = 2;
break;
case GL_TRIANGLES:
mergeGranularity = 3;
break;
default:
break;
}
if (mergeGranularity != 0) {
const RenderStateParameters& rsp = MG_Pipe::gPipeInputs->GetRenderStateParameters();
@@ -823,10 +810,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
++idraw;
continue;
}
if (head.indexCount % mergeGranularity != 0 ||
next.instanceCount != 1 ||
next.vertexOffset != head.vertexOffset ||
next.firstInstance != head.firstInstance ||
if (head.indexCount % mergeGranularity != 0 || next.instanceCount != 1 ||
next.vertexOffset != head.vertexOffset || next.firstInstance != head.firstInstance ||
next.firstIndex != head.firstIndex + head.indexCount ||
head.indexCount + next.indexCount < head.indexCount) {
break;
@@ -219,19 +219,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
for (Uint32 i = 0; i < 2; ++i) {
if (depthRange[i] != other.depthRange[i] ||
stencilValueMask[i] != other.stencilValueMask[i] ||
stencilWriteMask[i] != other.stencilWriteMask[i] ||
stencilRef[i] != other.stencilRef[i]) {
if (depthRange[i] != other.depthRange[i] || stencilValueMask[i] != other.stencilValueMask[i] ||
stencilWriteMask[i] != other.stencilWriteMask[i] || stencilRef[i] != other.stencilRef[i]) {
return false;
}
}
return polygonOffsetFactor == other.polygonOffsetFactor &&
polygonOffsetUnits == other.polygonOffsetUnits &&
polygonOffsetClamp == other.polygonOffsetClamp && lineWidth == other.lineWidth &&
extentX == other.extentX && extentY == other.extentY &&
preTransform == other.preTransform && scissorEnabled == other.scissorEnabled &&
isDefaultFbo == other.isDefaultFbo;
extentX == other.extentX && extentY == other.extentY && preTransform == other.preTransform &&
scissorEnabled == other.scissorEnabled && isDefaultFbo == other.isDefaultFbo;
}
};
DynamicTailKey dynamicTailKey{};
@@ -270,43 +267,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static void GetImageTransitionSourceState(VkImageLayout oldLayout, VkPipelineStageFlags& outSrcStageMask,
VkAccessFlags& outSrcAccessMask) {
switch (oldLayout) {
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
outSrcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
outSrcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_UNDEFINED:
case VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:
outSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
outSrcAccessMask = 0;
break;
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
outSrcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
outSrcStageMask =
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
outSrcAccessMask =
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL:
case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
break;
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
break;
case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT;
break;
default:
outSrcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
outSrcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
break;
}
}
} // namespace
@@ -324,24 +321,43 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool stencilOnly = format == GL_STENCIL_INDEX;
const auto readDepth = [&](SizeT i) -> Float {
if (sourceFormat == VK_FORMAT_D16_UNORM) {
Uint16 word{}; Memcpy(&word, depth + i * 2, sizeof(word));
Uint16 word{};
Memcpy(&word, depth + i * 2, sizeof(word));
return static_cast<Float>(word) / 65535.0f;
}
if (sourceFormat == VK_FORMAT_X8_D24_UNORM_PACK32 || sourceFormat == VK_FORMAT_D24_UNORM_S8_UINT) {
Uint32 word{}; Memcpy(&word, depth + i * 4, sizeof(word));
Uint32 word{};
Memcpy(&word, depth + i * 4, sizeof(word));
return static_cast<Float>(word & 0xffffffu) / 16777215.0f;
}
Float value{}; Memcpy(&value, depth + i * sizeof(value), sizeof(value));
Float value{};
Memcpy(&value, depth + i * sizeof(value), sizeof(value));
return value;
};
SizeT size = 0;
switch (type) {
case GL_BYTE: case GL_UNSIGNED_BYTE: size = 1; break;
case GL_SHORT: case GL_UNSIGNED_SHORT: case GL_HALF_FLOAT: size = 2; break;
case GL_INT: case GL_UNSIGNED_INT: case GL_FLOAT: size = 4; break;
case GL_UNSIGNED_INT_24_8: size = 4; break;
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV: size = 8; break;
default: return false;
case GL_BYTE:
case GL_UNSIGNED_BYTE:
size = 1;
break;
case GL_SHORT:
case GL_UNSIGNED_SHORT:
case GL_HALF_FLOAT:
size = 2;
break;
case GL_INT:
case GL_UNSIGNED_INT:
case GL_FLOAT:
size = 4;
break;
case GL_UNSIGNED_INT_24_8:
size = 4;
break;
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
size = 8;
break;
default:
return false;
}
const Bool packed = type == GL_UNSIGNED_INT_24_8 || type == GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
if ((format == GL_DEPTH_STENCIL) != packed) return false;
@@ -353,11 +369,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
switch (type) {
case GL_FLOAT: {
const Float value = stencilOnly ? static_cast<Float>(s) : d;
Memcpy(dst, &value, sizeof(value)); break;
Memcpy(dst, &value, sizeof(value));
break;
}
case GL_HALF_FLOAT: {
const Uint16 value = MG_Util::EncodeFloatToHalfBits(stencilOnly ? static_cast<Float>(s) : d);
Memcpy(dst, &value, sizeof(value)); break;
Memcpy(dst, &value, sizeof(value));
break;
}
case GL_UNSIGNED_INT_24_8: {
Uint32 depth24 = 0;
@@ -368,17 +386,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
depth24 = static_cast<Uint32>(std::llround(std::clamp<double>(d, 0.0, 1.0) * 16777215.0));
}
const Uint32 value = (depth24 << 8) | s;
Memcpy(dst, &value, sizeof(value)); break;
Memcpy(dst, &value, sizeof(value));
break;
}
case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
Memcpy(dst, &d, sizeof(d)); Memcpy(dst + 4, &s, sizeof(s)); break;
Memcpy(dst, &d, sizeof(d));
Memcpy(dst + 4, &s, sizeof(s));
break;
default: {
const Bool signedType = type == GL_BYTE || type == GL_SHORT || type == GL_INT;
const Uint64 maximum = signedType ? ((Uint64{1} << (size * 8 - 1)) - 1)
: ((Uint64{1} << (size * 8)) - 1);
const Uint32 value = stencilOnly ? s : static_cast<Uint32>(
std::llround(std::clamp<double>(d, 0.0, 1.0) * static_cast<double>(maximum)));
Memcpy(dst, &value, size); break;
const Uint64 maximum =
signedType ? ((Uint64{1} << (size * 8 - 1)) - 1) : ((Uint64{1} << (size * 8)) - 1);
const Uint32 value = stencilOnly ? s
: static_cast<Uint32>(std::llround(std::clamp<double>(d, 0.0, 1.0) *
static_cast<double>(maximum)));
Memcpy(dst, &value, size);
break;
}
}
}
@@ -10,13 +10,8 @@
namespace MobileGL::MG_Backend::DirectVulkan {
static VkPipelineColorBlendAttachmentState MakeColorBlendAttachmentState(
Bool blendEnable,
VkBlendFactor srcColorBlendFactor,
VkBlendFactor dstColorBlendFactor,
VkBlendOp colorBlendOp,
VkBlendFactor srcAlphaBlendFactor,
VkBlendFactor dstAlphaBlendFactor,
VkBlendOp alphaBlendOp,
Bool blendEnable, VkBlendFactor srcColorBlendFactor, VkBlendFactor dstColorBlendFactor, VkBlendOp colorBlendOp,
VkBlendFactor srcAlphaBlendFactor, VkBlendFactor dstAlphaBlendFactor, VkBlendOp alphaBlendOp,
VkColorComponentFlags colorWriteMask) {
VkPipelineColorBlendAttachmentState attachment{};
attachment.blendEnable = blendEnable ? VK_TRUE : VK_FALSE;
@@ -96,8 +91,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool mirrorX = false;
};
static DefaultFramebufferRectMapping GetDefaultFramebufferRectMapping(
VkSurfaceTransformFlagBitsKHR preTransform) {
static DefaultFramebufferRectMapping GetDefaultFramebufferRectMapping(VkSurfaceTransformFlagBitsKHR preTransform) {
if (preTransform == VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR) return {false, true};
if (IsQuarterTurnPreTransform(preTransform)) return {false, false};
return {true, false};
@@ -113,8 +107,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static void ShadowedSetScissor(VkCommandBuffer commandBuffer, const VkRect2D& scissor) {
auto& shadow = *g_dynamicStateShadow;
if (shadow.scissorValid && shadow.scissor.offset.x == scissor.offset.x &&
shadow.scissor.offset.y == scissor.offset.y &&
shadow.scissor.extent.width == scissor.extent.width &&
shadow.scissor.offset.y == scissor.offset.y && shadow.scissor.extent.width == scissor.extent.width &&
shadow.scissor.extent.height == scissor.extent.height) {
return;
}
@@ -128,10 +121,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// arithmetic as index 0 - the default-framebuffer Y-flip and pre-transform rotation
// especially, which is the classic way a multi-viewport port comes out upside down for every
// index but the one that was tested.
static VkViewport ComputeGLViewport(Uint32 index,
const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFramebuffer) {
static VkViewport ComputeGLViewport(Uint32 index, const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFramebuffer) {
// Snapped to integers. The viewport is float STATE (glViewportIndexedf may set a
// fractional origin, and GetFloati_v hands it back verbatim), but what rasterizes here is
// the rounded rectangle - a deliberate, documented infidelity rather than a spec claim:
@@ -142,14 +133,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// default-framebuffer Y-flip/pre-transform helper to floats. See the KNOWN INFIDELITY
// note in MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp.
const FloatVec4& stored = MG_Pipe::gPipeInputs->GetViewportIndexed(index);
const IntVec4 viewportState(static_cast<Int>(std::lround(stored.x())),
static_cast<Int>(std::lround(stored.y())),
static_cast<Int>(std::lround(stored.z())),
static_cast<Int>(std::lround(stored.w())));
const IntVec4 viewportState(
static_cast<Int>(std::lround(stored.x())), static_cast<Int>(std::lround(stored.y())),
static_cast<Int>(std::lround(stored.z())), static_cast<Int>(std::lround(stored.w())));
const FloatVec2& depthRange = MG_Pipe::gPipeInputs->GetDepthRangeIndexed(index);
const IntVec2 logicalExtent = isDefaultFramebuffer
? ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent)
: framebufferExtent;
? ResolveDefaultFramebufferLogicalExtent(preTransform, framebufferExtent)
: framebufferExtent;
Int viewportX = viewportState.x();
Int viewportY = viewportState.y();
@@ -168,10 +158,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// its exact behaviour.
if (isDefaultFramebuffer) {
const DefaultFramebufferRectMapping mapping = GetDefaultFramebufferRectMapping(preTransform);
viewportX = MapDefaultFramebufferRectAxis(viewportX, viewportWidth, framebufferExtent.x(),
mapping.mirrorX);
viewportY = MapDefaultFramebufferRectAxis(viewportY, viewportHeight, framebufferExtent.y(),
mapping.flipY);
viewportX = MapDefaultFramebufferRectAxis(viewportX, viewportWidth, framebufferExtent.x(), mapping.mirrorX);
viewportY = MapDefaultFramebufferRectAxis(viewportY, viewportHeight, framebufferExtent.y(), mapping.flipY);
}
VkViewport viewport{};
@@ -184,10 +172,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return viewport;
}
static void ApplyGLViewportState(VkCommandBuffer commandBuffer,
const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFramebuffer) {
static void ApplyGLViewportState(VkCommandBuffer commandBuffer, const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFramebuffer) {
const VkViewport viewport = ComputeGLViewport(0, framebufferExtent, preTransform, isDefaultFramebuffer);
auto& shadow = *g_dynamicStateShadow;
if (shadow.viewportValid && shadow.viewport.x == viewport.x && shadow.viewport.y == viewport.y &&
@@ -273,7 +259,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
} else
#endif
if (MG_Backend::pActiveBackendObject != nullptr) {
if (MG_Backend::pActiveBackendObject != nullptr) {
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
const Float minLineWidth = dynamicParameters.AliasedLineWidthRangeMin;
const Float maxLineWidth = dynamicParameters.AliasedLineWidthRangeMax;
@@ -320,8 +306,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return scissor;
}
static VkRect2D MakeDefaultFramebufferScissorRect(const IntVec4& scissorBox,
const IntVec2& framebufferExtent,
static VkRect2D MakeDefaultFramebufferScissorRect(const IntVec4& scissorBox, const IntVec2& framebufferExtent,
VkSurfaceTransformFlagBitsKHR preTransform) {
if (!IsQuarterTurnPreTransform(preTransform)) {
return MapScissorRectToDefaultFramebuffer(MakeClampedScissorRect(scissorBox, framebufferExtent),
@@ -367,8 +352,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (shadow.stencilValid && shadow.stencilFrontCompareMask == frontStencil.ValueMask &&
shadow.stencilBackCompareMask == backStencil.ValueMask &&
shadow.stencilFrontWriteMask == frontStencil.WriteMask &&
shadow.stencilBackWriteMask == backStencil.WriteMask &&
shadow.stencilFrontReference == frontReference && shadow.stencilBackReference == backReference) {
shadow.stencilBackWriteMask == backStencil.WriteMask && shadow.stencilFrontReference == frontReference &&
shadow.stencilBackReference == backReference) {
return;
}
shadow.stencilValid = true;
@@ -486,8 +471,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
}
static Bool TryCoerceVertexFormatNumericDomain(VkFormat sourceFormat,
NumericDomain targetDomain,
static Bool TryCoerceVertexFormatNumericDomain(VkFormat sourceFormat, NumericDomain targetDomain,
VkFormat& outFormat) {
const NumericDomain sourceDomain = GetNumericDomainForVertexFormat(sourceFormat);
if (sourceDomain == targetDomain || targetDomain == NumericDomain::Unknown) {
@@ -557,7 +541,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16_UINT : VK_FORMAT_R16G16B16_SSCALED;
return true;
case VK_FORMAT_R16G16B16A16_SINT:
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_R16G16B16A16_SSCALED;
outFormat =
targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16A16_UINT : VK_FORMAT_R16G16B16A16_SSCALED;
return true;
case VK_FORMAT_R16_UINT:
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16_SINT : VK_FORMAT_R16_USCALED;
@@ -569,7 +554,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16_SINT : VK_FORMAT_R16G16B16_USCALED;
return true;
case VK_FORMAT_R16G16B16A16_UINT:
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16A16_SINT : VK_FORMAT_R16G16B16A16_USCALED;
outFormat =
targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16A16_SINT : VK_FORMAT_R16G16B16A16_USCALED;
return true;
case VK_FORMAT_R8_SINT:
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8_UINT : VK_FORMAT_R8_SSCALED;
@@ -674,7 +660,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return flags;
}
// Value hash over every fixed-function GL state the pipeline payload reads that
// the memo key's other fields (mode, program hash, vertex-input hash, render-pass
// hash, transform flags) do not already pin down. Enumerated against the payload
@@ -716,7 +701,6 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return MG_Pipe::gPipeInputs->GetRenderStateParameters().SampleMaskValue;
}
Uint64 VulkanRenderer::ComputePipelineSubsetStateHashFallback() const {
// The client's own hash, over the client's own definition of the pipeline subset - the
// seven pipeline chunks of the P2 chunk table, which is a strict SUPERSET of what
@@ -785,18 +769,20 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkPipeline VulkanRenderer::GetOrCreatePipelineWithInput(GLenum mode, const MagmaProgramSource& program,
const ProgramFactory::VkProgramObject& programObj, ProgramFactory::CompileOptionFlags transformFlags,
const VertexInputStateFactory::BackendVertexInputState& vis, const RenderPassEntry& renderPassEntry,
Bool primitiveRestartEnable, Bool layeredFramebuffer, Uint64 wireRenderPassCompatibilityId) {
const ProgramFactory::VkProgramObject& programObj,
ProgramFactory::CompileOptionFlags transformFlags,
const VertexInputStateFactory::BackendVertexInputState& vis,
const RenderPassEntry& renderPassEntry,
Bool primitiveRestartEnable, Bool layeredFramebuffer,
Uint64 wireRenderPassCompatibilityId) {
Bool invertClockwise = transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip;
const Bool nonLayeredStages = !layeredFramebuffer && !programObj.nonLayeredStages.empty();
// GL 4.6 core 17.3.3: the shader's gl_SampleMask is a multisample fragment operation, a
// no-op on a single-sample framebuffer or with MULTISAMPLE disabled - the same gate
// ResolveEffectiveSampleMask applies to the fixed-function mask.
const Bool fullCoverageSampleMask =
programObj.fullCoverageFragmentModule != VK_NULL_HANDLE &&
(renderPassEntry.sampleCount == VK_SAMPLE_COUNT_1_BIT ||
!MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::Multisample));
const Bool fullCoverageSampleMask = programObj.fullCoverageFragmentModule != VK_NULL_HANDLE &&
(renderPassEntry.sampleCount == VK_SAMPLE_COUNT_1_BIT ||
!MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::Multisample));
if (programObj.stages.empty()) {
MGLOG_D("GetOrCreatePipeline skipped: program has no shader stages");
return VK_NULL_HANDLE;
@@ -814,9 +800,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// layouts share one pipeline.
// The one-arg fetch rides the VAO's state-pointer memo (no hash, no map).
const Uint64 vertexLayoutHash = vis.layoutHash;
const Uint64 renderPassHash =
wireRenderPassCompatibilityId != 0 ? 0 :
renderPassEntry.hash;
const Uint64 renderPassHash = wireRenderPassCompatibilityId != 0 ? 0 : renderPassEntry.hash;
// The pipeline-relevant subset only: glViewport / glScissor / glBlendColor / glStencilMask
// and friends are dynamic state or not pipeline state at all, and keying the memo on the
// all-state counter made any of them evict a perfectly good VkPipeline. The memo compares
@@ -835,21 +819,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint64 pipelineStateHash =
!MG_Pipe::MGPipeHandleIsNull(renderStateCso)
? 0
: ResolveFallbackPipelineStateHash(renderStateVersion,
renderPassEntry.colorAttachmentCount,
: ResolveFallbackPipelineStateHash(renderStateVersion, renderPassEntry.colorAttachmentCount,
renderPassEntry.sampleCount);
for (Uint32 i = 0; i < m_pipelineMemoCount; ++i) {
const PipelineMemoEntry& entry = m_pipelineMemo[i];
if (entry.pipeline != VK_NULL_HANDLE && entry.mode == mode &&
entry.programHash == programObj.hash && entry.vertexInputHash == vertexLayoutHash &&
entry.renderPassHash == renderPassHash &&
if (entry.pipeline != VK_NULL_HANDLE && entry.mode == mode && entry.programHash == programObj.hash &&
entry.vertexInputHash == vertexLayoutHash && entry.renderPassHash == renderPassHash &&
entry.wireRenderPassCompatibilityId == wireRenderPassCompatibilityId &&
entry.pipelineStateHash == pipelineStateHash &&
entry.renderStateCso == renderStateCso &&
entry.primitiveRestartEnable == primitiveRestartEnable &&
entry.nonLayeredStages == nonLayeredStages &&
entry.fullCoverageSampleMask == fullCoverageSampleMask &&
entry.transformFlags == transformFlags) {
entry.pipelineStateHash == pipelineStateHash && entry.renderStateCso == renderStateCso &&
entry.primitiveRestartEnable == primitiveRestartEnable && entry.nonLayeredStages == nonLayeredStages &&
entry.fullCoverageSampleMask == fullCoverageSampleMask && entry.transformFlags == transformFlags) {
if (MG_Util::PipeStats::Enabled()) {
// Gate 5 of section 2.3.1. On a hit this whole function cost the one
// GetPipelineStateVersion read above plus this value compare.
@@ -883,13 +862,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
for (const auto& stage : programObj.stages) {
if (stage.module == VK_NULL_HANDLE) {
MGLOG_E_ONCE("GetOrCreatePipeline skipped: program=%u has a null shader module for stage 0x%x",
program.GetExternalIndex(), static_cast<unsigned>(stage.stage));
program.GetExternalIndex(), static_cast<unsigned>(stage.stage));
return VK_NULL_HANDLE;
}
if (stage.stage == VK_SHADER_STAGE_COMPUTE_BIT) {
MGLOG_E_ONCE("GetOrCreatePipeline skipped: program=%u carries a compute stage, which no graphics "
"pipeline may contain",
program.GetExternalIndex());
"pipeline may contain",
program.GetExternalIndex());
return VK_NULL_HANDLE;
}
if (stage.stage == VK_SHADER_STAGE_VERTEX_BIT) {
@@ -908,9 +887,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
MOBILEGL_ASSERT(
programObj.fragmentInputComponentCount <= limits.maxFragmentInputComponents,
"GetOrCreatePipeline: fragmentInputComponents=%u exceeds device limit=%u program=%u producerStage=%d",
programObj.fragmentInputComponentCount,
limits.maxFragmentInputComponents,
program.GetExternalIndex(),
programObj.fragmentInputComponentCount, limits.maxFragmentInputComponents, program.GetExternalIndex(),
static_cast<Int>(programObj.rasterizationProducerStage));
}
if (programObj.producerOutputComponentCount != 0) {
@@ -929,13 +906,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
break;
}
if (producerOutputLimit != 0) {
MOBILEGL_ASSERT(
programObj.producerOutputComponentCount <= producerOutputLimit,
"GetOrCreatePipeline: producerOutputComponents=%u exceeds stage limit=%u program=%u producerStage=%d",
programObj.producerOutputComponentCount,
producerOutputLimit,
program.GetExternalIndex(),
static_cast<Int>(programObj.rasterizationProducerStage));
MOBILEGL_ASSERT(programObj.producerOutputComponentCount <= producerOutputLimit,
"GetOrCreatePipeline: producerOutputComponents=%u exceeds stage limit=%u program=%u "
"producerStage=%d",
programObj.producerOutputComponentCount, producerOutputLimit,
program.GetExternalIndex(), static_cast<Int>(programObj.rasterizationProducerStage));
}
}
#endif
@@ -960,21 +935,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkFormat patchedFormat = VK_FORMAT_UNDEFINED;
const Bool canPatch = TryCoerceVertexFormatNumericDomain(attribute.format, shaderInputDomain, patchedFormat);
const Bool canPatch =
TryCoerceVertexFormatNumericDomain(attribute.format, shaderInputDomain, patchedFormat);
MOBILEGL_ASSERT(
canPatch,
"GetOrCreatePipeline: vertex input location=%u format=%d mismatches shader input type=%u program=%u",
attribute.location,
static_cast<Int>(attribute.format),
static_cast<Uint32>(shaderInputType),
attribute.location, static_cast<Int>(attribute.format), static_cast<Uint32>(shaderInputType),
program.GetExternalIndex());
MGLOG_W_ONCE("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input type=%u for program=%u",
attribute.location,
static_cast<Int>(attribute.format),
static_cast<Int>(patchedFormat),
static_cast<Uint32>(shaderInputType),
program.GetExternalIndex());
MGLOG_W_ONCE("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input "
"type=%u for program=%u",
attribute.location, static_cast<Int>(attribute.format), static_cast<Int>(patchedFormat),
static_cast<Uint32>(shaderInputType), program.GetExternalIndex());
attribute.format = patchedFormat;
hasPatchedVertexAttributes = true;
}
@@ -997,9 +969,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
VkFormat format = VK_FORMAT_UNDEFINED;
const Bool supported = TryGetCurrentVertexAttributeFormat(programObj.vertexInputTypes[location], format);
const Bool supported =
TryGetCurrentVertexAttributeFormat(programObj.vertexInputTypes[location], format);
MOBILEGL_ASSERT(supported,
"DirectVulkan does not support current generic vertex attribute type yet: program=%u location=%u type=0x%x activeAttribMask=0x%x vertexInputAttribMask=0x%x",
"DirectVulkan does not support current generic vertex attribute type yet: program=%u "
"location=%u type=0x%x activeAttribMask=0x%x vertexInputAttribMask=0x%x",
program.GetExternalIndex(), location, programObj.vertexInputTypes[location],
activeAttribMask, vertexInputAttribMask);
@@ -1020,11 +994,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
auto cullFaceEnabled = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::CullFace);
auto depthTestEnabled = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::DepthTest);
auto polygonOffsetFillEnabled =
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::PolygonOffsetFill) &&
DrawModeUsesPolygonFill(mode);
auto rasterizerDiscardEnabled =
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard);
auto polygonOffsetFillEnabled = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::PolygonOffsetFill) &&
DrawModeUsesPolygonFill(mode);
auto rasterizerDiscardEnabled = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard);
auto colorLogicOpEnabled =
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::ColorLogicOp) && m_logicOpFeatureEnabled;
auto stencilTestEnabled = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::StencilTest);
@@ -1045,9 +1017,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// VK_POLYGON_MODE_LINE/_POINT require the fillModeNonSolid device feature; fall back to
// VK_POLYGON_MODE_FILL when the device lacks it.
const VkPolygonMode effectivePolygonMode =
(requestedPolygonMode == VK_POLYGON_MODE_FILL || m_fillModeNonSolidFeatureEnabled)
? requestedPolygonMode
: VK_POLYGON_MODE_FILL;
(requestedPolygonMode == VK_POLYGON_MODE_FILL || m_fillModeNonSolidFeatureEnabled) ? requestedPolygonMode
: VK_POLYGON_MODE_FILL;
const VkPrimitiveTopology vkTopology = MG_Util::ConvertPrimitiveModeToVkEnum(mode);
// Resolved by the caller (ResolvePrimitiveRestartEnable), which knows whether the draw is
@@ -1083,8 +1054,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// init and then calls glDrawArrays(GL_TRIANGLES, ...) rendered nothing at all.
const auto isListTopology = [](VkPrimitiveTopology t) {
return t == VK_PRIMITIVE_TOPOLOGY_POINT_LIST || t == VK_PRIMITIVE_TOPOLOGY_LINE_LIST ||
t == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST ||
t == VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY ||
t == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST || t == VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY ||
t == VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
};
if (primitiveRestartEnabled && !m_primitiveTopologyListRestartFeatureEnabled && isListTopology(vkTopology)) {
@@ -1114,7 +1084,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// reads - are excluded for the same reason, so this stays a LOWER bound.
MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::AccessorCalls, 15);
}
PipelineFactory::PipelineCreatePayload payload {
PipelineFactory::PipelineCreatePayload payload{
.programHash = programObj.hash,
.vertexInputHash = vertexLayoutHash,
.pipelineLayout = programObj.pipelineLayout,
@@ -1141,8 +1111,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
fullCoverageSampleMask ? programObj.fullCoverageFragmentModule : VK_NULL_HANDLE,
.polygonMode = effectivePolygonMode,
.cullMode = cullFaceEnabled
? MG_Util::ConvertCullFaceModeToVkEnum(MG_Pipe::gPipeInputs->GetCullFaceMode(), false)
: VK_CULL_MODE_NONE,
? MG_Util::ConvertCullFaceModeToVkEnum(MG_Pipe::gPipeInputs->GetCullFaceMode(), false)
: VK_CULL_MODE_NONE,
// GL's counter-clockwise is Vulkan's clockwise (the two area formulas differ in sign
// over the same rows), and a Y-negated position - the default framebuffer's flip or
// an upper-left clip origin - reverses the winding once more. Expressed as the front
@@ -1150,8 +1120,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// so glFrontFace(GL_CW) is honoured.
.frontFace = ((MG_Pipe::gPipeInputs->GetRenderStateParameters().FrontFaceModeSetting ==
FrontFaceMode::Clockwise) != invertClockwise)
? VK_FRONT_FACE_COUNTER_CLOCKWISE
: VK_FRONT_FACE_CLOCKWISE,
? VK_FRONT_FACE_COUNTER_CLOCKWISE
: VK_FRONT_FACE_CLOCKWISE,
// Read the geometry stage off the program's own shader list rather than
// programObj.rasterizationProducerStage: that field is filled by the clip-fixup analysis,
// which does not run for every program, so it reads Unknown for exactly the
@@ -1160,8 +1130,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// SetupDrawSnapshot fast path key on - so no memo can hand back a pipeline built for the
// other mode. IsTransformFeedbackActive() would be a live bug here: neither memo key
// moves on glBeginTransformFeedback.
.provokingVertexMode = SelectProvokingVertexMode(
vkTopology, ProgramCapturesXfbFromGeometryStage(program)),
.provokingVertexMode = SelectProvokingVertexMode(vkTopology, ProgramCapturesXfbFromGeometryStage(program)),
.depthTestEnable = depthTestEnabled,
.depthWriteEnable = depthTestEnabled && MG_Pipe::gPipeInputs->GetDepthMask(),
.depthBiasEnable = polygonOffsetFillEnabled,
@@ -1182,8 +1151,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
.fragmentShadingRateDynamic = m_fragmentShadingRate.Pipeline,
.stages = nonLayeredStages ? &programObj.nonLayeredStages : &programObj.stages,
.vertexInputState = pipelineVertexInputState,
.stageSpirvDigests = &programObj.stageSpirvDigests
};
.stageSpirvDigests = &programObj.stageSpirvDigests};
// A program with a tessellation evaluation stage and no control stage relies on GL's
// fixed-function pass-through (GL 4.6 core 11.2.2), which Vulkan does not have. Build the
// stage GL describes for THIS draw's patch size - PATCH_VERTICES is draw state, not link
@@ -1228,10 +1196,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool hasDepthStencilAttachment = renderPassEntry.hasDepthStencilAttachment;
if (!hasDepthStencilAttachment &&
(payload.depthTestEnable || payload.depthWriteEnable || payload.stencilTestEnable)) {
MGLOG_D("GetOrCreatePipeline: disabling depth/stencil tests for program=%u because render pass has no depth attachment (attachmentCount=%u colorAttachmentCount=%u)",
program.GetExternalIndex(),
renderPassEntry.attachmentCount,
renderPassEntry.colorAttachmentCount);
MGLOG_D("GetOrCreatePipeline: disabling depth/stencil tests for program=%u because render pass has no "
"depth attachment (attachmentCount=%u colorAttachmentCount=%u)",
program.GetExternalIndex(), renderPassEntry.attachmentCount, renderPassEntry.colorAttachmentCount);
payload.depthTestEnable = false;
payload.depthWriteEnable = false;
payload.stencilTestEnable = false;
@@ -1268,8 +1235,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Bool isDefaultDrawFbo = wireFbo->IsDefault;
Array<FramebufferAttachmentType, MG_Pipe::kMGPipeMaxColorAttachments> drawBuffers{};
for (SizeT i = 0; i < drawBuffers.size(); ++i)
drawBuffers[i] = wireFbo->DrawBuffers[i] < 0 ? FramebufferAttachmentType::None :
static_cast<FramebufferAttachmentType>(static_cast<Int>(FramebufferAttachmentType::Color0) + wireFbo->DrawBuffers[i]);
drawBuffers[i] = wireFbo->DrawBuffers[i] < 0
? FramebufferAttachmentType::None
: static_cast<FramebufferAttachmentType>(
static_cast<Int>(FramebufferAttachmentType::Color0) + wireFbo->DrawBuffers[i]);
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
BlendFactor srcRGB = BlendFactor::One;
BlendFactor dstRGB = BlendFactor::Zero;
@@ -1286,13 +1255,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const BoolVec4 bufferMask =
MG_Pipe::gPipeInputs->GetColorMaskIndexed(m_independentBlendFeatureEnabled ? i : 0);
VkColorComponentFlags attachmentColorWriteMask = static_cast<VkColorComponentFlags>(
(bufferMask.r() ? VK_COLOR_COMPONENT_R_BIT : 0u) |
(bufferMask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
(bufferMask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) |
(bufferMask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u));
(bufferMask.r() ? VK_COLOR_COMPONENT_R_BIT : 0u) | (bufferMask.g() ? VK_COLOR_COMPONENT_G_BIT : 0u) |
(bufferMask.b() ? VK_COLOR_COMPONENT_B_BIT : 0u) | (bufferMask.a() ? VK_COLOR_COMPONENT_A_BIT : 0u));
Bool effectiveBlendEnabled = blendEnabled;
if (isDefaultDrawFbo && i < drawBuffers.size() &&
drawBuffers[i] == FramebufferAttachmentType::None) {
if (isDefaultDrawFbo && i < drawBuffers.size() && drawBuffers[i] == FramebufferAttachmentType::None) {
// The default framebuffer spans the same MAX_DRAW_BUFFERS slots as an FBO
// (slot 0 is the back buffer, or None after glDrawBuffer(GL_NONE); slots
// 1+ are always None). Discard writes and blend state for the None slots
@@ -1311,14 +1277,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
if (effectiveBlendEnabled) {
MOBILEGL_ASSERT(i < drawBuffers.size(),
"GetOrCreatePipeline: color attachment %u is out of draw buffer range %zu",
i, drawBuffers.size());
"GetOrCreatePipeline: color attachment %u is out of draw buffer range %zu", i,
drawBuffers.size());
VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED;
Int textureExternalIndex = -1;
if (!isDefaultDrawFbo) {
const Int32 slot = wireFbo->DrawBuffers[i];
colorAttachmentFormat = ResolveWireImage(*wireFbo, wireFbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT).format;
colorAttachmentFormat =
ResolveWireImage(*wireFbo, wireFbo->Color[slot], VK_IMAGE_ASPECT_COLOR_BIT).format;
} else {
colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format;
}
@@ -1338,19 +1305,21 @@ namespace MobileGL::MG_Backend::DirectVulkan {
blendSupportIt =
formatBlendSupport.emplace(static_cast<Int>(colorAttachmentFormat), blendable).first;
if (!blendable) {
MGLOG_E_ONCE("GetOrCreatePipeline: format=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; "
"disabling blending on attachments with this format (first hit: attachment %u textureId=%d program=%u)",
static_cast<Int>(colorAttachmentFormat), i, textureExternalIndex,
program.GetExternalIndex());
MGLOG_E_ONCE(
"GetOrCreatePipeline: format=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT; "
"disabling blending on attachments with this format (first hit: attachment %u textureId=%d "
"program=%u)",
static_cast<Int>(colorAttachmentFormat), i, textureExternalIndex,
program.GetExternalIndex());
if (PipelineFactory::IsSuppressBlendedDepthWriteEnabled()) {
// With blending force-disabled the blended depth-write quirk can
// never fire for pipelines on this format, so a depth-equality
// chain that accumulates into it (MC 26.3 OIT depth_bounds on
// RGBA32F) keeps its depth writes and may flicker on this driver.
MGLOG_W_ONCE("GetOrCreatePipeline: format=%d is not blendable, so the blended "
"depth-write quirk cannot apply to it; depth-equality chains "
"accumulating into this format may flicker",
static_cast<Int>(colorAttachmentFormat));
"depth-write quirk cannot apply to it; depth-equality chains "
"accumulating into this format may flicker",
static_cast<Int>(colorAttachmentFormat));
}
}
}
@@ -1373,13 +1342,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (!m_dualSrcBlendFeatureEnabled &&
(IsDualSourceBlendFactor(srcRGB) || IsDualSourceBlendFactor(dstRGB) ||
IsDualSourceBlendFactor(srcAlpha) || IsDualSourceBlendFactor(dstAlpha))) {
MGLOG_E_ONCE(
"GetOrCreatePipeline: dual-source blending (GL_SRC1_* blend factor) was requested on "
"color attachment %u, but the Vulkan device does not support the dualSrcBlend feature "
"(see the dualSrcBlend row in the driver POST). Blending is DECLINED on that "
"attachment - the fragment's first output is written unblended and the second source "
"is dropped (program=%u)",
i, program.GetExternalIndex());
MGLOG_E_ONCE("GetOrCreatePipeline: dual-source blending (GL_SRC1_* blend factor) was requested on "
"color attachment %u, but the Vulkan device does not support the dualSrcBlend feature "
"(see the dualSrcBlend row in the driver POST). Blending is DECLINED on that "
"attachment - the fragment's first output is written unblended and the second source "
"is dropped (program=%u)",
i, program.GetExternalIndex());
effectiveBlendEnabled = false;
srcRGB = BlendFactor::One;
dstRGB = BlendFactor::Zero;
@@ -1387,14 +1355,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
dstAlpha = BlendFactor::Zero;
}
payload.colorBlendAttachments[i] = MakeColorBlendAttachmentState(
effectiveBlendEnabled,
MG_Util::ConvertBlendFactorToVkEnum(srcRGB),
MG_Util::ConvertBlendFactorToVkEnum(dstRGB),
MG_Util::ConvertBlendEquationToVkEnum(colorEquation),
MG_Util::ConvertBlendFactorToVkEnum(srcAlpha),
MG_Util::ConvertBlendFactorToVkEnum(dstAlpha),
MG_Util::ConvertBlendEquationToVkEnum(alphaEquation),
attachmentColorWriteMask);
effectiveBlendEnabled, MG_Util::ConvertBlendFactorToVkEnum(srcRGB),
MG_Util::ConvertBlendFactorToVkEnum(dstRGB), MG_Util::ConvertBlendEquationToVkEnum(colorEquation),
MG_Util::ConvertBlendFactorToVkEnum(srcAlpha), MG_Util::ConvertBlendFactorToVkEnum(dstAlpha),
MG_Util::ConvertBlendEquationToVkEnum(alphaEquation), attachmentColorWriteMask);
}
#if MOBILEGL_BUILD_DISAGGREGATED
// P7 wave 2-B: MOBILEGL_TEST_PIPELINE_CREATE_DELAY_MS.
@@ -1438,8 +1402,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GL scissor test is disabled gets the whole framebuffer, which is exactly "the test always
// passes" (GL 4.6 core 17.3.2).
VkRect2D VulkanRenderer::ComputeGLScissorRect(Uint32 index, const IntVec2& extent,
VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFbo) const {
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFbo) const {
const auto& parameters = MG_Pipe::gPipeInputs->GetRenderStateParameters();
if (!GLScissorTestClips(index)) {
VkRect2D full{};
@@ -1461,8 +1424,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// single viewport instead of believing the array's element 0 is already bound.
void VulkanRenderer::ApplyMultiViewportDynamicState(VkCommandBuffer commandBuffer, Uint32 viewportCount,
const IntVec2& extent,
VkSurfaceTransformFlagBitsKHR preTransform,
Bool isDefaultFbo) {
VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFbo) {
MOBILEGL_ASSERT(viewportCount <= RenderStateParameters::MAX_VIEWPORTS,
"ApplyMultiViewportDynamicState: viewportCount=%u exceeds the indexed state width",
viewportCount);
@@ -1607,7 +1569,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
: MakeClampedScissorRect(scissorBox, extent);
} else {
scissor.offset = {0, 0};
scissor.extent = { (Uint)extent.x(), (Uint)extent.y() };
scissor.extent = {(Uint)extent.x(), (Uint)extent.y()};
}
ShadowedSetScissor(frame.commandBuffer, scissor);
shadow.dynamicTailValid = true;
@@ -1647,8 +1609,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
Bool VulkanRenderer::SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const DrawCmdParam& drawParams,
const IndexBufferView* pIndexBufferView) {
const DrawCmdParam& drawParams, const IndexBufferView* pIndexBufferView) {
// First, before anything of this draw is recorded or resolved - on the wire arm too: a
// split server records every draw its client sends into the same frame command buffer, so
// the same loading frame grows it the same way there.
@@ -1688,17 +1649,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkMemoryBarrier memoryBarrier{};
memoryBarrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
memoryBarrier.srcAccessMask =
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
memoryBarrier.dstAccessMask =
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT |
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT |
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT |
VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT |
VK_ACCESS_UNIFORM_READ_BIT | VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT | VK_ACCESS_INDEX_READ_BIT | VK_ACCESS_UNIFORM_READ_BIT |
VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
if ((barriers & GL_COMMAND_BARRIER_BIT) != 0) {
memoryBarrier.dstAccessMask |= VK_ACCESS_INDIRECT_COMMAND_READ_BIT;
@@ -1722,8 +1681,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ALL_COMMANDS does not include HOST. The barrier's
// HOST_WRITE access must have a matching source stage.
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0,
1, &memoryBarrier, 0, nullptr, 0, nullptr);
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memoryBarrier, 0, nullptr, 0, nullptr);
}
void VulkanRenderer::DestroyComputePipelines() {
@@ -1766,12 +1724,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return it->second;
}
const auto stageIt = std::find_if(programObj.stages.begin(), programObj.stages.end(),
[](const VkPipelineShaderStageCreateInfo& stage) {
return stage.stage == VK_SHADER_STAGE_COMPUTE_BIT;
});
MOBILEGL_ASSERT(stageIt != programObj.stages.end(),
"GetOrCreateComputePipeline: program has no compute stage");
const auto stageIt = std::find_if(
programObj.stages.begin(), programObj.stages.end(),
[](const VkPipelineShaderStageCreateInfo& stage) { return stage.stage == VK_SHADER_STAGE_COMPUTE_BIT; });
MOBILEGL_ASSERT(stageIt != programObj.stages.end(), "GetOrCreateComputePipeline: program has no compute stage");
if (stageIt == programObj.stages.end()) {
return VK_NULL_HANDLE;
}
@@ -55,8 +55,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// flushes retired. Wait the full prefix before FrameContext may
// free its retired command buffers and reset the slot fence.
const Uint64 slotSubmit = m_frameContext.GetCurrent().lastSubmitIndex;
if (slotSubmit > m_completedSubmitCounter &&
!WaitForSubmitsUpTo(slotSubmit, UINT64_MAX)) {
if (slotSubmit > m_completedSubmitCounter && !WaitForSubmitsUpTo(slotSubmit, UINT64_MAX)) {
if (LatchWireDeviceLoss("deferred-acquire-submit-wait")) return;
MagmaWireFatal("deferred-acquire-submit-wait");
}
@@ -94,8 +93,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_samplerManager->OnFrameBoundary();
auto& frame = m_frameContext.GetCurrent();
auto* activeRenderPass = VkRenderPassManager::GetActiveRenderPass();
if (activeRenderPass)
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
if (activeRenderPass) VkRenderPassManager::EndRenderPass(frame.commandBuffer);
#if MOBILEGL_BUILD_DISAGGREGATED
// The frame boundary of the shared images this frame read (WireSharedImage.cpp): handed
// back to the foreign family in this recording, and this submission signals the read
@@ -141,10 +139,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const GpuProgressMarkers::Bracket bracket = m_progressMarkers.Acquire(m_completedSubmitCounter);
const VkResult presentSubmit = GpuProgressMarkers::SubmitBracketed(m_graphicsQueue, submitPacket.submitInfo,
frame.imageInFlightFence, bracket);
if (presentSubmit == VK_SUCCESS) m_progressMarkers.Submitted(bracket, m_submitCounter + 1); // RegisterSubmit below
else m_progressMarkers.Abandon(bracket);
if (presentSubmit == VK_SUCCESS)
m_progressMarkers.Submitted(bracket, m_submitCounter + 1); // RegisterSubmit below
else
m_progressMarkers.Abandon(bracket);
#else
const VkResult presentSubmit = vkQueueSubmit(m_graphicsQueue, 1, &submitPacket.submitInfo, frame.imageInFlightFence);
const VkResult presentSubmit =
vkQueueSubmit(m_graphicsQueue, 1, &submitPacket.submitInfo, frame.imageInFlightFence);
#endif
NoteDeviceLoss(presentSubmit, "Present vkQueueSubmit");
if (presentSubmit != VK_SUCCESS && LatchWireDeviceLoss("present-submit")) return;
@@ -240,8 +241,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// earlier pooled flushes, so wait every registered submission in this
// slot's prefix BEFORE the reset/free happens.
const Uint64 slotSubmit = m_frameContext.GetCurrent().lastSubmitIndex;
if (slotSubmit > m_completedSubmitCounter &&
!WaitForSubmitsUpTo(slotSubmit, UINT64_MAX)) {
if (slotSubmit > m_completedSubmitCounter && !WaitForSubmitsUpTo(slotSubmit, UINT64_MAX)) {
if (LatchWireDeviceLoss("present-slot-submit-wait")) return;
MagmaWireFatal("present-slot-submit-wait");
}
@@ -394,8 +394,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
VkSurfaceCapabilitiesKHR surfaceCaps{};
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice.handle, m_surface, &surfaceCaps) !=
VK_SUCCESS) {
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physicalDevice.handle, m_surface, &surfaceCaps) != VK_SUCCESS) {
return false;
}
// A driver-defined currentExtent (UINT32_MAX) means the surface takes its size from the
@@ -408,14 +407,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the swapchain's own (quarter-turn swapped) extent here would report a difference on
// every rotated frame and rebuild forever.
const VkExtent2D builtFrom = m_swapchainObject.GetSurfaceExtent();
const Bool extentChanged = surfaceCaps.currentExtent.width != builtFrom.width ||
surfaceCaps.currentExtent.height != builtFrom.height;
const Bool extentChanged =
surfaceCaps.currentExtent.width != builtFrom.width || surfaceCaps.currentExtent.height != builtFrom.height;
const Bool transformChanged = surfaceCaps.currentTransform != m_swapchainObject.GetPreTransform();
if (!extentChanged && !transformChanged) {
return false;
}
MGLOG_D("Swapchain out of date: surface %ux%u transform %u -> %ux%u transform %u",
builtFrom.width, builtFrom.height, static_cast<Uint32>(m_swapchainObject.GetPreTransform()),
MGLOG_D("Swapchain out of date: surface %ux%u transform %u -> %ux%u transform %u", builtFrom.width,
builtFrom.height, static_cast<Uint32>(m_swapchainObject.GetPreTransform()),
surfaceCaps.currentExtent.width, surfaceCaps.currentExtent.height,
static_cast<Uint32>(surfaceCaps.currentTransform));
return true;
@@ -529,8 +528,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_device, static_cast<Uint32>(m_swapchainObject.GetImageCount())),
"ActivateSurfaceTarget, InitializeSwapchainSemaphores");
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE) {
VkResult result = m_frameContext.AcquireNextImageInCurrentSlot(
m_device, m_swapchainObject.GetHandle(), m_imageIndexAcquired);
VkResult result = m_frameContext.AcquireNextImageInCurrentSlot(m_device, m_swapchainObject.GetHandle(),
m_imageIndexAcquired);
if (result == VK_SUBOPTIMAL_KHR) result = VK_SUCCESS;
VK_VERIFY(result, "ActivateSurfaceTarget, AcquireNextImageInCurrentSlot");
m_defaultFramebufferImageIndex = m_imageIndexAcquired;
@@ -49,14 +49,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// OnSubmitsCompletedUpTo erases records, so copy fields and restart the
// search after every wait instead of continuing an invalidated iterator.
for (;;) {
const auto record = std::find_if(m_inFlightSubmits.begin(), m_inFlightSubmits.end(),
[serial](const auto& candidate) {
const auto record =
std::find_if(m_inFlightSubmits.begin(), m_inFlightSubmits.end(), [serial](const auto& candidate) {
return candidate.frameSerial >= serial && candidate.fence != VK_NULL_HANDLE;
});
if (record == m_inFlightSubmits.end()) break;
const Uint64 submitIndex = record->submitIndex;
if (!WaitForSubmitsUpTo(submitIndex, UINT64_MAX))
break; // fall through to the queue drain below
if (!WaitForSubmitsUpTo(submitIndex, UINT64_MAX)) break; // fall through to the queue drain below
TryDrainFrameTransients();
if (IsFrameSerialComplete(serial)) return true;
}
@@ -88,8 +87,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return false;
}
const auto& frame = m_frameContext.GetCurrent();
return frame.isCommandRecording || frame.hasCommandBufferRecorded ||
frame.isPreCommandRecording || frame.hasPreCommandBufferRecorded;
return frame.isCommandRecording || frame.hasCommandBufferRecorded || frame.isPreCommandRecording ||
frame.hasPreCommandBufferRecorded;
}
Bool VulkanRenderer::RewindWireDescriptorSetsIfDue() {
@@ -117,12 +116,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
OnSubmitsCompletedUpTo(through);
}
if (HasPendingRecordedWork() || m_completedSubmitCounter < through)
MagmaWireFatal("descriptor-rewind-proof");
if (HasPendingRecordedWork() || m_completedSubmitCounter < through) MagmaWireFatal("descriptor-rewind-proof");
const SizeT cachedSets = m_uniformManager->RewindWireDescriptorSets(frameIndex);
MGLOG_I("Magma descriptor rewind: frame=%u retiredSubmit=%llu budget=%u cached=%zu",
frameIndex, static_cast<unsigned long long>(through),
UniformManager::kWireDescriptorSetBudget, cachedSets);
MGLOG_I("Magma descriptor rewind: frame=%u retiredSubmit=%llu budget=%u cached=%zu", frameIndex,
static_cast<unsigned long long>(through), UniformManager::kWireDescriptorSetBudget, cachedSets);
return true;
}
@@ -138,8 +135,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// treats "not yet" as "still busy" - rename, defer, report unsignaled - so a late "yes" costs
// at most kUnsignaledPollQuietNs of extra caution, never correctness.
constexpr Uint64 kUnsignaledPollQuietNs = 50000;
const Uint64 now = static_cast<Uint64>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
const Uint64 now = static_cast<Uint64>(
std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch())
.count());
if (!m_inFlightSubmits.empty() && m_unsignaledPollFront == m_inFlightSubmits.front().submitIndex &&
now - m_unsignaledPollNs < kUnsignaledPollQuietNs) {
return false;
@@ -179,17 +177,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (submitIndex <= m_completedSubmitCounter) return true;
if (submitIndex > m_submitCounter || m_device == VK_NULL_HANDLE) return false;
Vector<VkFence> fences;
if (!CollectSubmitFencePrefix(m_inFlightSubmits, submitIndex, VkFence{}, fences))
return false;
const VkResult result = vkWaitForFences(m_device, static_cast<Uint32>(fences.size()),
fences.data(), VK_TRUE, timeoutNs);
if (!CollectSubmitFencePrefix(m_inFlightSubmits, submitIndex, VkFence{}, fences)) return false;
const VkResult result =
vkWaitForFences(m_device, static_cast<Uint32>(fences.size()), fences.data(), VK_TRUE, timeoutNs);
if (result == VK_SUCCESS) {
OnSubmitsCompletedUpTo(submitIndex);
return true;
}
NoteDeviceLoss(result, "WaitForSubmitsUpTo vkWaitForFences");
if (result != VK_TIMEOUT)
MGLOG_E_ONCE("WaitForSubmitsUpTo: vkWaitForFences returned %d", result);
if (result != VK_TIMEOUT) MGLOG_E_ONCE("WaitForSubmitsUpTo: vkWaitForFences returned %d", result);
return false;
}
@@ -266,11 +262,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}();
if (probe) {
Uint64 minRemaining = ~Uint64{0};
for (const auto& r : m_inFlightSubmits) minRemaining = std::min(minRemaining, r.frameSerial);
for (const auto& r : m_inFlightSubmits)
minRemaining = std::min(minRemaining, r.frameSerial);
MGLOG_I("FLOOR retire upTo=%llu advanceTo=%llu remaining=%zu minRemainingSerial=%lld "
"frameSerial=%llu",
static_cast<unsigned long long>(submitIndex),
static_cast<unsigned long long>(advanceTo), m_inFlightSubmits.size(),
static_cast<unsigned long long>(submitIndex), static_cast<unsigned long long>(advanceTo),
m_inFlightSubmits.size(),
m_inFlightSubmits.empty() ? -1LL : static_cast<long long>(minRemaining),
static_cast<unsigned long long>(m_bufferManager.GetFrameSerial()));
}
@@ -453,8 +450,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
AttachSharedImageSync(submitInfo, sharedImageSync);
const GpuProgressMarkers::Bracket bracket = m_progressMarkers.Acquire(m_completedSubmitCounter);
const VkResult result = GpuProgressMarkers::SubmitBracketed(m_graphicsQueue, submitInfo, fence, bracket);
if (result == VK_SUCCESS) m_progressMarkers.Submitted(bracket, m_submitCounter + 1); // RegisterSubmit below
else m_progressMarkers.Abandon(bracket);
if (result == VK_SUCCESS)
m_progressMarkers.Submitted(bracket, m_submitCounter + 1); // RegisterSubmit below
else
m_progressMarkers.Abandon(bracket);
#else
const VkResult result = vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, fence);
#endif
@@ -483,8 +482,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// pooled fences and mid-frame command buffers.
RefreshCompletedSubmits();
auto& frame = m_frameContext.GetCurrent();
if (!frame.isCommandRecording && !frame.hasCommandBufferRecorded &&
!frame.isPreCommandRecording && !frame.hasPreCommandBufferRecorded) {
if (!frame.isCommandRecording && !frame.hasCommandBufferRecorded && !frame.isPreCommandRecording &&
!frame.hasPreCommandBufferRecorded) {
// GL flush semantics still demand batched texture uploads start
// executing in finite time even when no draw was recorded.
if (m_textureManager) {
@@ -526,7 +525,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// draining this submission so reusing the buffer stays legal.
const VkResult retireResult = m_frameContext.RetireCurrentCommandBuffer(submittingPreCommandBuffer);
if (retireResult != VK_SUCCESS) {
MGLOG_E_ONCE("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission", retireResult);
MGLOG_E_ONCE("FlushPendingCommands: RetireCurrentCommandBuffer returned %d; draining submission",
retireResult);
if (WaitForSubmitsUpTo(m_submitCounter, UINT64_MAX)) {
// Every registered fence through this submission was waited.
} else if (vkQueueWaitIdle(m_graphicsQueue) == VK_SUCCESS) {
@@ -606,8 +606,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_deviceLost = true;
MGLOG_E("DirectVulkan: the GPU hang watch named this session's VkDevice (a submission ran %llu ms "
"without finishing, at %s); the device is treated as LOST and nothing more is submitted",
static_cast<unsigned long long>(m_progressMarkers.HungRunningMs()),
site != nullptr ? site : "?");
static_cast<unsigned long long>(m_progressMarkers.HungRunningMs()), site != nullptr ? site : "?");
}
// Where no session latch is armed (no served session) the device only stays "lost".
if (!m_deviceLossLatched && MG_Pipe::MGPipeSessionLatchArmed()) {
@@ -19,14 +19,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
static BlitSurfaceTransform ToBlitSurfaceTransform(VkSurfaceTransformFlagBitsKHR preTransform) {
switch (preTransform) {
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
return BlitSurfaceTransform::Rotate90;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
return BlitSurfaceTransform::Rotate180;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
return BlitSurfaceTransform::Rotate270;
default:
return BlitSurfaceTransform::Identity;
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
return BlitSurfaceTransform::Rotate90;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
return BlitSurfaceTransform::Rotate180;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
return BlitSurfaceTransform::Rotate270;
default:
return BlitSurfaceTransform::Identity;
}
}
} // namespace
@@ -130,9 +130,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
m_wireColorBlitResources = nullptr;
}
Bool VulkanRenderer::BlitWireColorToDefault(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter) {
Bool VulkanRenderer::BlitWireColorToDefault(WireImage source, WireImage destination, GLint sx0, GLint sy0,
GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1,
GLenum filter) {
if (!destination.isDefault) return false;
return BlitWireColorImage(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, filter, false);
}
@@ -150,19 +150,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// there is no defined result to be wrong about - only a shape that used to end the session
// and now produces the source's bits. For a float source of the wrong VIEW TYPE the copy is
// a plain slice extraction and the result is exactly right.
Bool VulkanRenderer::BlitWireColorToDefaultThroughScratch(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter) {
Bool VulkanRenderer::BlitWireColorToDefaultThroughScratch(WireImage source, WireImage destination, GLint sx0,
GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0,
GLint dx1, GLint dy1, GLenum filter) {
if (!destination.isDefault || source.isDefault) return false;
if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT)
return false;
if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT) return false;
if (!source.image || !destination.image || source.image == destination.image) return false;
const Int64 width = std::abs(static_cast<Int64>(sx1) - sx0);
const Int64 height = std::abs(static_cast<Int64>(sy1) - sy0);
if (!width || !height || dx0 == dx1 || dy0 == dy1) return true;
if (std::min(sx0, sx1) < 0 || std::min(sy0, sy1) < 0 ||
static_cast<Uint64>(std::max(sx0, sx1)) > source.extent.width ||
static_cast<Uint64>(std::max(sy0, sy1)) > source.extent.height) return false;
static_cast<Uint64>(std::max(sy0, sy1)) > source.extent.height)
return false;
// A compressed source cannot be a colour attachment, so it cannot be a framebuffer blit's
// source either - but the block arithmetic below would silently produce nonsense for one,
// and "cannot happen" is the reason the guards this package retires were written as
@@ -171,16 +171,28 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (vkuFormatIsCompressed(source.format)) return false;
VkFormat scratchFormat = VK_FORMAT_UNDEFINED;
switch (vkuFormatTexelBlockSize(source.format)) {
case 1: scratchFormat = VK_FORMAT_R8_UNORM; break;
case 2: scratchFormat = VK_FORMAT_R8G8_UNORM; break;
case 4: scratchFormat = VK_FORMAT_R8G8B8A8_UNORM; break;
case 8: scratchFormat = VK_FORMAT_R16G16B16A16_SFLOAT; break;
case 16: scratchFormat = VK_FORMAT_R32G32B32A32_SFLOAT; break;
default: return false;
case 1:
scratchFormat = VK_FORMAT_R8_UNORM;
break;
case 2:
scratchFormat = VK_FORMAT_R8G8_UNORM;
break;
case 4:
scratchFormat = VK_FORMAT_R8G8B8A8_UNORM;
break;
case 8:
scratchFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
break;
case 16:
scratchFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
break;
default:
return false;
}
VkFormatProperties scratchProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, scratchFormat, &scratchProperties);
const VkFormatFeatureFlags scratchRequired = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
const VkFormatFeatureFlags scratchRequired =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
(filter == GL_LINEAR ? VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT : 0);
if ((scratchProperties.optimalTilingFeatures & scratchRequired) != scratchRequired) return false;
@@ -197,8 +209,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VmaAllocationCreateInfo allocation{};
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
allocation.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
if (vmaCreateImage(m_allocator, &info, &allocation, &scratch->image, &scratch->allocation,
nullptr) != VK_SUCCESS)
if (vmaCreateImage(m_allocator, &info, &allocation, &scratch->image, &scratch->allocation, nullptr) !=
VK_SUCCESS)
return false;
scratch->format = scratchFormat;
scratch->extent = {info.extent.width, info.extent.height};
@@ -218,14 +230,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// A 3D source carries its slice on z and always names layer 0; every other source
// carries it on the layer axis, which is the same split ResolveWireImage records.
copy.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, source.level, source.is3D ? 0u : source.layer, 1};
copy.srcOffset = {std::min(sx0, sx1), std::min(sy0, sy1),
source.is3D ? static_cast<Int32>(source.layer) : 0};
copy.srcOffset = {std::min(sx0, sx1), std::min(sy0, sy1), source.is3D ? static_cast<Int32>(source.layer) : 0};
copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
copy.dstOffset = {0, 0, 0};
copy.extent = {static_cast<Uint32>(width), static_cast<Uint32>(height), 1};
vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, staged.image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
staged.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy);
staged.layout = scratch->layout;
// The scratch holds the region at its own origin, so a reversed source rectangle
// becomes a reversed rectangle OVER THE SCRATCH - the multisample resolve arm's shape.
@@ -233,8 +243,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const GLint rx1 = sx1 > sx0 ? static_cast<GLint>(width) : 0;
const GLint ry0 = sy1 > sy0 ? 0 : static_cast<GLint>(height);
const GLint ry1 = sy1 > sy0 ? static_cast<GLint>(height) : 0;
const Bool blitted = BlitWireColorImage(staged, destination, rx0, ry0, rx1, ry1,
dx0, dy0, dx1, dy1, filter, false);
const Bool blitted =
BlitWireColorImage(staged, destination, rx0, ry0, rx1, ry1, dx0, dy0, dx1, dy1, filter, false);
// Retired whether or not the blit took: the copy above is already recorded against this
// image, so the submission that consumes it owns the storage either way.
WireRetiredObjects retired{};
@@ -243,39 +253,44 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return blitted;
}
Bool VulkanRenderer::BlitWireColorImage(WireImage source, WireImage destination,
GLint sx0, GLint sy0, GLint sx1, GLint sy1,
GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter, Bool mipmap) {
Bool VulkanRenderer::BlitWireColorImage(WireImage source, WireImage destination, GLint sx0, GLint sy0, GLint sx1,
GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLenum filter,
Bool mipmap) {
const Bool sameImage = source.image == destination.image;
if (source.isDefault || (sameImage && source.level == destination.level && source.layer == destination.layer) ||
source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT ||
source.is3D || destination.is3D || (source.viewType != VK_IMAGE_VIEW_TYPE_2D &&
source.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT || source.is3D ||
destination.is3D ||
(source.viewType != VK_IMAGE_VIEW_TYPE_2D && source.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
(destination.viewType != VK_IMAGE_VIEW_TYPE_2D && destination.viewType != VK_IMAGE_VIEW_TYPE_2D_ARRAY) ||
!vkuFormatIsSampledFloat(source.format)) return false;
!vkuFormatIsSampledFloat(source.format))
return false;
if (sx0 == sx1 || sy0 == sy1 || dx0 == dx1 || dy0 == dy1) return true;
if (!source.extent.width || !source.extent.height ||
!destination.extent.width || !destination.extent.height) return false;
const auto transform = destination.isDefault ? m_swapchainObject.GetPreTransform() :
VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR &&
transform != VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR &&
if (!source.extent.width || !source.extent.height || !destination.extent.width || !destination.extent.height)
return false;
const auto transform =
destination.isDefault ? m_swapchainObject.GetPreTransform() : VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR && transform != VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR &&
transform != VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR &&
transform != VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) return false;
transform != VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR)
return false;
VkFormatProperties sourceProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, source.format, &sourceProperties);
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
const VkFormatFeatureFlags required =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
(filter == GL_LINEAR ? VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT : 0);
if ((sourceProperties.optimalTilingFeatures & required) != required) return false;
// Mip generation samples and writes the texture's own color space;
// GL_FRAMEBUFFER_SRGB only controls framebuffer operations.
if (!mipmap) destination.format = ResolveSrgbAttachmentWriteFormat(destination.format,
MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
if (!mipmap)
destination.format = ResolveSrgbAttachmentWriteFormat(
destination.format, MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb));
VkFormatProperties destinationProperties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, destination.format, &destinationProperties);
if (!(destinationProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) return false;
const VkImageLayout sourceLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
const VkImageLayout destinationLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
const VkImageLayout sourceLayout =
sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
const VkImageLayout destinationLayout =
sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
struct Constants {
float srcRect[4];
@@ -294,8 +309,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
shader(kWireColorBlitVertexSpirv, sizeof(kWireColorBlitVertexSpirv), cache.vertex);
shader(kWireColorBlitFragmentSpirv, sizeof(kWireColorBlitFragmentSpirv), cache.fragment);
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1,
VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
VkDescriptorSetLayoutCreateInfo layout{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
layout.bindingCount = 1;
layout.pBindings = &binding;
@@ -319,7 +334,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& cache = *m_wireColorBlitResources;
WireColorBlitResources::Target* target = nullptr;
for (auto& entry : cache.targets) {
if (entry.format == destination.format && entry.layout == destinationLayout) { target = &entry; break; }
if (entry.format == destination.format && entry.layout == destinationLayout) {
target = &entry;
break;
}
}
if (!target) {
cache.targets.emplace_back();
@@ -354,8 +372,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
stages[0].module = cache.vertex;
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = cache.fragment;
const VkPipelineVertexInputStateCreateInfo vertex{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
const VkPipelineVertexInputStateCreateInfo vertex{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
VkPipelineInputAssemblyStateCreateInfo assembly{
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
viewport.viewportCount = viewport.scissorCount = 1;
@@ -367,8 +387,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineColorBlendAttachmentState colorMask{};
colorMask.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorMask.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT;
VkPipelineColorBlendStateCreateInfo blend{VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO};
blend.attachmentCount = 1;
blend.pAttachments = &colorMask;
@@ -396,8 +416,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// output. GENERAL keeps the whole-image layout tracker truthful; the
// transition also orders the previous mip's attachment write.
TransitionWireImage(source, sourceLayout);
if (sameImage) destination.layout = source.layout;
else TransitionWireImage(destination, destinationLayout);
if (sameImage)
destination.layout = source.layout;
else
TransitionWireImage(destination, destinationLayout);
const auto makeView = [&](const WireImage& image) {
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view.image = image.image;
@@ -440,7 +462,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
auto& cachedTargets = cache.cachedTargets;
WireColorBlitResources::CachedTarget* hit = nullptr;
for (auto& entry : cachedTargets)
if (sameTarget(entry)) { hit = &entry; break; }
if (sameTarget(entry)) {
hit = &entry;
break;
}
if (hit == nullptr) {
if (cachedTargets.size() >= WireColorBlitResources::kCachedTargets) {
WireRetiredObjects evicted{};
@@ -487,7 +512,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VK_VERIFY(vkCreateDescriptorPool(m_device, &pool, nullptr, &cache.framePools[frameIndex]));
}
alloc.descriptorPool = cache.framePools[frameIndex];
if (vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet) != VK_SUCCESS) descriptorSet = VK_NULL_HANDLE;
if (vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet) != VK_SUCCESS)
descriptorSet = VK_NULL_HANDLE;
}
if (descriptorSet == VK_NULL_HANDLE) {
const VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1};
@@ -504,10 +530,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// at a quantised coordinate is not (WireColorBlitFilter.h). The format check above still
// asks for SAMPLED_IMAGE_FILTER_LINEAR whenever GL said GL_LINEAR, so which blits reach
// this pass is unchanged.
const VkFilter samplerFilter = WireColorBlitFilterFor({sx0, sy0, sx1, sy1}, {dx0, dy0, dx1, dy1},
filter, transform);
const VkFilter samplerFilter =
WireColorBlitFilterFor({sx0, sy0, sx1, sy1}, {dx0, dy0, dx1, dy1}, filter, transform);
const VkDescriptorImageInfo sampled{samplerFilter == VK_FILTER_LINEAR ? cache.linear : cache.nearest,
sourceView, sourceLayout};
sourceView, sourceLayout};
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
write.dstSet = descriptorSet;
write.dstBinding = 0;
@@ -517,8 +543,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
vkUpdateDescriptorSets(m_device, 1, &write, 0, nullptr);
const float sw = static_cast<float>(source.extent.width), sh = static_cast<float>(source.extent.height);
const Bool quarterTurn = transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
const Bool quarterTurn =
transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || transform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
const float dw = static_cast<float>(quarterTurn ? destination.extent.height : destination.extent.width);
const float dh = static_cast<float>(quarterTurn ? destination.extent.width : destination.extent.height);
// The shared vertex shader flips window Y. Offscreen images keep the
@@ -527,11 +553,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
dy0 = static_cast<GLint>(destination.extent.height) - dy0;
dy1 = static_cast<GLint>(destination.extent.height) - dy1;
}
const Constants constants{{sx0 / sw, sy0 / sh, (static_cast<float>(sx1) - sx0) / sw,
(static_cast<float>(sy1) - sy0) / sh},
{dx0 / dw, dy0 / dh, (static_cast<float>(dx1) - dx0) / dw,
(static_cast<float>(dy1) - dy0) / dh},
static_cast<Int32>(ToBlitSurfaceTransform(transform))};
const Constants constants{
{sx0 / sw, sy0 / sh, (static_cast<float>(sx1) - sx0) / sw, (static_cast<float>(sy1) - sy0) / sh},
{dx0 / dw, dy0 / dh, (static_cast<float>(dx1) - dx0) / dw, (static_cast<float>(dy1) - dy0) / dh},
static_cast<Int32>(ToBlitSurfaceTransform(transform))};
auto& frame = m_frameContext.GetCurrent();
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = target->renderPass;
@@ -540,15 +565,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
EndActiveRenderPassOn(frame.commandBuffer);
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline);
const VkViewport viewport{0.0f, 0.0f, static_cast<float>(destination.extent.width),
static_cast<float>(destination.extent.height), 0.0f, 1.0f};
const VkViewport viewport{
0.0f, 0.0f, static_cast<float>(destination.extent.width), static_cast<float>(destination.extent.height),
0.0f, 1.0f};
const VkRect2D scissor{{0, 0}, destination.extent};
vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
cache.pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
vkCmdPushConstants(frame.commandBuffer, cache.pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT,
0, sizeof(constants), &constants);
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cache.pipelineLayout, 0, 1,
&descriptorSet, 0, nullptr);
vkCmdPushConstants(frame.commandBuffer, cache.pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(constants),
&constants);
vkCmdDraw(frame.commandBuffer, 6, 1, 0, 0);
vkCmdEndRenderPass(frame.commandBuffer);
ResetDynamicStateShadow();
@@ -44,15 +44,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Same extent on both axes, orientation aside. The extents are taken in 64 bits: GL accepts any
// GLint corner, and INT_MAX - INT_MIN does not fit in one.
inline Bool WireBlitIsUnscaled(const WireBlitRect& source, const WireBlitRect& destination) {
const auto extent = [](GLint a, GLint b) {
return std::abs(static_cast<Int64>(b) - static_cast<Int64>(a));
};
const auto extent = [](GLint a, GLint b) { return std::abs(static_cast<Int64>(b) - static_cast<Int64>(a)); };
return extent(source.x0, source.x1) == extent(destination.x0, destination.x1) &&
extent(source.y0, source.y1) == extent(destination.y0, destination.y1);
}
inline VkFilter WireColorBlitFilterFor(const WireBlitRect& source, const WireBlitRect& destination,
GLenum filter, VkSurfaceTransformFlagBitsKHR transform) {
inline VkFilter WireColorBlitFilterFor(const WireBlitRect& source, const WireBlitRect& destination, GLenum filter,
VkSurfaceTransformFlagBitsKHR transform) {
if (filter != GL_LINEAR) return VK_FILTER_NEAREST;
const Bool expressed = transform == VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR ||
transform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR ||
@@ -17,124 +17,111 @@
#pragma once
static constexpr Uint32 kWireColorBlitVertexSpirv[] = {
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000070u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu,
0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u,
0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000016u, 0x0000005du, 0x00000066u,
0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u,
0x00000009u, 0x00007675u, 0x00060005u, 0x00000016u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u,
0x00050005u, 0x00000019u, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00030005u, 0x0000001cu, 0x00000070u,
0x00050005u, 0x0000001eu, 0x74696c42u, 0x74636552u, 0x00000000u, 0x00050006u, 0x0000001eu, 0x00000000u,
0x52637273u, 0x00746365u, 0x00050006u, 0x0000001eu, 0x00000001u, 0x52747364u, 0x00746365u, 0x00080006u,
0x0000001eu, 0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u, 0x00040005u,
0x00000020u, 0x74636572u, 0x00000000u, 0x00060005u, 0x0000005bu, 0x505f6c67u, 0x65567265u, 0x78657472u,
0x00000000u, 0x00060006u, 0x0000005bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u,
0x0000005bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000005bu,
0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000005bu, 0x00000003u,
0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000005du, 0x00000000u, 0x00050005u,
0x00000066u, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000016u, 0x0000000bu, 0x0000002au,
0x00030047u, 0x0000001eu, 0x00000002u, 0x00050048u, 0x0000001eu, 0x00000000u, 0x00000023u, 0x00000000u,
0x00050048u, 0x0000001eu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000001eu, 0x00000002u,
0x00000023u, 0x00000020u, 0x00030047u, 0x0000005bu, 0x00000002u, 0x00050048u, 0x0000005bu, 0x00000000u,
0x0000000bu, 0x00000000u, 0x00050048u, 0x0000005bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u,
0x0000005bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000005bu, 0x00000003u, 0x0000000bu,
0x00000004u, 0x00040047u, 0x00000066u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u,
0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u,
0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u,
0x00000000u, 0x0004002bu, 0x0000000au, 0x0000000bu, 0x00000006u, 0x0004001cu, 0x0000000cu, 0x00000007u,
0x0000000bu, 0x0004002bu, 0x00000006u, 0x0000000du, 0x00000000u, 0x0005002cu, 0x00000007u, 0x0000000eu,
0x0000000du, 0x0000000du, 0x0004002bu, 0x00000006u, 0x0000000fu, 0x3f800000u, 0x0005002cu, 0x00000007u,
0x00000010u, 0x0000000fu, 0x0000000du, 0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu,
0x0005002cu, 0x00000007u, 0x00000012u, 0x0000000fu, 0x0000000fu, 0x0009002cu, 0x0000000cu, 0x00000013u,
0x0000000eu, 0x00000010u, 0x00000011u, 0x00000011u, 0x00000010u, 0x00000012u, 0x00040015u, 0x00000014u,
0x00000020u, 0x00000001u, 0x00040020u, 0x00000015u, 0x00000001u, 0x00000014u, 0x0004003bu, 0x00000015u,
0x00000016u, 0x00000001u, 0x00040020u, 0x00000018u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001du,
0x00000006u, 0x00000004u, 0x0005001eu, 0x0000001eu, 0x0000001du, 0x0000001du, 0x00000014u, 0x00040020u,
0x0000001fu, 0x00000009u, 0x0000001eu, 0x0004003bu, 0x0000001fu, 0x00000020u, 0x00000009u, 0x0004002bu,
0x00000014u, 0x00000021u, 0x00000001u, 0x00040020u, 0x00000022u, 0x00000009u, 0x0000001du, 0x0004002bu,
0x00000006u, 0x0000002cu, 0x40000000u, 0x0004002bu, 0x0000000au, 0x00000030u, 0x00000001u, 0x00040020u,
0x00000031u, 0x00000007u, 0x00000006u, 0x0004002bu, 0x00000014u, 0x00000036u, 0x00000002u, 0x00040020u,
0x00000037u, 0x00000009u, 0x00000014u, 0x00020014u, 0x0000003au, 0x0004002bu, 0x0000000au, 0x00000041u,
0x00000000u, 0x0004002bu, 0x00000014u, 0x00000050u, 0x00000003u, 0x0004001cu, 0x0000005au, 0x00000006u,
0x00000030u, 0x0006001eu, 0x0000005bu, 0x0000001du, 0x00000006u, 0x0000005au, 0x0000005au, 0x00040020u,
0x0000005cu, 0x00000003u, 0x0000005bu, 0x0004003bu, 0x0000005cu, 0x0000005du, 0x00000003u, 0x0004002bu,
0x00000014u, 0x0000005eu, 0x00000000u, 0x00040020u, 0x00000063u, 0x00000003u, 0x0000001du, 0x00040020u,
0x00000065u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000065u, 0x00000066u, 0x00000003u, 0x00050036u,
0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000008u,
0x00000009u, 0x00000007u, 0x0004003bu, 0x00000018u, 0x00000019u, 0x00000007u, 0x0004003bu, 0x00000008u,
0x0000001cu, 0x00000007u, 0x0004003du, 0x00000014u, 0x00000017u, 0x00000016u, 0x0003003eu, 0x00000019u,
0x00000013u, 0x00050041u, 0x00000008u, 0x0000001au, 0x00000019u, 0x00000017u, 0x0004003du, 0x00000007u,
0x0000001bu, 0x0000001au, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x00050041u, 0x00000022u, 0x00000023u,
0x00000020u, 0x00000021u, 0x0004003du, 0x0000001du, 0x00000024u, 0x00000023u, 0x0007004fu, 0x00000007u,
0x00000025u, 0x00000024u, 0x00000024u, 0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x00000026u,
0x00000009u, 0x00050041u, 0x00000022u, 0x00000027u, 0x00000020u, 0x00000021u, 0x0004003du, 0x0000001du,
0x00000028u, 0x00000027u, 0x0007004fu, 0x00000007u, 0x00000029u, 0x00000028u, 0x00000028u, 0x00000002u,
0x00000003u, 0x00050085u, 0x00000007u, 0x0000002au, 0x00000026u, 0x00000029u, 0x00050081u, 0x00000007u,
0x0000002bu, 0x00000025u, 0x0000002au, 0x0005008eu, 0x00000007u, 0x0000002du, 0x0000002bu, 0x0000002cu,
0x00050050u, 0x00000007u, 0x0000002eu, 0x0000000fu, 0x0000000fu, 0x00050083u, 0x00000007u, 0x0000002fu,
0x0000002du, 0x0000002eu, 0x0003003eu, 0x0000001cu, 0x0000002fu, 0x00050041u, 0x00000031u, 0x00000032u,
0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000033u, 0x00000032u, 0x0004007fu, 0x00000006u,
0x00000034u, 0x00000033u, 0x00050041u, 0x00000031u, 0x00000035u, 0x0000001cu, 0x00000030u, 0x0003003eu,
0x00000035u, 0x00000034u, 0x00050041u, 0x00000037u, 0x00000038u, 0x00000020u, 0x00000036u, 0x0004003du,
0x00000014u, 0x00000039u, 0x00000038u, 0x000500aau, 0x0000003au, 0x0000003bu, 0x00000039u, 0x00000021u,
0x000300f7u, 0x0000003du, 0x00000000u, 0x000400fau, 0x0000003bu, 0x0000003cu, 0x00000045u, 0x000200f8u,
0x0000003cu, 0x00050041u, 0x00000031u, 0x0000003eu, 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u,
0x0000003fu, 0x0000003eu, 0x0004007fu, 0x00000006u, 0x00000040u, 0x0000003fu, 0x00050041u, 0x00000031u,
0x00000042u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u, 0x00000043u, 0x00000042u, 0x00050050u,
0x00000007u, 0x00000044u, 0x00000040u, 0x00000043u, 0x0003003eu, 0x0000001cu, 0x00000044u, 0x000200f9u,
0x0000003du, 0x000200f8u, 0x00000045u, 0x00050041u, 0x00000037u, 0x00000046u, 0x00000020u, 0x00000036u,
0x0004003du, 0x00000014u, 0x00000047u, 0x00000046u, 0x000500aau, 0x0000003au, 0x00000048u, 0x00000047u,
0x00000036u, 0x000300f7u, 0x0000004au, 0x00000000u, 0x000400fau, 0x00000048u, 0x00000049u, 0x0000004du,
0x000200f8u, 0x00000049u, 0x0004003du, 0x00000007u, 0x0000004bu, 0x0000001cu, 0x0004007fu, 0x00000007u,
0x0000004cu, 0x0000004bu, 0x0003003eu, 0x0000001cu, 0x0000004cu, 0x000200f9u, 0x0000004au, 0x000200f8u,
0x0000004du, 0x00050041u, 0x00000037u, 0x0000004eu, 0x00000020u, 0x00000036u, 0x0004003du, 0x00000014u,
0x0000004fu, 0x0000004eu, 0x000500aau, 0x0000003au, 0x00000051u, 0x0000004fu, 0x00000050u, 0x000300f7u,
0x00000053u, 0x00000000u, 0x000400fau, 0x00000051u, 0x00000052u, 0x00000053u, 0x000200f8u, 0x00000052u,
0x00050041u, 0x00000031u, 0x00000054u, 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000055u,
0x00000054u, 0x00050041u, 0x00000031u, 0x00000056u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u,
0x00000057u, 0x00000056u, 0x0004007fu, 0x00000006u, 0x00000058u, 0x00000057u, 0x00050050u, 0x00000007u,
0x00000059u, 0x00000055u, 0x00000058u, 0x0003003eu, 0x0000001cu, 0x00000059u, 0x000200f9u, 0x00000053u,
0x000200f8u, 0x00000053u, 0x000200f9u, 0x0000004au, 0x000200f8u, 0x0000004au, 0x000200f9u, 0x0000003du,
0x000200f8u, 0x0000003du, 0x0004003du, 0x00000007u, 0x0000005fu, 0x0000001cu, 0x00050051u, 0x00000006u,
0x00000060u, 0x0000005fu, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000061u, 0x0000005fu, 0x00000001u,
0x00070050u, 0x0000001du, 0x00000062u, 0x00000060u, 0x00000061u, 0x0000000du, 0x0000000fu, 0x00050041u,
0x00000063u, 0x00000064u, 0x0000005du, 0x0000005eu, 0x0003003eu, 0x00000064u, 0x00000062u, 0x00050041u,
0x00000022u, 0x00000067u, 0x00000020u, 0x0000005eu, 0x0004003du, 0x0000001du, 0x00000068u, 0x00000067u,
0x0007004fu, 0x00000007u, 0x00000069u, 0x00000068u, 0x00000068u, 0x00000000u, 0x00000001u, 0x0004003du,
0x00000007u, 0x0000006au, 0x00000009u, 0x00050041u, 0x00000022u, 0x0000006bu, 0x00000020u, 0x0000005eu,
0x0004003du, 0x0000001du, 0x0000006cu, 0x0000006bu, 0x0007004fu, 0x00000007u, 0x0000006du, 0x0000006cu,
0x0000006cu, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000006eu, 0x0000006au, 0x0000006du,
0x00050081u, 0x00000007u, 0x0000006fu, 0x00000069u, 0x0000006eu, 0x0003003eu, 0x00000066u, 0x0000006fu,
0x000100fdu, 0x00010038u,
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000070u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u,
0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u,
0x00000004u, 0x6e69616du, 0x00000000u, 0x00000016u, 0x0000005du, 0x00000066u, 0x00030003u, 0x00000002u, 0x000001c2u,
0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, 0x00000009u, 0x00007675u, 0x00060005u, 0x00000016u,
0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00050005u, 0x00000019u, 0x65646e69u, 0x6c626178u, 0x00000065u,
0x00030005u, 0x0000001cu, 0x00000070u, 0x00050005u, 0x0000001eu, 0x74696c42u, 0x74636552u, 0x00000000u, 0x00050006u,
0x0000001eu, 0x00000000u, 0x52637273u, 0x00746365u, 0x00050006u, 0x0000001eu, 0x00000001u, 0x52747364u, 0x00746365u,
0x00080006u, 0x0000001eu, 0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u, 0x00040005u,
0x00000020u, 0x74636572u, 0x00000000u, 0x00060005u, 0x0000005bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u,
0x00060006u, 0x0000005bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000005bu, 0x00000001u,
0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000005bu, 0x00000002u, 0x435f6c67u, 0x4470696cu,
0x61747369u, 0x0065636eu, 0x00070006u, 0x0000005bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu,
0x00030005u, 0x0000005du, 0x00000000u, 0x00050005u, 0x00000066u, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u,
0x00000016u, 0x0000000bu, 0x0000002au, 0x00030047u, 0x0000001eu, 0x00000002u, 0x00050048u, 0x0000001eu, 0x00000000u,
0x00000023u, 0x00000000u, 0x00050048u, 0x0000001eu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000001eu,
0x00000002u, 0x00000023u, 0x00000020u, 0x00030047u, 0x0000005bu, 0x00000002u, 0x00050048u, 0x0000005bu, 0x00000000u,
0x0000000bu, 0x00000000u, 0x00050048u, 0x0000005bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000005bu,
0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000005bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u,
0x00000066u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u,
0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u,
0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, 0x00000000u, 0x0004002bu, 0x0000000au, 0x0000000bu, 0x00000006u,
0x0004001cu, 0x0000000cu, 0x00000007u, 0x0000000bu, 0x0004002bu, 0x00000006u, 0x0000000du, 0x00000000u, 0x0005002cu,
0x00000007u, 0x0000000eu, 0x0000000du, 0x0000000du, 0x0004002bu, 0x00000006u, 0x0000000fu, 0x3f800000u, 0x0005002cu,
0x00000007u, 0x00000010u, 0x0000000fu, 0x0000000du, 0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu,
0x0005002cu, 0x00000007u, 0x00000012u, 0x0000000fu, 0x0000000fu, 0x0009002cu, 0x0000000cu, 0x00000013u, 0x0000000eu,
0x00000010u, 0x00000011u, 0x00000011u, 0x00000010u, 0x00000012u, 0x00040015u, 0x00000014u, 0x00000020u, 0x00000001u,
0x00040020u, 0x00000015u, 0x00000001u, 0x00000014u, 0x0004003bu, 0x00000015u, 0x00000016u, 0x00000001u, 0x00040020u,
0x00000018u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001du, 0x00000006u, 0x00000004u, 0x0005001eu, 0x0000001eu,
0x0000001du, 0x0000001du, 0x00000014u, 0x00040020u, 0x0000001fu, 0x00000009u, 0x0000001eu, 0x0004003bu, 0x0000001fu,
0x00000020u, 0x00000009u, 0x0004002bu, 0x00000014u, 0x00000021u, 0x00000001u, 0x00040020u, 0x00000022u, 0x00000009u,
0x0000001du, 0x0004002bu, 0x00000006u, 0x0000002cu, 0x40000000u, 0x0004002bu, 0x0000000au, 0x00000030u, 0x00000001u,
0x00040020u, 0x00000031u, 0x00000007u, 0x00000006u, 0x0004002bu, 0x00000014u, 0x00000036u, 0x00000002u, 0x00040020u,
0x00000037u, 0x00000009u, 0x00000014u, 0x00020014u, 0x0000003au, 0x0004002bu, 0x0000000au, 0x00000041u, 0x00000000u,
0x0004002bu, 0x00000014u, 0x00000050u, 0x00000003u, 0x0004001cu, 0x0000005au, 0x00000006u, 0x00000030u, 0x0006001eu,
0x0000005bu, 0x0000001du, 0x00000006u, 0x0000005au, 0x0000005au, 0x00040020u, 0x0000005cu, 0x00000003u, 0x0000005bu,
0x0004003bu, 0x0000005cu, 0x0000005du, 0x00000003u, 0x0004002bu, 0x00000014u, 0x0000005eu, 0x00000000u, 0x00040020u,
0x00000063u, 0x00000003u, 0x0000001du, 0x00040020u, 0x00000065u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000065u,
0x00000066u, 0x00000003u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u,
0x0004003bu, 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000018u, 0x00000019u, 0x00000007u, 0x0004003bu,
0x00000008u, 0x0000001cu, 0x00000007u, 0x0004003du, 0x00000014u, 0x00000017u, 0x00000016u, 0x0003003eu, 0x00000019u,
0x00000013u, 0x00050041u, 0x00000008u, 0x0000001au, 0x00000019u, 0x00000017u, 0x0004003du, 0x00000007u, 0x0000001bu,
0x0000001au, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x00050041u, 0x00000022u, 0x00000023u, 0x00000020u, 0x00000021u,
0x0004003du, 0x0000001du, 0x00000024u, 0x00000023u, 0x0007004fu, 0x00000007u, 0x00000025u, 0x00000024u, 0x00000024u,
0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x00000026u, 0x00000009u, 0x00050041u, 0x00000022u, 0x00000027u,
0x00000020u, 0x00000021u, 0x0004003du, 0x0000001du, 0x00000028u, 0x00000027u, 0x0007004fu, 0x00000007u, 0x00000029u,
0x00000028u, 0x00000028u, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000002au, 0x00000026u, 0x00000029u,
0x00050081u, 0x00000007u, 0x0000002bu, 0x00000025u, 0x0000002au, 0x0005008eu, 0x00000007u, 0x0000002du, 0x0000002bu,
0x0000002cu, 0x00050050u, 0x00000007u, 0x0000002eu, 0x0000000fu, 0x0000000fu, 0x00050083u, 0x00000007u, 0x0000002fu,
0x0000002du, 0x0000002eu, 0x0003003eu, 0x0000001cu, 0x0000002fu, 0x00050041u, 0x00000031u, 0x00000032u, 0x0000001cu,
0x00000030u, 0x0004003du, 0x00000006u, 0x00000033u, 0x00000032u, 0x0004007fu, 0x00000006u, 0x00000034u, 0x00000033u,
0x00050041u, 0x00000031u, 0x00000035u, 0x0000001cu, 0x00000030u, 0x0003003eu, 0x00000035u, 0x00000034u, 0x00050041u,
0x00000037u, 0x00000038u, 0x00000020u, 0x00000036u, 0x0004003du, 0x00000014u, 0x00000039u, 0x00000038u, 0x000500aau,
0x0000003au, 0x0000003bu, 0x00000039u, 0x00000021u, 0x000300f7u, 0x0000003du, 0x00000000u, 0x000400fau, 0x0000003bu,
0x0000003cu, 0x00000045u, 0x000200f8u, 0x0000003cu, 0x00050041u, 0x00000031u, 0x0000003eu, 0x0000001cu, 0x00000030u,
0x0004003du, 0x00000006u, 0x0000003fu, 0x0000003eu, 0x0004007fu, 0x00000006u, 0x00000040u, 0x0000003fu, 0x00050041u,
0x00000031u, 0x00000042u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u, 0x00000043u, 0x00000042u, 0x00050050u,
0x00000007u, 0x00000044u, 0x00000040u, 0x00000043u, 0x0003003eu, 0x0000001cu, 0x00000044u, 0x000200f9u, 0x0000003du,
0x000200f8u, 0x00000045u, 0x00050041u, 0x00000037u, 0x00000046u, 0x00000020u, 0x00000036u, 0x0004003du, 0x00000014u,
0x00000047u, 0x00000046u, 0x000500aau, 0x0000003au, 0x00000048u, 0x00000047u, 0x00000036u, 0x000300f7u, 0x0000004au,
0x00000000u, 0x000400fau, 0x00000048u, 0x00000049u, 0x0000004du, 0x000200f8u, 0x00000049u, 0x0004003du, 0x00000007u,
0x0000004bu, 0x0000001cu, 0x0004007fu, 0x00000007u, 0x0000004cu, 0x0000004bu, 0x0003003eu, 0x0000001cu, 0x0000004cu,
0x000200f9u, 0x0000004au, 0x000200f8u, 0x0000004du, 0x00050041u, 0x00000037u, 0x0000004eu, 0x00000020u, 0x00000036u,
0x0004003du, 0x00000014u, 0x0000004fu, 0x0000004eu, 0x000500aau, 0x0000003au, 0x00000051u, 0x0000004fu, 0x00000050u,
0x000300f7u, 0x00000053u, 0x00000000u, 0x000400fau, 0x00000051u, 0x00000052u, 0x00000053u, 0x000200f8u, 0x00000052u,
0x00050041u, 0x00000031u, 0x00000054u, 0x0000001cu, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000055u, 0x00000054u,
0x00050041u, 0x00000031u, 0x00000056u, 0x0000001cu, 0x00000041u, 0x0004003du, 0x00000006u, 0x00000057u, 0x00000056u,
0x0004007fu, 0x00000006u, 0x00000058u, 0x00000057u, 0x00050050u, 0x00000007u, 0x00000059u, 0x00000055u, 0x00000058u,
0x0003003eu, 0x0000001cu, 0x00000059u, 0x000200f9u, 0x00000053u, 0x000200f8u, 0x00000053u, 0x000200f9u, 0x0000004au,
0x000200f8u, 0x0000004au, 0x000200f9u, 0x0000003du, 0x000200f8u, 0x0000003du, 0x0004003du, 0x00000007u, 0x0000005fu,
0x0000001cu, 0x00050051u, 0x00000006u, 0x00000060u, 0x0000005fu, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000061u,
0x0000005fu, 0x00000001u, 0x00070050u, 0x0000001du, 0x00000062u, 0x00000060u, 0x00000061u, 0x0000000du, 0x0000000fu,
0x00050041u, 0x00000063u, 0x00000064u, 0x0000005du, 0x0000005eu, 0x0003003eu, 0x00000064u, 0x00000062u, 0x00050041u,
0x00000022u, 0x00000067u, 0x00000020u, 0x0000005eu, 0x0004003du, 0x0000001du, 0x00000068u, 0x00000067u, 0x0007004fu,
0x00000007u, 0x00000069u, 0x00000068u, 0x00000068u, 0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x0000006au,
0x00000009u, 0x00050041u, 0x00000022u, 0x0000006bu, 0x00000020u, 0x0000005eu, 0x0004003du, 0x0000001du, 0x0000006cu,
0x0000006bu, 0x0007004fu, 0x00000007u, 0x0000006du, 0x0000006cu, 0x0000006cu, 0x00000002u, 0x00000003u, 0x00050085u,
0x00000007u, 0x0000006eu, 0x0000006au, 0x0000006du, 0x00050081u, 0x00000007u, 0x0000006fu, 0x00000069u, 0x0000006eu,
0x0003003eu, 0x00000066u, 0x0000006fu, 0x000100fdu, 0x00010038u,
};
static constexpr Uint32 kWireColorBlitFragmentSpirv[] = {
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000027u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu,
0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u,
0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000au, 0x0000001fu, 0x00030010u,
0x00000004u, 0x00000007u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du,
0x00000000u, 0x00050005u, 0x0000000au, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00050005u, 0x0000001fu,
0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00050005u, 0x00000023u, 0x72756f73u, 0x6d496563u, 0x00656761u,
0x00040047u, 0x0000000au, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000001fu, 0x0000001eu, 0x00000000u,
0x00040047u, 0x00000023u, 0x00000021u, 0x00000000u, 0x00040047u, 0x00000023u, 0x00000022u, 0x00000000u,
0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00020014u, 0x00000006u, 0x00030016u,
0x00000007u, 0x00000020u, 0x00040017u, 0x00000008u, 0x00000007u, 0x00000002u, 0x00040020u, 0x00000009u,
0x00000001u, 0x00000008u, 0x0004003bu, 0x00000009u, 0x0000000au, 0x00000001u, 0x0004002bu, 0x00000007u,
0x0000000cu, 0x00000000u, 0x0005002cu, 0x00000008u, 0x0000000du, 0x0000000cu, 0x0000000cu, 0x00040017u,
0x0000000eu, 0x00000006u, 0x00000002u, 0x0004002bu, 0x00000007u, 0x00000015u, 0x3f800000u, 0x0005002cu,
0x00000008u, 0x00000016u, 0x00000015u, 0x00000015u, 0x00040017u, 0x0000001du, 0x00000007u, 0x00000004u,
0x00040020u, 0x0000001eu, 0x00000003u, 0x0000001du, 0x0004003bu, 0x0000001eu, 0x0000001fu, 0x00000003u,
0x00090019u, 0x00000020u, 0x00000007u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u,
0x00000000u, 0x0003001bu, 0x00000021u, 0x00000020u, 0x00040020u, 0x00000022u, 0x00000000u, 0x00000021u,
0x0004003bu, 0x00000022u, 0x00000023u, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u,
0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000008u, 0x0000000bu, 0x0000000au, 0x000500b8u,
0x0000000eu, 0x0000000fu, 0x0000000bu, 0x0000000du, 0x0004009au, 0x00000006u, 0x00000010u, 0x0000000fu,
0x000400a8u, 0x00000006u, 0x00000011u, 0x00000010u, 0x000300f7u, 0x00000013u, 0x00000000u, 0x000400fau,
0x00000011u, 0x00000012u, 0x00000013u, 0x000200f8u, 0x00000012u, 0x0004003du, 0x00000008u, 0x00000014u,
0x0000000au, 0x000500bau, 0x0000000eu, 0x00000017u, 0x00000014u, 0x00000016u, 0x0004009au, 0x00000006u,
0x00000018u, 0x00000017u, 0x000200f9u, 0x00000013u, 0x000200f8u, 0x00000013u, 0x000700f5u, 0x00000006u,
0x00000019u, 0x00000010u, 0x00000005u, 0x00000018u, 0x00000012u, 0x000300f7u, 0x0000001bu, 0x00000000u,
0x000400fau, 0x00000019u, 0x0000001au, 0x0000001bu, 0x000200f8u, 0x0000001au, 0x000100fcu, 0x000200f8u,
0x0000001bu, 0x0004003du, 0x00000021u, 0x00000024u, 0x00000023u, 0x0004003du, 0x00000008u, 0x00000025u,
0x0000000au, 0x00070058u, 0x0000001du, 0x00000026u, 0x00000024u, 0x00000025u, 0x00000002u, 0x0000000cu,
0x0003003eu, 0x0000001fu, 0x00000026u, 0x000100fdu, 0x00010038u,
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000027u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u,
0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u,
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000au, 0x0000001fu, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030003u,
0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00050005u, 0x0000000au, 0x43786574u,
0x64726f6fu, 0x00000000u, 0x00050005u, 0x0000001fu, 0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00050005u, 0x00000023u,
0x72756f73u, 0x6d496563u, 0x00656761u, 0x00040047u, 0x0000000au, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000001fu,
0x0000001eu, 0x00000000u, 0x00040047u, 0x00000023u, 0x00000021u, 0x00000000u, 0x00040047u, 0x00000023u, 0x00000022u,
0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00020014u, 0x00000006u, 0x00030016u,
0x00000007u, 0x00000020u, 0x00040017u, 0x00000008u, 0x00000007u, 0x00000002u, 0x00040020u, 0x00000009u, 0x00000001u,
0x00000008u, 0x0004003bu, 0x00000009u, 0x0000000au, 0x00000001u, 0x0004002bu, 0x00000007u, 0x0000000cu, 0x00000000u,
0x0005002cu, 0x00000008u, 0x0000000du, 0x0000000cu, 0x0000000cu, 0x00040017u, 0x0000000eu, 0x00000006u, 0x00000002u,
0x0004002bu, 0x00000007u, 0x00000015u, 0x3f800000u, 0x0005002cu, 0x00000008u, 0x00000016u, 0x00000015u, 0x00000015u,
0x00040017u, 0x0000001du, 0x00000007u, 0x00000004u, 0x00040020u, 0x0000001eu, 0x00000003u, 0x0000001du, 0x0004003bu,
0x0000001eu, 0x0000001fu, 0x00000003u, 0x00090019u, 0x00000020u, 0x00000007u, 0x00000001u, 0x00000000u, 0x00000000u,
0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x00000021u, 0x00000020u, 0x00040020u, 0x00000022u, 0x00000000u,
0x00000021u, 0x0004003bu, 0x00000022u, 0x00000023u, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u,
0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000008u, 0x0000000bu, 0x0000000au, 0x000500b8u, 0x0000000eu,
0x0000000fu, 0x0000000bu, 0x0000000du, 0x0004009au, 0x00000006u, 0x00000010u, 0x0000000fu, 0x000400a8u, 0x00000006u,
0x00000011u, 0x00000010u, 0x000300f7u, 0x00000013u, 0x00000000u, 0x000400fau, 0x00000011u, 0x00000012u, 0x00000013u,
0x000200f8u, 0x00000012u, 0x0004003du, 0x00000008u, 0x00000014u, 0x0000000au, 0x000500bau, 0x0000000eu, 0x00000017u,
0x00000014u, 0x00000016u, 0x0004009au, 0x00000006u, 0x00000018u, 0x00000017u, 0x000200f9u, 0x00000013u, 0x000200f8u,
0x00000013u, 0x000700f5u, 0x00000006u, 0x00000019u, 0x00000010u, 0x00000005u, 0x00000018u, 0x00000012u, 0x000300f7u,
0x0000001bu, 0x00000000u, 0x000400fau, 0x00000019u, 0x0000001au, 0x0000001bu, 0x000200f8u, 0x0000001au, 0x000100fcu,
0x000200f8u, 0x0000001bu, 0x0004003du, 0x00000021u, 0x00000024u, 0x00000023u, 0x0004003du, 0x00000008u, 0x00000025u,
0x0000000au, 0x00070058u, 0x0000001du, 0x00000026u, 0x00000024u, 0x00000025u, 0x00000002u, 0x0000000cu, 0x0003003eu,
0x0000001fu, 0x00000026u, 0x000100fdu, 0x00010038u,
};
@@ -53,9 +53,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkFence fence = VK_NULL_HANDLE;
void Destroy() {
for (VkImage image : images) vkDestroyImage(device, image, nullptr);
for (VkBuffer buffer : buffers) vkDestroyBuffer(device, buffer, nullptr);
for (VkDeviceMemory memory : memories) vkFreeMemory(device, memory, nullptr);
for (VkImage image : images)
vkDestroyImage(device, image, nullptr);
for (VkBuffer buffer : buffers)
vkDestroyBuffer(device, buffer, nullptr);
for (VkDeviceMemory memory : memories)
vkFreeMemory(device, memory, nullptr);
vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyFence(device, fence, nullptr);
}
@@ -97,7 +100,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
measurement.failureReason = "no device or queue";
return measurement;
}
constexpr VkFormatFeatureFlags kRequired = VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
constexpr VkFormatFeatureFlags kRequired =
VK_FORMAT_FEATURE_TRANSFER_SRC_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT;
for (const VkFormat format : {kSourceFormat, kDestinationFormat}) {
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(context.physicalDevice, format, &properties);
@@ -142,16 +146,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
info.arrayLayers = volume ? 1u : kSlices;
info.samples = VK_SAMPLE_COUNT_1_BIT;
info.tiling = VK_IMAGE_TILING_OPTIMAL;
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
(attachable ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0u);
info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
VK_IMAGE_USAGE_TRANSFER_DST_BIT | (attachable ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT : 0u);
info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
if (vkCreateImage(device, &info, nullptr, &out) != VK_SUCCESS) return false;
objects.images.push_back(out);
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device, out, &requirements);
const Optional<Uint32> type = PickMemoryType(memoryProperties, requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
const Optional<Uint32> type =
PickMemoryType(memoryProperties, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
if (!type) return false;
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocate.allocationSize = requirements.size;
@@ -212,8 +216,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
!makeBuffer(kTexels * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, subjectOut) ||
!makeBuffer(kTexels * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT, controlOut))
return fail("buffer creation failed");
if (!access(sourceWords, true, [](Uint32* words) { for (Uint32 i = 0; i < kTexels; ++i) words[i] = Word(i, 1); }) ||
!access(sentinelWords, true, [](Uint32* words) { for (Uint32 i = 0; i < kDestinationTexels; ++i) words[i] = Word(i, 2); }))
if (!access(sourceWords, true,
[](Uint32* words) {
for (Uint32 i = 0; i < kTexels; ++i)
words[i] = Word(i, 1);
}) ||
!access(sentinelWords, true, [](Uint32* words) {
for (Uint32 i = 0; i < kDestinationTexels; ++i)
words[i] = Word(i, 2);
}))
return fail("vkMapMemory (upload) failed");
VkCommandPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
@@ -245,8 +256,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkBufferImageCopy wholeVolume = volume;
wholeVolume.imageExtent = {kDestinationExtent, kDestinationExtent, kDestinationDepth};
vkCmdCopyBufferToImage(cmd, sourceWords.buffer, source, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &layered);
vkCmdCopyBufferToImage(cmd, sentinelWords.buffer, subject, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &wholeVolume);
vkCmdCopyBufferToImage(cmd, sentinelWords.buffer, control, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &wholeVolume);
vkCmdCopyBufferToImage(cmd, sentinelWords.buffer, subject, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&wholeVolume);
vkCmdCopyBufferToImage(cmd, sentinelWords.buffer, control, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&wholeVolume);
ImageBarrier(cmd, source, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT);
for (const VkImage image : {subject, control})
@@ -308,9 +321,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (exactSlices && sliceMismatches == 0) ++*exactSlices;
}
};
if (!access(subjectOut, false, [&](Uint32* words) {
tally(words, measurement.subjectMismatches, &measurement.subjectExactSlices);
}) ||
if (!access(
subjectOut, false,
[&](Uint32* words) { tally(words, measurement.subjectMismatches, &measurement.subjectExactSlices); }) ||
!access(controlOut, false, [&](Uint32* words) { tally(words, measurement.controlMismatches, nullptr); }))
return fail("vkMapMemory (readback) failed");
measurement.texels = kTexels;
@@ -324,8 +337,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkPhysicalDevice physicalDevice,
Uint32 graphicsQueueFamilyIndex) {
WireCopySliceProbeMeasurement measurement;
const auto createDevice =
reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
const auto createDevice = reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
if (createDevice == nullptr) {
measurement.failureReason = "no vkCreateDevice";
return measurement;
@@ -55,10 +55,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
inline const char* WireCopySliceProbeVerdictName(WireCopySliceProbeVerdict verdict) {
switch (verdict) {
case WireCopySliceProbeVerdict::NotRun: return "not-run";
case WireCopySliceProbeVerdict::Inconclusive: return "inconclusive";
case WireCopySliceProbeVerdict::Clean: return "clean";
case WireCopySliceProbeVerdict::CrossFormatSlicesDropped: return "cross-format-slices-dropped";
case WireCopySliceProbeVerdict::NotRun:
return "not-run";
case WireCopySliceProbeVerdict::Inconclusive:
return "inconclusive";
case WireCopySliceProbeVerdict::Clean:
return "clean";
case WireCopySliceProbeVerdict::CrossFormatSlicesDropped:
return "cross-format-slices-dropped";
}
return "?";
}
@@ -17,135 +17,133 @@
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 {
// 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
};
Count
};
inline const char* WireDeclineSiteName(WireDeclineSite site) {
switch (site) {
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)) {
case WireDeclineSite::Count:
break;
}
}
// The frame-serial floor's own counter. An "unsound serial complete" is an advance of
// VkBufferManager's completed-frame-serial floor to a serial that ANOTHER submission still
// in flight also carries - i.e. the renderer telling the buffer manager "frame N-1 is done"
// while the submission holding frame N-1's buffer copies is still executing. It is what
// lets a streamed glBufferSubData take the unordered host path and tear one draw's vertex
// range. With a provable floor it is unreachable, so this counter is the red-once's reading:
// non-zero on the wire arms before the fix, zero after, zero on monolith either way.
static void CountUnsoundSerialComplete() {
UnsoundSerial().fetch_add(1, std::memory_order_relaxed);
}
static Uint64 UnsoundSerialCompleteEvents() { return UnsoundSerial().load(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);
UnsoundSerial().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);
return "Unnamed";
}
// 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) {
// The floor counter prints unconditionally: "zero unsound advances" is the reading the
// red-once needs, and an absent line cannot be told from a lane that never ran.
MGLOG_I("MGWIRE-FLOOR[%s] unsoundSerialComplete=%llu", tag,
static_cast<unsigned long long>(UnsoundSerialCompleteEvents()));
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));
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);
}
}
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;
}
static std::atomic<Uint64>& UnsoundSerial() {
static std::atomic<Uint64> value{0};
return value;
}
};
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)) {}
}
// The frame-serial floor's own counter. An "unsound serial complete" is an advance of
// VkBufferManager's completed-frame-serial floor to a serial that ANOTHER submission still
// in flight also carries - i.e. the renderer telling the buffer manager "frame N-1 is done"
// while the submission holding frame N-1's buffer copies is still executing. It is what
// lets a streamed glBufferSubData take the unordered host path and tear one draw's vertex
// range. With a provable floor it is unreachable, so this counter is the red-once's reading:
// non-zero on the wire arms before the fix, zero after, zero on monolith either way.
static void CountUnsoundSerialComplete() { UnsoundSerial().fetch_add(1, std::memory_order_relaxed); }
static Uint64 UnsoundSerialCompleteEvents() { return UnsoundSerial().load(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);
UnsoundSerial().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) {
// The floor counter prints unconditionally: "zero unsound advances" is the reading the
// red-once needs, and an absent line cannot be told from a lane that never ran.
MGLOG_I("MGWIRE-FLOOR[%s] unsoundSerialComplete=%llu", tag,
static_cast<unsigned long long>(UnsoundSerialCompleteEvents()));
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;
}
static std::atomic<Uint64>& UnsoundSerial() {
static std::atomic<Uint64> value{0};
return value;
}
};
// Every decline goes through this: the tally moves ALWAYS, the log line is once per site.
// `MGL_WIRE_DECLINE_AT(Site, "why", ...); return false;` is the statement pair that replaces
@@ -161,10 +159,9 @@ private:
// sync counts once and is then a Fatal, not a skipped draw.
#define MGL_WIRE_DECLINE_AT(site, fmt, ...) \
do { \
::MobileGL::MG_Backend::DirectVulkan::WireDeclineTally::Count( \
::MobileGL::MG_Backend::DirectVulkan::WireDeclineTally::Count( \
::MobileGL::MG_Backend::DirectVulkan::WireDeclineSite::site); \
MGLOG_E_ONCE("Magma wire decline [" #site "]: " fmt, ##__VA_ARGS__); \
} while (0)
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -68,11 +68,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool VulkanRenderer::GenerateWireDepthMipLevel(WireImage source, WireImage destination) {
const Bool sameImage = source.image == destination.image;
if (!source.image || !destination.image) return false;
if (sameImage && source.level == destination.level && source.layer == destination.layer)
return false;
if (sameImage && source.level == destination.level && source.layer == destination.layer) return false;
if (source.isDefault || destination.isDefault) return false;
if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT)
return false;
if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT) return false;
if (source.is3D || destination.is3D) return false;
// P8-SV: THE PASS MAKES 2D VIEWS (makeView below), which a 1D, 1D-array or 3D image does
// not admit. A caller that fills viewType from the image gets a refusal here, and the
@@ -88,14 +86,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// else - the stencil aspect would come out of the render pass undefined, which is
// not what GL's glGenerateMipmap promises for the level it just produced. The
// caller declines that shape rather than producing half a level.
if (source.aspect != VK_IMAGE_ASPECT_DEPTH_BIT ||
destination.aspect != VK_IMAGE_ASPECT_DEPTH_BIT) return false;
if (!source.extent.width || !source.extent.height ||
!destination.extent.width || !destination.extent.height) return false;
if (source.aspect != VK_IMAGE_ASPECT_DEPTH_BIT || destination.aspect != VK_IMAGE_ASPECT_DEPTH_BIT) return false;
if (!source.extent.width || !source.extent.height || !destination.extent.width || !destination.extent.height)
return false;
VkFormatProperties properties{};
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, source.format, &properties);
const VkFormatFeatureFlags required = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
const VkFormatFeatureFlags required =
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
if ((properties.optimalTilingFeatures & required) != required) return false;
// The baked block's layout, and the offsets are not a guess: the generated header
@@ -122,8 +119,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
shader(kWireDepthMipmapVertexSpirv, sizeof(kWireDepthMipmapVertexSpirv), cache.vertex);
shader(kWireDepthMipmapFragmentSpirv, sizeof(kWireDepthMipmapFragmentSpirv), cache.fragment);
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
const VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1,
VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
VkDescriptorSetLayoutCreateInfo layout{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
layout.bindingCount = 1;
layout.pBindings = &binding;
@@ -131,8 +128,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// BOTH STAGES ON ONE RANGE: the block is declared identically in the vertex and
// fragment sources (a push-constant block must be), the vertex stage reads the
// rects and the fragment stage reads srcTexelSize.
const VkPushConstantRange push{VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(Constants)};
const VkPushConstantRange push{VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
sizeof(Constants)};
VkPipelineLayoutCreateInfo pipelineLayout{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
pipelineLayout.setLayoutCount = 1;
pipelineLayout.pSetLayouts = &cache.descriptorLayout;
@@ -145,18 +142,17 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// depth formats, which is most of them.
VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW =
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler.addressModeU = sampler.addressModeV = sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
sampler.magFilter = sampler.minFilter = VK_FILTER_NEAREST;
sampler.maxLod = 0.0f;
VK_VERIFY(vkCreateSampler(m_device, &sampler, nullptr, &cache.nearest));
}
auto& cache = *m_wireDepthMipmapResources;
const VkImageLayout sourceLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
const VkImageLayout destinationLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
const VkImageLayout sourceLayout =
sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
const VkImageLayout destinationLayout =
sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
WireDepthMipmapResources::Target* target = nullptr;
for (auto& entry : cache.targets) {
if (entry.format == destination.format && entry.layout == destinationLayout) {
@@ -199,8 +195,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
stages[0].module = cache.vertex;
stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
stages[1].module = cache.fragment;
const VkPipelineVertexInputStateCreateInfo vertex{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
const VkPipelineVertexInputStateCreateInfo vertex{
VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO};
VkPipelineInputAssemblyStateCreateInfo assembly{
VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO};
assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
VkPipelineViewportStateCreateInfo viewport{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO};
viewport.viewportCount = viewport.scissorCount = 1;
@@ -213,7 +211,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// ALWAYS + write enabled: the fragment shader's gl_FragDepth is the answer, and a
// comparison against whatever DONT_CARE left behind would discard some of it.
VkPipelineDepthStencilStateCreateInfo depthStencil{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
VkPipelineDepthStencilStateCreateInfo depthStencil{
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depthStencil.depthTestEnable = VK_TRUE;
depthStencil.depthWriteEnable = VK_TRUE;
depthStencil.depthCompareOp = VK_COMPARE_OP_ALWAYS;
@@ -246,8 +245,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// GENERAL keeps the whole-image layout tracker truthful; the transition also orders
// the previous level's attachment write against this level's sample of it.
TransitionWireImage(source, sourceLayout);
if (sameImage) destination.layout = source.layout;
else TransitionWireImage(destination, destinationLayout);
if (sameImage)
destination.layout = source.layout;
else
TransitionWireImage(destination, destinationLayout);
const auto makeView = [&](const WireImage& image) {
VkImageViewCreateInfo view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
view.image = image.image;
@@ -299,11 +300,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the same source texels the monolith arm's fullscreen triangle lands on - which is
// the point: §3.2 retires a refusal by making the wire arm observably the monolith's,
// half-texel offset included.
const Constants constants{
{0.0f, 0.0f, 1.0f, 1.0f},
{0.0f, 1.0f, 1.0f, -1.0f},
0, 0,
{static_cast<Int32>(source.extent.width), static_cast<Int32>(source.extent.height)}};
const Constants constants{{0.0f, 0.0f, 1.0f, 1.0f},
{0.0f, 1.0f, 1.0f, -1.0f},
0,
0,
{static_cast<Int32>(source.extent.width), static_cast<Int32>(source.extent.height)}};
auto& frame = m_frameContext.GetCurrent();
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = target->renderPass;
@@ -312,16 +313,16 @@ namespace MobileGL::MG_Backend::DirectVulkan {
EndActiveRenderPassOn(frame.commandBuffer);
vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline);
const VkViewport viewport{0.0f, 0.0f, static_cast<float>(destination.extent.width),
static_cast<float>(destination.extent.height), 0.0f, 1.0f};
const VkViewport viewport{
0.0f, 0.0f, static_cast<float>(destination.extent.width), static_cast<float>(destination.extent.height),
0.0f, 1.0f};
const VkRect2D scissor{{0, 0}, destination.extent};
vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor);
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
cache.pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
vkCmdBindDescriptorSets(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, cache.pipelineLayout, 0, 1,
&descriptorSet, 0, nullptr);
vkCmdPushConstants(frame.commandBuffer, cache.pipelineLayout,
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
0, sizeof(constants), &constants);
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(constants), &constants);
vkCmdDraw(frame.commandBuffer, 6, 1, 0, 0);
vkCmdEndRenderPass(frame.commandBuffer);
ResetDynamicStateShadow();
@@ -14,169 +14,151 @@
#pragma once
static constexpr Uint32 kWireDepthMipmapVertexSpirv[] = {
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000071u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu,
0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u,
0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000016u, 0x0000005eu, 0x00000067u,
0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u,
0x00000009u, 0x00007675u, 0x00060005u, 0x00000016u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u,
0x00050005u, 0x00000019u, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00030005u, 0x0000001cu, 0x00000070u,
0x00060005u, 0x0000001fu, 0x74706544u, 0x70694d68u, 0x5270616du, 0x00746365u, 0x00050006u, 0x0000001fu,
0x00000000u, 0x52637273u, 0x00746365u, 0x00050006u, 0x0000001fu, 0x00000001u, 0x52747364u, 0x00746365u,
0x00080006u, 0x0000001fu, 0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u,
0x00070006u, 0x0000001fu, 0x00000003u, 0x54637273u, 0x6c657865u, 0x657a6953u, 0x00000000u, 0x00040005u,
0x00000021u, 0x74636572u, 0x00000000u, 0x00060005u, 0x0000005cu, 0x505f6c67u, 0x65567265u, 0x78657472u,
0x00000000u, 0x00060006u, 0x0000005cu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u,
0x0000005cu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000005cu,
0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000005cu, 0x00000003u,
0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000005eu, 0x00000000u, 0x00050005u,
0x00000067u, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000016u, 0x0000000bu, 0x0000002au,
0x00030047u, 0x0000001fu, 0x00000002u, 0x00050048u, 0x0000001fu, 0x00000000u, 0x00000023u, 0x00000000u,
0x00050048u, 0x0000001fu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000001fu, 0x00000002u,
0x00000023u, 0x00000020u, 0x00050048u, 0x0000001fu, 0x00000003u, 0x00000023u, 0x00000028u, 0x00030047u,
0x0000005cu, 0x00000002u, 0x00050048u, 0x0000005cu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u,
0x0000005cu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000005cu, 0x00000002u, 0x0000000bu,
0x00000003u, 0x00050048u, 0x0000005cu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000067u,
0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u,
0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000008u,
0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, 0x00000000u, 0x0004002bu, 0x0000000au,
0x0000000bu, 0x00000006u, 0x0004001cu, 0x0000000cu, 0x00000007u, 0x0000000bu, 0x0004002bu, 0x00000006u,
0x0000000du, 0x00000000u, 0x0005002cu, 0x00000007u, 0x0000000eu, 0x0000000du, 0x0000000du, 0x0004002bu,
0x00000006u, 0x0000000fu, 0x3f800000u, 0x0005002cu, 0x00000007u, 0x00000010u, 0x0000000fu, 0x0000000du,
0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu, 0x0005002cu, 0x00000007u, 0x00000012u,
0x0000000fu, 0x0000000fu, 0x0009002cu, 0x0000000cu, 0x00000013u, 0x0000000eu, 0x00000010u, 0x00000011u,
0x00000011u, 0x00000010u, 0x00000012u, 0x00040015u, 0x00000014u, 0x00000020u, 0x00000001u, 0x00040020u,
0x00000015u, 0x00000001u, 0x00000014u, 0x0004003bu, 0x00000015u, 0x00000016u, 0x00000001u, 0x00040020u,
0x00000018u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001du, 0x00000006u, 0x00000004u, 0x00040017u,
0x0000001eu, 0x00000014u, 0x00000002u, 0x0006001eu, 0x0000001fu, 0x0000001du, 0x0000001du, 0x00000014u,
0x0000001eu, 0x00040020u, 0x00000020u, 0x00000009u, 0x0000001fu, 0x0004003bu, 0x00000020u, 0x00000021u,
0x00000009u, 0x0004002bu, 0x00000014u, 0x00000022u, 0x00000001u, 0x00040020u, 0x00000023u, 0x00000009u,
0x0000001du, 0x0004002bu, 0x00000006u, 0x0000002du, 0x40000000u, 0x0004002bu, 0x0000000au, 0x00000031u,
0x00000001u, 0x00040020u, 0x00000032u, 0x00000007u, 0x00000006u, 0x0004002bu, 0x00000014u, 0x00000037u,
0x00000002u, 0x00040020u, 0x00000038u, 0x00000009u, 0x00000014u, 0x00020014u, 0x0000003bu, 0x0004002bu,
0x0000000au, 0x00000042u, 0x00000000u, 0x0004002bu, 0x00000014u, 0x00000051u, 0x00000003u, 0x0004001cu,
0x0000005bu, 0x00000006u, 0x00000031u, 0x0006001eu, 0x0000005cu, 0x0000001du, 0x00000006u, 0x0000005bu,
0x0000005bu, 0x00040020u, 0x0000005du, 0x00000003u, 0x0000005cu, 0x0004003bu, 0x0000005du, 0x0000005eu,
0x00000003u, 0x0004002bu, 0x00000014u, 0x0000005fu, 0x00000000u, 0x00040020u, 0x00000064u, 0x00000003u,
0x0000001du, 0x00040020u, 0x00000066u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000066u, 0x00000067u,
0x00000003u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u,
0x0004003bu, 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000018u, 0x00000019u, 0x00000007u,
0x0004003bu, 0x00000008u, 0x0000001cu, 0x00000007u, 0x0004003du, 0x00000014u, 0x00000017u, 0x00000016u,
0x0003003eu, 0x00000019u, 0x00000013u, 0x00050041u, 0x00000008u, 0x0000001au, 0x00000019u, 0x00000017u,
0x0004003du, 0x00000007u, 0x0000001bu, 0x0000001au, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x00050041u,
0x00000023u, 0x00000024u, 0x00000021u, 0x00000022u, 0x0004003du, 0x0000001du, 0x00000025u, 0x00000024u,
0x0007004fu, 0x00000007u, 0x00000026u, 0x00000025u, 0x00000025u, 0x00000000u, 0x00000001u, 0x0004003du,
0x00000007u, 0x00000027u, 0x00000009u, 0x00050041u, 0x00000023u, 0x00000028u, 0x00000021u, 0x00000022u,
0x0004003du, 0x0000001du, 0x00000029u, 0x00000028u, 0x0007004fu, 0x00000007u, 0x0000002au, 0x00000029u,
0x00000029u, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000002bu, 0x00000027u, 0x0000002au,
0x00050081u, 0x00000007u, 0x0000002cu, 0x00000026u, 0x0000002bu, 0x0005008eu, 0x00000007u, 0x0000002eu,
0x0000002cu, 0x0000002du, 0x00050050u, 0x00000007u, 0x0000002fu, 0x0000000fu, 0x0000000fu, 0x00050083u,
0x00000007u, 0x00000030u, 0x0000002eu, 0x0000002fu, 0x0003003eu, 0x0000001cu, 0x00000030u, 0x00050041u,
0x00000032u, 0x00000033u, 0x0000001cu, 0x00000031u, 0x0004003du, 0x00000006u, 0x00000034u, 0x00000033u,
0x0004007fu, 0x00000006u, 0x00000035u, 0x00000034u, 0x00050041u, 0x00000032u, 0x00000036u, 0x0000001cu,
0x00000031u, 0x0003003eu, 0x00000036u, 0x00000035u, 0x00050041u, 0x00000038u, 0x00000039u, 0x00000021u,
0x00000037u, 0x0004003du, 0x00000014u, 0x0000003au, 0x00000039u, 0x000500aau, 0x0000003bu, 0x0000003cu,
0x0000003au, 0x00000022u, 0x000300f7u, 0x0000003eu, 0x00000000u, 0x000400fau, 0x0000003cu, 0x0000003du,
0x00000046u, 0x000200f8u, 0x0000003du, 0x00050041u, 0x00000032u, 0x0000003fu, 0x0000001cu, 0x00000031u,
0x0004003du, 0x00000006u, 0x00000040u, 0x0000003fu, 0x0004007fu, 0x00000006u, 0x00000041u, 0x00000040u,
0x00050041u, 0x00000032u, 0x00000043u, 0x0000001cu, 0x00000042u, 0x0004003du, 0x00000006u, 0x00000044u,
0x00000043u, 0x00050050u, 0x00000007u, 0x00000045u, 0x00000041u, 0x00000044u, 0x0003003eu, 0x0000001cu,
0x00000045u, 0x000200f9u, 0x0000003eu, 0x000200f8u, 0x00000046u, 0x00050041u, 0x00000038u, 0x00000047u,
0x00000021u, 0x00000037u, 0x0004003du, 0x00000014u, 0x00000048u, 0x00000047u, 0x000500aau, 0x0000003bu,
0x00000049u, 0x00000048u, 0x00000037u, 0x000300f7u, 0x0000004bu, 0x00000000u, 0x000400fau, 0x00000049u,
0x0000004au, 0x0000004eu, 0x000200f8u, 0x0000004au, 0x0004003du, 0x00000007u, 0x0000004cu, 0x0000001cu,
0x0004007fu, 0x00000007u, 0x0000004du, 0x0000004cu, 0x0003003eu, 0x0000001cu, 0x0000004du, 0x000200f9u,
0x0000004bu, 0x000200f8u, 0x0000004eu, 0x00050041u, 0x00000038u, 0x0000004fu, 0x00000021u, 0x00000037u,
0x0004003du, 0x00000014u, 0x00000050u, 0x0000004fu, 0x000500aau, 0x0000003bu, 0x00000052u, 0x00000050u,
0x00000051u, 0x000300f7u, 0x00000054u, 0x00000000u, 0x000400fau, 0x00000052u, 0x00000053u, 0x00000054u,
0x000200f8u, 0x00000053u, 0x00050041u, 0x00000032u, 0x00000055u, 0x0000001cu, 0x00000031u, 0x0004003du,
0x00000006u, 0x00000056u, 0x00000055u, 0x00050041u, 0x00000032u, 0x00000057u, 0x0000001cu, 0x00000042u,
0x0004003du, 0x00000006u, 0x00000058u, 0x00000057u, 0x0004007fu, 0x00000006u, 0x00000059u, 0x00000058u,
0x00050050u, 0x00000007u, 0x0000005au, 0x00000056u, 0x00000059u, 0x0003003eu, 0x0000001cu, 0x0000005au,
0x000200f9u, 0x00000054u, 0x000200f8u, 0x00000054u, 0x000200f9u, 0x0000004bu, 0x000200f8u, 0x0000004bu,
0x000200f9u, 0x0000003eu, 0x000200f8u, 0x0000003eu, 0x0004003du, 0x00000007u, 0x00000060u, 0x0000001cu,
0x00050051u, 0x00000006u, 0x00000061u, 0x00000060u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000062u,
0x00000060u, 0x00000001u, 0x00070050u, 0x0000001du, 0x00000063u, 0x00000061u, 0x00000062u, 0x0000000du,
0x0000000fu, 0x00050041u, 0x00000064u, 0x00000065u, 0x0000005eu, 0x0000005fu, 0x0003003eu, 0x00000065u,
0x00000063u, 0x00050041u, 0x00000023u, 0x00000068u, 0x00000021u, 0x0000005fu, 0x0004003du, 0x0000001du,
0x00000069u, 0x00000068u, 0x0007004fu, 0x00000007u, 0x0000006au, 0x00000069u, 0x00000069u, 0x00000000u,
0x00000001u, 0x0004003du, 0x00000007u, 0x0000006bu, 0x00000009u, 0x00050041u, 0x00000023u, 0x0000006cu,
0x00000021u, 0x0000005fu, 0x0004003du, 0x0000001du, 0x0000006du, 0x0000006cu, 0x0007004fu, 0x00000007u,
0x0000006eu, 0x0000006du, 0x0000006du, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000006fu,
0x0000006bu, 0x0000006eu, 0x00050081u, 0x00000007u, 0x00000070u, 0x0000006au, 0x0000006fu, 0x0003003eu,
0x00000067u, 0x00000070u, 0x000100fdu, 0x00010038u,
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000071u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u,
0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u,
0x00000004u, 0x6e69616du, 0x00000000u, 0x00000016u, 0x0000005eu, 0x00000067u, 0x00030003u, 0x00000002u, 0x000001c2u,
0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, 0x00000009u, 0x00007675u, 0x00060005u, 0x00000016u,
0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00050005u, 0x00000019u, 0x65646e69u, 0x6c626178u, 0x00000065u,
0x00030005u, 0x0000001cu, 0x00000070u, 0x00060005u, 0x0000001fu, 0x74706544u, 0x70694d68u, 0x5270616du, 0x00746365u,
0x00050006u, 0x0000001fu, 0x00000000u, 0x52637273u, 0x00746365u, 0x00050006u, 0x0000001fu, 0x00000001u, 0x52747364u,
0x00746365u, 0x00080006u, 0x0000001fu, 0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u,
0x00070006u, 0x0000001fu, 0x00000003u, 0x54637273u, 0x6c657865u, 0x657a6953u, 0x00000000u, 0x00040005u, 0x00000021u,
0x74636572u, 0x00000000u, 0x00060005u, 0x0000005cu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u,
0x0000005cu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000005cu, 0x00000001u, 0x505f6c67u,
0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000005cu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u,
0x0065636eu, 0x00070006u, 0x0000005cu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u,
0x0000005eu, 0x00000000u, 0x00050005u, 0x00000067u, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000016u,
0x0000000bu, 0x0000002au, 0x00030047u, 0x0000001fu, 0x00000002u, 0x00050048u, 0x0000001fu, 0x00000000u, 0x00000023u,
0x00000000u, 0x00050048u, 0x0000001fu, 0x00000001u, 0x00000023u, 0x00000010u, 0x00050048u, 0x0000001fu, 0x00000002u,
0x00000023u, 0x00000020u, 0x00050048u, 0x0000001fu, 0x00000003u, 0x00000023u, 0x00000028u, 0x00030047u, 0x0000005cu,
0x00000002u, 0x00050048u, 0x0000005cu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000005cu, 0x00000001u,
0x0000000bu, 0x00000001u, 0x00050048u, 0x0000005cu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000005cu,
0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000067u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u,
0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u,
0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, 0x00000000u,
0x0004002bu, 0x0000000au, 0x0000000bu, 0x00000006u, 0x0004001cu, 0x0000000cu, 0x00000007u, 0x0000000bu, 0x0004002bu,
0x00000006u, 0x0000000du, 0x00000000u, 0x0005002cu, 0x00000007u, 0x0000000eu, 0x0000000du, 0x0000000du, 0x0004002bu,
0x00000006u, 0x0000000fu, 0x3f800000u, 0x0005002cu, 0x00000007u, 0x00000010u, 0x0000000fu, 0x0000000du, 0x0005002cu,
0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu, 0x0005002cu, 0x00000007u, 0x00000012u, 0x0000000fu, 0x0000000fu,
0x0009002cu, 0x0000000cu, 0x00000013u, 0x0000000eu, 0x00000010u, 0x00000011u, 0x00000011u, 0x00000010u, 0x00000012u,
0x00040015u, 0x00000014u, 0x00000020u, 0x00000001u, 0x00040020u, 0x00000015u, 0x00000001u, 0x00000014u, 0x0004003bu,
0x00000015u, 0x00000016u, 0x00000001u, 0x00040020u, 0x00000018u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001du,
0x00000006u, 0x00000004u, 0x00040017u, 0x0000001eu, 0x00000014u, 0x00000002u, 0x0006001eu, 0x0000001fu, 0x0000001du,
0x0000001du, 0x00000014u, 0x0000001eu, 0x00040020u, 0x00000020u, 0x00000009u, 0x0000001fu, 0x0004003bu, 0x00000020u,
0x00000021u, 0x00000009u, 0x0004002bu, 0x00000014u, 0x00000022u, 0x00000001u, 0x00040020u, 0x00000023u, 0x00000009u,
0x0000001du, 0x0004002bu, 0x00000006u, 0x0000002du, 0x40000000u, 0x0004002bu, 0x0000000au, 0x00000031u, 0x00000001u,
0x00040020u, 0x00000032u, 0x00000007u, 0x00000006u, 0x0004002bu, 0x00000014u, 0x00000037u, 0x00000002u, 0x00040020u,
0x00000038u, 0x00000009u, 0x00000014u, 0x00020014u, 0x0000003bu, 0x0004002bu, 0x0000000au, 0x00000042u, 0x00000000u,
0x0004002bu, 0x00000014u, 0x00000051u, 0x00000003u, 0x0004001cu, 0x0000005bu, 0x00000006u, 0x00000031u, 0x0006001eu,
0x0000005cu, 0x0000001du, 0x00000006u, 0x0000005bu, 0x0000005bu, 0x00040020u, 0x0000005du, 0x00000003u, 0x0000005cu,
0x0004003bu, 0x0000005du, 0x0000005eu, 0x00000003u, 0x0004002bu, 0x00000014u, 0x0000005fu, 0x00000000u, 0x00040020u,
0x00000064u, 0x00000003u, 0x0000001du, 0x00040020u, 0x00000066u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000066u,
0x00000067u, 0x00000003u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u,
0x0004003bu, 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000018u, 0x00000019u, 0x00000007u, 0x0004003bu,
0x00000008u, 0x0000001cu, 0x00000007u, 0x0004003du, 0x00000014u, 0x00000017u, 0x00000016u, 0x0003003eu, 0x00000019u,
0x00000013u, 0x00050041u, 0x00000008u, 0x0000001au, 0x00000019u, 0x00000017u, 0x0004003du, 0x00000007u, 0x0000001bu,
0x0000001au, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x00050041u, 0x00000023u, 0x00000024u, 0x00000021u, 0x00000022u,
0x0004003du, 0x0000001du, 0x00000025u, 0x00000024u, 0x0007004fu, 0x00000007u, 0x00000026u, 0x00000025u, 0x00000025u,
0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x00000027u, 0x00000009u, 0x00050041u, 0x00000023u, 0x00000028u,
0x00000021u, 0x00000022u, 0x0004003du, 0x0000001du, 0x00000029u, 0x00000028u, 0x0007004fu, 0x00000007u, 0x0000002au,
0x00000029u, 0x00000029u, 0x00000002u, 0x00000003u, 0x00050085u, 0x00000007u, 0x0000002bu, 0x00000027u, 0x0000002au,
0x00050081u, 0x00000007u, 0x0000002cu, 0x00000026u, 0x0000002bu, 0x0005008eu, 0x00000007u, 0x0000002eu, 0x0000002cu,
0x0000002du, 0x00050050u, 0x00000007u, 0x0000002fu, 0x0000000fu, 0x0000000fu, 0x00050083u, 0x00000007u, 0x00000030u,
0x0000002eu, 0x0000002fu, 0x0003003eu, 0x0000001cu, 0x00000030u, 0x00050041u, 0x00000032u, 0x00000033u, 0x0000001cu,
0x00000031u, 0x0004003du, 0x00000006u, 0x00000034u, 0x00000033u, 0x0004007fu, 0x00000006u, 0x00000035u, 0x00000034u,
0x00050041u, 0x00000032u, 0x00000036u, 0x0000001cu, 0x00000031u, 0x0003003eu, 0x00000036u, 0x00000035u, 0x00050041u,
0x00000038u, 0x00000039u, 0x00000021u, 0x00000037u, 0x0004003du, 0x00000014u, 0x0000003au, 0x00000039u, 0x000500aau,
0x0000003bu, 0x0000003cu, 0x0000003au, 0x00000022u, 0x000300f7u, 0x0000003eu, 0x00000000u, 0x000400fau, 0x0000003cu,
0x0000003du, 0x00000046u, 0x000200f8u, 0x0000003du, 0x00050041u, 0x00000032u, 0x0000003fu, 0x0000001cu, 0x00000031u,
0x0004003du, 0x00000006u, 0x00000040u, 0x0000003fu, 0x0004007fu, 0x00000006u, 0x00000041u, 0x00000040u, 0x00050041u,
0x00000032u, 0x00000043u, 0x0000001cu, 0x00000042u, 0x0004003du, 0x00000006u, 0x00000044u, 0x00000043u, 0x00050050u,
0x00000007u, 0x00000045u, 0x00000041u, 0x00000044u, 0x0003003eu, 0x0000001cu, 0x00000045u, 0x000200f9u, 0x0000003eu,
0x000200f8u, 0x00000046u, 0x00050041u, 0x00000038u, 0x00000047u, 0x00000021u, 0x00000037u, 0x0004003du, 0x00000014u,
0x00000048u, 0x00000047u, 0x000500aau, 0x0000003bu, 0x00000049u, 0x00000048u, 0x00000037u, 0x000300f7u, 0x0000004bu,
0x00000000u, 0x000400fau, 0x00000049u, 0x0000004au, 0x0000004eu, 0x000200f8u, 0x0000004au, 0x0004003du, 0x00000007u,
0x0000004cu, 0x0000001cu, 0x0004007fu, 0x00000007u, 0x0000004du, 0x0000004cu, 0x0003003eu, 0x0000001cu, 0x0000004du,
0x000200f9u, 0x0000004bu, 0x000200f8u, 0x0000004eu, 0x00050041u, 0x00000038u, 0x0000004fu, 0x00000021u, 0x00000037u,
0x0004003du, 0x00000014u, 0x00000050u, 0x0000004fu, 0x000500aau, 0x0000003bu, 0x00000052u, 0x00000050u, 0x00000051u,
0x000300f7u, 0x00000054u, 0x00000000u, 0x000400fau, 0x00000052u, 0x00000053u, 0x00000054u, 0x000200f8u, 0x00000053u,
0x00050041u, 0x00000032u, 0x00000055u, 0x0000001cu, 0x00000031u, 0x0004003du, 0x00000006u, 0x00000056u, 0x00000055u,
0x00050041u, 0x00000032u, 0x00000057u, 0x0000001cu, 0x00000042u, 0x0004003du, 0x00000006u, 0x00000058u, 0x00000057u,
0x0004007fu, 0x00000006u, 0x00000059u, 0x00000058u, 0x00050050u, 0x00000007u, 0x0000005au, 0x00000056u, 0x00000059u,
0x0003003eu, 0x0000001cu, 0x0000005au, 0x000200f9u, 0x00000054u, 0x000200f8u, 0x00000054u, 0x000200f9u, 0x0000004bu,
0x000200f8u, 0x0000004bu, 0x000200f9u, 0x0000003eu, 0x000200f8u, 0x0000003eu, 0x0004003du, 0x00000007u, 0x00000060u,
0x0000001cu, 0x00050051u, 0x00000006u, 0x00000061u, 0x00000060u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000062u,
0x00000060u, 0x00000001u, 0x00070050u, 0x0000001du, 0x00000063u, 0x00000061u, 0x00000062u, 0x0000000du, 0x0000000fu,
0x00050041u, 0x00000064u, 0x00000065u, 0x0000005eu, 0x0000005fu, 0x0003003eu, 0x00000065u, 0x00000063u, 0x00050041u,
0x00000023u, 0x00000068u, 0x00000021u, 0x0000005fu, 0x0004003du, 0x0000001du, 0x00000069u, 0x00000068u, 0x0007004fu,
0x00000007u, 0x0000006au, 0x00000069u, 0x00000069u, 0x00000000u, 0x00000001u, 0x0004003du, 0x00000007u, 0x0000006bu,
0x00000009u, 0x00050041u, 0x00000023u, 0x0000006cu, 0x00000021u, 0x0000005fu, 0x0004003du, 0x0000001du, 0x0000006du,
0x0000006cu, 0x0007004fu, 0x00000007u, 0x0000006eu, 0x0000006du, 0x0000006du, 0x00000002u, 0x00000003u, 0x00050085u,
0x00000007u, 0x0000006fu, 0x0000006bu, 0x0000006eu, 0x00050081u, 0x00000007u, 0x00000070u, 0x0000006au, 0x0000006fu,
0x0003003eu, 0x00000067u, 0x00000070u, 0x000100fdu, 0x00010038u,
};
static constexpr Uint32 kWireDepthMipmapFragmentSpirv[] = {
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000059u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu,
0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u,
0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x0000004fu, 0x00030010u,
0x00000004u, 0x00000007u, 0x00030010u, 0x00000004u, 0x0000000cu, 0x00030003u, 0x00000002u, 0x000001c2u,
0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00040005u, 0x00000009u, 0x42637273u, 0x00657361u,
0x00050005u, 0x0000000du, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00060005u, 0x00000010u, 0x74706544u,
0x70694d68u, 0x5270616du, 0x00746365u, 0x00050006u, 0x00000010u, 0x00000000u, 0x52637273u, 0x00746365u,
0x00050006u, 0x00000010u, 0x00000001u, 0x52747364u, 0x00746365u, 0x00080006u, 0x00000010u, 0x00000002u,
0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u, 0x00070006u, 0x00000010u, 0x00000003u,
0x54637273u, 0x6c657865u, 0x657a6953u, 0x00000000u, 0x00040005u, 0x00000012u, 0x74636572u, 0x00000000u,
0x00040005u, 0x0000001au, 0x4d637273u, 0x00007861u, 0x00040005u, 0x00000021u, 0x74706564u, 0x00003068u,
0x00050005u, 0x00000025u, 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00040005u, 0x00000031u, 0x74706564u,
0x00003168u, 0x00040005u, 0x0000003bu, 0x74706564u, 0x00003268u, 0x00040005u, 0x00000045u, 0x74706564u,
0x00003368u, 0x00060005u, 0x0000004fu, 0x465f6c67u, 0x44676172u, 0x68747065u, 0x00000000u, 0x00040047u,
0x0000000du, 0x0000001eu, 0x00000000u, 0x00030047u, 0x00000010u, 0x00000002u, 0x00050048u, 0x00000010u,
0x00000000u, 0x00000023u, 0x00000000u, 0x00050048u, 0x00000010u, 0x00000001u, 0x00000023u, 0x00000010u,
0x00050048u, 0x00000010u, 0x00000002u, 0x00000023u, 0x00000020u, 0x00050048u, 0x00000010u, 0x00000003u,
0x00000023u, 0x00000028u, 0x00040047u, 0x00000025u, 0x00000021u, 0x00000000u, 0x00040047u, 0x00000025u,
0x00000022u, 0x00000000u, 0x00040047u, 0x0000004fu, 0x0000000bu, 0x00000016u, 0x00020013u, 0x00000002u,
0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, 0x00000006u, 0x00000020u, 0x00000001u, 0x00040017u,
0x00000007u, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00030016u,
0x0000000au, 0x00000020u, 0x00040017u, 0x0000000bu, 0x0000000au, 0x00000002u, 0x00040020u, 0x0000000cu,
0x00000001u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000001u, 0x00040017u, 0x0000000fu,
0x0000000au, 0x00000004u, 0x0006001eu, 0x00000010u, 0x0000000fu, 0x0000000fu, 0x00000006u, 0x00000007u,
0x00040020u, 0x00000011u, 0x00000009u, 0x00000010u, 0x0004003bu, 0x00000011u, 0x00000012u, 0x00000009u,
0x0004002bu, 0x00000006u, 0x00000013u, 0x00000003u, 0x00040020u, 0x00000014u, 0x00000009u, 0x00000007u,
0x0004002bu, 0x00000006u, 0x0000001du, 0x00000001u, 0x0005002cu, 0x00000007u, 0x0000001eu, 0x0000001du,
0x0000001du, 0x00040020u, 0x00000020u, 0x00000007u, 0x0000000au, 0x00090019u, 0x00000022u, 0x0000000au,
0x00000001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x00000023u,
0x00000022u, 0x00040020u, 0x00000024u, 0x00000000u, 0x00000023u, 0x0004003bu, 0x00000024u, 0x00000025u,
0x00000000u, 0x0004002bu, 0x00000006u, 0x00000028u, 0x00000000u, 0x0005002cu, 0x00000007u, 0x00000029u,
0x00000028u, 0x00000028u, 0x00040015u, 0x0000002eu, 0x00000020u, 0x00000000u, 0x0004002bu, 0x0000002eu,
0x0000002fu, 0x00000000u, 0x0005002cu, 0x00000007u, 0x00000034u, 0x0000001du, 0x00000028u, 0x0005002cu,
0x00000007u, 0x0000003eu, 0x00000028u, 0x0000001du, 0x00040020u, 0x0000004eu, 0x00000003u, 0x0000000au,
0x0004003bu, 0x0000004eu, 0x0000004fu, 0x00000003u, 0x0004002bu, 0x0000000au, 0x00000050u, 0x3e800000u,
0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu,
0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000008u, 0x0000001au, 0x00000007u, 0x0004003bu,
0x00000020u, 0x00000021u, 0x00000007u, 0x0004003bu, 0x00000020u, 0x00000031u, 0x00000007u, 0x0004003bu,
0x00000020u, 0x0000003bu, 0x00000007u, 0x0004003bu, 0x00000020u, 0x00000045u, 0x00000007u, 0x0004003du,
0x0000000bu, 0x0000000eu, 0x0000000du, 0x00050041u, 0x00000014u, 0x00000015u, 0x00000012u, 0x00000013u,
0x0004003du, 0x00000007u, 0x00000016u, 0x00000015u, 0x0004006fu, 0x0000000bu, 0x00000017u, 0x00000016u,
0x00050085u, 0x0000000bu, 0x00000018u, 0x0000000eu, 0x00000017u, 0x0004006eu, 0x00000007u, 0x00000019u,
0x00000018u, 0x0003003eu, 0x00000009u, 0x00000019u, 0x00050041u, 0x00000014u, 0x0000001bu, 0x00000012u,
0x00000013u, 0x0004003du, 0x00000007u, 0x0000001cu, 0x0000001bu, 0x00050082u, 0x00000007u, 0x0000001fu,
0x0000001cu, 0x0000001eu, 0x0003003eu, 0x0000001au, 0x0000001fu, 0x0004003du, 0x00000023u, 0x00000026u,
0x00000025u, 0x0004003du, 0x00000007u, 0x00000027u, 0x00000009u, 0x0004003du, 0x00000007u, 0x0000002au,
0x0000001au, 0x0008000cu, 0x00000007u, 0x0000002bu, 0x00000001u, 0x0000002du, 0x00000027u, 0x00000029u,
0x0000002au, 0x00040064u, 0x00000022u, 0x0000002cu, 0x00000026u, 0x0007005fu, 0x0000000fu, 0x0000002du,
0x0000002cu, 0x0000002bu, 0x00000002u, 0x00000028u, 0x00050051u, 0x0000000au, 0x00000030u, 0x0000002du,
0x00000000u, 0x0003003eu, 0x00000021u, 0x00000030u, 0x0004003du, 0x00000023u, 0x00000032u, 0x00000025u,
0x0004003du, 0x00000007u, 0x00000033u, 0x00000009u, 0x00050080u, 0x00000007u, 0x00000035u, 0x00000033u,
0x00000034u, 0x0004003du, 0x00000007u, 0x00000036u, 0x0000001au, 0x0008000cu, 0x00000007u, 0x00000037u,
0x00000001u, 0x0000002du, 0x00000035u, 0x00000029u, 0x00000036u, 0x00040064u, 0x00000022u, 0x00000038u,
0x00000032u, 0x0007005fu, 0x0000000fu, 0x00000039u, 0x00000038u, 0x00000037u, 0x00000002u, 0x00000028u,
0x00050051u, 0x0000000au, 0x0000003au, 0x00000039u, 0x00000000u, 0x0003003eu, 0x00000031u, 0x0000003au,
0x0004003du, 0x00000023u, 0x0000003cu, 0x00000025u, 0x0004003du, 0x00000007u, 0x0000003du, 0x00000009u,
0x00050080u, 0x00000007u, 0x0000003fu, 0x0000003du, 0x0000003eu, 0x0004003du, 0x00000007u, 0x00000040u,
0x0000001au, 0x0008000cu, 0x00000007u, 0x00000041u, 0x00000001u, 0x0000002du, 0x0000003fu, 0x00000029u,
0x00000040u, 0x00040064u, 0x00000022u, 0x00000042u, 0x0000003cu, 0x0007005fu, 0x0000000fu, 0x00000043u,
0x00000042u, 0x00000041u, 0x00000002u, 0x00000028u, 0x00050051u, 0x0000000au, 0x00000044u, 0x00000043u,
0x00000000u, 0x0003003eu, 0x0000003bu, 0x00000044u, 0x0004003du, 0x00000023u, 0x00000046u, 0x00000025u,
0x0004003du, 0x00000007u, 0x00000047u, 0x00000009u, 0x00050080u, 0x00000007u, 0x00000048u, 0x00000047u,
0x0000001eu, 0x0004003du, 0x00000007u, 0x00000049u, 0x0000001au, 0x0008000cu, 0x00000007u, 0x0000004au,
0x00000001u, 0x0000002du, 0x00000048u, 0x00000029u, 0x00000049u, 0x00040064u, 0x00000022u, 0x0000004bu,
0x00000046u, 0x0007005fu, 0x0000000fu, 0x0000004cu, 0x0000004bu, 0x0000004au, 0x00000002u, 0x00000028u,
0x00050051u, 0x0000000au, 0x0000004du, 0x0000004cu, 0x00000000u, 0x0003003eu, 0x00000045u, 0x0000004du,
0x0004003du, 0x0000000au, 0x00000051u, 0x00000021u, 0x0004003du, 0x0000000au, 0x00000052u, 0x00000031u,
0x00050081u, 0x0000000au, 0x00000053u, 0x00000051u, 0x00000052u, 0x0004003du, 0x0000000au, 0x00000054u,
0x0000003bu, 0x00050081u, 0x0000000au, 0x00000055u, 0x00000053u, 0x00000054u, 0x0004003du, 0x0000000au,
0x00000056u, 0x00000045u, 0x00050081u, 0x0000000au, 0x00000057u, 0x00000055u, 0x00000056u, 0x00050085u,
0x0000000au, 0x00000058u, 0x00000050u, 0x00000057u, 0x0003003eu, 0x0000004fu, 0x00000058u, 0x000100fdu,
0x00010038u,
0x07230203u, 0x00010000u, 0x0008000bu, 0x00000059u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u,
0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u,
0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x0000004fu, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030010u,
0x00000004u, 0x0000000cu, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u,
0x00040005u, 0x00000009u, 0x42637273u, 0x00657361u, 0x00050005u, 0x0000000du, 0x43786574u, 0x64726f6fu, 0x00000000u,
0x00060005u, 0x00000010u, 0x74706544u, 0x70694d68u, 0x5270616du, 0x00746365u, 0x00050006u, 0x00000010u, 0x00000000u,
0x52637273u, 0x00746365u, 0x00050006u, 0x00000010u, 0x00000001u, 0x52747364u, 0x00746365u, 0x00080006u, 0x00000010u,
0x00000002u, 0x66727573u, 0x54656361u, 0x736e6172u, 0x6d726f66u, 0x00000000u, 0x00070006u, 0x00000010u, 0x00000003u,
0x54637273u, 0x6c657865u, 0x657a6953u, 0x00000000u, 0x00040005u, 0x00000012u, 0x74636572u, 0x00000000u, 0x00040005u,
0x0000001au, 0x4d637273u, 0x00007861u, 0x00040005u, 0x00000021u, 0x74706564u, 0x00003068u, 0x00050005u, 0x00000025u,
0x72756f73u, 0x6d496563u, 0x00656761u, 0x00040005u, 0x00000031u, 0x74706564u, 0x00003168u, 0x00040005u, 0x0000003bu,
0x74706564u, 0x00003268u, 0x00040005u, 0x00000045u, 0x74706564u, 0x00003368u, 0x00060005u, 0x0000004fu, 0x465f6c67u,
0x44676172u, 0x68747065u, 0x00000000u, 0x00040047u, 0x0000000du, 0x0000001eu, 0x00000000u, 0x00030047u, 0x00000010u,
0x00000002u, 0x00050048u, 0x00000010u, 0x00000000u, 0x00000023u, 0x00000000u, 0x00050048u, 0x00000010u, 0x00000001u,
0x00000023u, 0x00000010u, 0x00050048u, 0x00000010u, 0x00000002u, 0x00000023u, 0x00000020u, 0x00050048u, 0x00000010u,
0x00000003u, 0x00000023u, 0x00000028u, 0x00040047u, 0x00000025u, 0x00000021u, 0x00000000u, 0x00040047u, 0x00000025u,
0x00000022u, 0x00000000u, 0x00040047u, 0x0000004fu, 0x0000000bu, 0x00000016u, 0x00020013u, 0x00000002u, 0x00030021u,
0x00000003u, 0x00000002u, 0x00040015u, 0x00000006u, 0x00000020u, 0x00000001u, 0x00040017u, 0x00000007u, 0x00000006u,
0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00030016u, 0x0000000au, 0x00000020u, 0x00040017u,
0x0000000bu, 0x0000000au, 0x00000002u, 0x00040020u, 0x0000000cu, 0x00000001u, 0x0000000bu, 0x0004003bu, 0x0000000cu,
0x0000000du, 0x00000001u, 0x00040017u, 0x0000000fu, 0x0000000au, 0x00000004u, 0x0006001eu, 0x00000010u, 0x0000000fu,
0x0000000fu, 0x00000006u, 0x00000007u, 0x00040020u, 0x00000011u, 0x00000009u, 0x00000010u, 0x0004003bu, 0x00000011u,
0x00000012u, 0x00000009u, 0x0004002bu, 0x00000006u, 0x00000013u, 0x00000003u, 0x00040020u, 0x00000014u, 0x00000009u,
0x00000007u, 0x0004002bu, 0x00000006u, 0x0000001du, 0x00000001u, 0x0005002cu, 0x00000007u, 0x0000001eu, 0x0000001du,
0x0000001du, 0x00040020u, 0x00000020u, 0x00000007u, 0x0000000au, 0x00090019u, 0x00000022u, 0x0000000au, 0x00000001u,
0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x00000023u, 0x00000022u, 0x00040020u,
0x00000024u, 0x00000000u, 0x00000023u, 0x0004003bu, 0x00000024u, 0x00000025u, 0x00000000u, 0x0004002bu, 0x00000006u,
0x00000028u, 0x00000000u, 0x0005002cu, 0x00000007u, 0x00000029u, 0x00000028u, 0x00000028u, 0x00040015u, 0x0000002eu,
0x00000020u, 0x00000000u, 0x0004002bu, 0x0000002eu, 0x0000002fu, 0x00000000u, 0x0005002cu, 0x00000007u, 0x00000034u,
0x0000001du, 0x00000028u, 0x0005002cu, 0x00000007u, 0x0000003eu, 0x00000028u, 0x0000001du, 0x00040020u, 0x0000004eu,
0x00000003u, 0x0000000au, 0x0004003bu, 0x0000004eu, 0x0000004fu, 0x00000003u, 0x0004002bu, 0x0000000au, 0x00000050u,
0x3e800000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu,
0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000008u, 0x0000001au, 0x00000007u, 0x0004003bu, 0x00000020u,
0x00000021u, 0x00000007u, 0x0004003bu, 0x00000020u, 0x00000031u, 0x00000007u, 0x0004003bu, 0x00000020u, 0x0000003bu,
0x00000007u, 0x0004003bu, 0x00000020u, 0x00000045u, 0x00000007u, 0x0004003du, 0x0000000bu, 0x0000000eu, 0x0000000du,
0x00050041u, 0x00000014u, 0x00000015u, 0x00000012u, 0x00000013u, 0x0004003du, 0x00000007u, 0x00000016u, 0x00000015u,
0x0004006fu, 0x0000000bu, 0x00000017u, 0x00000016u, 0x00050085u, 0x0000000bu, 0x00000018u, 0x0000000eu, 0x00000017u,
0x0004006eu, 0x00000007u, 0x00000019u, 0x00000018u, 0x0003003eu, 0x00000009u, 0x00000019u, 0x00050041u, 0x00000014u,
0x0000001bu, 0x00000012u, 0x00000013u, 0x0004003du, 0x00000007u, 0x0000001cu, 0x0000001bu, 0x00050082u, 0x00000007u,
0x0000001fu, 0x0000001cu, 0x0000001eu, 0x0003003eu, 0x0000001au, 0x0000001fu, 0x0004003du, 0x00000023u, 0x00000026u,
0x00000025u, 0x0004003du, 0x00000007u, 0x00000027u, 0x00000009u, 0x0004003du, 0x00000007u, 0x0000002au, 0x0000001au,
0x0008000cu, 0x00000007u, 0x0000002bu, 0x00000001u, 0x0000002du, 0x00000027u, 0x00000029u, 0x0000002au, 0x00040064u,
0x00000022u, 0x0000002cu, 0x00000026u, 0x0007005fu, 0x0000000fu, 0x0000002du, 0x0000002cu, 0x0000002bu, 0x00000002u,
0x00000028u, 0x00050051u, 0x0000000au, 0x00000030u, 0x0000002du, 0x00000000u, 0x0003003eu, 0x00000021u, 0x00000030u,
0x0004003du, 0x00000023u, 0x00000032u, 0x00000025u, 0x0004003du, 0x00000007u, 0x00000033u, 0x00000009u, 0x00050080u,
0x00000007u, 0x00000035u, 0x00000033u, 0x00000034u, 0x0004003du, 0x00000007u, 0x00000036u, 0x0000001au, 0x0008000cu,
0x00000007u, 0x00000037u, 0x00000001u, 0x0000002du, 0x00000035u, 0x00000029u, 0x00000036u, 0x00040064u, 0x00000022u,
0x00000038u, 0x00000032u, 0x0007005fu, 0x0000000fu, 0x00000039u, 0x00000038u, 0x00000037u, 0x00000002u, 0x00000028u,
0x00050051u, 0x0000000au, 0x0000003au, 0x00000039u, 0x00000000u, 0x0003003eu, 0x00000031u, 0x0000003au, 0x0004003du,
0x00000023u, 0x0000003cu, 0x00000025u, 0x0004003du, 0x00000007u, 0x0000003du, 0x00000009u, 0x00050080u, 0x00000007u,
0x0000003fu, 0x0000003du, 0x0000003eu, 0x0004003du, 0x00000007u, 0x00000040u, 0x0000001au, 0x0008000cu, 0x00000007u,
0x00000041u, 0x00000001u, 0x0000002du, 0x0000003fu, 0x00000029u, 0x00000040u, 0x00040064u, 0x00000022u, 0x00000042u,
0x0000003cu, 0x0007005fu, 0x0000000fu, 0x00000043u, 0x00000042u, 0x00000041u, 0x00000002u, 0x00000028u, 0x00050051u,
0x0000000au, 0x00000044u, 0x00000043u, 0x00000000u, 0x0003003eu, 0x0000003bu, 0x00000044u, 0x0004003du, 0x00000023u,
0x00000046u, 0x00000025u, 0x0004003du, 0x00000007u, 0x00000047u, 0x00000009u, 0x00050080u, 0x00000007u, 0x00000048u,
0x00000047u, 0x0000001eu, 0x0004003du, 0x00000007u, 0x00000049u, 0x0000001au, 0x0008000cu, 0x00000007u, 0x0000004au,
0x00000001u, 0x0000002du, 0x00000048u, 0x00000029u, 0x00000049u, 0x00040064u, 0x00000022u, 0x0000004bu, 0x00000046u,
0x0007005fu, 0x0000000fu, 0x0000004cu, 0x0000004bu, 0x0000004au, 0x00000002u, 0x00000028u, 0x00050051u, 0x0000000au,
0x0000004du, 0x0000004cu, 0x00000000u, 0x0003003eu, 0x00000045u, 0x0000004du, 0x0004003du, 0x0000000au, 0x00000051u,
0x00000021u, 0x0004003du, 0x0000000au, 0x00000052u, 0x00000031u, 0x00050081u, 0x0000000au, 0x00000053u, 0x00000051u,
0x00000052u, 0x0004003du, 0x0000000au, 0x00000054u, 0x0000003bu, 0x00050081u, 0x0000000au, 0x00000055u, 0x00000053u,
0x00000054u, 0x0004003du, 0x0000000au, 0x00000056u, 0x00000045u, 0x00050081u, 0x0000000au, 0x00000057u, 0x00000055u,
0x00000056u, 0x00050085u, 0x0000000au, 0x00000058u, 0x00000050u, 0x00000057u, 0x0003003eu, 0x0000004fu, 0x00000058u,
0x000100fdu, 0x00010038u,
};
@@ -47,8 +47,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The format has this aspect and the render-pass arm's copy of it was read back.
Bool measured = false;
Uint32 texels = 0;
Uint32 expected = 0; // the multisample source's cleared value
Uint32 sentinel = 0; // what the resolve target was pre-filled with
Uint32 expected = 0; // the multisample source's cleared value
Uint32 sentinel = 0; // what the resolve target was pre-filled with
Uint32 tolerance = 0;
// THE SUBJECT: VK_KHR_depth_stencil_resolve render pass, no draw.
Uint32 renderPassMatches = 0; // texels within tolerance of `expected`
@@ -62,8 +62,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
};
struct WireDepthResolveFormatReading {
String name; // e.g. "D24_UNORM_S8_UINT"
Int32 format = 0; // the VkFormat
String name; // e.g. "D24_UNORM_S8_UINT"
Int32 format = 0; // the VkFormat
Uint32 samples = 0;
// False when the format was not probed; `skipReason` says why (unsupported, the render-pass
// arm would not be taken for it, an object the probe needed could not be created).
@@ -103,10 +103,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
inline const char* WireDepthResolveProbeVerdictName(WireDepthResolveProbeVerdict verdict) {
switch (verdict) {
case WireDepthResolveProbeVerdict::NotRun: return "not-run";
case WireDepthResolveProbeVerdict::Inconclusive: return "inconclusive";
case WireDepthResolveProbeVerdict::Clean: return "clean";
case WireDepthResolveProbeVerdict::RenderPassResolveBroken: return "render-pass-resolve-broken";
case WireDepthResolveProbeVerdict::NotRun:
return "not-run";
case WireDepthResolveProbeVerdict::Inconclusive:
return "inconclusive";
case WireDepthResolveProbeVerdict::Clean:
return "clean";
case WireDepthResolveProbeVerdict::RenderPassResolveBroken:
return "render-pass-resolve-broken";
}
return "?";
}
@@ -122,7 +126,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// An aspect only counts where its control holds; a format is conclusive when any of its
// aspects is. The render pass resolves every aspect of the format in one pass, so one aspect
// it fails is the defect for the whole arm.
inline WireDepthResolveProbeVerdict EvaluateWireDepthResolveProbe(const WireDepthResolveProbeMeasurement& measurement) {
inline WireDepthResolveProbeVerdict EvaluateWireDepthResolveProbe(
const WireDepthResolveProbeMeasurement& measurement) {
if (!measurement.ran || measurement.fenceWaitTimedOut) return WireDepthResolveProbeVerdict::NotRun;
Bool conclusive = false, broken = false;
for (const WireDepthResolveFormatReading& reading : measurement.formats) {
@@ -153,7 +158,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// the real readback keeps the sentinel while the shader control still runs. The
// DirectVulkan.{Split,Spawn}.MsResolveElide. entries then require the defect verdict from the
// measurement itself.
enum class WireDepthResolveProbeKnob : Uint8 { Measure, ForceBug, ForceClean, ElideSubject, Unrecognised };
enum class WireDepthResolveProbeKnob : Uint8 {
Measure,
ForceBug,
ForceClean,
ElideSubject,
Unrecognised
};
inline WireDepthResolveProbeKnob ParseWireDepthResolveProbeKnob(const char* value) {
if (value == nullptr || *value == '\0') return WireDepthResolveProbeKnob::Measure;
@@ -244,9 +255,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The renderer's identity from its device's VkPhysicalDeviceProperties - the ONE place the fields
// are filled (VulkanRenderer::ArmWireDepthResolveOrder calls it), so the unit test proves that each
// property reaches the key: with only the cache tested, a field left out here stayed green.
inline WireDepthResolveDeviceIdentity MakeWireDepthResolveDeviceIdentity(const VkPhysicalDeviceProperties& properties,
Bool renderPassArmAvailable,
WireDepthResolveProbeKnob knob) {
inline WireDepthResolveDeviceIdentity MakeWireDepthResolveDeviceIdentity(
const VkPhysicalDeviceProperties& properties, Bool renderPassArmAvailable, WireDepthResolveProbeKnob knob) {
WireDepthResolveDeviceIdentity identity;
identity.vendorID = properties.vendorID;
identity.deviceID = properties.deviceID;
@@ -317,7 +327,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Every format, "; "-separated, for the POST row.
inline String DescribeWireDepthResolveProbe(const WireDepthResolveProbeMeasurement& measurement) {
if (!measurement.ran) {
return measurement.failureReason.empty() ? String("not run") : "not run (" + measurement.failureReason + ")";
return measurement.failureReason.empty() ? String("not run")
: "not run (" + measurement.failureReason + ")";
}
String out;
for (const WireDepthResolveFormatReading& reading : measurement.formats) {
@@ -161,8 +161,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkResult CreateWireShaderResolvePipeline(VkDevice device, const WireShaderResolveKit& kit, VkRenderPass pass,
VkImageAspectFlags aspect, VkPipeline* pipeline) {
const VkShaderModule fragment =
aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? kit.stencilFragment : kit.depthFragment;
const VkShaderModule fragment = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? kit.stencilFragment : kit.depthFragment;
if (fragment == VK_NULL_HANDLE || kit.vertex == VK_NULL_HANDLE) return VK_ERROR_FEATURE_NOT_PRESENT;
VkPipelineShaderStageCreateInfo stages[2]{};
for (auto& stage : stages) {
@@ -185,7 +184,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
raster.lineWidth = 1.0f;
VkPipelineMultisampleStateCreateInfo samples{VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO};
samples.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
VkPipelineDepthStencilStateCreateInfo depthStencilState{VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
VkPipelineDepthStencilStateCreateInfo depthStencilState{
VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO};
depthStencilState.minDepthBounds = 0.0f;
depthStencilState.maxDepthBounds = 1.0f;
if (aspect == VK_IMAGE_ASPECT_DEPTH_BIT) {
@@ -228,9 +228,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &info, nullptr, pipeline);
}
void RecordWireShaderResolveDraw(VkCommandBuffer commandBuffer, const WireShaderResolveKit& kit,
VkRenderPass pass, VkPipeline pipeline, VkFramebuffer framebuffer,
VkDescriptorSet descriptorSet, VkExtent2D attachmentExtent, VkRect2D rect) {
void RecordWireShaderResolveDraw(VkCommandBuffer commandBuffer, const WireShaderResolveKit& kit, VkRenderPass pass,
VkPipeline pipeline, VkFramebuffer framebuffer, VkDescriptorSet descriptorSet,
VkExtent2D attachmentExtent, VkRect2D rect) {
VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
begin.renderPass = pass;
begin.framebuffer = framebuffer;
@@ -244,8 +244,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// interchangeable here: gl_FragCoord is what the fragment shader fetches by, so the
// clip-space-to-attachment mapping has to be the identity for every rectangle. A viewport
// shrunk onto the rect would slide the whole image instead of moving a window over it.
const VkViewport viewport{0.0f, 0.0f, static_cast<float>(attachmentExtent.width),
static_cast<float>(attachmentExtent.height), 0.0f, 1.0f};
const VkViewport viewport{
0.0f, 0.0f, static_cast<float>(attachmentExtent.width), static_cast<float>(attachmentExtent.height),
0.0f, 1.0f};
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
vkCmdSetScissor(commandBuffer, 0, 1, &rect);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, kit.pipelineLayout, 0, 1,
@@ -294,7 +295,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return format == VK_FORMAT_D32_SFLOAT || format == VK_FORMAT_D32_SFLOAT_S8_UINT;
}
Uint32 DepthCopyBytes(VkFormat format) { return format == VK_FORMAT_D16_UNORM ? 2u : 4u; }
Uint32 DepthCopyBytes(VkFormat format) {
return format == VK_FORMAT_D16_UNORM ? 2u : 4u;
}
Uint32 EncodeDepth(VkFormat format, Float depth) {
if (IsFloatDepth(format)) return std::bit_cast<Uint32>(depth);
@@ -314,7 +317,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return IsFloatDepth(format) ? word : (word & 0x00FFFFFFu);
}
Uint32 WordDistance(Uint32 a, Uint32 b) { return a > b ? a - b : b - a; }
Uint32 WordDistance(Uint32 a, Uint32 b) {
return a > b ? a - b : b - a;
}
struct ProbeImage {
VkImage image = VK_NULL_HANDLE;
@@ -361,13 +366,19 @@ namespace MobileGL::MG_Backend::DirectVulkan {
WireShaderResolveKit kit;
void Destroy() {
for (VkPipeline pipeline : pipelines) vkDestroyPipeline(device, pipeline, nullptr);
for (VkFramebuffer framebuffer : framebuffers) vkDestroyFramebuffer(device, framebuffer, nullptr);
for (VkRenderPass pass : passes) vkDestroyRenderPass(device, pass, nullptr);
for (VkImageView view : views) vkDestroyImageView(device, view, nullptr);
for (VkImage image : images) vkDestroyImage(device, image, nullptr);
for (VkPipeline pipeline : pipelines)
vkDestroyPipeline(device, pipeline, nullptr);
for (VkFramebuffer framebuffer : framebuffers)
vkDestroyFramebuffer(device, framebuffer, nullptr);
for (VkRenderPass pass : passes)
vkDestroyRenderPass(device, pass, nullptr);
for (VkImageView view : views)
vkDestroyImageView(device, view, nullptr);
for (VkImage image : images)
vkDestroyImage(device, image, nullptr);
vkDestroyBuffer(device, buffer, nullptr);
for (VkDeviceMemory memory : memories) vkFreeMemory(device, memory, nullptr);
for (VkDeviceMemory memory : memories)
vkFreeMemory(device, memory, nullptr);
vkDestroyDescriptorPool(device, descriptorPool, nullptr);
vkDestroyCommandPool(device, commandPool, nullptr);
vkDestroyFence(device, fence, nullptr);
@@ -446,8 +457,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
objects.images.push_back(out.image);
VkMemoryRequirements requirements{};
vkGetImageMemoryRequirements(device, out.image, &requirements);
const Optional<Uint32> type = PickMemoryType(memoryProperties, requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
const Optional<Uint32> type =
PickMemoryType(memoryProperties, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0);
if (!type) return false;
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocate.allocationSize = requirements.size;
@@ -483,8 +494,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Uint32 stencil = 0;
};
Vector<ClearPass> clears;
const auto makeClear = [&](const ProbeImage& image, VkFormat format, VkSampleCountFlagBits samples,
Float depth, Uint32 stencil) -> Bool {
const auto makeClear = [&](const ProbeImage& image, VkFormat format, VkSampleCountFlagBits samples, Float depth,
Uint32 stencil) -> Bool {
const VkImageAspectFlags aspects = WireDepthStencilFormatAspects(format);
VkImageView view = VK_NULL_HANDLE;
if (!makeView(WireDepthStencilViewInfo(image.image, format, aspects, 0, 0), view)) return false;
@@ -628,8 +639,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
control.targetView) ||
!makeFramebuffer(control.pass, &control.targetView, 1, control.framebuffer))
return;
if (!makeClear(control.target, format, VK_SAMPLE_COUNT_1_BIT, kSentinelDepth, kSentinelStencil))
return;
if (!makeClear(control.target, format, VK_SAMPLE_COUNT_1_BIT, kSentinelDepth, kSentinelStencil)) return;
control.ready = true;
};
if (aspects & VK_IMAGE_ASPECT_DEPTH_BIT) makeControl(VK_IMAGE_ASPECT_DEPTH_BIT, item.depthControl);
@@ -698,8 +708,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
PickMemoryType(memoryProperties, requirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
if (!type) return fail("no host-visible memory type for the readback buffer");
bufferCoherent = (memoryProperties.memoryTypes[*type].propertyFlags &
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
bufferCoherent =
(memoryProperties.memoryTypes[*type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0;
VkMemoryAllocateInfo allocate{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
allocate.allocationSize = requirements.size;
allocate.memoryTypeIndex = *type;
@@ -907,8 +917,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
aspect.sentinel = kSentinelStencil;
aspect.tolerance = 0;
tally(aspect, item.subjectStencilOffset, 1, false, item.format, true);
if (item.stencilControl.ready)
tally(aspect, item.stencilControl.offset, 1, false, item.format, false);
if (item.stencilControl.ready) tally(aspect, item.stencilControl.offset, 1, false, item.format, false);
}
}
vkUnmapMemory(device, bufferMemory);
@@ -927,8 +936,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (std::strcmp(extension.extensionName, name) == 0) return true;
return false;
};
renderPassArmAvailable = has(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) &&
has(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
renderPassArmAvailable =
has(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME) && has(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
shaderStencilExport = has(VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME);
if (!renderPassArmAvailable) {
measurement.failureReason = "no VK_KHR_create_renderpass2 + VK_KHR_depth_stencil_resolve";
@@ -941,8 +950,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
if (getProperties2 == nullptr)
getProperties2 = reinterpret_cast<PFN_vkGetPhysicalDeviceProperties2>(
getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"));
const auto createDevice =
reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
const auto createDevice = reinterpret_cast<PFN_vkCreateDevice>(getInstanceProcAddr(instance, "vkCreateDevice"));
if (properties.apiVersion < VK_API_VERSION_1_1 || getProperties2 == nullptr || createDevice == nullptr) {
renderPassArmAvailable = false;
measurement.failureReason = "a pre-1.1 device, or no vkGetPhysicalDeviceProperties2 / vkCreateDevice";

Some files were not shown because too many files have changed in this diff Show More