mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -124,7 +124,7 @@ namespace MobileGL {
|
||||
|
||||
// TODO: add and use Destroy functions for other subsystems
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Initialize() {
|
||||
if (g_isInitialized) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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, ©Region);
|
||||
vkCmdCopyImage(frame.commandBuffer, copySourceImage, copySourceImageLayout, dstImage.image,
|
||||
copyDestinationLayout, 1, ©Region);
|
||||
}
|
||||
// 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(), ©Offset,
|
||||
©Extent);
|
||||
x, y, width, height, defaultFboExtent, m_swapchainObject.GetPreTransform(), ©Offset, ©Extent);
|
||||
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, ©);
|
||||
vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
||||
staged.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©);
|
||||
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
Reference in New Issue
Block a user