diff --git a/MobileGL/BackendCaps.h b/MobileGL/BackendCaps.h index b56f7bff..5cac1154 100644 --- a/MobileGL/BackendCaps.h +++ b/MobileGL/BackendCaps.h @@ -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 { diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 37726b00..a8a60ab6 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -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 = ) - NamedPipe = 4, // P6: Windows named pipe (Endpoint = ) + 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 = ) + NamedPipe = 4, // P6: Windows named pipe (Endpoint = ) }; // 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 diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index 4a80b120..546e99f7 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -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(features.PipePush), - static_cast(MG_Pipe::kMGPipeSubsystemsMigratedAtP5e & - ~features.PipePush)); + static_cast(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(1u, std::min(8u, MG_Config::Features.FramesInFlight > 2 - ? MG_Config::Features.FramesInFlight - 2 - : 1u)); + const Uint32 derived = std::max( + 1u, std::min(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(ipc.StrictErrors), static_cast(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(ipc.StrictErrors), static_cast(ipc.Audit), static_cast(ipc.RoleSplitState), ipc.ServerAffinity.c_str(), MG_Config::ServerOwnedWindowSurfaces() ? "server" : "offscreen"); if (ipc.VerbBarrier == 0) { diff --git a/MobileGL/Defines.h b/MobileGL/Defines.h index 3fabfc82..98e94515 100644 --- a/MobileGL/Defines.h +++ b/MobileGL/Defines.h @@ -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 diff --git a/MobileGL/Init.cpp b/MobileGL/Init.cpp index 01406ffd..bf8dd9e9 100644 --- a/MobileGL/Init.cpp +++ b/MobileGL/Init.cpp @@ -124,7 +124,7 @@ namespace MobileGL { // TODO: add and use Destroy functions for other subsystems } - } + } // namespace void Initialize() { if (g_isInitialized) { diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index 18f070d1..8246536c 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -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(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 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; } diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 8fabf46d..37e9aaec 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -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& readFramebuffer, const SharedPtr& 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); } diff --git a/MobileGL/MG_Backend/ClientReadback.cpp b/MobileGL/MG_Backend/ClientReadback.cpp index 084184b5..988dee9d 100644 --- a/MobileGL/MG_Backend/ClientReadback.cpp +++ b/MobileGL/MG_Backend/ClientReadback.cpp @@ -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(std::clamp(v, -2147483648LL, 2147483647LL)); + const auto out = static_cast(std::clamp(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(reinterpret_cast(srcPixel)[c]) / 65535.0f; + srcValues[c] = static_cast(reinterpret_cast(srcPixel)[c]) / 65535.0f; } break; case GL_SHORT: @@ -277,24 +345,20 @@ namespace MobileGL::MG_Backend { Uint8* dstComponent = dstPixel + static_cast(ch) * dstComponentSize; switch (type) { case GL_UNSIGNED_BYTE: - *dstComponent = - static_cast(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0)); + *dstComponent = static_cast(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0)); break; case GL_BYTE: { - const auto out = - static_cast(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0)); + const auto out = static_cast(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(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0)); + const auto out = static_cast(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(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0)); + const auto out = static_cast(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(packParams.RowLength > 0 ? packParams.RowLength : width); const SizeT dstRowStride = AlignReadbackRow(rowPixels * dstPixelBytes, packParams.Alignment); - const SizeT imageRows = - applyPackImageParams && packParams.ImageHeight > 0 - ? static_cast(packParams.ImageHeight) - : static_cast(sliceHeight); + const SizeT imageRows = applyPackImageParams && packParams.ImageHeight > 0 + ? static_cast(packParams.ImageHeight) + : static_cast(sliceHeight); const SizeT dstImageStride = imageRows * dstRowStride; const SizeT skipImages = applyPackImageParams ? static_cast(std::max(packParams.SkipImages, 0)) : SizeT{0}; @@ -375,7 +438,7 @@ namespace MobileGL::MG_Backend { } const SizeT dstOffset = dstSkipOffset + static_cast(slice) * dstImageStride + - static_cast(row) * dstRowStride; + static_cast(row) * dstRowStride; Memcpy(static_cast(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(slice) * - static_cast(sliceHeight) + - static_cast(row); - const Uint8* srcRow = - wide + flatRow * static_cast(width) * srcPixelBytes; - ConvertWideReadbackRow(srcRow, dstRow, static_cast(width), wideType, - mapping, type); - }); + return StoreClientRows( + dstPixelBytes, swapGroupSize, width, sliceHeight, sliceCount, pixels, applyPackImageParams, + [&](GLsizei slice, GLsizei row, Uint8* dstRow) { + const SizeT flatRow = + static_cast(slice) * static_cast(sliceHeight) + static_cast(row); + const Uint8* srcRow = wide + flatRow * static_cast(width) * srcPixelBytes; + ConvertWideReadbackRow(srcRow, dstRow, static_cast(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(width) * 4; return StoreClientRows(4, packedLayout.byteSize, width, sliceHeight, sliceCount, pixels, - applyPackImageParams, - [&](GLsizei slice, GLsizei row, Uint8* dstRow) { - const SizeT flatRow = static_cast(slice) * - static_cast(sliceHeight) + - static_cast(row); + applyPackImageParams, [&](GLsizei slice, GLsizei row, Uint8* dstRow) { + const SizeT flatRow = + static_cast(slice) * static_cast(sliceHeight) + + static_cast(row); Memcpy(dstRow, srcWords + flatRow * srcRowBytes, srcRowBytes); }); } diff --git a/MobileGL/MG_Backend/ClientReadback.h b/MobileGL/MG_Backend/ClientReadback.h index aba9f32a..09943be5 100644 --- a/MobileGL/MG_Backend/ClientReadback.h +++ b/MobileGL/MG_Backend/ClientReadback.h @@ -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 diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 27edca22..bc5126f9 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -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 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 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(window)) || - published; + published = + DirectGLES::RepublishWindowSurfaceShape(native->second, reinterpret_cast(window)) || + published; } return published; } @@ -1443,8 +1449,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } Vector BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, - Bool drawIndirectSupported, - Bool nonZeroIndirectBaseInstanceSupported, + Bool drawIndirectSupported, Bool nonZeroIndirectBaseInstanceSupported, Bool textureViewSupported, Bool cubeMapArraySupported, const AdvertisedFeatureSupport& features) { Vector 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 " diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index 25ce807b..5dfa5251 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -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 BuildAdvertisedExtensions(Bool timerQueriesSupported, Bool anisotropicFilteringSupported, - Bool drawIndirectSupported, - Bool nonZeroIndirectBaseInstanceSupported, + Bool drawIndirectSupported, Bool nonZeroIndirectBaseInstanceSupported, Bool textureViewSupported, Bool cubeMapArraySupported, const AdvertisedFeatureSupport& features = {}); diff --git a/MobileGL/MG_Backend/DirectGLES/ContextEpoch.h b/MobileGL/MG_Backend/DirectGLES/ContextEpoch.h index 200a09fd..489d826d 100644 --- a/MobileGL/MG_Backend/DirectGLES/ContextEpoch.h +++ b/MobileGL/MG_Backend/DirectGLES/ContextEpoch.h @@ -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 diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index fb92801a..e6f5a56c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -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& func) { static_cast(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() { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index 5c0a5791..19cf563c 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -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 void ForEach(F&& visit) { const std::lock_guard 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 lock(m_mutex); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESBind.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESBind.cpp index 95a72661..9830e2bd 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESBind.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESBind.cpp @@ -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(range.Offset), static_cast(range.Offset + range.Size), i, range.Res}); - BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), - resource->id, static_cast(range.Offset), static_cast(range.Size)); + BufferImpl::BindBufferRangeCached(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(i), resource->id, + static_cast(range.Offset), + static_cast(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(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(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(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(viewRecord->View.Target) - : TextureTarget::Unknown; + const auto target = viewRecord != nullptr ? static_cast(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(unit), view.View.Slot, view.View.Gen, - view.Texture.Slot, view.Texture.Gen); + static_cast(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( - TextureImpl::MapToBackendTextureTarget(target))] = true; + boundBackendTargets[static_cast(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(t); if (!TextureImpl::IsSupportedTextureTarget(target)) continue; - const auto backendTargetIndex = - static_cast(TextureImpl::MapToBackendTextureTarget(target)); + const auto backendTargetIndex = static_cast(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(maxTouchedUnit + 1) * sizeof(SamplerImpl::g_boundSamplersCache[0]); + const SizeT rowBytes = static_cast(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(maxTouchedUnit + 1) * sizeof(TextureImpl::g_boundTexturesCache[0]); + const SizeT shadowBytes = static_cast(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 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(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(globalConstants->GlobalConstants.data()) \ +#define MGB_UBO_BYTES \ + (globalConstants ? static_cast(globalConstants->GlobalConstants.data()) \ : static_cast(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(globalUboSize)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageUboGlobal, + static_cast(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(windowStart) + - st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassUniform]; - if (static_cast(binding) < windowStart || - static_cast(binding) >= windowEnd) { + const Uint32 windowStart = st.ShaderBufferStart[MG_Pipe::kMGPipeShaderBufferClassUniform]; + const SizeT windowEnd = static_cast(windowStart) + + st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassUniform]; + if (static_cast(binding) < windowStart || static_cast(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(static_cast(entry.Offset), storage); - const SizeT rangeEnd = std::min( - static_cast(entry.Offset + entry.Size), storage); + const SizeT rangeStart = std::min(static_cast(entry.Offset), storage); + const SizeT rangeEnd = + std::min(static_cast(entry.Offset + entry.Size), storage); BufferImpl::BindBufferRangeCached( GL_UNIFORM_BUFFER, lastUBOBinding, backendResource->id, - static_cast(rangeStart), - static_cast(rangeEnd - rangeStart)); + static_cast(rangeStart), static_cast(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(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(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; } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESBlit.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESBlit.cpp index 9fa0b5be..4a6f7bde 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESBlit.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESBlit.cpp @@ -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( - target == FramebufferTarget::Read ? MG_Pipe::MGPipeFramebufferTarget::Read - : MG_Pipe::MGPipeFramebufferTarget::Draw)], - target); + SyncAndBindFramebufferByHandle(MG_Pipe::MGPipeApplier().BoundFramebuffer[static_cast( + 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(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(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(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(drawBuffer)))) { + if (IsSameDriverImage( + source, QueryDriverAttachmentImage(GL_DRAW_FRAMEBUFFER, static_cast(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(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(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(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(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(previousRenderbuffer)); @@ -896,8 +891,7 @@ namespace MobileGL::MG_Backend::DirectGLES { } const GLbitfield stagedMask = (colorFormat != 0 ? static_cast(GL_COLOR_BUFFER_BIT) : 0) | - (depthStencilFormat != 0 ? mask & static_cast(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT) - : 0); + (depthStencilFormat != 0 ? mask & static_cast(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( - static_cast(FramebufferAttachmentType::Color0) + static_cast(i)); + color.Layer == record.ReadSurface.Layer && color.UploadTarget == record.ReadSurface.UploadTarget) { + return static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(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((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) | - (hasStencil ? GL_STENCIL_BUFFER_BIT : 0)) - : static_cast(GL_COLOR_BUFFER_BIT); + const GLbitfield aspectBits = hasDepth || hasStencil + ? static_cast((hasDepth ? GL_DEPTH_BUFFER_BIT : 0) | + (hasStencil ? GL_STENCIL_BUFFER_BIT : 0)) + : static_cast(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(destinationSurface->TextureTarget)); ClearGLErrors(); - g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, static_cast(sourceSurface->Level), - srcX0, srcY0, static_cast(sourceSurface->Layer), - destinationName, destinationTarget, - static_cast(destinationSurface->Level), dstX0, dstY0, - static_cast(destinationSurface->Layer), width, height, 1); + g_GLESFuncs.glCopyImageSubData(sourceName, sourceTarget, static_cast(sourceSurface->Level), srcX0, + srcY0, static_cast(sourceSurface->Layer), destinationName, + destinationTarget, static_cast(destinationSurface->Level), dstX0, + dstY0, static_cast(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); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESBuffers.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESBuffers.cpp index 70ebfe5a..4533a76b 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESBuffers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESBuffers.cpp @@ -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(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(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(static_cast(entry.Offset), storage); - const SizeT rangeEnd = - std::min(static_cast(entry.Offset + entry.Size), storage); + const SizeT rangeEnd = std::min(static_cast(entry.Offset + entry.Size), storage); BindBufferRangeCached(glTarget, static_cast(i), backendBufferId, static_cast(rangeStart), static_cast(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(windowStart) + - st.ShaderBufferCount[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter]; + const SizeT windowEnd = + static_cast(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(glBinding) >= windowStart && - static_cast(glBinding) < windowEnd) { + if (static_cast(glBinding) >= windowStart && static_cast(glBinding) < windowEnd) { entry = &st.BoundShaderBuffers[MG_Pipe::kMGPipeShaderBufferClassAtomicCounter] [static_cast(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(esslBinding), - backendResource->id); + BindBufferBaseCached(GL_SHADER_STORAGE_BUFFER, static_cast(esslBinding), backendResource->id); } else { const SizeT storage = backendResource->storageSize; const SizeT rangeStart = std::min(static_cast(entry->Offset), storage); - const SizeT rangeEnd = - std::min(static_cast(entry->Offset + entry->Size), storage); - BindBufferRangeCached(GL_SHADER_STORAGE_BUFFER, static_cast(esslBinding), - backendResource->id, static_cast(rangeStart), + const SizeT rangeEnd = std::min(static_cast(entry->Offset + entry->Size), storage); + BindBufferRangeCached(GL_SHADER_STORAGE_BUFFER, static_cast(esslBinding), backendResource->id, + static_cast(rangeStart), static_cast(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(static_cast(mapped) + off), - static_cast(bytes), MG_Pipe::kMGHostSpanSegNone, 0}); + MG_Pipe::MGPBlobRef{reinterpret_cast(static_cast(mapped) + off), + static_cast(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(packedStride * vertices), - GL_MAP_READ_BIT); + const void* packed = g_GLESFuncs.glMapBufferRange( + BufferImpl::TempBufferTarget, 0, static_cast(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(staged.data() + off), - static_cast(bytes), - MG_Pipe::kMGHostSpanSegNone, 0}); + static_cast(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(target.start), + g_GLESFuncs.glBufferSubData(BufferImpl::TempBufferTarget, static_cast(target.start), static_cast(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; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESContext.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESContext.cpp index 982e0f67..deb804cb 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESContext.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESContext.cpp @@ -157,13 +157,17 @@ namespace MobileGL::MG_Backend::DirectGLES { namespace { std::atomic 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 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 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(dlsym(x11Lib, "XOpenDisplay")); - auto* xGetWindowAttributes = - reinterpret_cast(dlsym(x11Lib, "XGetWindowAttributes")); + auto* xGetWindowAttributes = reinterpret_cast(dlsym(x11Lib, "XGetWindowAttributes")); auto* xVisualIDFromVisual = reinterpret_cast(dlsym(x11Lib, "XVisualIDFromVisual")); auto* xCloseDisplay = reinterpret_cast(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 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(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(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 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(MG_Util::Damage::PackRects(region, rects, MG_Util::Damage::kMaxRects)); + region.IsFull() + ? 0 + : static_cast(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(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 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 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 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 g_currentFrameSerial{0}; - std::atomic 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 g_currentFrameSerial{0}; + std::atomic 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()); } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESCopy.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESCopy.cpp index 7676b7cc..a4201d15 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESCopy.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESCopy.cpp @@ -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 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(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(applier.VerbMipLevelCount, desc.Levels); if (record.ParamsSerial) end = std::min(end, static_cast(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(largest, baseExtent[axis]); + for (Int axis = 0; axis < shrinking && axis < 3; ++axis) + largest = std::max(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("DirectGLES", operation, - MG_Util::ConvertGLEnumToString(err))); + MakeUnique("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& backendFBO) { + [&](MG_Pipe::MGPipeHandle fbo, const SharedPtr& 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( - static_cast(FramebufferAttachmentType::Color0) + i)); + detachPoint(record->Color[i], static_cast( + static_cast(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(window.MinLayer + i); if (window.Depth) { BlitDepthTextureLayer(window.StorageTextureId, srcLevel, layer, static_cast(srcSize.x()), - static_cast(srcSize.y()), window.StorageTextureId, dstLevel, - layer, static_cast(dstSize.x()), static_cast(dstSize.y())); + static_cast(srcSize.y()), window.StorageTextureId, dstLevel, layer, + static_cast(dstSize.x()), static_cast(dstSize.y())); } else { BlitColorTextureLayer(window.StorageTextureId, srcLevel, layer, static_cast(srcSize.x()), - static_cast(srcSize.y()), window.StorageTextureId, dstLevel, - layer, static_cast(dstSize.x()), - static_cast(dstSize.y()), window.Filter); + static_cast(srcSize.y()), window.StorageTextureId, dstLevel, layer, + static_cast(dstSize.x()), static_cast(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(storageRecord->Desc.ArrayLayers, 1u)) - return false; + if (view.MinLayer == 0 && view.NumLayers >= std::max(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(applier.VerbMipLevelCount), - static_cast(record.Desc.Levels), - static_cast(view.NumLevels)}); + static_cast(record.Desc.Levels), static_cast(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(level - 1), 0, 0, - static_cast(srcSize.x()), static_cast(srcSize.y()), textureId, - static_cast(level), 0, 0, static_cast(dstSize.x()), - static_cast(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(level - 1), 0, 0, static_cast(srcSize.x()), + static_cast(srcSize.y()), textureId, static_cast(level), 0, 0, + static_cast(dstSize.x()), static_cast(dstSize.y())); } } @@ -776,10 +775,10 @@ namespace MobileGL::MG_Backend::DirectGLES { IsIntegerColorFormat(static_cast(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(level - 1), 0, 0, static_cast(srcSize.x()), static_cast(srcSize.y()), textureId, static_cast(level), 0, 0, static_cast(dstSize.x()), static_cast(dstSize.y()), filter); @@ -1015,8 +1014,8 @@ namespace MobileGL::MG_Backend::DirectGLES { const MG_Pipe::MGPipeResourceRecord& record, const SharedPtr& backendTexture, TextureTarget textureTarget, TextureUploadTarget face, Uint32 level, - const IntVec3& extent, Bool driverWrittenBefore, - Vector& rgb, const char*& declined) { + const IntVec3& extent, Bool driverWrittenBefore, Vector& rgb, + const char*& declined) { const auto format = static_cast(record.Desc.InternalFormat); const SizeT texels = static_cast(extent.x()) * static_cast(extent.y()); Vector 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& backendTexture, - Bool driverWrittenBefore, const char* why) { + static void GenerateThreeChannelFloatMipmapOnServer( + MG_Pipe::MGPipeHandle mipRes, const MG_Pipe::MGPipeResourceRecord& record, + const SharedPtr& backendTexture, Bool driverWrittenBefore, const char* why) { const auto& desc = record.Desc; const auto format = static_cast(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( - static_cast(TextureUploadTarget::CubeMapPositiveX) + face)); + faces.push_back( + static_cast(static_cast(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 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(y) * static_cast(dstExtent.x()) + static_cast(x)) * - 3 + static_cast(c)] = + 3 + + static_cast(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(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(textureTarget)]; + auto* cachedBound = + TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit][static_cast(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(); 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(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(srcLevel), IntVec3{srcX, srcY, srcZ}, - dstEndpoint.TextureHandle, static_cast(dstLevel), IntVec3{dstX, dstY, dstZ}, - IntVec3{srcWidth, srcHeight, srcDepth}); + TextureImpl::FollowCopyImageInStagedStore(srcEndpoint.TextureHandle, static_cast(srcLevel), + IntVec3{srcX, srcY, srcZ}, dstEndpoint.TextureHandle, + static_cast(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; } diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESDraw.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESDraw.cpp index 2cbf3041..ca87f348 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESDraw.cpp @@ -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(std::lround(viewport.x())), - static_cast(std::lround(viewport.y())), - static_cast(std::lround(viewport.z())), - static_cast(std::lround(viewport.w()))); + pass.viewport = + IntVec4(static_cast(std::lround(viewport.x())), static_cast(std::lround(viewport.y())), + static_cast(std::lround(viewport.z())), static_cast(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(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(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(cmdByteOffset)); }); @@ -513,8 +502,8 @@ namespace MobileGL::MG_Backend::DirectGLES { SetCurrentDrawID(static_cast(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(cmd.first), static_cast(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(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(value); break; + case 1: + destination[i] = static_cast(value); + break; case 2: { const Uint16 narrow = static_cast(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(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(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(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(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(indirect); const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(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(actualDrawCount, static_cast(maxdrawcount)); - ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset, - nullptr, static_cast(actualDrawCount), stride, - "MultiDrawElementsIndirectCount"); + ExecuteIndexedIndirectCommands(mode, type, indexSize, drawBytes + commandOffset, commandOffset, nullptr, + static_cast(actualDrawCount), stride, "MultiDrawElementsIndirectCount"); return; } @@ -1203,8 +1189,8 @@ namespace MobileGL::MG_Backend::DirectGLES { stride = sizeof(DrawArraysIndirectCommand); } if (stride < static_cast(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(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(indirect); const SizeT commandBytes = commandOffset + static_cast(stride) * static_cast(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(actualDrawCount, static_cast(maxdrawcount)); ExecuteArraysIndirectCommands(mode, drawBytes + commandOffset, commandOffset, nullptr, - static_cast(actualDrawCount), stride, - "MultiDrawArraysIndirectCount"); + static_cast(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) { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESInternal.h b/MobileGL/MG_Backend/DirectGLES/DirectGLESInternal.h index 007f4adc..41ef980a 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESInternal.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESInternal.h @@ -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(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(TextureTarget::Texture2D)]; + auto* cachedBound = TextureImpl::g_boundTexturesCache[TextureImpl::TempTextureUnit] + [static_cast(TextureTarget::Texture2D)]; g_GLESFuncs.glBindTexture(GL_TEXTURE_2D, cachedBound ? cachedBound->GetBackendTextureId() : 0); TextureImpl::ActivateTextureUnit(m_activeTextureUnit); VertexArrayImpl::BindBackendVAOId(static_cast(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(m_stencilFunc[face]), - m_stencilRef[face], - static_cast(m_stencilValueMask[face])); + m_stencilRef[face], static_cast(m_stencilValueMask[face])); g_GLESFuncs.glStencilOpSeparate(faces[face], static_cast(m_stencilFail[face]), static_cast(m_stencilDepthFail[face]), static_cast(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 g_nativeRegistryEpoch; extern std::atomic 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& 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 diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESReadback.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESReadback.cpp index 33bd5aae..4e7b6390 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESReadback.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESReadback.cpp @@ -57,8 +57,7 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(std::max(packParams.SkipPixels, 0)) * dstPixelBytes; const SizeT rowBytes = static_cast(width) * dstPixelBytes; const SizeT packedSize = dstOffset + static_cast(height - 1) * dstRowStride + rowBytes; - const auto& pixelPackBufferObject = - SplitReadbackPackBuffer(); + const auto& pixelPackBufferObject = SplitReadbackPackBuffer(); const SizeT pboOffset = reinterpret_cast(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(width) / static_cast(slot.width); const Float uvScaleY = static_cast(height) / static_cast(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(row) * static_cast(width); - for (GLsizei col = 0; col < width; ++col) { - const Float depthValue = srcRow[col]; - switch (type) { - case GL_BYTE: - reinterpret_cast(dst)[col] = static_cast( - NormalizedDepthToUnsigned(depthValue, 127.0)); - break; - case GL_UNSIGNED_BYTE: - dst[col] = static_cast( - NormalizedDepthToUnsigned(depthValue, 255.0)); - break; - case GL_SHORT: - reinterpret_cast(dst)[col] = static_cast( - NormalizedDepthToUnsigned(depthValue, 32767.0)); - break; - case GL_UNSIGNED_SHORT: - reinterpret_cast(dst)[col] = static_cast( - NormalizedDepthToUnsigned(depthValue, 65535.0)); - break; - case GL_INT: - reinterpret_cast(dst)[col] = static_cast( - NormalizedDepthToUnsigned(depthValue, 2147483647.0)); - break; - case GL_UNSIGNED_INT: - reinterpret_cast(dst)[col] = - NormalizedDepthToUnsigned(depthValue, 4294967295.0); - break; - case GL_HALF_FLOAT: - reinterpret_cast(dst)[col] = - MG_Util::EncodeFloatToHalfBits(depthValue); - break; - default: - reinterpret_cast(dst)[col] = depthValue; - break; - } - } - }); + StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "depth", [&](GLsizei row, Uint8* dst) { + const Float* srcRow = depth.data() + static_cast(row) * static_cast(width); + for (GLsizei col = 0; col < width; ++col) { + const Float depthValue = srcRow[col]; + switch (type) { + case GL_BYTE: + reinterpret_cast(dst)[col] = static_cast(NormalizedDepthToUnsigned(depthValue, 127.0)); + break; + case GL_UNSIGNED_BYTE: + dst[col] = static_cast(NormalizedDepthToUnsigned(depthValue, 255.0)); + break; + case GL_SHORT: + reinterpret_cast(dst)[col] = + static_cast(NormalizedDepthToUnsigned(depthValue, 32767.0)); + break; + case GL_UNSIGNED_SHORT: + reinterpret_cast(dst)[col] = + static_cast(NormalizedDepthToUnsigned(depthValue, 65535.0)); + break; + case GL_INT: + reinterpret_cast(dst)[col] = + static_cast(NormalizedDepthToUnsigned(depthValue, 2147483647.0)); + break; + case GL_UNSIGNED_INT: + reinterpret_cast(dst)[col] = NormalizedDepthToUnsigned(depthValue, 4294967295.0); + break; + case GL_HALF_FLOAT: + reinterpret_cast(dst)[col] = MG_Util::EncodeFloatToHalfBits(depthValue); + break; + default: + reinterpret_cast(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(row) * static_cast(width); - for (GLsizei col = 0; col < width; ++col) { - switch (type) { - case GL_UNSIGNED_BYTE: - case GL_BYTE: - dst[static_cast(col)] = srcRow[col]; - break; - case GL_UNSIGNED_SHORT: - case GL_SHORT: - reinterpret_cast(dst)[col] = srcRow[col]; - break; - case GL_FLOAT: - reinterpret_cast(dst)[col] = static_cast(srcRow[col]); - break; - case GL_HALF_FLOAT: - reinterpret_cast(dst)[col] = - MG_Util::EncodeFloatToHalfBits(static_cast(srcRow[col])); - break; - default: - reinterpret_cast(dst)[col] = srcRow[col]; - break; - } - } - }); + StoreReadbackRowsToClient(width, height, dstPixelBytes, pixels, "stencil", [&](GLsizei row, Uint8* dst) { + const Uint8* srcRow = raw.data() + static_cast(row) * static_cast(width); + for (GLsizei col = 0; col < width; ++col) { + switch (type) { + case GL_UNSIGNED_BYTE: + case GL_BYTE: + dst[static_cast(col)] = srcRow[col]; + break; + case GL_UNSIGNED_SHORT: + case GL_SHORT: + reinterpret_cast(dst)[col] = srcRow[col]; + break; + case GL_FLOAT: + reinterpret_cast(dst)[col] = static_cast(srcRow[col]); + break; + case GL_HALF_FLOAT: + reinterpret_cast(dst)[col] = + MG_Util::EncodeFloatToHalfBits(static_cast(srcRow[col])); + break; + default: + reinterpret_cast(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(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(implFormat), static_cast(implType)}; } - candidates[candidateCount++] = { - GL_RGBA_INTEGER, - attachmentComponentType == GL_INT ? static_cast(GL_INT) : static_cast(GL_UNSIGNED_INT)}; + candidates[candidateCount++] = {GL_RGBA_INTEGER, attachmentComponentType == GL_INT + ? static_cast(GL_INT) + : static_cast(GL_UNSIGNED_INT)}; } else { if (GetWideReadChannelCount(static_cast(implFormat)) > 0 && !IsIntegerReadFormat(implFormat) && (CanDecodeWideSourceType(static_cast(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(width) * static_cast(height) * - static_cast(channels) * candidateComponentSize); + wide.resize(static_cast(width) * static_cast(height) * static_cast(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(normalizedCarrier.ChannelMax[channel]); if (normalizedCarrier.SignedNormalized) { const auto signedCode = static_cast(static_cast(code)); - dst[i * 4 + channel] = - std::max(static_cast(signedCode) / denominator, -1.0f); + dst[i * 4 + channel] = std::max(static_cast(signedCode) / denominator, -1.0f); } else { dst[i * 4 + channel] = static_cast(code) / denominator; } @@ -1236,7 +1242,8 @@ namespace MobileGL::MG_Backend::DirectGLES { switch (wideType) { case GL_FLOAT: { auto* values = reinterpret_cast(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(wide.data()); - for (SizeT i = 0; i < valueCount; ++i) values[i] = std::max(values[i], 0); + for (SizeT i = 0; i < valueCount; ++i) + values[i] = std::max(values[i], 0); break; } case GL_BYTE: { auto* values = reinterpret_cast(wide.data()); - for (SizeT i = 0; i < valueCount; ++i) values[i] = std::max(values[i], 0); + for (SizeT i = 0; i < valueCount; ++i) + values[i] = std::max(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(TextureTarget::Texture2D)]; + [static_cast(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( - 1, static_cast(std::min(kMaxScratchTexels / static_cast(width), - static_cast(height)))); + const GLsizei bandRows = + std::max(1, static_cast(std::min(kMaxScratchTexels / static_cast(width), + static_cast(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(row) * wordsPerRow; Uint32* dstRow = outWords + static_cast(y + row) * static_cast(width); - for (GLsizei x = 0; x < width; ++x) dstRow[x] = srcRow[static_cast(x) * 4]; + for (GLsizei x = 0; x < width; ++x) + dstRow[x] = srcRow[static_cast(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& bytes) { + static Bool ReadWireColorRowsTight(GLsizei width, GLsizei height, GLenum format, GLenum type, Bool forceOpaqueAlpha, + const NormalizedImageCarrierRead& carrier, Vector& 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 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(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(carrier.ChannelMax[c]); - dst[i * 4 + c] = carrier.SignedNormalized - ? std::max(static_cast(static_cast(static_cast(src[i * 4 + c]))) / divisor, -1.0f) - : static_cast(src[i * 4 + c]) / divisor; + dst[i * 4 + c] = + carrier.SignedNormalized + ? std::max(static_cast(static_cast(static_cast(src[i * 4 + c]))) / + divisor, + -1.0f) + : static_cast(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 decoded(pixels * 4 * sizeof(Float)); auto* dst = reinterpret_cast(decoded.data()); @@ -1542,7 +1571,9 @@ namespace MobileGL::MG_Backend::DirectGLES { dst[i * 4 + 2] = static_cast((word >> 20) & 1023u) / 1023.0f; dst[i * 4 + 3] = static_cast((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(width) * 4 * GetReadbackComponentSize(wideType); for (GLsizei row = 0; row < height; ++row) ClientReadback::ConvertWideReadbackRow(wide.data() + static_cast(row) * sourceRowBytes, - converted.data() + static_cast(row) * width * pixelBytes, width, wideType, mapping, type); + converted.data() + static_cast(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& 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(extent.x()) * extent.y(); Vector 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 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(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(stencil[i])); + const Uint16 value = + MG_Util::EncodeFloatToHalfBits(needDepth ? depth[i] : static_cast(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(value); - else if (pixelBytes == 2) { const Uint16 v = static_cast(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(value); + else if (pixelBytes == 2) { + const Uint16 v = static_cast(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(extent.x()) * extent.y() * pixelBytes; Vector result(sliceBytes * static_cast(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(uploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX) : 0; + const GLint face = + uploadTarget >= TextureUploadTarget::CubeMapPositiveX && + uploadTarget <= TextureUploadTarget::CubeMapNegativeZ + ? static_cast(uploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX) + : 0; for (GLint layer = 0; layer < extent.z(); ++layer) - if (!ReadPackedLevelWordsViaScratch(texture, nativeTarget, level, face + layer, extent.x(), extent.y(), - reinterpret_cast(result.data() + static_cast(layer) * sliceBytes))) return false; + if (!ReadPackedLevelWordsViaScratch( + texture, nativeTarget, level, face + layer, extent.x(), extent.y(), + reinterpret_cast(result.data() + static_cast(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 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(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(uploadTarget); const auto mip = static_cast(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(std::max(extent.x(), 0)) * static_cast(std::max(extent.y(), 0)) * static_cast(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(info.Box.W), static_cast(info.Box.H), - wholeCube ? 1 : static_cast(info.Box.D)}; + wholeCube ? 1 : static_cast(info.Box.D)}; const auto logicalFormat = static_cast(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 result; for (Int face = 0; face < (wholeCube ? 6 : 1); ++face) { - const auto uploadTarget = wholeCube - ? static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + face) - : MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target); + const auto uploadTarget = + wholeCube + ? static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + face) + : MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target); Vector 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) { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESSharedImage.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESSharedImage.cpp index c9cb8e1d..6b926818 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESSharedImage.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESSharedImage.cpp @@ -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(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(error)); + MGLOG_E_ONCE("Shared images: the YUV conversion draw failed (0x%04x)", + static_cast(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(view.Id), static_cast(kFenceCpuWaitMs)); @@ -820,8 +822,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (readBuffer != GL_BACK) g_GLESFuncs.glReadBuffer(static_cast(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(error)); + sourceWidth, sourceHeight, view.Width, view.Height, static_cast(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 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; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESState.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESState.cpp index 55edb215..99d047ef 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESState.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESState.cpp @@ -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(described.Fbo.Slot), - static_cast(described.Fbo.Gen)); + static_cast(described.Fbo.Slot), static_cast(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( - st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Slot), - static_cast( - st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Gen), - static_cast( - st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Slot), - static_cast( - st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Gen), - static_cast(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(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Slot), + static_cast(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)].Gen), + static_cast(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Slot), + static_cast(st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Read)].Gen), + static_cast(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(record.Fbo.Slot), - static_cast(record.Fbo.Gen), - static_cast( - g_backendFramebufferObjects.LiveGenAt(record.Fbo.Slot))); + static_cast(record.Fbo.Slot), static_cast(record.Fbo.Gen), + static_cast(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(handle.Slot), static_cast(handle.Gen), - static_cast(index), - static_cast(table->size())); + static_cast(index), static_cast(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(handle.Slot), static_cast(handle.Gen), - static_cast(record->Desc.Cso.Slot), - static_cast(record->Desc.Cso.Gen)); + static_cast(record->Desc.Cso.Slot), static_cast(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 || diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESSync.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESSync.cpp index bd75f00c..fcc2fb01 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESSync.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESSync.cpp @@ -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 g_eglVerifiedFrameSerial{~0ull}; - std::atomic 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 g_eglVerifiedFrameSerial{~0ull}; + std::atomic 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(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(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(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(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(kFrameFenceRingDepth) && g_GLESFuncs.glClientWaitSync && - presentedSerial + 1 > framesInFlight) { + if (presentedSerial != 0 && framesInFlight != 0 && framesInFlight < static_cast(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) { diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLESTextures.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLESTextures.cpp index cf23002e..17960f2d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLESTextures.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLESTextures.cpp @@ -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 - g_writableImageBufferUnits{}; + static Array 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(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(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{}, - nullptr, &format, &type); + MG_Util::TextureFormatProcessor::NormalizePixelFormat( + internalFormat, Flags{}, 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( - std::max(st.ShaderImageStart + st.ShaderImageCount, - static_cast(g_imageUnitHighWaterMark)), - static_cast(unitCount)); + const Uint32 end = std::min(std::max(st.ShaderImageStart + st.ShaderImageCount, + static_cast(g_imageUnitHighWaterMark)), + static_cast(unitCount)); for (Uint32 unit = 0; unit < end; ++unit) { if (unit >= st.BoundShaderImages.size()) break; // The union can include a slot no set_shader_images diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index f29babe7..328c4c79 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -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(source[pos - 1])) || source[pos - 1] == '_'); + const Bool leftIsIdent = + pos > 0 && (std::isalnum(static_cast(source[pos - 1])) || source[pos - 1] == '_'); const SizeT end = pos + from.size(); - const Bool rightIsIdent = end < source.size() && - (std::isalnum(static_cast(source[end])) || source[end] == '_'); + const Bool rightIsIdent = + end < source.size() && (std::isalnum(static_cast(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(mask), - static_cast(required), - static_cast(required & ~mask), - MG_Pipe::MGPipeSubsystemDependencyWhy(family)); + what, static_cast(mask), static_cast(required), + static_cast(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(std::max(g_GLESCapabilities.MaxColorAttachments, 0)) > - MG_Pipe::kMGPipeMaxColorAttachments) { + if (!refused && static_cast(std::max(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(std::max(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(MobileGL::TextureTarget::TextureTargetCount); ++i) { const auto target = static_cast(i); - if (MG_Pipe::MGPipeResourceTargetForTextureTarget(target) == - static_cast(record->Desc.Target)) { + if (MG_Pipe::MGPipeResourceTargetForTextureTarget(target) == static_cast(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(level), static_cast(carried), - static_cast(record.Desc.Target)); + static_cast(level), static_cast(carried), static_cast(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; diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 14fbf9b7..365e8222 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -307,7 +307,6 @@ namespace MobileGL::MG_Backend::DirectGLES { template class StateBackendObjectRegistry { public: - using BackendPtr = SharedPtr; using SlotTable = BackendSlotTable; // 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 + extern TwinRegistry 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& SyncTextureToBackendByHandle( - MG_Pipe::MGPipeHandle texture, Bool imageBindableStorageRequired = false); + SharedPtr& 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(FramebufferAttachmentType::Color31) - - static_cast(FramebufferAttachmentType::Color0) + 1; + static constexpr Uint MAX_COLOR_ATTACHMENT_SLOTS = static_cast(FramebufferAttachmentType::Color31) - + static_cast(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 + extern TwinRegistry 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& GetLinkedShaderStages() const { return LinkedStages; } - const UnorderedMap& GetShaderStorageBlockBindingOverrides() const { - return StorageOverrides; - } + const UnorderedMap& 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(Link->glBlockIndexToTProgram.size()); - } + Int GetActiveUniformBlocksCount() const { return static_cast(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& 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>& shaderSpirvs, const String& vertexStageEssl, - const String& tessEvalStageEssl); + void AttachPassthroughTessControlStage(const ProgramBuildSource& src, Int tessEvalShaderIndex, + const Vector>& 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& storageBlockBindingOverrides, const std::map& inputBlockRenames, - const std::map& outputBlockRenames, - Bool stripInputBlockLocations, Bool stripOutputBlockLocations, - Int atomicCounterEsslBindingTop, Bool enableSpirvValidation, - String& outSource, + const std::map& outputBlockRenames, Bool stripInputBlockLocations, + Bool stripOutputBlockLocations, Int atomicCounterEsslBindingTop, + Bool enableSpirvValidation, String& outSource, std::set& outFlattenedXfbBlockNames, Vector& 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; + using BackendSamplerViewTable = SlotTableRegistry; 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 + extern TwinRegistry g_backendRenderbufferObjects; } // namespace RenderbufferImpl diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersBuffers.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersBuffers.cpp index 39509e18..b7b84d44 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersBuffers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersBuffers.cpp @@ -28,9 +28,9 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool g_boundPixelUnpackBufferKnown = false; } // namespace - // Bumped whenever the backend ES context is destroyed; resources with - // an older generation hold ids from a dead context. - Uint g_bufferContextGeneration = 1; + // Bumped whenever the backend ES context is destroyed; resources with + // an older generation hold ids from a dead context. + Uint g_bufferContextGeneration = 1; namespace { // Buffer-mutation epoch backing store (contract, mutation-site list and @@ -88,10 +88,9 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool IsPoolable(const GLESBufferResource& r) { // Require working fences: recycling is gated on the frame-completion // watermark, which only advances if Present can insert/poll fences. - return g_GLESFuncs.glFenceSync != nullptr && g_GLESFuncs.glGetSynciv != nullptr && - r.id != 0 && !r.persistentMapped && !r.immutableStorage && - r.contextGeneration == g_bufferContextGeneration && r.storageInitialized && - r.storageSize > 0 && r.storageSize <= kMaxPoolableBufferBytes; + return g_GLESFuncs.glFenceSync != nullptr && g_GLESFuncs.glGetSynciv != nullptr && r.id != 0 && + !r.persistentMapped && !r.immutableStorage && r.contextGeneration == g_bufferContextGeneration && + r.storageInitialized && r.storageSize > 0 && r.storageSize <= kMaxPoolableBufferBytes; } // Retire a buffer id into the pool (owning thread; caller verified IsPoolable). @@ -117,8 +116,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // +1: Present increments the serial at frame END, so during the frame // now being built CurrentFrameSerial() reads (frame-1). A buffer used // this frame is only GPU-done once THIS frame's fence (serial+1) signals. - bucket.push_back( - {r.id, r.storageSize, r.contextGeneration, DirectGLES::CurrentFrameSerial() + 1}); + bucket.push_back({r.id, r.storageSize, r.contextGeneration, DirectGLES::CurrentFrameSerial() + 1}); g_pooledBytes += r.storageSize; r.id = 0; } @@ -167,31 +165,37 @@ namespace MobileGL::MG_Backend::DirectGLES { std::atomic g_ringGenerationBases{0}; } // namespace - RingSet::RingSet() { - const Uint32 base = (g_ringGenerationBases.fetch_add(1, std::memory_order_relaxed) + 1) << 12; - ubo.store.generation = base; - unpack.store.generation = base; - upload.store.generation = base; - } + RingSet::RingSet() { + const Uint32 base = (g_ringGenerationBases.fetch_add(1, std::memory_order_relaxed) + 1) << 12; + ubo.store.generation = base; + unpack.store.generation = base; + upload.store.generation = base; + } - RingSet::~RingSet() { - const std::lock_guard lock(g_orphanRingBuffersMutex); - for (const PersistentRing* ring : {&ubo, &unpack, &upload}) { - if (ring->store.id != 0) { - OrphanRingBuffers().push_back({ring->store.id, ring->store.contextGeneration}); - } - for (const auto& retired : ring->retired) { - if (retired.id != 0) OrphanRingBuffers().push_back({retired.id, retired.contextGeneration}); - } + RingSet::~RingSet() { + const std::lock_guard lock(g_orphanRingBuffersMutex); + for (const PersistentRing* ring : {&ubo, &unpack, &upload}) { + if (ring->store.id != 0) { + OrphanRingBuffers().push_back({ring->store.id, ring->store.contextGeneration}); + } + for (const auto& retired : ring->retired) { + if (retired.id != 0) OrphanRingBuffers().push_back({retired.id, retired.contextGeneration}); } } + } - DirectGLES::PerNativeContext g_ringSets; + DirectGLES::PerNativeContext g_ringSets; namespace { - PersistentRing& UboRing() { return g_ringSets.Current().ubo; } - PersistentRing& UnpackRing() { return g_ringSets.Current().unpack; } - PersistentRing& UploadRing() { return g_ringSets.Current().upload; } + PersistentRing& UboRing() { + return g_ringSets.Current().ubo; + } + PersistentRing& UnpackRing() { + return g_ringSets.Current().unpack; + } + PersistentRing& UploadRing() { + return g_ringSets.Current().upload; + } // The ES context the ring's id/map belonged to is gone (or was never // seen): drop every handle without GL calls and re-arm creation. The @@ -231,8 +235,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // off the applier's stored descriptor and the shadow base the call carried; nothing // else in here differs, so there is ONE glBufferData and one extent rule rather // than two that can drift apart. - void RespecifyStorageWith(GLESBufferResource& resource, SizeT size, GLenum usage, - const void* initialData, Uint64 syncedSerial) { + void RespecifyStorageWith(GLESBufferResource& resource, SizeT size, GLenum usage, const void* initialData, + Uint64 syncedSerial) { #ifdef TRACY_ENABLE ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif @@ -245,8 +249,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (MG_Util::PipeStats::Enabled() && initialData != nullptr) { // An ORPHANING respecify passes NULL and moves nothing, which is exactly // why the test is on initialData rather than on size. - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, - static_cast(size)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast(size)); } resource.storageSize = size; resource.storageInitialized = true; @@ -289,26 +292,25 @@ namespace MobileGL::MG_Backend::DirectGLES { } } // namespace - // ------------------------------------------------------------------------------- - // R-11 - THE SERVER'S OWN COPY OF THE STAGED BYTES. Package v1. - // - // The rule, the evidence and the reason the storage is a side table rather than a - // member of GLESBufferResource are all in MG_Remote/Server/StagedShadow.h. This is - // only the instance and the four call sites. - // - // ONE PER PROCESS, and its copying arm is decided ONCE at first use: the two arms - // hold the authoritative bytes in DIFFERENT places, so an answer that changed - // mid-run would strand every resource already staged - the same reason - // ResolveResourceSubsystemArm latches (Managers.h). Leaked at exit like every other - // MG_Remote singleton (ID-8): ~BufferObject reaches the destroy path from exit - // handlers, after this TU's globals would already be gone. - // ------------------------------------------------------------------------------- - MG_Record::StagedShadowStore& ServerStaged() { - static MG_Record::StagedShadowStore& store = - *new MG_Record::StagedShadowStore( - MG_Config::Transport != MG_Config::TransportMode::Monolith); - return store; - } + // ------------------------------------------------------------------------------- + // R-11 - THE SERVER'S OWN COPY OF THE STAGED BYTES. Package v1. + // + // The rule, the evidence and the reason the storage is a side table rather than a + // member of GLESBufferResource are all in MG_Remote/Server/StagedShadow.h. This is + // only the instance and the four call sites. + // + // ONE PER PROCESS, and its copying arm is decided ONCE at first use: the two arms + // hold the authoritative bytes in DIFFERENT places, so an answer that changed + // mid-run would strand every resource already staged - the same reason + // ResolveResourceSubsystemArm latches (Managers.h). Leaked at exit like every other + // MG_Remote singleton (ID-8): ~BufferObject reaches the destroy path from exit + // handlers, after this TU's globals would already be gone. + // ------------------------------------------------------------------------------- + MG_Record::StagedShadowStore& ServerStaged() { + static MG_Record::StagedShadowStore& store = + *new MG_Record::StagedShadowStore(MG_Config::Transport != MG_Config::TransportMode::Monolith); + return store; + } namespace { #if MOBILEGL_BUILD_DISAGGREGATED @@ -345,10 +347,10 @@ namespace MobileGL::MG_Backend::DirectGLES { void SweepT0Retired(Bool contextGone) { for (SizeT i = 0; i < g_t0Retired.size();) { T0RetiredImport& retired = g_t0Retired[i]; - const Bool sameContext = !contextGone && retired.generation == g_bufferContextGeneration && - CanTouchGLNow(); - Bool done = contextGone || retired.generation != g_bufferContextGeneration || - retired.fence == nullptr; + const Bool sameContext = + !contextGone && retired.generation == g_bufferContextGeneration && CanTouchGLNow(); + Bool done = + contextGone || retired.generation != g_bufferContextGeneration || retired.fence == nullptr; if (!done && sameContext && g_GLESFuncs.glClientWaitSync) { const GLenum status = g_GLESFuncs.glClientWaitSync(retired.fence, 0, 0); done = status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED || @@ -433,100 +435,98 @@ namespace MobileGL::MG_Backend::DirectGLES { void FlushPendingRangesFrom(GLESBufferResource& resource, const Uint8* hostBase, SizeT frontendSize, SizeT hostBaseFrom, SizeT hostBaseTo) { #ifdef TRACY_ENABLE - ZoneScopedC(TRACY_ZONECOLOR_BACKEND); + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif - VecRange1D ranges; - { - const std::lock_guard lock(resource.pendingMutex); - if (resource.pendingRanges.empty()) return; - ranges = std::move(resource.pendingRanges); - resource.pendingRanges.clear(); + VecRange1D ranges; + { + const std::lock_guard lock(resource.pendingMutex); + if (resource.pendingRanges.empty()) return; + ranges = std::move(resource.pendingRanges); + resource.pendingRanges.clear(); + } + // No shadow to read from: only reachable on the handle arm, where the base + // arrives with the call, and only for a resource that queued a range before any + // content-carrying call named one. The queue is already drained, so the next + // full re-upload is what puts the store right. + if (hostBase == nullptr) return; + // Clamp against BOTH extents: the readback flush may run while the + // frontend size and the backend store disagree (a pending respecify + // resolves that later; bytes past either end have nowhere to land). + const SizeT limit = std::min(frontendSize, resource.storageSize); + const Bool mapUsable = !MG_Config::Features.EsprytDisableInvalidateFlush && g_GLESFuncs.glMapBufferRange && + g_GLESFuncs.glUnmapBuffer; + const Bool ringUsable = UploadRingUsableNow(); + for (const auto& range : ranges) { + const SizeT end = std::min(range.end, limit); + const SizeT start = std::min(range.start, end); + const SizeT size = end - start; + if (size == 0) continue; + if (MG_Util::PipeStats::Enabled()) { + // Counted once per queued range, before the three delivery shapes + // below diverge: all three move exactly these bytes, and it is the + // byte count - not the shape - that sizes SEG_STAGE. + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast(size)); } - // No shadow to read from: only reachable on the handle arm, where the base - // arrives with the call, and only for a resource that queued a range before any - // content-carrying call named one. The queue is already drained, so the next - // full re-upload is what puts the store right. - if (hostBase == nullptr) return; - // Clamp against BOTH extents: the readback flush may run while the - // frontend size and the backend store disagree (a pending respecify - // resolves that later; bytes past either end have nowhere to land). - const SizeT limit = std::min(frontendSize, resource.storageSize); - const Bool mapUsable = !MG_Config::Features.EsprytDisableInvalidateFlush && - g_GLESFuncs.glMapBufferRange && g_GLESFuncs.glUnmapBuffer; - const Bool ringUsable = UploadRingUsableNow(); - for (const auto& range : ranges) { - const SizeT end = std::min(range.end, limit); - const SizeT start = std::min(range.start, end); - const SizeT size = end - start; - if (size == 0) continue; - if (MG_Util::PipeStats::Enabled()) { - // Counted once per queued range, before the three delivery shapes - // below diverge: all three move exactly these bytes, and it is the - // byte count - not the shape - that sizes SEG_STAGE. - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, - static_cast(size)); - } - // The invalidating map's fast path is SHAPE-dependent on this Mali - // driver: a whole-buffer invalidation renames the store outright, - // and a large range gets fresh pages - but a small unaligned range - // of a busy store makes the map WAIT (osup_sync_object_wait, ~9% - // of a Minecraft 26.3 replay). So: whole buffer -> orphan-map; - // large range -> range-invalidating map; small range -> the staged - // ring copy, whose worst case (a whole-destination ghost) is only - // ever the small destination itself. - // - // The map covers EXACTLY the queued range: only those bytes are the - // shadow's to rewrite. Widening to page bounds looked free and was - // not - the widened bytes clobbered GPU-written data (an SSBO - // counter beside the app's SubData) with the stale shadow. - // The extent the bytes behind `hostBase` are actually good for, clamped - // to the store. With the default it IS [start, end), so the refusal below - // cannot fire and the ladder is unchanged; with a real SEG_STAGE snapshot - // it is the snapshot's window and a disagreement drops this range to the - // staging ring instead of letting a widened INVALIDATE_RANGE declare bytes - // dead that nothing is about to rewrite. - const SizeT coveredFrom = hostBaseFrom > start ? hostBaseFrom : start; - const SizeT coveredTo = hostBaseTo < end ? hostBaseTo : end; + // The invalidating map's fast path is SHAPE-dependent on this Mali + // driver: a whole-buffer invalidation renames the store outright, + // and a large range gets fresh pages - but a small unaligned range + // of a busy store makes the map WAIT (osup_sync_object_wait, ~9% + // of a Minecraft 26.3 replay). So: whole buffer -> orphan-map; + // large range -> range-invalidating map; small range -> the staged + // ring copy, whose worst case (a whole-destination ghost) is only + // ever the small destination itself. + // + // The map covers EXACTLY the queued range: only those bytes are the + // shadow's to rewrite. Widening to page bounds looked free and was + // not - the widened bytes clobbered GPU-written data (an SSBO + // counter beside the app's SubData) with the stale shadow. + // The extent the bytes behind `hostBase` are actually good for, clamped + // to the store. With the default it IS [start, end), so the refusal below + // cannot fire and the ladder is unchanged; with a real SEG_STAGE snapshot + // it is the snapshot's window and a disagreement drops this range to the + // staging ring instead of letting a widened INVALIDATE_RANGE declare bytes + // dead that nothing is about to rewrite. + const SizeT coveredFrom = hostBaseFrom > start ? hostBaseFrom : start; + const SizeT coveredTo = hostBaseTo < end ? hostBaseTo : end; #if MOBILEGL_PIPE_VERIFY - if (coveredFrom != start || coveredTo != end) { - MGLOG_E_ONCE("MGPipe: Fatal{StageSnapshotTooNarrow} flush_pending_ranges: the " - "staged bytes cover [%zu, %zu) and the queued range is [%zu, %zu) " - "- tiers 2 and 3 would copy from outside the snapshot", - hostBaseFrom, hostBaseTo, start, end); - } + if (coveredFrom != start || coveredTo != end) { + MGLOG_E_ONCE("MGPipe: Fatal{StageSnapshotTooNarrow} flush_pending_ranges: the " + "staged bytes cover [%zu, %zu) and the queued range is [%zu, %zu) " + "- tiers 2 and 3 would copy from outside the snapshot", + hostBaseFrom, hostBaseTo, start, end); + } #endif - // Read per range, like the pull arm: a ring copy staged for an earlier range of - // this same drain puts every later partial range of the store behind it - // (InvalidateFlushAccessFor's queued-copy rule, dev f973008c). - const Bool ringCopyInFlight = resource.ringCopyRetireSerial > DirectGLES::CompletedFrameSerial(); - const GLbitfield access = - mapUsable ? InvalidateFlushAccessFor(start, end, coveredFrom, coveredTo, limit, - resource.storageSize, ringCopyInFlight) - : 0u; - if (access != 0) { - BindBufferId(TempBufferTarget, resource.id); - void* dst = g_GLESFuncs.glMapBufferRange(TempBufferTarget, (GLintptr)start, - (GLsizeiptr)size, access); - if (dst) { - Memcpy(dst, hostBase + start, size); - g_GLESFuncs.glUnmapBuffer(TempBufferTarget); - continue; - } - } - SizeT ringOffset = 0; - if (ringUsable && size <= kUploadRingMaxBytes && - RingAllocate(UploadRing(), size, ringOffset)) { - Memcpy(UploadRing().store.mappedPtr + ringOffset, hostBase + start, size); - BindBufferId(GL_COPY_READ_BUFFER, UploadRing().store.id); - BindBufferId(GL_COPY_WRITE_BUFFER, resource.id); - g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, - (GLintptr)ringOffset, (GLintptr)start, (GLsizeiptr)size); - // Commands recorded now retire with the fence the next Present inserts. - resource.ringCopyRetireSerial = DirectGLES::CurrentFrameSerial() + 1; - } else { - UploadRangeFrom(resource, hostBase, start, end); + // Read per range, like the pull arm: a ring copy staged for an earlier range of + // this same drain puts every later partial range of the store behind it + // (InvalidateFlushAccessFor's queued-copy rule, dev f973008c). + const Bool ringCopyInFlight = resource.ringCopyRetireSerial > DirectGLES::CompletedFrameSerial(); + const GLbitfield access = mapUsable + ? InvalidateFlushAccessFor(start, end, coveredFrom, coveredTo, limit, + resource.storageSize, ringCopyInFlight) + : 0u; + if (access != 0) { + BindBufferId(TempBufferTarget, resource.id); + void* dst = + g_GLESFuncs.glMapBufferRange(TempBufferTarget, (GLintptr)start, (GLsizeiptr)size, access); + if (dst) { + Memcpy(dst, hostBase + start, size); + g_GLESFuncs.glUnmapBuffer(TempBufferTarget); + continue; } } + SizeT ringOffset = 0; + if (ringUsable && size <= kUploadRingMaxBytes && RingAllocate(UploadRing(), size, ringOffset)) { + Memcpy(UploadRing().store.mappedPtr + ringOffset, hostBase + start, size); + BindBufferId(GL_COPY_READ_BUFFER, UploadRing().store.id); + BindBufferId(GL_COPY_WRITE_BUFFER, resource.id); + g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, (GLintptr)ringOffset, + (GLintptr)start, (GLsizeiptr)size); + // Commands recorded now retire with the fence the next Present inserts. + resource.ringCopyRetireSerial = DirectGLES::CurrentFrameSerial() + 1; + } else { + UploadRangeFrom(resource, hostBase, start, end); + } + } } namespace { @@ -589,56 +589,53 @@ namespace MobileGL::MG_Backend::DirectGLES { } } // namespace - // Land the app bytes queued for an ADOPTED store on the GPU timeline: staged - // into the upload ring and delivered by glCopyBufferSubData. The destination - // is the IMMUTABLE persistent store, which the driver can neither rename nor - // ghost, so the copy is plain job ordering - after every in-flight reader, - // before the next consumer - which is exactly glBufferSubData's contract. - // (The in-place host write these bytes replaced tore the frames still - // reading the old vertex data: one-frame wrong geometry during fast camera - // movement.) Fallback: direct glBufferSubData - the adopted store carries - // DYNAMIC_STORAGE, and immutability again forbids the whole-store ghost. - // - // It never read the frontend object (the bytes are on the resource's own queue and - // the extent is the backend store's), so it takes none: the handle arm calls exactly - // this function with exactly these semantics. - void DrainResidentWritesNow(GLESBufferResource& resource) { + // Land the app bytes queued for an ADOPTED store on the GPU timeline: staged + // into the upload ring and delivered by glCopyBufferSubData. The destination + // is the IMMUTABLE persistent store, which the driver can neither rename nor + // ghost, so the copy is plain job ordering - after every in-flight reader, + // before the next consumer - which is exactly glBufferSubData's contract. + // (The in-place host write these bytes replaced tore the frames still + // reading the old vertex data: one-frame wrong geometry during fast camera + // movement.) Fallback: direct glBufferSubData - the adopted store carries + // DYNAMIC_STORAGE, and immutability again forbids the whole-store ghost. + // + // It never read the frontend object (the bytes are on the resource's own queue and + // the extent is the backend store's), so it takes none: the handle arm calls exactly + // this function with exactly these semantics. + void DrainResidentWritesNow(GLESBufferResource& resource) { #ifdef TRACY_ENABLE - ZoneScopedC(TRACY_ZONECOLOR_BACKEND); + ZoneScopedC(TRACY_ZONECOLOR_BACKEND); #endif - Vector writes; - { - const std::lock_guard lock(resource.pendingMutex); - if (resource.pendingResidentWrites.empty()) return; - writes = std::move(resource.pendingResidentWrites); - resource.pendingResidentWrites.clear(); + Vector writes; + { + const std::lock_guard lock(resource.pendingMutex); + if (resource.pendingResidentWrites.empty()) return; + writes = std::move(resource.pendingResidentWrites); + resource.pendingResidentWrites.clear(); + } + const SizeT limit = resource.storageSize; + const Bool ringUsable = UploadRingUsableNow(); + for (const auto& write : writes) { + if (write.offset >= limit) continue; + const SizeT size = std::min(write.bytes.size(), limit - write.offset); + if (size == 0) continue; + if (MG_Util::PipeStats::Enabled()) { + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast(size)); } - const SizeT limit = resource.storageSize; - const Bool ringUsable = UploadRingUsableNow(); - for (const auto& write : writes) { - if (write.offset >= limit) continue; - const SizeT size = std::min(write.bytes.size(), limit - write.offset); - if (size == 0) continue; - if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, - static_cast(size)); - } - SizeT ringOffset = 0; - if (ringUsable && size <= kUploadRingMaxBytes && - RingAllocate(UploadRing(), size, ringOffset)) { - Memcpy(UploadRing().store.mappedPtr + ringOffset, write.bytes.data(), size); - BindBufferId(GL_COPY_READ_BUFFER, UploadRing().store.id); - BindBufferId(GL_COPY_WRITE_BUFFER, resource.id); - g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, - (GLintptr)ringOffset, (GLintptr)write.offset, - (GLsizeiptr)size); - } else { - BindBufferId(TempBufferTarget, resource.id); - g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)write.offset, (GLsizeiptr)size, - write.bytes.data()); - } + SizeT ringOffset = 0; + if (ringUsable && size <= kUploadRingMaxBytes && RingAllocate(UploadRing(), size, ringOffset)) { + Memcpy(UploadRing().store.mappedPtr + ringOffset, write.bytes.data(), size); + BindBufferId(GL_COPY_READ_BUFFER, UploadRing().store.id); + BindBufferId(GL_COPY_WRITE_BUFFER, resource.id); + g_GLESFuncs.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, (GLintptr)ringOffset, + (GLintptr)write.offset, (GLsizeiptr)size); + } else { + BindBufferId(TempBufferTarget, resource.id); + g_GLESFuncs.glBufferSubData(TempBufferTarget, (GLintptr)write.offset, (GLsizeiptr)size, + write.bytes.data()); } } + } namespace { // EXT_buffer_storage bit values (same numeric values as the desktop ARB @@ -652,7 +649,7 @@ namespace MobileGL::MG_Backend::DirectGLES { if (!resource) return; auto* glesResource = static_cast(resource.get()); if (glesResource->contextGeneration != g_bufferContextGeneration) { - glesResource->id = 0; // id belonged to a destroyed context + glesResource->id = 0; // id belonged to a destroyed context RetireT0Import(*glesResource); // P11 B2: its GL store died with that context return; } @@ -732,8 +729,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // resource_subdata records right behind this record), so in practice // this arm is the monolith one and the else arm is the split one. resource->hostBytes = MGL_SERVER_STAGED_ADOPT(*resource, static_cast(desc.Width), - initialBytes, 0, - static_cast(desc.Width)); + initialBytes, 0, static_cast(desc.Width)); } else { MGL_SERVER_STAGED_DROP(*resource); resource->hostBytes = nullptr; @@ -771,8 +767,7 @@ namespace MobileGL::MG_Backend::DirectGLES { resource->pendingResidentWrites.clear(); return; } - if (!CanTouchGLNow() || resource->id == 0 || - resource->contextGeneration != g_bufferContextGeneration) { + if (!CanTouchGLNow() || resource->id == 0 || resource->contextGeneration != g_bufferContextGeneration) { resource->pendingRespecify = true; resource->pendingRanges.clear(); resource->pendingResidentWrites.clear(); @@ -842,9 +837,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // applier has already counted THIS write into the record's serial, so the ensure // of a built store would read it as an untracked mutation and re-upload the // shadow over the runs this landing wrote before it. - if (CanTouchGLNow() && (resource->id == 0 || !resource->storageInitialized || - resource->pendingRespecify || - resource->contextGeneration != g_bufferContextGeneration)) { + if (CanTouchGLNow() && + (resource->id == 0 || !resource->storageInitialized || resource->pendingRespecify || + resource->contextGeneration != g_bufferContextGeneration)) { resource = EnsureBufferResourceForHandle(nullptr, res); } if (resource != nullptr && CanTouchGLNow() && resource->id != 0 && resource->storageInitialized && @@ -889,13 +884,11 @@ namespace MobileGL::MG_Backend::DirectGLES { // into server-owned storage and points hostBytes at that. Inside the SAME // lock as the queued range, because the base and the range are one fact // ("these bytes, at this base") and the drain takes this mutex to lift them. - resource->hostBytes = - MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, offset, size); + resource->hostBytes = MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, offset, size); if (size != 0) resource->contentSinceRespecify = true; } if (resource->pendingRespecify) return; // full re-upload pending anyway - if (!CanTouchGLNow() || resource->id == 0 || - resource->contextGeneration != g_bufferContextGeneration || + if (!CanTouchGLNow() || resource->id == 0 || resource->contextGeneration != g_bufferContextGeneration || !StorageMatchesSize(*resource, ResourceWidthOf(res))) { const std::lock_guard lock(resource->pendingMutex); resource->pendingRanges.Add({offset, offset + size}); @@ -934,8 +927,7 @@ namespace MobileGL::MG_Backend::DirectGLES { write.bytes.assign(source, source + size); } - void Ops_H_FlushRange(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPFlushRange& record, - const void* bytes) { + void Ops_H_FlushRange(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPFlushRange& record, const void* bytes) { auto* resource = FindBufferResourceForHandle(res); if (!resource) return; const SizeT start = static_cast(record.Offset); @@ -950,13 +942,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // exactly what the ladder must read. if (bytes != nullptr) { const std::lock_guard lock(resource->pendingMutex); - resource->hostBytes = MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, - start, end - start); + resource->hostBytes = + MGL_SERVER_STAGED_ADOPT(*resource, ResourceWidthOf(res), bytes, start, end - start); if (end > start) resource->contentSinceRespecify = true; } if (resource->pendingRespecify) return; - if (!CanTouchGLNow() || resource->id == 0 || - resource->contextGeneration != g_bufferContextGeneration || + if (!CanTouchGLNow() || resource->id == 0 || resource->contextGeneration != g_bufferContextGeneration || !StorageMatchesSize(*resource, ResourceWidthOf(res))) { const std::lock_guard lock(resource->pendingMutex); resource->pendingRanges.Add({start, end}); @@ -987,13 +978,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // GL_MAP_INVALIDATE_RANGE_BIT - i.e. the one that tells the driver the old // bytes are dead. Bytes outside the staged coverage are exactly the ones // that claim is false for. - MGL_SERVER_STAGED_REQUIRE(*resource, resource->hostBytes, start, end, - "flush_range_invalidate_map"); + MGL_SERVER_STAGED_REQUIRE(*resource, resource->hostBytes, start, end, "flush_range_invalidate_map"); BindBufferId(TempBufferTarget, resource->id); - void* mappedData = g_GLESFuncs.glMapBufferRange( - TempBufferTarget, (GLintptr)start, (GLsizeiptr)(end - start), - GL_MAP_WRITE_BIT | (invalidate ? GL_MAP_INVALIDATE_RANGE_BIT : 0) | - (unsynchronized ? GL_MAP_UNSYNCHRONIZED_BIT : 0)); + void* mappedData = + g_GLESFuncs.glMapBufferRange(TempBufferTarget, (GLintptr)start, (GLsizeiptr)(end - start), + GL_MAP_WRITE_BIT | (invalidate ? GL_MAP_INVALIDATE_RANGE_BIT : 0) | + (unsynchronized ? GL_MAP_UNSYNCHRONIZED_BIT : 0)); if (mappedData) { Memcpy(mappedData, resource->hostBytes + start, end - start); g_GLESFuncs.glUnmapBuffer(TempBufferTarget); @@ -1031,8 +1021,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // backend has not applied yet) would otherwise be mapped past its end. const SizeT readOffset = static_cast(record.Offset); if (readOffset >= resource->storageSize) return; - const SizeT size = - std::min(static_cast(record.Size), resource->storageSize - readOffset); + const SizeT size = std::min(static_cast(record.Size), resource->storageSize - readOffset); if (size == 0) return; // Queued app writes must land in the backend store before it is read back, or @@ -1067,10 +1056,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // draw-clean memo re-probed by the bump can never observe a // half-reconciled resource. The bump must NEVER move earlier. if (MG_Pipe::gMGPipeCallbacks.OnBufferWriteback != nullptr) { - MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( - res, record.Offset, - MG_Pipe::MGPBlobRef{reinterpret_cast(mapped), static_cast(size), - MG_Pipe::kMGHostSpanSegNone, 0}); + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(res, record.Offset, + MG_Pipe::MGPBlobRef{reinterpret_cast(mapped), + static_cast(size), + MG_Pipe::kMGHostSpanSegNone, 0}); } else { MGLOG_E_ONCE("Ops_H_Readback: no reverse channel is installed, so the GPU-written bytes of " "buffer %u cannot reach the client shadow", @@ -1214,8 +1203,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Ops_H_Respecify(res, desc, initialBytes); BumpBufferMutationEpoch(); } - void Ops_H_SubDataTracked(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPSubData& record, - const void* bytes) { + void Ops_H_SubDataTracked(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPSubData& record, const void* bytes) { Ops_H_SubData(res, record, bytes); BumpBufferMutationEpoch(); } @@ -1303,8 +1291,7 @@ namespace MobileGL::MG_Backend::DirectGLES { void DrainGLErrors() { if (!g_GLESFuncs.glGetError) return; - 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) {} } // A fresh GL buffer whose storage IS `ahb`, mapped persistent + coherent. 0 on failure @@ -1326,14 +1313,17 @@ namespace MobileGL::MG_Backend::DirectGLES { return 0; } DrainGLErrors(); - if (throwaway) g_GLESFuncs.glBindBuffer(TempBufferTarget, id); - else BindBufferId(TempBufferTarget, id); + if (throwaway) + g_GLESFuncs.glBindBuffer(TempBufferTarget, id); + else + BindBufferId(TempBufferTarget, id); g_t0BufferStorageExternal(TempBufferTarget, 0, static_cast(size), clientBuffer, kT0StorageFlags); const GLenum stored = g_GLESFuncs.glGetError ? g_GLESFuncs.glGetError() : GL_NO_ERROR; - void* ptr = stored == GL_NO_ERROR - ? g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(size), kT0MapFlags) - : nullptr; + void* ptr = + stored == GL_NO_ERROR + ? g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(size), kT0MapFlags) + : nullptr; if (ptr == nullptr) { char text[160]; std::snprintf(text, sizeof(text), "glBufferStorageExternalEXT -> 0x%04x, map %s", stored, @@ -1410,8 +1400,8 @@ namespace MobileGL::MG_Backend::DirectGLES { MGL_SERVER_STAGED_DROP(*resource); resource->hostBytes = nullptr; resource->syncedChangeSerial = ResourceSerialOf(res); - MGLOG_D("Espryt T0: imported {slot=%u, gen=%u} as buffer %u (%llu bytes); %llu live imports", - res.Slot, res.Gen, id, static_cast(size), + MGLOG_D("Espryt T0: imported {slot=%u, gen=%u} as buffer %u (%llu bytes); %llu live imports", res.Slot, + res.Gen, id, static_cast(size), static_cast(g_t0ImportsLive)); return true; } @@ -1437,16 +1427,15 @@ namespace MobileGL::MG_Backend::DirectGLES { cached ^= held[kFillAtWord + i]; } (void)cached; - static const char* kSource = - "#version 310 es\n" - "layout(local_size_x = 64) in;\n" - "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" - "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" - "void main() {\n" - " uint i = gl_GlobalInvocationID.x;\n" - " outb.w[i] = ext.w[1024u + i];\n" - " ext.w[4096u + i] = 0x3D5EA11Eu ^ i;\n" - "}\n"; + static const char* kSource = "#version 310 es\n" + "layout(local_size_x = 64) in;\n" + "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" + "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" + "void main() {\n" + " uint i = gl_GlobalInvocationID.x;\n" + " outb.w[i] = ext.w[1024u + i];\n" + " ext.w[4096u + i] = 0x3D5EA11Eu ^ i;\n" + "}\n"; const GLuint program = g_GLESFuncs.glCreateShaderProgramv(GL_COMPUTE_SHADER, 1, &kSource); GLint linked = 0; if (program != 0) g_GLESFuncs.glGetProgramiv(program, GL_LINK_STATUS, &linked); @@ -1520,10 +1509,12 @@ namespace MobileGL::MG_Backend::DirectGLES { return answer(false, "no ES3 pbuffer config for the throwaway context"); const EGLint surfaceAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE}; const EGLSurface surface = g_EGLFuncs.eglCreatePbufferSurface(display, config, surfaceAttribs); - const EGLint contextAttribs[] = {0x3098 /*EGL_CONTEXT_MAJOR_VERSION*/, 3, 0x30FB /*MINOR*/, 1, EGL_NONE}; - const EGLContext context = surface != EGL_NO_SURFACE - ? g_EGLFuncs.eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs) - : EGL_NO_CONTEXT; + const EGLint contextAttribs[] = {0x3098 /*EGL_CONTEXT_MAJOR_VERSION*/, 3, 0x30FB /*MINOR*/, 1, + EGL_NONE}; + const EGLContext context = + surface != EGL_NO_SURFACE + ? g_EGLFuncs.eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs) + : EGL_NO_CONTEXT; const auto restore = [&] { g_EGLFuncs.eglMakeCurrent(display, savedDraw, savedRead, savedContext); if (context != EGL_NO_CONTEXT) g_EGLFuncs.eglDestroyContext(display, context); @@ -1535,11 +1526,12 @@ namespace MobileGL::MG_Backend::DirectGLES { } constexpr Uint64 kBytes = 64 * 1024; - constexpr Uint32 kWords = 1024; // one 4 KiB region + constexpr Uint32 kWords = 1024; // one 4 KiB region constexpr Uint32 kFillAtWord = 4 * kWords; // region 4 MG_Remote::Transport::AdoptT0::HeldStore held; Vector pattern(kBytes / 4); - for (SizeT i = 0; i < pattern.size(); ++i) pattern[i] = 0x5A000000u ^ static_cast(i * 2654435761u); + for (SizeT i = 0; i < pattern.size(); ++i) + pattern[i] = 0x5A000000u ^ static_cast(i * 2654435761u); if (!MG_Remote::Transport::AdoptT0::AllocateHeld(kBytes, pattern.data(), held, why)) { restore(); return answer(false, "hold: %s", why.c_str()); @@ -1552,19 +1544,19 @@ namespace MobileGL::MG_Backend::DirectGLES { if (imported == 0) { std::snprintf(detail, sizeof(detail), "import: %s", why.c_str()); } else if (std::memcmp(hostMap, pattern.data(), kWords * 4) != 0) { - std::snprintf(detail, sizeof(detail), "read: the server's host map of the import does not " - "hold the CPU's pattern"); + std::snprintf(detail, sizeof(detail), + "read: the server's host map of the import does not " + "hold the CPU's pattern"); } else { - static const char* kSource = - "#version 310 es\n" - "layout(local_size_x = 64) in;\n" - "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" - "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" - "void main() {\n" - " uint i = gl_GlobalInvocationID.x;\n" - " outb.w[i] = ext.w[i];\n" - " ext.w[4096u + i] = 0x6C0FFEE0u ^ i;\n" - "}\n"; + static const char* kSource = "#version 310 es\n" + "layout(local_size_x = 64) in;\n" + "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" + "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" + "void main() {\n" + " uint i = gl_GlobalInvocationID.x;\n" + " outb.w[i] = ext.w[i];\n" + " ext.w[4096u + i] = 0x6C0FFEE0u ^ i;\n" + "}\n"; program = g_GLESFuncs.glCreateShaderProgramv(GL_COMPUTE_SHADER, 1, &kSource); GLint linked = 0; if (program != 0) g_GLESFuncs.glGetProgramiv(program, GL_LINK_STATUS, &linked); @@ -1586,8 +1578,8 @@ namespace MobileGL::MG_Backend::DirectGLES { for (Uint32 i = 0; i < kWords; ++i) if (heldWords[kFillAtWord + i] == (0x6C0FFEE0u ^ i)) ++heldSaw; g_GLESFuncs.glBindBuffer(GL_SHADER_STORAGE_BUFFER, out); - const auto* copied = static_cast(g_GLESFuncs.glMapBufferRange( - GL_SHADER_STORAGE_BUFFER, 0, kWords * 4, GL_MAP_READ_BIT)); + const auto* copied = static_cast( + g_GLESFuncs.glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kWords * 4, GL_MAP_READ_BIT)); if (copied != nullptr) { for (Uint32 i = 0; i < kWords; ++i) if (copied[i] == pattern[i]) ++gpuRead; @@ -1612,86 +1604,110 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif // MOBILEGL_BUILD_DISAGGREGATED && __ANDROID__ } // namespace - // ---- P5c (tx): the TEXTURE half of the resource family, staged server-side -------- - // - // The buffer half's R-11 pattern (ServerStaged() above), for texture levels: the - // staged bytes of resource_subdata's texture half are adopted into the server's - // StagedTextureStore AT APPLY TIME, because `bytes` names SEG_STAGE and is dead the - // moment the record retires (rule C). The store, its ownership and its coverage - // rules are documented in MG_Remote/Server/StagedTextureStore.h; these are only the - // three hook bodies. All three are no-ops in monolith (CopiesIntoServerStorage() - // false), which is what keeps the monolith expression character for character. - // - // NOTHING here mints or reads a texture twin: the store is keyed by the wire handle - // the record carried (StagedTextureStore.h explains why not the twin address), so - // adoption needs no GL call and no frontend object. + // ---- P5c (tx): the TEXTURE half of the resource family, staged server-side -------- + // + // The buffer half's R-11 pattern (ServerStaged() above), for texture levels: the + // staged bytes of resource_subdata's texture half are adopted into the server's + // StagedTextureStore AT APPLY TIME, because `bytes` names SEG_STAGE and is dead the + // moment the record retires (rule C). The store, its ownership and its coverage + // rules are documented in MG_Remote/Server/StagedTextureStore.h; these are only the + // three hook bodies. All three are no-ops in monolith (CopiesIntoServerStorage() + // false), which is what keeps the monolith expression character for character. + // + // NOTHING here mints or reads a texture twin: the store is keyed by the wire handle + // the record carried (StagedTextureStore.h explains why not the twin address), so + // adoption needs no GL call and no frontend object. - // GetUploadTargets() answered from the descriptor's Target: one static list per - // target, matching the frontend classes' own lists member for member - // (TextureObject*.h / TextureObjectStubs.h: a cube is six faces, a cube array is - // the single CubeMapArray target, everything else its own one). - const Vector& StagedUploadTargetsForPipeTarget(Uint8 pipeResourceTarget) { - static const Vector kUnknown{}; - static const Vector kTex1D{TextureUploadTarget::Texture1D}; - static const Vector kTex2D{TextureUploadTarget::Texture2D}; - static const Vector kTex3D{TextureUploadTarget::Texture3D}; - static const Vector kTex1DArray{TextureUploadTarget::Texture1DArray}; - static const Vector kTex2DArray{TextureUploadTarget::Texture2DArray}; - static const Vector kTexCube{ - TextureUploadTarget::CubeMapPositiveX, TextureUploadTarget::CubeMapNegativeX, - TextureUploadTarget::CubeMapPositiveY, TextureUploadTarget::CubeMapNegativeY, - TextureUploadTarget::CubeMapPositiveZ, TextureUploadTarget::CubeMapNegativeZ}; - static const Vector kTexCubeArray{TextureUploadTarget::CubeMapArray}; - static const Vector kTex2DMS{TextureUploadTarget::Texture2DMultisample}; - static const Vector kTex2DMSArray{TextureUploadTarget::Texture2DMultisampleArray}; - static const Vector kTexRect{TextureUploadTarget::TextureRectangle}; - static const Vector kTexBuffer{TextureUploadTarget::TextureBuffer}; - switch (static_cast(pipeResourceTarget)) { - case MG_Pipe::MGPipeResourceTarget::Tex1D: return kTex1D; - case MG_Pipe::MGPipeResourceTarget::Tex2D: return kTex2D; - case MG_Pipe::MGPipeResourceTarget::Tex3D: return kTex3D; - case MG_Pipe::MGPipeResourceTarget::Tex1DArray: return kTex1DArray; - case MG_Pipe::MGPipeResourceTarget::Tex2DArray: return kTex2DArray; - case MG_Pipe::MGPipeResourceTarget::TexCube: return kTexCube; - case MG_Pipe::MGPipeResourceTarget::TexCubeArray: return kTexCubeArray; - case MG_Pipe::MGPipeResourceTarget::Tex2DMS: return kTex2DMS; - case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray: return kTex2DMSArray; - case MG_Pipe::MGPipeResourceTarget::TexRect: return kTexRect; - case MG_Pipe::MGPipeResourceTarget::TexBuffer: return kTexBuffer; - default: return kUnknown; - } + // GetUploadTargets() answered from the descriptor's Target: one static list per + // target, matching the frontend classes' own lists member for member + // (TextureObject*.h / TextureObjectStubs.h: a cube is six faces, a cube array is + // the single CubeMapArray target, everything else its own one). + const Vector& StagedUploadTargetsForPipeTarget(Uint8 pipeResourceTarget) { + static const Vector kUnknown{}; + static const Vector kTex1D{TextureUploadTarget::Texture1D}; + static const Vector kTex2D{TextureUploadTarget::Texture2D}; + static const Vector kTex3D{TextureUploadTarget::Texture3D}; + static const Vector kTex1DArray{TextureUploadTarget::Texture1DArray}; + static const Vector kTex2DArray{TextureUploadTarget::Texture2DArray}; + static const Vector kTexCube{ + TextureUploadTarget::CubeMapPositiveX, TextureUploadTarget::CubeMapNegativeX, + TextureUploadTarget::CubeMapPositiveY, TextureUploadTarget::CubeMapNegativeY, + TextureUploadTarget::CubeMapPositiveZ, TextureUploadTarget::CubeMapNegativeZ}; + static const Vector kTexCubeArray{TextureUploadTarget::CubeMapArray}; + static const Vector kTex2DMS{TextureUploadTarget::Texture2DMultisample}; + static const Vector kTex2DMSArray{TextureUploadTarget::Texture2DMultisampleArray}; + static const Vector kTexRect{TextureUploadTarget::TextureRectangle}; + static const Vector kTexBuffer{TextureUploadTarget::TextureBuffer}; + switch (static_cast(pipeResourceTarget)) { + case MG_Pipe::MGPipeResourceTarget::Tex1D: + return kTex1D; + case MG_Pipe::MGPipeResourceTarget::Tex2D: + return kTex2D; + case MG_Pipe::MGPipeResourceTarget::Tex3D: + return kTex3D; + case MG_Pipe::MGPipeResourceTarget::Tex1DArray: + return kTex1DArray; + case MG_Pipe::MGPipeResourceTarget::Tex2DArray: + return kTex2DArray; + case MG_Pipe::MGPipeResourceTarget::TexCube: + return kTexCube; + case MG_Pipe::MGPipeResourceTarget::TexCubeArray: + return kTexCubeArray; + case MG_Pipe::MGPipeResourceTarget::Tex2DMS: + return kTex2DMS; + case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray: + return kTex2DMSArray; + case MG_Pipe::MGPipeResourceTarget::TexRect: + return kTexRect; + case MG_Pipe::MGPipeResourceTarget::TexBuffer: + return kTexBuffer; + default: + return kUnknown; } + } -// P5e (ID-124): OUT OF THE DISAGGREGATED BLOCK ON PURPOSE. This is a pure -// MGPipeResourceTarget -> TextureTarget switch with no wire, no record and no role in it, but -// it sat inside the nested `#if MOBILEGL_BUILD_DISAGGREGATED` below while its only caller, -// MGB_TEXPARAM_TARGET, is guarded by `#if MOBILEGL_PIPE_PUSH` alone - so a push build without -// disaggregation did not compile. The narrower fix (guarding the macro block too) is wrong: -// that block also carries TextureDiagName and MGB_TEXTURE_RECORD_ARM_SELECTED, which the push -// flavour does need. Caught by the `flavours` gate step; see ID-124. - // The inverse of MG_Pipe::MGPipeResourceTargetForTextureTarget, for the sync's - // target reads (ConvertTextureTargetToBackendGLEnum and MapToBackendTextureTarget - // both want the frontend enum). - TextureTarget StagedTextureTargetForPipeTarget(Uint8 pipeResourceTarget) { - switch (static_cast(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; - } + // P5e (ID-124): OUT OF THE DISAGGREGATED BLOCK ON PURPOSE. This is a pure + // MGPipeResourceTarget -> TextureTarget switch with no wire, no record and no role in it, but + // it sat inside the nested `#if MOBILEGL_BUILD_DISAGGREGATED` below while its only caller, + // MGB_TEXPARAM_TARGET, is guarded by `#if MOBILEGL_PIPE_PUSH` alone - so a push build without + // disaggregation did not compile. The narrower fix (guarding the macro block too) is wrong: + // that block also carries TextureDiagName and MGB_TEXTURE_RECORD_ARM_SELECTED, which the push + // flavour does need. Caught by the `flavours` gate step; see ID-124. + // The inverse of MG_Pipe::MGPipeResourceTargetForTextureTarget, for the sync's + // target reads (ConvertTextureTargetToBackendGLEnum and MapToBackendTextureTarget + // both want the frontend enum). + TextureTarget StagedTextureTargetForPipeTarget(Uint8 pipeResourceTarget) { + switch (static_cast(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; } + } namespace { - void Ops_H_TextureSubData(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPSubData& record, - const void* bytes, const MG_Pipe::MGPSubRegion* regions) { + void Ops_H_TextureSubData(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPSubData& record, const void* bytes, + const MG_Pipe::MGPSubRegion* regions) { // The region set is the upload planner's shape and stays in the applier's // pending set; the store's coverage is the staged run itself // (StagedTextureStore.h's coverage ruling). It is ALSO what places that run in @@ -1728,8 +1744,8 @@ namespace MobileGL::MG_Backend::DirectGLES { boxes.push_back({region.X, region.Y, region.Z, region.W, region.H, region.D}); } } - const Uint64 imageOffset = record.RegionCount == 0 - ? 0 : MG_Record::StagedTextureRunImageOffset(record, regions); + const Uint64 imageOffset = + record.RegionCount == 0 ? 0 : MG_Record::StagedTextureRunImageOffset(record, regions); if (store.AdoptClientBoxes(key, uploadTarget, record.Level, extent, imageOffset, bytes, runBytes, boxes.data(), boxes.size())) { return; @@ -1746,8 +1762,8 @@ namespace MobileGL::MG_Backend::DirectGLES { return; } store.AdoptRun(key, uploadTarget, record.Level, extent, - MG_Record::StagedTextureRunImageOffset(record, regions), bytes, - runBytes, /*frontendShadow=*/true); + MG_Record::StagedTextureRunImageOffset(record, regions), bytes, runBytes, + /*frontendShadow=*/true); } void Ops_H_TextureRespecify(MG_Pipe::MGPipeHandle res, const MG_Pipe::MGPResourceDesc& desc, @@ -1758,11 +1774,10 @@ namespace MobileGL::MG_Backend::DirectGLES { if (level != nullptr) { // ONE glTexImage*D redefined one level: its exact extent crossed with the // accepted scope carrier, including a legal noncanonical mutable mip. - store.NoteLevelDefined( - key, MG_Pipe::MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level, - IntVec3{static_cast(level->Width), static_cast(level->Height), - static_cast(level->Depth)}, - desc.Target, desc.InternalFormat); + store.NoteLevelDefined(key, MG_Pipe::MGPipeSubDataUploadTargetOf(level->UploadTarget), level->Level, + IntVec3{static_cast(level->Width), static_cast(level->Height), + static_cast(level->Depth)}, + desc.Target, desc.InternalFormat); return; } // A whole-resource redefinition: every level's old coordinate system is gone. @@ -1774,11 +1789,10 @@ namespace MobileGL::MG_Backend::DirectGLES { if (desc.Immutable == 0 || desc.Levels == 0) return; for (const auto& uploadTarget : StagedUploadTargetsForPipeTarget(desc.Target)) { for (Uint32 levelIndex = 0; levelIndex < desc.Levels; ++levelIndex) { - store.NoteLevelDefined( - key, static_cast(uploadTarget), static_cast(levelIndex), - MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, - desc.Depth, levelIndex), - desc.Target, desc.InternalFormat); + store.NoteLevelDefined(key, static_cast(uploadTarget), static_cast(levelIndex), + MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, + desc.Depth, levelIndex), + desc.Target, desc.InternalFormat); } } } @@ -1787,8 +1801,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Deliberately NOT gated on CopiesIntoServerStorage(): Drop's own m_any gate // makes the monolith call one acquire load, and an unconditional drop cannot // strand a key the latch state was misread for. - MG_Record::ServerStagedTexture().Drop( - MG_Record::StagedTextureStore::KeyForHandle(res)); + MG_Record::ServerStagedTexture().Drop(MG_Record::StagedTextureStore::KeyForHandle(res)); } const MG_Pipe::MGPipeResourceOps g_glesResourceOps = { @@ -1865,8 +1878,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // call them from the test thread beside a started loop) are the server role without // being the apply thread, and taking those down would be this pin policing a // fixture rather than the draw path. - const Uint64 applyThreadKey = - MG_Remote::Server::Detail::g_applyThreadKey.load(std::memory_order_relaxed); + const Uint64 applyThreadKey = MG_Remote::Server::Detail::g_applyThreadKey.load(std::memory_order_relaxed); if (MG_Config::Transport != MG_Config::TransportMode::Monolith && applyThreadKey != 0 && !MG_Remote::Server::ServerLoop::OnApplyThread()) { g_bufferMutationEpochBumpsOffTheApplyThread.fetch_add(1, std::memory_order_relaxed); @@ -1941,8 +1953,8 @@ namespace MobileGL::MG_Backend::DirectGLES { return twin ? twin->get() : nullptr; } - 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) { ServerStaged().RequireCoverage(&resource, hostBase, start, end, site); } @@ -1978,9 +1990,15 @@ namespace MobileGL::MG_Backend::DirectGLES { resource.hostBytes = MGL_SERVER_STAGED_ADOPT(resource, ResourceWidthOf(res), bytes, offset, size); } - SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res) { return ResourceWidthOf(res); } - Uint64 ResourceSerialForHandle(MG_Pipe::MGPipeHandle res) { return ResourceSerialOf(res); } - Uint CurrentBufferContextGeneration() { return g_bufferContextGeneration; } + SizeT ResourceWidthForHandle(MG_Pipe::MGPipeHandle res) { + return ResourceWidthOf(res); + } + Uint64 ResourceSerialForHandle(MG_Pipe::MGPipeHandle res) { + return ResourceSerialOf(res); + } + Uint CurrentBufferContextGeneration() { + return g_bufferContextGeneration; + } // See the declaration: re-mints of a live resource's driver id. Written only on // the context thread (all re-mint sites run there), read only by the VAO sync. @@ -1988,10 +2006,10 @@ namespace MobileGL::MG_Backend::DirectGLES { void RegisterBufferBackendOps() { // P3a: the handle-shaped resource table (P13: the only one - the legacy - // BufferBackendOps table is deleted). Registration is UNCONDITIONAL: the subsystem bit is the FRONTEND's dispatch predicate - // (MGPipeResourceSubsystemEnabled reads the bit AND this table's presence), so a - // build with bit 7 clear registers a table nobody calls and the A/B stays a pure - // configuration question rather than a bring-up-order one. + // BufferBackendOps table is deleted). Registration is UNCONDITIONAL: the subsystem bit is the FRONTEND's + // dispatch predicate (MGPipeResourceSubsystemEnabled reads the bit AND this table's presence), so a build + // with bit 7 clear registers a table nobody calls and the A/B stays a pure configuration question rather + // than a bring-up-order one. MG_Pipe::MGPipeSetResourceOps(&g_glesResourceOps); // Frontend writes issued while ops were unregistered advanced change // serials with no per-op bump; re-open every draw-clean memo. Advanced directly, @@ -2114,7 +2132,8 @@ namespace MobileGL::MG_Backend::DirectGLES { } g_hasT0DeferredDestroys.store(!g_t0DeferredDestroys.empty(), std::memory_order_release); } - for (auto& resource : ready) RetireT0Import(*static_cast(resource.get())); + for (auto& resource : ready) + RetireT0Import(*static_cast(resource.get())); } #endif @@ -2186,8 +2205,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // ROADMAP/D-N explicitly keeps where it is for P3a (the per-site attribution table is // P8's to execute). Every other line here is handle-shaped. When P8 moves that call to // the client this signature loses its last frontend argument. - GLESBufferResource* EnsureBufferResourceForHandle(const SharedPtr& bufferObject, - MG_Pipe::MGPipeHandle res) { + GLESBufferResource* EnsureBufferResourceForHandle( + const SharedPtr& bufferObject, MG_Pipe::MGPipeHandle res) { auto* resource = GetOrCreateBufferResourceForHandle(res); if (!resource) return nullptr; @@ -2240,8 +2259,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // run on every twin's first ensure - harmless there only because liveHostBase() // prefers MappedData() under monolith, and "under monolith nothing changes" should // be true by construction rather than by luck. - if (ServerStaged().CopiesIntoServerStorage() && - !ServerStaged().HasShadow(resource)) { + if (ServerStaged().CopiesIntoServerStorage() && !ServerStaged().HasShadow(resource)) { resource->hostBytes = nullptr; } #if defined(__ANDROID__) && MOBILEGL_BUILD_DISAGGREGATED @@ -2253,8 +2271,8 @@ namespace MobileGL::MG_Backend::DirectGLES { String why; void* ptr = nullptr; const Uint64 size = ResourceWidthOf(res); - const Uint id = ResolveT0EntryPoints(why) ? ImportAhbAsBuffer(resource->externalAhb, size, &ptr, why) - : 0; + const Uint id = + ResolveT0EntryPoints(why) ? ImportAhbAsBuffer(resource->externalAhb, size, &ptr, why) : 0; if (id != 0) { InstallT0Store(*resource, id, ptr, size); MGLOG_I("Espryt T0: store {slot=%u, gen=%u} re-imported on the new context (%llu bytes)", @@ -2294,8 +2312,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // a buffer recycling pool ids - a change to D-F's pool behaviour, not to "the // source of bytes" the conversion is allowed to move, and one that moves // AcquireFromPool call counts StreamedArenaScenario observes. - const Uint reused = - (poolSize > 0 && !resource->persistentMapped) ? AcquireFromPool(poolSize) : 0; + const Uint reused = (poolSize > 0 && !resource->persistentMapped) ? AcquireFromPool(poolSize) : 0; if (reused != 0) { resource->id = reused; resource->storageSize = poolSize; @@ -2311,8 +2328,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // legal (ResourceContentIsDeclared, above). RequireCoverage is a no-op for the // legacy arm's MappedData() and for a non-copying store. if (ResourceContentIsDeclared(res)) { - MGL_SERVER_STAGED_REQUIRE(*resource, liveHostBase(), 0, poolSize, - "pool_reuse_whole_store"); + MGL_SERVER_STAGED_REQUIRE(*resource, liveHostBase(), 0, poolSize, "pool_reuse_whole_store"); } g_GLESFuncs.glBufferSubData(TempBufferTarget, 0, (GLsizeiptr)poolSize, liveHostBase()); if (MG_Util::PipeStats::Enabled()) { @@ -2387,8 +2403,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // application's own declaration and the upload stays a pure NULL reallocation. const Bool shadowHasContent = (record->Desc.HasDefinedContent != 0 || - (ServerStaged().CopiesIntoServerStorage() ? ServerStaged().CoveredRunCount(resource) != 0 - : resource->contentSinceRespecify)); + (ServerStaged().CopiesIntoServerStorage() ? ServerStaged().CoveredRunCount(resource) != 0 + : resource->contentSinceRespecify)); const void* initialData = shadowHasContent ? hostBase : nullptr; // M-3 / codex 4: a RespecifyStorageWith(..., initialData != nullptr) is a WHOLE-STORE // [0, size) upload from the base, so it owes the same coverage the pending-range drain @@ -2403,8 +2419,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // ResourceContentIsDeclared. Round 2 refused that idiom and aborted six joint entries. const auto requireWholeStoreCoverage = [&]() { if (initialData != nullptr && record->Desc.HasDefinedContent != 0) { - MGL_SERVER_STAGED_REQUIRE(*resource, static_cast(initialData), 0, - size, "respecify_whole_store"); + MGL_SERVER_STAGED_REQUIRE(*resource, static_cast(initialData), 0, size, + "respecify_whole_store"); } }; @@ -2566,7 +2582,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // are whatever the newly bound object holds, which is NOT necessarily base(0), and // scrubbing would let a later bind of 0 be false-skipped. void InvalidateTransformFeedbackBindingShadows() { - for (auto& binding : g_indexedXFBBindings) binding.known = false; + for (auto& binding : g_indexedXFBBindings) + binding.known = false; } void BindBufferBaseCached(GLenum glTarget, Uint index, Uint id) { @@ -2584,9 +2601,12 @@ namespace MobileGL::MG_Backend::DirectGLES { } void InvalidateIndexedBufferBindingCache() { - for (auto& b : g_indexedUBOBindings) b = {}; - for (auto& b : g_indexedSSBOBindings) b = {}; - for (auto& b : g_indexedXFBBindings) b = {}; + for (auto& b : g_indexedUBOBindings) + b = {}; + for (auto& b : g_indexedSSBOBindings) + b = {}; + for (auto& b : g_indexedXFBBindings) + b = {}; } void TrimBufferPool() { @@ -2629,67 +2649,68 @@ namespace MobileGL::MG_Backend::DirectGLES { } // --- Persistent-mapped bump rings (see Managers.h) ------------------------ - // (Re)create the ring store with room for at least minBytes. Any live - // store is retired (deleted once the GPU finished the last frame that - // could reference its slots), never deleted in place. Returns false and - // leaves the current store untouched when minBytes cannot fit under the - // size cap; a GL failure loses the store and latches creationFailed so - // later uses stop retrying under this context. - Bool CreateRingStorage(PersistentRing& ring, SizeT minBytes) { - SizeT newSize = ring.initialBytes; - while (newSize < minBytes) newSize *= 2; - if (newSize > ring.maxBytes) return false; + // (Re)create the ring store with room for at least minBytes. Any live + // store is retired (deleted once the GPU finished the last frame that + // could reference its slots), never deleted in place. Returns false and + // leaves the current store untouched when minBytes cannot fit under the + // size cap; a GL failure loses the store and latches creationFailed so + // later uses stop retrying under this context. + Bool CreateRingStorage(PersistentRing& ring, SizeT minBytes) { + SizeT newSize = ring.initialBytes; + while (newSize < minBytes) + newSize *= 2; + if (newSize > ring.maxBytes) return false; - if (ring.store.id != 0) { - ring.retired.push_back( - {ring.store.id, ring.store.contextGeneration, DirectGLES::CurrentFrameSerial() + 1}); - } - const Uint32 nextGeneration = ring.store.generation + 1; - ring.store.id = 0; - ring.store.mappedPtr = nullptr; - - Uint id = 0; - g_GLESFuncs.glGenBuffers(1, &id); - if (id != 0) { - BindBufferId(TempBufferTarget, id); - g_GLESFuncs.glBufferStorageEXT(TempBufferTarget, static_cast(newSize), nullptr, - GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit); - void* ptr = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(newSize), - GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit); - if (!ptr) { - // The dying id is what the array-buffer cache has recorded as - // bound; a later buffer recycling the name would false-skip. - InvalidateArrayBufferBindingCache(); - g_GLESFuncs.glDeleteBuffers(1, &id); - id = 0; - } else { - ring.store.mappedPtr = static_cast(ptr); - } - } - if (id == 0) { - MGLOG_E_ONCE("%s: persistent storage creation failed (%zu bytes); falling back to the " - "client-memory upload path.", - ring.label, newSize); - ring.store.creationFailed = true; - return false; - } - - SizeT alignment = ring.fixedAlignment; - if (alignment == 0) { - const GLint capsAlignment = g_GLESCapabilities.UniformBufferOffsetAlignment; - alignment = capsAlignment > 0 ? static_cast(capsAlignment) : 256; - } - ring.store.id = id; - ring.store.size = newSize; - ring.store.head = 0; - ring.store.tail = 0; - ring.store.generation = nextGeneration; - ring.store.alignment = alignment; - ring.frameMarks.clear(); - MGLOG_D("%s: %zu MiB persistent store ready (id %u, gen %u, align %zu).", ring.label, - newSize / (1024u * 1024u), id, nextGeneration, alignment); - return true; + if (ring.store.id != 0) { + ring.retired.push_back( + {ring.store.id, ring.store.contextGeneration, DirectGLES::CurrentFrameSerial() + 1}); } + const Uint32 nextGeneration = ring.store.generation + 1; + ring.store.id = 0; + ring.store.mappedPtr = nullptr; + + Uint id = 0; + g_GLESFuncs.glGenBuffers(1, &id); + if (id != 0) { + BindBufferId(TempBufferTarget, id); + g_GLESFuncs.glBufferStorageEXT(TempBufferTarget, static_cast(newSize), nullptr, + GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit); + void* ptr = g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(newSize), + GL_MAP_WRITE_BIT | kMapPersistentBit | kMapCoherentBit); + if (!ptr) { + // The dying id is what the array-buffer cache has recorded as + // bound; a later buffer recycling the name would false-skip. + InvalidateArrayBufferBindingCache(); + g_GLESFuncs.glDeleteBuffers(1, &id); + id = 0; + } else { + ring.store.mappedPtr = static_cast(ptr); + } + } + if (id == 0) { + MGLOG_E_ONCE("%s: persistent storage creation failed (%zu bytes); falling back to the " + "client-memory upload path.", + ring.label, newSize); + ring.store.creationFailed = true; + return false; + } + + SizeT alignment = ring.fixedAlignment; + if (alignment == 0) { + const GLint capsAlignment = g_GLESCapabilities.UniformBufferOffsetAlignment; + alignment = capsAlignment > 0 ? static_cast(capsAlignment) : 256; + } + ring.store.id = id; + ring.store.size = newSize; + ring.store.head = 0; + ring.store.tail = 0; + ring.store.generation = nextGeneration; + ring.store.alignment = alignment; + ring.frameMarks.clear(); + MGLOG_D("%s: %zu MiB persistent store ready (id %u, gen %u, align %zu).", ring.label, + newSize / (1024u * 1024u), id, nextGeneration, alignment); + return true; + } namespace { // Shared half of the availability gate (the per-ring kill switch sits in @@ -2719,121 +2740,120 @@ namespace MobileGL::MG_Backend::DirectGLES { } } // namespace - // Bump-allocate `size` bytes out of `ring`. - // - // Fast path: a live store under the current context with room before both - // the wrap boundary and the in-flight tail. Touches no GL and probes no - // frame marks - callers sit behind the ring's availability gate, so the - // context checks have already run for this draw/upload. `tail` may be stale - // here (marks are only retired on Present and on the slow path); staleness - // is conservative - the in-flight span reads too large, the check fails, - // and the slow path retires marks and re-tries. - Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset) { - if (size == 0) return false; - auto& store = ring.store; - if (store.id != 0 && store.contextGeneration == g_bufferContextGeneration) { - const SizeT alignedSize = RingAlignUp(size, store.alignment); - // Ring sizes are the ring's initial size (a power of two) doubled - // some number of times, so the offset modulo reduces to a mask. - static_assert((kUboRingInitialBytes & (kUboRingInitialBytes - 1)) == 0, - "ring offset mask below requires power-of-two ring sizes"); - static_assert((kUnpackRingInitialBytes & (kUnpackRingInitialBytes - 1)) == 0, - "ring offset mask below requires power-of-two ring sizes"); - static_assert((kUploadRingInitialBytes & (kUploadRingInitialBytes - 1)) == 0, - "ring offset mask below requires power-of-two ring sizes"); - const SizeT offset = static_cast(store.head & (store.size - 1)); - if (offset + alignedSize <= store.size && store.head + alignedSize - store.tail <= store.size) { - store.head += alignedSize; - outOffset = offset; - return true; - } + // Bump-allocate `size` bytes out of `ring`. + // + // Fast path: a live store under the current context with room before both + // the wrap boundary and the in-flight tail. Touches no GL and probes no + // frame marks - callers sit behind the ring's availability gate, so the + // context checks have already run for this draw/upload. `tail` may be stale + // here (marks are only retired on Present and on the slow path); staleness + // is conservative - the in-flight span reads too large, the check fails, + // and the slow path retires marks and re-tries. + Bool RingAllocate(PersistentRing& ring, SizeT size, SizeT& outOffset) { + if (size == 0) return false; + auto& store = ring.store; + if (store.id != 0 && store.contextGeneration == g_bufferContextGeneration) { + const SizeT alignedSize = RingAlignUp(size, store.alignment); + // Ring sizes are the ring's initial size (a power of two) doubled + // some number of times, so the offset modulo reduces to a mask. + static_assert((kUboRingInitialBytes & (kUboRingInitialBytes - 1)) == 0, + "ring offset mask below requires power-of-two ring sizes"); + static_assert((kUnpackRingInitialBytes & (kUnpackRingInitialBytes - 1)) == 0, + "ring offset mask below requires power-of-two ring sizes"); + static_assert((kUploadRingInitialBytes & (kUploadRingInitialBytes - 1)) == 0, + "ring offset mask below requires power-of-two ring sizes"); + const SizeT offset = static_cast(store.head & (store.size - 1)); + if (offset + alignedSize <= store.size && store.head + alignedSize - store.tail <= store.size) { + store.head += alignedSize; + outOffset = offset; + return true; } - return RingAllocateSlow(ring, size, outOffset); + } + return RingAllocateSlow(ring, size, outOffset); + } + + Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset) { + if (!RingAvailable(ring)) return false; + auto& store = ring.store; + // Sizing the store first, so the request is rounded with the alignment + // the live store actually carries rather than the pre-creation default. + if (store.id == 0 && !CreateRingStorage(ring, RingAlignUp(size, store.alignment))) { + return false; + } + const SizeT alignedSize = RingAlignUp(size, store.alignment); + + // Advance tail past every frame the GPU provably finished. + const Uint64 completed = DirectGLES::CompletedFrameSerial(); + SizeT retiredMarks = 0; + for (const auto& mark : ring.frameMarks) { + if (mark.frameSerial > completed) break; + if (mark.headAtPresent > store.tail) store.tail = mark.headAtPresent; + ++retiredMarks; + } + if (retiredMarks > 0) { + ring.frameMarks.erase(ring.frameMarks.begin(), + ring.frameMarks.begin() + static_cast(retiredMarks)); } - Bool RingAllocateSlow(PersistentRing& ring, SizeT size, SizeT& outOffset) { - if (!RingAvailable(ring)) return false; - auto& store = ring.store; - // Sizing the store first, so the request is rounded with the alignment - // the live store actually carries rather than the pre-creation default. - if (store.id == 0 && !CreateRingStorage(ring, RingAlignUp(size, store.alignment))) { - return false; - } - const SizeT alignedSize = RingAlignUp(size, store.alignment); + // A slot may not straddle the ring end; pad the cursor to the boundary. + SizeT offset = static_cast(store.head % store.size); + if (offset + alignedSize > store.size) { + store.head += store.size - offset; + offset = 0; + } - // Advance tail past every frame the GPU provably finished. - const Uint64 completed = DirectGLES::CompletedFrameSerial(); - SizeT retiredMarks = 0; - for (const auto& mark : ring.frameMarks) { - if (mark.frameSerial > completed) break; - if (mark.headAtPresent > store.tail) store.tail = mark.headAtPresent; - ++retiredMarks; - } - if (retiredMarks > 0) { - ring.frameMarks.erase(ring.frameMarks.begin(), - ring.frameMarks.begin() + static_cast(retiredMarks)); - } - - // A slot may not straddle the ring end; pad the cursor to the boundary. - SizeT offset = static_cast(store.head % store.size); - if (offset + alignedSize > store.size) { - store.head += store.size - offset; + if (store.head + alignedSize - store.tail > store.size) { + // In-flight span would overrun live slots: grow instead of overwrite. + if (CreateRingStorage(ring, std::max(store.size * 2, alignedSize))) { offset = 0; - } - - if (store.head + alignedSize - store.tail > store.size) { - // In-flight span would overrun live slots: grow instead of overwrite. - if (CreateRingStorage(ring, std::max(store.size * 2, alignedSize))) { - offset = 0; - } else if (store.creationFailed) { - return false; // store lost; callers fall back to their legacy path - } else { - // At the size cap (>maxBytes in flight). First try to free room - // by waiting for the OLDEST in-flight frames to retire - a - // bounded wait that ends as soon as enough tail space exists, - // instead of draining the entire queue. - constexpr Uint64 kFrameWaitNs = 50ull * 1000 * 1000; // 50ms per frame - while (!ring.frameMarks.empty() && - store.head + alignedSize - store.tail > store.size) { - const auto& oldest = ring.frameMarks.front(); - if (!DirectGLES::WaitForFrameSerialCompleted(oldest.frameSerial, kFrameWaitNs)) { - break; - } - if (oldest.headAtPresent > store.tail) store.tail = oldest.headAtPresent; - ring.frameMarks.erase(ring.frameMarks.begin()); + } else if (store.creationFailed) { + return false; // store lost; callers fall back to their legacy path + } else { + // At the size cap (>maxBytes in flight). First try to free room + // by waiting for the OLDEST in-flight frames to retire - a + // bounded wait that ends as soon as enough tail space exists, + // instead of draining the entire queue. + constexpr Uint64 kFrameWaitNs = 50ull * 1000 * 1000; // 50ms per frame + while (!ring.frameMarks.empty() && store.head + alignedSize - store.tail > store.size) { + const auto& oldest = ring.frameMarks.front(); + if (!DirectGLES::WaitForFrameSerialCompleted(oldest.frameSerial, kFrameWaitNs)) { + break; } - if (store.head + alignedSize - store.tail <= store.size) { - offset = static_cast(store.head % store.size); - if (offset + alignedSize > store.size) { - store.head += store.size - offset; - offset = 0; - } - store.head += alignedSize; - outOffset = offset; - return true; - } - // No usable fence covers the oldest frames: drain once rather than - // corrupt live slots. - if (g_GLESFuncs.glFinish) g_GLESFuncs.glFinish(); - store.tail = store.head; - ring.frameMarks.clear(); - // Same-frame slots written before the drain may now be recycled by - // the very next allocations; a generation bump keeps later draws - // from rebinding those cached offsets. - ++store.generation; + if (oldest.headAtPresent > store.tail) store.tail = oldest.headAtPresent; + ring.frameMarks.erase(ring.frameMarks.begin()); + } + if (store.head + alignedSize - store.tail <= store.size) { offset = static_cast(store.head % store.size); if (offset + alignedSize > store.size) { store.head += store.size - offset; offset = 0; } + store.head += alignedSize; + outOffset = offset; + return true; + } + // No usable fence covers the oldest frames: drain once rather than + // corrupt live slots. + if (g_GLESFuncs.glFinish) g_GLESFuncs.glFinish(); + store.tail = store.head; + ring.frameMarks.clear(); + // Same-frame slots written before the drain may now be recycled by + // the very next allocations; a generation bump keeps later draws + // from rebinding those cached offsets. + ++store.generation; + offset = static_cast(store.head % store.size); + if (offset + alignedSize > store.size) { + store.head += store.size - offset; + offset = 0; } } - - store.head += alignedSize; - outOffset = offset; - return true; } + store.head += alignedSize; + outOffset = offset; + return true; + } + namespace { void DeleteOrphanRingBuffers() { const std::lock_guard lock(g_orphanRingBuffersMutex); @@ -2899,7 +2919,9 @@ namespace MobileGL::MG_Backend::DirectGLES { return RingAvailable(UboRing()); } - Bool UboRingAllocate(SizeT size, SizeT& outOffset) { return RingAllocate(UboRing(), size, outOffset); } + Bool UboRingAllocate(SizeT size, SizeT& outOffset) { + return RingAllocate(UboRing(), size, outOffset); + } UboRingSlotState UboRingPrepareSlot(UboRingAllocation& slot, Uint32 contentVersion, Uint64 frameSerial, SizeT bindSize, void*& outWrite, Uint& outBufferId) { @@ -2920,11 +2942,19 @@ namespace MobileGL::MG_Backend::DirectGLES { return UboRingSlotState::Allocated; } - void* UboRingMappedPtr() { return UboRing().store.mappedPtr; } - Uint UboRingBufferId() { return UboRing().store.id; } - Uint32 UboRingGeneration() { return UboRing().store.generation; } + void* UboRingMappedPtr() { + return UboRing().store.mappedPtr; + } + Uint UboRingBufferId() { + return UboRing().store.id; + } + Uint32 UboRingGeneration() { + return UboRing().store.generation; + } - void UboRingOnPresent() { RingOnPresent(UboRing()); } + void UboRingOnPresent() { + RingOnPresent(UboRing()); + } Bool UnpackRingAvailable() { if (MG_Config::Features.EsprytDisableUnpackRing) return false; @@ -2938,12 +2968,22 @@ namespace MobileGL::MG_Backend::DirectGLES { return RingAllocate(UnpackRing(), size, outOffset); } - void* UnpackRingMappedPtr() { return UnpackRing().store.mappedPtr; } - Uint UnpackRingBufferId() { return UnpackRing().store.id; } - SizeT UnpackRingMaxBytes() { return kUnpackRingMaxBytes; } + void* UnpackRingMappedPtr() { + return UnpackRing().store.mappedPtr; + } + Uint UnpackRingBufferId() { + return UnpackRing().store.id; + } + SizeT UnpackRingMaxBytes() { + return kUnpackRingMaxBytes; + } - void UnpackRingOnPresent() { RingOnPresent(UnpackRing()); } + void UnpackRingOnPresent() { + RingOnPresent(UnpackRing()); + } - void UploadRingOnPresent() { RingOnPresent(UploadRing()); } + void UploadRingOnPresent() { + RingOnPresent(UploadRing()); + } } // namespace BufferImpl } // namespace MobileGL::MG_Backend::DirectGLES diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersFramebuffers.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersFramebuffers.cpp index 74a76bc5..35ccd317 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersFramebuffers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersFramebuffers.cpp @@ -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(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(surface.Level), - static_cast(surface.Layer)); + g_GLESFuncs.glFramebufferTextureLayer( + glFBOTarget, glBackendAttachment, backendTextureObject->GetBackendTextureId(), + static_cast(surface.Level), static_cast(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(record->ReadSurface.InternalFormat)); + IsSnormFormat(static_cast(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( static_cast(FramebufferAttachmentType::Color0) + static_cast(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(frontendBuf) - static_cast(FramebufferAttachmentType::Color0); + const Uint a = static_cast(frontendBuf) - static_cast(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 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(point) - static_cast(FramebufferAttachmentType::Color0); + const Int index = static_cast(point) - static_cast(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 defaults = { - static_cast(record.Width), static_cast(record.Height), - static_cast(record.Layers), + static_cast(record.Width), static_cast(record.Height), static_cast(record.Layers), std::min(static_cast(record.Samples), g_GLESCapabilities.MaxFramebufferSamples), static_cast(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(pushedRecord->Desc.InternalFormat) \ + (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.InternalFormat) \ : stateRBOObject->GetInternalFormat()) -#define MGB_RBO_WIDTH (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Width) : static_cast(stateRBOObject->GetWidth())) +#define MGB_RBO_WIDTH \ + (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Width) \ + : static_cast(stateRBOObject->GetWidth())) #define MGB_RBO_HEIGHT \ - (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Height) : static_cast(stateRBOObject->GetHeight())) + (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Height) \ + : static_cast(stateRBOObject->GetHeight())) #define MGB_RBO_SAMPLES \ - (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Samples) : static_cast(stateRBOObject->GetSamples())) + (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.Samples) \ + : static_cast(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(ClampSamplesToBackendSupport( GetRenderbufferFormatCapabilityTargetIndex(), internalFormat, glFormat, samples)); g_GLESFuncs.glRenderbufferStorageMultisample(GL_RENDERBUFFER, backendSamples, glInternalFormat, - static_cast(width), - static_cast(height)); + static_cast(width), static_cast(height)); } else { g_GLESFuncs.glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, static_cast(width), static_cast(height)); @@ -1473,7 +1460,8 @@ namespace MobileGL::MG_Backend::DirectGLES { m_isInitialized = true; } - TwinRegistry + TwinRegistry g_backendRenderbufferObjects; } // namespace RenderbufferImpl } // namespace MobileGL::MG_Backend::DirectGLES diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersInternal.h b/MobileGL/MG_Backend/DirectGLES/ManagersInternal.h index 2a6612a2..afc311c9 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersInternal.h +++ b/MobileGL/MG_Backend/DirectGLES/ManagersInternal.h @@ -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(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(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(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(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& StagedUploadTargetsForPipeTarget(Uint8 pipeResourceTarget); + const Vector& 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& bufferObject, MG_Pipe::MGPipeHandle res); + GLESBufferResource* EnsureBufferResourceForHandle( + const SharedPtr& 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 - 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 + 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 @@ -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; } diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersPrograms.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersPrograms.cpp index 8ec01af5..766e92ad 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersPrograms.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersPrograms.cpp @@ -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 g_backendProgramObjects; + TwinRegistry + 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(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(record.BlockBindings[index]) - : 0; + return index < record.BlockBindings.size() ? static_cast(record.BlockBindings[index]) : 0; } if (!record.Archive || index >= record.Archive->Link.uniformBlockBinding.size()) return 0; return static_cast(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(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(loc), static_cast(element)); + for (Uint element = loc; + element <= maxUniformLoc && + src.UniformLocationsAliasSameUniform(static_cast(loc), static_cast(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& spirvCode, const GLenum glShaderType, const std::set& xfbCaptureBlockNames, const ImageFormatBakeInputs& imageFormatBake, const UnorderedMap& storageBlockBindingOverrides, - const std::map& inputBlockRenames, - const std::map& outputBlockRenames, const Bool stripInputBlockLocations, - const Bool stripOutputBlockLocations, + const std::map& inputBlockRenames, const std::map& outputBlockRenames, + const Bool stripInputBlockLocations, const Bool stripOutputBlockLocations, const Int atomicCounterEsslBindingTop, const Bool enableSpirvValidation, String& outSource, std::set& outFlattenedXfbBlockNames, Vector& outAtomicCounterGlBindings, String& outError) const { @@ -873,7 +896,8 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector loweredSpirv; const Vector* 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 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 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 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 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 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 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(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(tessEvalShaderIndex) >= shaderSpirvs.size()) { + if (tessEvalShaderIndex < 0 || static_cast(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 nonConsecutiveImageArrays = - CollectNonConsecutiveImageArrayPlans(src); + const Vector 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 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 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(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(std::max(linkedXfbVaryings, 0)) != declaredXfbVaryingCount || static_cast(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(linkedXfbBufferMode)).c_str()); + MG_Util::ConvertGLEnumToString(static_cast(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(linkedXfbBufferMode)).c_str()); + MG_Util::ConvertGLEnumToString(static_cast(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(i)] = static_cast(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); } } diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersSamplers.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersSamplers.cpp index f46a12a3..03fb1d8c 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersSamplers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersSamplers.cpp @@ -51,9 +51,8 @@ namespace MobileGL::MG_Backend::DirectGLES { m_backendSamplerId = 0; } - void BackendSamplerObject::SyncToBackend( - const SharedPtr& stateSamplerObject, - MG_Pipe::MGPipeHandle pushedCso) { + void BackendSamplerObject::SyncToBackend(const SharedPtr& 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 g_boundSamplersCache; - TwinRegistry g_backendSamplerObjects; + TwinRegistry + 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 diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersTextureSync.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersTextureSync.cpp index ed0da334..bfc87db4 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersTextureSync.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersTextureSync.cpp @@ -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& widenedData, Bool integerData, - Uint32 alphaOneCodeOverride) { + const void* PrepareChannelWidenedUpload(Uint componentCount, const IntVec3& texelSize, const void* data, + SizeT byteSize, GLenum uploadType, Vector& 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& 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& widenedData) { + static const void* PreparePackedFloatWidenedUpload(const IntVec3& texelSize, const void* data, SizeT byteSize, + Vector& 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& widenedData) { + const void* PreparePackedIntWidenedUpload(const IntVec3& texelSize, const void* data, SizeT byteSize, + Vector& 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(tgt), \ - static_cast(lvl)) \ - : (obj)->GetMipmapTexelSize(tgt, lvl)) + (MGB_STAGED_TEXTURE_LIVE ? MG_Record::ServerStagedTexture().LevelExtentOrUndefined( \ + MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast(tgt), \ + static_cast(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(tgt), \ - static_cast(lvl)) \ + ? MG_Record::ServerStagedTexture().LevelByteSize(MG_Record::StagedTextureStore::KeyForHandle(pushedRes), \ + static_cast(tgt), static_cast(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(tgt), \ - static_cast(lvl), site) \ - : (obj)->MapMipmapData(tgt, lvl)) + (MGB_STAGED_TEXTURE_LIVE ? MG_Record::ServerStagedTexture().RequireLevelBytes( \ + MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast(tgt), \ + static_cast(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(tgt), static_cast(lvl)) != nullptr || \ - MG_Record::ServerStagedTexture().IsLevelGpuDirty( \ - MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast(tgt), \ - static_cast(lvl))) \ + ? (FindPipeTextureUpload(*pushedStorage, static_cast(tgt), static_cast(lvl)) != nullptr || \ + MG_Record::ServerStagedTexture().IsLevelGpuDirty(MG_Record::StagedTextureStore::KeyForHandle(pushedRes), \ + static_cast(tgt), static_cast(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(tgt), static_cast(lvl)); \ + ConsumePipeTextureUpload(pushedRes, static_cast(tgt), static_cast(lvl)); \ if (MGB_STAGED_TEXTURE_LIVE) { \ - MG_Record::ServerStagedTexture().MarkLevelGpuDirty( \ - MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast(tgt), \ + MG_Record::ServerStagedTexture().MarkLevelGpuDirty( \ + MG_Record::StagedTextureStore::KeyForHandle(pushedRes), static_cast(tgt), \ static_cast(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(pushedStorage->Desc.InternalFormat) \ + (pushedStorage != nullptr ? static_cast(pushedStorage->Desc.InternalFormat) \ : (obj)->GetFormat()) #define MGB_STORAGE_BASE_SIZE(obj) \ - (pushedStorage != nullptr ? IntVec3{static_cast(pushedStorage->Desc.Width), \ - static_cast(pushedStorage->Desc.Height), \ - static_cast(pushedStorage->Desc.Depth)} \ - : (obj)->GetBaseSize()) + (pushedStorage != nullptr \ + ? IntVec3{static_cast(pushedStorage->Desc.Width), static_cast(pushedStorage->Desc.Height), \ + static_cast(pushedStorage->Desc.Depth)} \ + : (obj)->GetBaseSize()) #define MGB_STORAGE_LEVELS(obj) \ (pushedStorage != nullptr ? static_cast(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(pushedStorage->Desc.StorageKind) \ + (pushedStorage != nullptr ? static_cast(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(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(stagedDesc.InternalFormat) != - TextureInternalFormat::Unknown; + anyDefined = + static_cast(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(stateTextureObject.get()); - const auto probeBaseSize = stateTextureObject->GetBaseSize(); - StateTextureBasicInfo probe = {stateTextureObject->GetFormat(), - static_cast(probeBaseSize.x()), - static_cast(probeBaseSize.y()), - static_cast(probeBaseSize.z()), - static_cast(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(stateTextureObject.get()); + const auto probeBaseSize = stateTextureObject->GetBaseSize(); + StateTextureBasicInfo probe = {stateTextureObject->GetFormat(), + static_cast(probeBaseSize.x()), + static_cast(probeBaseSize.y()), + static_cast(probeBaseSize.z()), + static_cast(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 convertedUploadData; Vector widenedUploadData; const void* uploadData = PrepareFallbackUpload( MGB_STORAGE_FORMAT(textureMipmapObject), targetInternal, levelTexelSize, pData, levelByteSize, glType, convertedUploadData, widenedUploadData); Vector 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(level), (GLint)glInternalFormat, - static_cast(uploadSize.x()), static_cast(uploadSize.y()), - 0, glFormat, glType, uploadData); + g_GLESFuncs.glTexImage2D(glUploadTarget, static_cast(level), + (GLint)glInternalFormat, static_cast(uploadSize.x()), + static_cast(uploadSize.y()), 0, glFormat, glType, + uploadData); break; case TextureTarget::Texture3D: case TextureTarget::Texture2DArray: @@ -1146,8 +1129,9 @@ namespace MobileGL::MG_Backend::DirectGLES { static_cast(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(baseSize.x()), static_cast(baseSize.y()), + target, backendSamples, glInternalFormat, static_cast(baseSize.x()), + static_cast(baseSize.y()), MGB_STORAGE_FIXED_SAMPLE_LOCATIONS(stateTextureObject) ? GL_TRUE : GL_FALSE); break; case TextureTarget::Texture2DMultisampleArray: g_GLESFuncs.glTexStorage3DMultisample( - target, backendSamples, glInternalFormat, - static_cast(baseSize.x()), static_cast(baseSize.y()), - static_cast(baseSize.z()), + target, backendSamples, glInternalFormat, static_cast(baseSize.x()), + static_cast(baseSize.y()), static_cast(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 convertedUploadData; Vector 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(level), 0, 0, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), glFormat, glType, uploadData); + g_GLESFuncs.glTexSubImage2D(glUploadTarget, static_cast(level), 0, 0, + static_cast(uploadSize.x()), + static_cast(uploadSize.y()), glFormat, + glType, uploadData); break; case TextureTarget::Texture3D: case TextureTarget::Texture2DArray: case TextureTarget::TextureCubeMapArray: g_GLESFuncs.glTexSubImage3D( glUploadTarget, static_cast(level), 0, 0, 0, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), + static_cast(uploadSize.x()), static_cast(uploadSize.y()), static_cast(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 convertedUploadData; Vector widenedUploadData; const void* uploadData = PrepareFallbackUpload( @@ -1396,8 +1376,8 @@ namespace MobileGL::MG_Backend::DirectGLES { case TextureTarget::TextureCubeMap: { g_GLESFuncs.glTexImage2D( glUploadTarget, static_cast(level), (GLint)glInternalFormat, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), 0, glFormat, glType, uploadData); + static_cast(uploadSize.x()), static_cast(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(level), (GLint)glInternalFormat, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), + static_cast(uploadSize.x()), static_cast(uploadSize.y()), static_cast(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 convertedUploadData; Vector 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(box.W), box.Y + static_cast(box.H), - box.Z + static_cast(box.D)}}; + return MG_State::GLState::MipmapDirtyRegion{IntVec3{box.X, box.Y, box.Z}, + IntVec3{box.X + static_cast(box.W), + box.Y + static_cast(box.H), + box.Z + static_cast(box.D)}}; }(); const SizeT texelCount = static_cast(texelSize.x()) * static_cast(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(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(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(level), - static_cast(region.SrcOffset), region.X, - region.Y, region.Z, - static_cast(derivedOffset)); + MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject), + static_cast(level), + static_cast(region.SrcOffset), region.X, region.Y, + region.Z, static_cast(derivedOffset)); break; } } - const Uint8* regionPtr = - static_cast(uploadData) + - static_cast(dirtyRegion.lo.z()) * levelSliceBytes + - static_cast(dirtyRegion.lo.y()) * levelRowBytes + - static_cast(dirtyRegion.lo.x()) * bpp; + const Uint8* regionPtr = static_cast(uploadData) + + static_cast(dirtyRegion.lo.z()) * levelSliceBytes + + static_cast(dirtyRegion.lo.y()) * levelRowBytes + + static_cast(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(region.W) * static_cast(region.H) * static_cast(std::max(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(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(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(rect.hi.x() - rect.lo.x()) * bpp, - static_cast(rect.hi.y() - rect.lo.y()), - static_cast(std::max(rect.hi.z() - rect.lo.z(), 1)), - levelRowBytes, - levelSliceBytes, - 0}; + stagingBlocks[r] = {MGB_RECT_SRC_PTR(r, rect), + static_cast(rect.hi.x() - rect.lo.x()) * bpp, + static_cast(rect.hi.y() - rect.lo.y()), + static_cast(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(uploadData), - static_cast(byteSize), - 1, - 1, - static_cast(byteSize), - static_cast(byteSize), - 0}; + stagingBlocks[0] = { + static_cast(uploadData), static_cast(byteSize), 1, 1, + static_cast(byteSize), static_cast(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(bpp); } } else if (subRectEligible) { - shippedBytes = static_cast(regionSize.x()) * - static_cast(regionSize.y()) * - static_cast(std::max(regionSize.z(), 1)) * - static_cast(bpp); + shippedBytes = + static_cast(regionSize.x()) * static_cast(regionSize.y()) * + static_cast(std::max(regionSize.z(), 1)) * static_cast(bpp); } else { shippedBytes = static_cast(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(level), rect.lo.x(), - rect.lo.y(), static_cast(rect.hi.x() - rect.lo.x()), - static_cast(rect.hi.y() - rect.lo.y()), glFormat, - glType, + glUploadTarget, static_cast(level), rect.lo.x(), rect.lo.y(), + static_cast(rect.hi.x() - rect.lo.x()), + static_cast(rect.hi.y() - rect.lo.y()), glFormat, glType, ringStaged ? UnpackRingPixelOffset(stagingBlocks[r].offset) : static_cast(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(level), 0, 0, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), glFormat, - glType, - ringStaged - ? UnpackRingPixelOffset(stagingBlocks[0].offset) - : uploadData); + g_GLESFuncs.glTexSubImage2D( + glUploadTarget, static_cast(level), 0, 0, + static_cast(uploadSize.x()), static_cast(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(level), box.x, box.y, box.z, - box.w, box.h, box.d, glFormat, glType, + glUploadTarget, static_cast(level), box.x, box.y, box.z, box.w, + box.h, box.d, glFormat, glType, ringStaged ? UnpackRingPixelOffset(stagingBlocks[r].offset) : static_cast(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(level), box.x, box.y, box.z, box.w, box.h, - box.d, glFormat, glType, - ringStaged ? UnpackRingPixelOffset(stagingBlocks[0].offset) - : static_cast(regionPtr)); + g_GLESFuncs.glTexSubImage3D(glUploadTarget, static_cast(level), box.x, box.y, + box.z, box.w, box.h, box.d, glFormat, glType, + ringStaged + ? UnpackRingPixelOffset(stagingBlocks[0].offset) + : static_cast(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(level), 0, 0, 0, - static_cast(uploadSize.x()), - static_cast(uploadSize.y()), - static_cast(uploadSize.z()), glFormat, - glType, - ringStaged - ? UnpackRingPixelOffset(stagingBlocks[0].offset) - : uploadData); + g_GLESFuncs.glTexSubImage3D( + glUploadTarget, static_cast(level), 0, 0, 0, + static_cast(uploadSize.x()), static_cast(uploadSize.y()), + static_cast(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 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(pushedStorage->Desc.BufSize)); + const SizeT rangeSize = pushedStorage == nullptr + ? textureBufferObject->GetBufferRangeSizeInBytes() + : (pushedStorage->Desc.BufSize == MG_Pipe::kMGPipeWholeBuffer + ? (buffer ? buffer->GetSize() : 0u) + : static_cast(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(rangeOffset), static_cast(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( - TextureTarget::TextureBuffer)] = nullptr; + g_boundTexturesCache[g_activeTextureUnit] + [static_cast(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(MGB_TEXTURE_STORAGE_KIND(pushedStorage, stateTextureObject)), - MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject)); + "skipping this sync", + static_cast(MGB_TEXTURE_STORAGE_KIND(pushedStorage, stateTextureObject)), + MGB_TEXTURE_DIAG_NAME(pushedStorage, stateTextureObject)); break; } diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp index 6a78fc75..ae1f8b8e 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersTextures.cpp @@ -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(uploadTarget); const auto levelCode = static_cast(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(extent.x()), - static_cast(extent.y()), static_cast(extent.z())}; + const MG_Pipe::MGPBox whole{0, + 0, + 0, + static_cast(extent.x()), + static_cast(extent.y()), + static_cast(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(level), extent); + m_isInitialized && NativeTextureLevelHasExtent(m_backendTextureId, target, uploadTarget, + static_cast(level), extent); if (nativeLevel && !(covered && pendingWhole)) { Vector snapshot; const Bool readBack = - !s_forceReadbackFailure && - ReadTextureLevelTight(m_backendTextureId, target, uploadTarget, format, - static_cast(level), extent, - /*sourceUsesImageCarrier=*/false, canonicalFormat, - canonicalType, snapshot); + !s_forceReadbackFailure && ReadTextureLevelTight(m_backendTextureId, target, uploadTarget, + format, static_cast(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(z + slice) * whole.H + y + row) * whole.W + x) * bpp; + const SizeT offset = + ((static_cast(z + slice) * whole.H + y + row) * whole.W + x) * bpp; Memcpy(snapshot.data() + offset, staged + offset, static_cast(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(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(view.InternalFormat), - &internalFormat, &transferFormat, &transferType, viewTarget); + GenerateTextureFormatInfo(static_cast(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(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(record.Desc.Width), static_cast(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(box.Y) * static_cast(rowTexels) + static_cast(box.X)) * 4; + bytes + + (static_cast(box.Y) * static_cast(rowTexels) + static_cast(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(box.W), static_cast(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(pushedRecord->Params.BaseLevel), \ + (pushedRecord != nullptr ? UintVec2{static_cast(pushedRecord->Params.BaseLevel), \ static_cast(pushedRecord->Params.MaxLevel)} \ : stateTextureObject->GetLevelRange()) #define MGB_TEXPARAM_FORMAT \ - (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.InternalFormat) \ + (pushedRecord != nullptr ? static_cast(pushedRecord->Desc.InternalFormat) \ : stateTextureObject->GetFormat()) #define MGB_TEXPARAM_SWIZZLE \ (pushedRecord != nullptr \ - ? Vec4{static_cast(pushedRecord->Params.Swizzle[0]), \ - static_cast(pushedRecord->Params.Swizzle[1]), \ - static_cast(pushedRecord->Params.Swizzle[2]), \ - static_cast(pushedRecord->Params.Swizzle[3])} \ + ? Vec4{static_cast(pushedRecord->Params.Swizzle[0]), \ + static_cast(pushedRecord->Params.Swizzle[1]), \ + static_cast(pushedRecord->Params.Swizzle[2]), \ + static_cast(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(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, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS> g_boundTexturesCache; - TwinRegistry g_backendTextureObjects; + TwinRegistry + 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(sourceLevel), sourceOrigin, - MG_Record::StagedTextureStore::KeyForHandle(destination), - destinationTarget, static_cast(destinationLevel), destinationOrigin, - size); + MG_Record::StagedTextureStore::KeyForHandle(destination), destinationTarget, + static_cast(destinationLevel), destinationOrigin, size); } } // namespace TextureImpl diff --git a/MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp b/MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp index d0cb6c19..24743b4d 100644 --- a/MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp +++ b/MobileGL/MG_Backend/DirectGLES/ManagersVertexArrays.cpp @@ -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(size), - GL_MAP_READ_BIT); + void* mapped = + g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(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(mapped), static_cast(size), - MG_Pipe::kMGHostSpanSegNone, 0}); + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback(res, 0, + MG_Pipe::MGPBlobRef{reinterpret_cast(mapped), + static_cast(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(size), - GL_MAP_READ_BIT); + void* mapped = + g_GLESFuncs.glMapBufferRange(TempBufferTarget, 0, static_cast(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& outData) { + void SwizzleBgraStreamToRgba(const Uint8* sourceBase, SizeT sourceStride, SizeT elementCount, DataType type, + Vector& 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(attrib.Offset), 0); + g_GLESFuncs.glBindVertexBuffer(attribIndex, backendResource->id, static_cast(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(glType) && - shadowPointer.stride == static_cast(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(glType) && + shadowPointer.stride == static_cast(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(glType), - static_cast(attrib.Stride), fetchOffset, attrib.Normalized != 0, - attrib.IsInteger != 0, true}; + shadowPointer = {attribBuffer->id, + glSize, + static_cast(glType), + static_cast(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(st.VertexBufferCount, - MG_Pipe::kMGPipeMaxVertexAttribs - st.VertexBufferStart); + m_syncedBufferCount = + std::min(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(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(converted.size() * sizeof(Float)), + g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast(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(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( - attrib.Divisor == 0 ? 0u : static_cast(fetchBaseInstance) * - componentCount * - sizeof(Float))); + g_GLESFuncs.glVertexAttribPointer( + attribIndex, attrib.Size, GL_FLOAT, GL_FALSE, + static_cast(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(attrib.Divisor == 0 ? 0u + : static_cast(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(uploadSize), - bgra ? static_cast(swizzled.data()) : clientData, - GL_STREAM_DRAW); + bgra ? static_cast(swizzled.data()) : clientData, GL_STREAM_DRAW); if (MG_Util::PipeStats::Enabled()) { MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageVertexClient, static_cast(uploadSize)); @@ -916,20 +922,20 @@ namespace MobileGL::MG_Backend::DirectGLES { // the same arithmetic SyncToBackend applies to a VBO-backed array. reinterpret_cast(BaseInstanceByteShift(attrib, fetchBaseInstance))); } else { - g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size, - MG_Util::ConvertDataTypeToGLEnum(attrib.Type), - static_cast(stride), - // See the pointer call above: the divisor'd - // arrays' baseInstance shift is the pointer. - reinterpret_cast( - BaseInstanceByteShift(attrib, fetchBaseInstance))); + g_GLESFuncs.glVertexAttribIPointer( + attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type), + static_cast(stride), + // See the pointer call above: the divisor'd + // arrays' baseInstance shift is the pointer. + reinterpret_cast(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(resource->persistentPtr) - : resource->hostBytes; + const Uint8* const sourceBase = (resource->persistentMapped && resource->persistentPtr != nullptr) + ? static_cast(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(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(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 + TwinRegistry g_backendVertexArrayObjects; } // namespace VertexArrayImpl } // namespace MobileGL::MG_Backend::DirectGLES diff --git a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp index 0ac3c0fb..3bb0a05f 100644 --- a/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp +++ b/MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp @@ -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(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(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(indices); } @@ -610,7 +621,8 @@ namespace MobileGL::MG_Backend::DirectGLES::MultiDrawImpl { for (GLsizei i = 0; i < drawcount; ++i) { if (feedDrawID) SetCurrentDrawID(static_cast(i)); if (feedBaseVertex) SetCurrentBaseVertex(basevertex ? basevertex[i] : 0); - const SizeT commandOffset = commandBase + static_cast(i) * sizeof(DrawElementsIndirectCommand); + const SizeT commandOffset = + commandBase + static_cast(i) * sizeof(DrawElementsIndirectCommand); ForEachViewportRoutingPass([&] { g_GLESFuncs.glDrawElementsIndirect(mode, type, reinterpret_cast(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 diff --git a/MobileGL/MG_Backend/DirectGLES/SlotTables.h b/MobileGL/MG_Backend/DirectGLES/SlotTables.h index 03183d4a..066b0cb3 100644 --- a/MobileGL/MG_Backend/DirectGLES/SlotTables.h +++ b/MobileGL/MG_Backend/DirectGLES/SlotTables.h @@ -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> Snapshot() const { Vector> 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 : all) table->Reset(); + for (const SharedPtr
& 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(index) + MG_Pipe::kMGPipeShaderCsoCompositeSlotBase, - entry.Gen}, + fn(MG_Pipe::MGPipeHandle{static_cast(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(slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase) - : static_cast(slot); + return SlotIsBanded(slot) ? static_cast(slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase) + : static_cast(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(this)->EntryOrNull(slot); - } + const Entry* EntryOrNull(Uint32 slot) const { return const_cast(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 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 diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.cpp b/MobileGL/MG_Backend/DirectGLES/Utils.cpp index 2b5c3a7d..5a4aafb8 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Utils.cpp @@ -82,22 +82,19 @@ namespace MobileGL::MG_Backend::DirectGLES { return options; } - Flags - GetRuntimeFallbackNormalizeOptions(GLenum requestedInternalFormat, - Flags extraOptions) { + Flags GetRuntimeFallbackNormalizeOptions( + GLenum requestedInternalFormat, Flags extraOptions) { using namespace MG_Util::TextureFormatProcessor; const Flags 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 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( - MG_Util::ShaderTranspiler::ShaderCompiler::WidenedCoreEsslImageFormat(requested)); + const auto carrier = + static_cast(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(std::stoul((*outputIt)[1].str())); if (location < 32 && (outputMask & (1u << location))) { - outputClamps.push_back({(*outputIt)[2].str(), static_cast(snormOutputMask & (1u << location))}); + outputClamps.push_back( + {(*outputIt)[2].str(), static_cast(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& esslFormatByUniformName) { + String BakeImageFormatQualifiers(String glslCode, const UnorderedMap& 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(essl[before - 1]))) --before; + while (before > 0 && std::isspace(static_cast(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(glslCode[before - 1]))) --before; + while (before > 0 && std::isspace(static_cast(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(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(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 uses; @@ -1559,9 +1556,8 @@ namespace MobileGL::MG_Backend::DirectGLES { Vector 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(c)) || c == '_'; } + Bool IsIdentifierChar(char c) { + return std::isalnum(static_cast(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(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 clampNames; // sampler name -> sample-max uniform name + UnorderedMap clampNames; // sampler name -> sample-max uniform name Vector> 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(); diff --git a/MobileGL/MG_Backend/DirectGLES/Utils.h b/MobileGL/MG_Backend/DirectGLES/Utils.h index 7bfc8b85..039b60d5 100644 --- a/MobileGL/MG_Backend/DirectGLES/Utils.h +++ b/MobileGL/MG_Backend/DirectGLES/Utils.h @@ -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. diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 91f64a94..d73c773d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -528,8 +528,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const std::lock_guard 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 BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Bool anisotropicFilteringSupported, - Bool nonZeroIndirectBaseInstanceSupported, - Bool cubeMapArraySupported, + Bool nonZeroIndirectBaseInstanceSupported, Bool cubeMapArraySupported, const AdvertisedFeatureSupport& features) { Vector 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 3af6ce40..85d1e4f9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -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 BuildAdvertisedExtensions(Bool shaderSubgroupSupported, Bool timerQueriesSupported, Bool anisotropicFilteringSupported, - Bool nonZeroIndirectBaseInstanceSupported, - Bool cubeMapArraySupported, + Bool nonZeroIndirectBaseInstanceSupported, Bool cubeMapArraySupported, const AdvertisedFeatureSupport& features = {}); // Format: , Vulkan , Driver — the exact diff --git a/MobileGL/MG_Backend/DirectVulkan/DescriptorPoolCensus.h b/MobileGL/MG_Backend/DirectVulkan/DescriptorPoolCensus.h index f3ca3020..cd2270e8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DescriptorPoolCensus.h +++ b/MobileGL/MG_Backend/DirectVulkan/DescriptorPoolCensus.h @@ -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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 07af4181..b4f6f8e2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -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(indirect), drawcount, - stride, indexed, counted, static_cast(countOffset))) + stride, indexed, counted, static_cast(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(countOffset), sizeof(count), &count)) return; + if (!buffers.ReadWireBuffer(state.VerbIndirectParameterBuffer, static_cast(countOffset), + sizeof(count), &count)) + return; drawcount = static_cast(std::min(count, static_cast(drawcount))); if (!drawcount) return; } @@ -185,8 +186,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint64 byteCount = static_cast(stride) * (drawcount - 1) + commandSize; if (byteCount > std::numeric_limits::max()) return; Vector bytes(static_cast(byteCount)); - if (!buffers.ReadWireBuffer(state.VerbIndirectBuffer, reinterpret_cast(indirect), - byteCount, bytes.data())) return; + if (!buffers.ReadWireBuffer(state.VerbIndirectBuffer, reinterpret_cast(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(i) * stride, sizeof(command)); - params[i] = {command.count, command.instanceCount, command.firstIndex, - command.baseVertex, std::bit_cast(command.baseInstance)}; + params[i] = {command.count, command.instanceCount, command.firstIndex, command.baseVertex, + std::bit_cast(command.baseInstance)}; const Uint64 end = (static_cast(command.firstIndex) + command.count) * indexSize; if (end > std::numeric_limits::max()) return; - payload.indexBufferView.indexByteSize = std::max(payload.indexBufferView.indexByteSize, - static_cast(end)); + payload.indexBufferView.indexByteSize = + std::max(payload.indexBufferView.indexByteSize, static_cast(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(storageBlockBinding)}); - else found->Binding = static_cast(storageBlockBinding); + else + found->Binding = static_cast(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(count) * width); const Uint64 offset = bound.Offset + reinterpret_cast(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(indices[i]) + count[i] * indexSize, - payload.indexBufferView.indexByteSize); + payload.indexBufferView.indexByteSize = std::max(reinterpret_cast(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 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 begin; SharedPtr 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() { diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 920f34c6..5a29bf69 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -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); diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h index 39074466..02bd25f7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkanResourceState.h @@ -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 diff --git a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp index 449700f9..824f668e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp @@ -40,7 +40,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { return static_cast(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 diff --git a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h index cbf63ff0..b17f9fcc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h +++ b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h @@ -104,9 +104,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { SessionLocal(const SessionLocal&) = delete; SessionLocal& operator=(const SessionLocal&) = delete; - T& Get() const { - return *static_cast(Detail::SessionLocalSlot(Detail::CurrentMagmaSession(), m_index)); - } + T& Get() const { return *static_cast(Detail::SessionLocalSlot(Detail::CurrentMagmaSession(), m_index)); } T& operator*() const { return Get(); } T* operator->() const { return &Get(); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h index e9fc509a..bea11fd9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index 62bc07a3..f916ac90 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h index b4c475c0..acef78d4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -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& other) { std::swap(m_swapchainImageRenderFinishedSemaphores, other); } + void ExchangeSwapchainSemaphores(Vector& other) { + std::swap(m_swapchainImageRenderFinishedSemaphores, other); + } VkResult WaitAndAcquireNextImage(VkDevice device, VkSwapchainKHR swapchain, Uint32& outImageIndex, Uint64 timeout = UINT64_MAX, VkFence acquireFence = VK_NULL_HANDLE); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.cpp index e0522e38..dac3bdad 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.cpp @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.h index f1649939..61ff7e5b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/GpuProgressMarkers.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h index 669f4658..e534fb59 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaPipeArms.h @@ -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(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(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(MG_Config::Features.PipePush | - MG_Pipe::kMGPipeSubsystemMagmaVertexInput), - static_cast(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(MG_Config::Features.PipePush | MG_Pipe::kMGPipeSubsystemMagmaVertexInput), + static_cast(MG_Pipe::kMGPipeSubsystemsMigratedAtP2)); std::abort(); } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h index a501e2bd..1f91347e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h @@ -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>& GetGeneratedSpirv() const { - return m_record->Archive->Spirv.generatedSpirv; - } + Bool GetSpirvValidationEnabled() const { return m_record->Desc.EnableSpirvValidation != 0; } + const Vector>& GetGeneratedSpirv() const { return m_record->Archive->Spirv.generatedSpirv; } Vector GetLinkedShaderStages() const { Vector result; - for (const auto stage : m_record->Archive->LinkedStages) result.push_back(static_cast(stage)); + for (const auto stage : m_record->Archive->LinkedStages) + result.push_back(static_cast(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(m_record->GlobalConstants.size()); } Uint32 GetUBOContentVersion() const { return m_record->GlobalConstantsVersion; } - Int GetActiveUniformBlocksCount() const { - return static_cast(Link().glBlockIndexToTProgram.size()); - } + Int GetActiveUniformBlocksCount() const { return static_cast(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(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(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(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex)) return -1; + static_cast(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex)) + return -1; const Int location = base + static_cast(element); return UniformLocationsAliasSameUniform(base, location) ? location : -1; } SizeT GetTransformFeedbackVaryingCount() const { return Link().xfbVaryings.size(); } - const Vector& GetTransformFeedbackVaryings() const { - return Link().xfbVaryings; - } + const Vector& 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(found - blocks.begin()); + return found == blocks.end() ? GL_INVALID_INDEX : static_cast(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(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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp index f8135d24..7baa54d2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.cpp @@ -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(hash), - static_cast(payload.programHash)); + static_cast(hash), static_cast(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(programHash)); + MGLOG_D("PipelineFactory::EvictByProgramHash: evicted %u pipelines for program hash 0x%llx", evicted, + static_cast(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(payload.programHash), - static_cast(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(payload.programHash), + static_cast(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(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(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(payload.programHash), - static_cast(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(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(payload.programHash), + static_cast(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(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(digest.wordCount) * sizeof(Uint32), + i, digest.stage, digest.wordCount, static_cast(digest.wordCount) * sizeof(Uint32), static_cast(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(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(attachment.colorWriteMask), + attachment.srcColorBlendFactor, attachment.dstColorBlendFactor, attachment.colorBlendOp, + attachment.srcAlphaBlendFactor, attachment.dstAlphaBlendFactor, attachment.alphaBlendOp); } } VK_VERIFY(result, "vkCreateGraphicsPipelines"); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h index 269f480f..c2bff746 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h @@ -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. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 87f109ed..88eb43e9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -68,9 +68,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { ProgramFactory::DescriptorBindingKind kind = ProgramFactory::DescriptorBindingKind::None; String name; - Bool operator==(const DescriptorKey& other) const { - return kind == other.kind && name == other.name; - } + Bool operator==(const DescriptorKey& other) const { return kind == other.kind && name == other.name; } }; struct DescriptorKeyHash { @@ -136,29 +134,43 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto signedness = variable.numeric.scalar.signedness; if ((flags & SPV_REFLECT_TYPE_FLAG_FLOAT) != 0) { switch (width) { - case 16: return SPVC_BASETYPE_FP16; - case 32: return SPVC_BASETYPE_FP32; - case 64: return SPVC_BASETYPE_FP64; - default: return SPVC_BASETYPE_UNKNOWN; + case 16: + return SPVC_BASETYPE_FP16; + case 32: + return SPVC_BASETYPE_FP32; + case 64: + return SPVC_BASETYPE_FP64; + default: + return SPVC_BASETYPE_UNKNOWN; } } if ((flags & SPV_REFLECT_TYPE_FLAG_INT) != 0) { if (signedness != 0) { switch (width) { - case 8: return SPVC_BASETYPE_INT8; - case 16: return SPVC_BASETYPE_INT16; - case 32: return SPVC_BASETYPE_INT32; - case 64: return SPVC_BASETYPE_INT64; - default: return SPVC_BASETYPE_UNKNOWN; + case 8: + return SPVC_BASETYPE_INT8; + case 16: + return SPVC_BASETYPE_INT16; + case 32: + return SPVC_BASETYPE_INT32; + case 64: + return SPVC_BASETYPE_INT64; + default: + return SPVC_BASETYPE_UNKNOWN; } } switch (width) { - case 8: return SPVC_BASETYPE_UINT8; - case 16: return SPVC_BASETYPE_UINT16; - case 32: return SPVC_BASETYPE_UINT32; - case 64: return SPVC_BASETYPE_UINT64; - default: return SPVC_BASETYPE_UNKNOWN; + case 8: + return SPVC_BASETYPE_UINT8; + case 16: + return SPVC_BASETYPE_UINT16; + case 32: + return SPVC_BASETYPE_UINT32; + case 64: + return SPVC_BASETYPE_UINT64; + default: + return SPVC_BASETYPE_UNKNOWN; } } if ((flags & SPV_REFLECT_TYPE_FLAG_BOOL) != 0) { @@ -203,43 +215,68 @@ namespace MobileGL::MG_Backend::DirectVulkan { switch (spvcType.basetype) { case SPVC_BASETYPE_BOOLEAN: switch (spvcType.vectorSize) { - case 1: return GL_BOOL; - case 2: return GL_BOOL_VEC2; - case 3: return GL_BOOL_VEC3; - case 4: return GL_BOOL_VEC4; - default: return GL_FALSE; + case 1: + return GL_BOOL; + case 2: + return GL_BOOL_VEC2; + case 3: + return GL_BOOL_VEC3; + case 4: + return GL_BOOL_VEC4; + default: + return GL_FALSE; } case SPVC_BASETYPE_INT32: switch (spvcType.vectorSize) { - case 1: return GL_INT; - case 2: return GL_INT_VEC2; - case 3: return GL_INT_VEC3; - case 4: return GL_INT_VEC4; - default: return GL_FALSE; + case 1: + return GL_INT; + case 2: + return GL_INT_VEC2; + case 3: + return GL_INT_VEC3; + case 4: + return GL_INT_VEC4; + default: + return GL_FALSE; } case SPVC_BASETYPE_UINT32: switch (spvcType.vectorSize) { - case 1: return GL_UNSIGNED_INT; - case 2: return GL_UNSIGNED_INT_VEC2; - case 3: return GL_UNSIGNED_INT_VEC3; - case 4: return GL_UNSIGNED_INT_VEC4; - default: return GL_FALSE; + case 1: + return GL_UNSIGNED_INT; + case 2: + return GL_UNSIGNED_INT_VEC2; + case 3: + return GL_UNSIGNED_INT_VEC3; + case 4: + return GL_UNSIGNED_INT_VEC4; + default: + return GL_FALSE; } case SPVC_BASETYPE_FP32: switch (spvcType.vectorSize) { - case 1: return GL_FLOAT; - case 2: return GL_FLOAT_VEC2; - case 3: return GL_FLOAT_VEC3; - case 4: return GL_FLOAT_VEC4; - default: return GL_FALSE; + case 1: + return GL_FLOAT; + case 2: + return GL_FLOAT_VEC2; + case 3: + return GL_FLOAT_VEC3; + case 4: + return GL_FLOAT_VEC4; + default: + return GL_FALSE; } case SPVC_BASETYPE_FP64: switch (spvcType.vectorSize) { - case 1: return GL_DOUBLE; - case 2: return GL_DOUBLE_VEC2; - case 3: return GL_DOUBLE_VEC3; - case 4: return GL_DOUBLE_VEC4; - default: return GL_FALSE; + case 1: + return GL_DOUBLE; + case 2: + return GL_DOUBLE_VEC2; + case 3: + return GL_DOUBLE_VEC3; + case 4: + return GL_DOUBLE_VEC4; + default: + return GL_FALSE; } default: return GL_FALSE; @@ -374,10 +411,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } auto context = spvtools::BuildModule( - GetSpirvTargetEnv(spirv), - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, - spirv.data(), - spirv.size()); + GetSpirvTargetEnv(spirv), [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + spirv.data(), spirv.size()); if (!context) { return true; } @@ -438,16 +473,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { const spv_target_env targetEnv = GetSpirvTargetEnv(spirv); spv_context context = spvContextCreate(targetEnv); - MOBILEGL_ASSERT(context != nullptr, - "ProgramFactory::ValidateTransformedSpirv: failed to create validator context for stage=%d program=%u", - static_cast(shaderStage), - programExternalIndex); + MOBILEGL_ASSERT( + context != nullptr, + "ProgramFactory::ValidateTransformedSpirv: failed to create validator context for stage=%d program=%u", + static_cast(shaderStage), programExternalIndex); spv_validator_options options = spvValidatorOptionsCreate(); - MOBILEGL_ASSERT(options != nullptr, - "ProgramFactory::ValidateTransformedSpirv: failed to create validator options for stage=%d program=%u", - static_cast(shaderStage), - programExternalIndex); + MOBILEGL_ASSERT( + options != nullptr, + "ProgramFactory::ValidateTransformedSpirv: failed to create validator options for stage=%d program=%u", + static_cast(shaderStage), programExternalIndex); spvValidatorOptionsSetFriendlyNames(options, true); spv_diagnostic diagnostic = nullptr; @@ -464,24 +499,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (outFailure != nullptr) { *outFailure = {String(message), static_cast(result), index}; } - MGLOG_D( - "ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d index=%zu msg=%s", - static_cast(shaderStage), - programExternalIndex, - static_cast(result), - index, - message); + MGLOG_D("ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d " + "index=%zu msg=%s", + static_cast(shaderStage), programExternalIndex, static_cast(result), index, message); } - MOBILEGL_ASSERT( - result == SPV_SUCCESS, - "ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d line=%zu column=%zu index=%zu msg=%s", - static_cast(shaderStage), - programExternalIndex, - static_cast(result), - diagnostic != nullptr ? diagnostic->position.line : 0, - diagnostic != nullptr ? diagnostic->position.column : 0, - diagnostic != nullptr ? diagnostic->position.index : 0, - diagnostic != nullptr && diagnostic->error != nullptr ? diagnostic->error : ""); + MOBILEGL_ASSERT(result == SPV_SUCCESS, + "ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u " + "result=%d line=%zu column=%zu index=%zu msg=%s", + static_cast(shaderStage), programExternalIndex, static_cast(result), + diagnostic != nullptr ? diagnostic->position.line : 0, + diagnostic != nullptr ? diagnostic->position.column : 0, + diagnostic != nullptr ? diagnostic->position.index : 0, + diagnostic != nullptr && diagnostic->error != nullptr ? diagnostic->error : ""); spvDiagnosticDestroy(diagnostic); spvValidatorOptionsDestroy(options); @@ -489,14 +518,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { return result == SPV_SUCCESS; } - void ReflectStageInterfaceVariable(const SpvReflectInterfaceVariable& variable, - Bool reflectInputs, - StageInterfaceSummary& outSummary, - Uint programExternalIndex, - const char* stageLabel, - StageInterfaceCursor& cursor, - Uint32 locationBase = 0, - Bool allowImplicitPacking = false, + void ReflectStageInterfaceVariable(const SpvReflectInterfaceVariable& variable, Bool reflectInputs, + StageInterfaceSummary& outSummary, Uint programExternalIndex, + const char* stageLabel, StageInterfaceCursor& cursor, + Uint32 locationBase = 0, Bool allowImplicitPacking = false, const char* inheritedName = nullptr) { if ((variable.decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) { return; @@ -510,12 +535,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { debugName = ""; } - const Bool hasConcreteLocation = - allowImplicitPacking ? (variable.location != 0 || variable.component != 0) - : variable.location != std::numeric_limits::max(); - const Bool hasConcreteComponent = - allowImplicitPacking ? (variable.component != 0) - : variable.component != std::numeric_limits::max(); + const Bool hasConcreteLocation = allowImplicitPacking + ? (variable.location != 0 || variable.component != 0) + : variable.location != std::numeric_limits::max(); + const Bool hasConcreteComponent = allowImplicitPacking + ? (variable.component != 0) + : variable.component != std::numeric_limits::max(); const Uint32 explicitLocationBase = locationBase + (hasConcreteLocation ? variable.location : 0u); if (variable.member_count > 0 && variable.members != nullptr) { @@ -526,8 +551,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } for (Uint32 memberIndex = 0; memberIndex < variable.member_count; ++memberIndex) { ReflectStageInterfaceVariable(variable.members[memberIndex], reflectInputs, outSummary, - programExternalIndex, stageLabel, memberCursor, - explicitLocationBase, true, debugName); + programExternalIndex, stageLabel, memberCursor, explicitLocationBase, + true, debugName); } if (memberCursor.location > cursor.location || (memberCursor.location == cursor.location && memberCursor.component > cursor.component)) { @@ -539,37 +564,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT( hasConcreteLocation || allowImplicitPacking || locationBase != 0, "ProgramFactory::ReflectStageInterface: missing concrete %s %s location for name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - debugName, - programExternalIndex); + stageLabel, reflectInputs ? "input" : "output", debugName, programExternalIndex); const Uint32 component = variable.component; - MOBILEGL_ASSERT( - component < 4 || component == std::numeric_limits::max(), - "ProgramFactory::ReflectStageInterface: unsupported %s %s component=%u at location=%u name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - component, - explicitLocationBase, - debugName, - programExternalIndex); + MOBILEGL_ASSERT(component < 4 || component == std::numeric_limits::max(), + "ProgramFactory::ReflectStageInterface: unsupported %s %s component=%u at location=%u " + "name='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", component, explicitLocationBase, debugName, + programExternalIndex); const Uint32 locationSignature = GetReflectInterfaceLocationSignature(variable); MOBILEGL_ASSERT( locationSignature != 0, - "ProgramFactory::ReflectStageInterface: unsupported %s %s type at location=%u name='%s' flags=0x%x width=%u signed=%u vec=%u rows=%u cols=%u program=%u", - stageLabel, - reflectInputs ? "input" : "output", - explicitLocationBase, - debugName, + "ProgramFactory::ReflectStageInterface: unsupported %s %s type at location=%u name='%s' flags=0x%x " + "width=%u signed=%u vec=%u rows=%u cols=%u program=%u", + stageLabel, reflectInputs ? "input" : "output", explicitLocationBase, debugName, static_cast(variable.type_description != nullptr ? variable.type_description->type_flags : 0), - variable.numeric.scalar.width, - variable.numeric.scalar.signedness, - variable.numeric.vector.component_count, - variable.numeric.matrix.row_count, - variable.numeric.matrix.column_count, - programExternalIndex); + variable.numeric.scalar.width, variable.numeric.scalar.signedness, + variable.numeric.vector.component_count, variable.numeric.matrix.row_count, + variable.numeric.matrix.column_count, programExternalIndex); const Uint32 vectorSize = GetReflectInterfaceVectorSize(variable); const Uint32 locationSpan = GetReflectInterfaceLocationSpan(variable); @@ -591,62 +604,43 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - MOBILEGL_ASSERT( - startComponent < 4, - "ProgramFactory::ReflectStageInterface: %s %s component overflow at location=%u component=%u name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - startLocation, - startComponent, - debugName, - programExternalIndex); - MOBILEGL_ASSERT( - locationSpan == 1 || startComponent == 0, - "ProgramFactory::ReflectStageInterface: %s %s multi-location variable starts at non-zero component location=%u component=%u name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - startLocation, - startComponent, - debugName, - programExternalIndex); + MOBILEGL_ASSERT(startComponent < 4, + "ProgramFactory::ReflectStageInterface: %s %s component overflow at location=%u " + "component=%u name='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", startLocation, startComponent, debugName, + programExternalIndex); + MOBILEGL_ASSERT(locationSpan == 1 || startComponent == 0, + "ProgramFactory::ReflectStageInterface: %s %s multi-location variable starts at non-zero " + "component location=%u component=%u name='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", startLocation, startComponent, debugName, + programExternalIndex); for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { const Uint32 expandedLocation = startLocation + locationOffset; const Uint32 componentBase = (locationOffset == 0) ? startComponent : 0u; - MOBILEGL_ASSERT( - expandedLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations, - "ProgramFactory::ReflectStageInterface: %s %s location=%u span=%u exceeds tracked limit for name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - startLocation, - locationSpan, - debugName, - programExternalIndex); - MOBILEGL_ASSERT( - componentBase + vectorSize <= 4, - "ProgramFactory::ReflectStageInterface: %s %s component span overflow at location=%u component=%u vec=%u name='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - expandedLocation, - componentBase, - vectorSize, - debugName, - programExternalIndex); + MOBILEGL_ASSERT(expandedLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations, + "ProgramFactory::ReflectStageInterface: %s %s location=%u span=%u exceeds tracked " + "limit for name='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", startLocation, locationSpan, debugName, + programExternalIndex); + MOBILEGL_ASSERT(componentBase + vectorSize <= 4, + "ProgramFactory::ReflectStageInterface: %s %s component span overflow at location=%u " + "component=%u vec=%u name='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", expandedLocation, componentBase, + vectorSize, debugName, programExternalIndex); for (Uint32 componentOffset = 0; componentOffset < vectorSize; ++componentOffset) { const Uint32 expandedComponent = componentBase + componentOffset; const Uint32 slotIndex = expandedLocation * 4 + expandedComponent; MOBILEGL_ASSERT( - outSummary.slotSignatures[slotIndex] == 0 || outSummary.slotSignatures[slotIndex] == locationSignature, - "ProgramFactory::ReflectStageInterface: conflicting %s %s type at location=%u component=%u existingSignature=0x%x existingName='%s' newSignature=0x%x newName='%s' program=%u", - stageLabel, - reflectInputs ? "input" : "output", - expandedLocation, - expandedComponent, + outSummary.slotSignatures[slotIndex] == 0 || + outSummary.slotSignatures[slotIndex] == locationSignature, + "ProgramFactory::ReflectStageInterface: conflicting %s %s type at location=%u component=%u " + "existingSignature=0x%x existingName='%s' newSignature=0x%x newName='%s' program=%u", + stageLabel, reflectInputs ? "input" : "output", expandedLocation, expandedComponent, outSummary.slotSignatures[slotIndex], - outSummary.slotDebugNames[slotIndex].empty() ? "" : outSummary.slotDebugNames[slotIndex].c_str(), - locationSignature, - debugName, - programExternalIndex); + outSummary.slotDebugNames[slotIndex].empty() ? "" + : outSummary.slotDebugNames[slotIndex].c_str(), + locationSignature, debugName, programExternalIndex); outSummary.slotSignatures[slotIndex] = locationSignature; outSummary.slotDebugNames[slotIndex] = debugName; } @@ -670,13 +664,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - void ReflectStageInterface(ShaderStage targetStage, - Bool reflectInputs, - const Vector& stages, - const Vector>& spirv, - StageInterfaceSummary& outSummary, - Uint programExternalIndex, - const char* stageLabel) { + void ReflectStageInterface(ShaderStage targetStage, Bool reflectInputs, const Vector& stages, + const Vector>& spirv, StageInterfaceSummary& outSummary, + Uint programExternalIndex, const char* stageLabel) { outSummary.slotSignatures.fill(0); for (SizeT moduleIndex = 0; moduleIndex < stages.size() && moduleIndex < spirv.size(); ++moduleIndex) { @@ -692,40 +682,35 @@ namespace MobileGL::MG_Backend::DirectVulkan { SpvReflectShaderModule reflectModule{}; const SpvReflectResult createResult = spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule); - MOBILEGL_ASSERT( - createResult == SPV_REFLECT_RESULT_SUCCESS, - "ProgramFactory::ReflectStageInterface: failed to create reflection module for %s %s (result=%d program=%u)", - stageLabel, - reflectInputs ? "input" : "output", - static_cast(createResult), - programExternalIndex); + MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectStageInterface: failed to create reflection module for %s %s " + "(result=%d program=%u)", + stageLabel, reflectInputs ? "input" : "output", static_cast(createResult), + programExternalIndex); if (createResult != SPV_REFLECT_RESULT_SUCCESS) { continue; } uint32_t variableCount = 0; - SpvReflectResult reflectResult = reflectInputs - ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, nullptr) - : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, nullptr); + SpvReflectResult reflectResult = + reflectInputs ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, nullptr) + : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, nullptr); MOBILEGL_ASSERT( reflectResult == SPV_REFLECT_RESULT_SUCCESS, "ProgramFactory::ReflectStageInterface: failed to enumerate %s %s variables (result=%d program=%u)", - stageLabel, - reflectInputs ? "input" : "output", - static_cast(reflectResult), + stageLabel, reflectInputs ? "input" : "output", static_cast(reflectResult), programExternalIndex); Vector variables(variableCount); if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && variableCount > 0) { - reflectResult = reflectInputs - ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, variables.data()) - : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, variables.data()); + reflectResult = + reflectInputs + ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, variables.data()) + : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, variables.data()); MOBILEGL_ASSERT( reflectResult == SPV_REFLECT_RESULT_SUCCESS, "ProgramFactory::ReflectStageInterface: failed to fetch %s %s variables (result=%d program=%u)", - stageLabel, - reflectInputs ? "input" : "output", - static_cast(reflectResult), + stageLabel, reflectInputs ? "input" : "output", static_cast(reflectResult), programExternalIndex); } @@ -748,10 +733,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - void ValidateRasterizationStageInterface(const Vector& stages, - const Vector>& spirv, - ProgramFactory::VkProgramObject& entry, - Uint programExternalIndex) { + void ValidateRasterizationStageInterface(const Vector& stages, const Vector>& spirv, + ProgramFactory::VkProgramObject& entry, Uint programExternalIndex) { const ShaderStage producerStage = PickClipFixupStage(stages); entry.rasterizationProducerStage = producerStage; entry.producerOutputComponentCount = 0; @@ -787,13 +770,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT( producerOutputs.slotSignatures[slotIndex] == fragmentInputs.slotSignatures[slotIndex], - "ProgramFactory::ValidateRasterizationStageInterface: location=%u component=%u producerSignature=0x%x producerName='%s' fragmentSignature=0x%x fragmentName='%s' program=%u", - slotIndex / 4, - slotIndex % 4, - producerOutputs.slotSignatures[slotIndex], - producerOutputs.slotDebugNames[slotIndex].empty() ? "" : producerOutputs.slotDebugNames[slotIndex].c_str(), + "ProgramFactory::ValidateRasterizationStageInterface: location=%u component=%u " + "producerSignature=0x%x producerName='%s' fragmentSignature=0x%x fragmentName='%s' program=%u", + slotIndex / 4, slotIndex % 4, producerOutputs.slotSignatures[slotIndex], + producerOutputs.slotDebugNames[slotIndex].empty() + ? "" + : producerOutputs.slotDebugNames[slotIndex].c_str(), fragmentInputs.slotSignatures[slotIndex], - fragmentInputs.slotDebugNames[slotIndex].empty() ? "" : fragmentInputs.slotDebugNames[slotIndex].c_str(), + fragmentInputs.slotDebugNames[slotIndex].empty() ? "" + : fragmentInputs.slotDebugNames[slotIndex].c_str(), programExternalIndex); } } @@ -866,8 +851,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // declares one of the expected type. Annotations are the search space deliberately: // they survive the link-time sanitize chain's interface delisting, which is the whole // reason EnsureEntryPointInterface exists. - Bool FindBuiltInTarget(spvtools::opt::IRContext* context, spv::BuiltIn builtIn, - BuiltInTypeCheckFn typeCheck, PositionTargetInfo* outTarget) { + Bool FindBuiltInTarget(spvtools::opt::IRContext* context, spv::BuiltIn builtIn, BuiltInTypeCheckFn typeCheck, + PositionTargetInfo* outTarget) { Vector> memberCandidates; constexpr auto kDecorationBuiltIn = static_cast(spv::Decoration::BuiltIn); const auto wantedBuiltIn = static_cast(builtIn); @@ -1054,8 +1039,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { zValueId = mappedZ->result_id(); } - auto* fixedPosition = builder.AddCompositeConstruct(target.vectorTypeId, - {xValueId, yValueId, zValueId, w->result_id()}); + auto* fixedPosition = + builder.AddCompositeConstruct(target.vectorTypeId, {xValueId, yValueId, zValueId, w->result_id()}); if (!fixedPosition) return false; return builder.AddStore(positionPtrId, fixedPosition->result_id()) != nullptr; @@ -1096,10 +1081,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool doYFlip = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionYFlip); const Bool doZRemap = (m_transformFlags & ProgramFactory::CompileOptionBit::PositionZRemap); const Bool doSurfaceRotate90 = (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate90); - const Bool doSurfaceRotate180 = - (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate180); - const Bool doSurfaceRotate270 = - (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate270); + const Bool doSurfaceRotate180 = (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate180); + const Bool doSurfaceRotate270 = (m_transformFlags & ProgramFactory::CompileOptionBit::SurfaceRotate270); Bool modified = false; for (auto& entryPoint : get_module()->entry_points()) { @@ -1372,7 +1355,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return 0; } return context()->get_type_mgr()->FindPointerToType(pointerType->GetSingleWordInOperand(1), - spv::StorageClass::Private); + spv::StorageClass::Private); } Uint32 m_framebufferHeight = 0; @@ -1436,9 +1419,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto decorateMemberForXfb = [&](Uint32 structTypeId, Uint32 memberIndex, Uint32 bufferIndex, Uint32 offsetBytes) { const Uint32 stride = bufferIndex < m_strides.size() ? m_strides[bufferIndex] : 0; - decorationManager->AddMemberDecoration(structTypeId, memberIndex, - static_cast(spv::Decoration::XfbBuffer), - bufferIndex); + decorationManager->AddMemberDecoration( + structTypeId, memberIndex, static_cast(spv::Decoration::XfbBuffer), bufferIndex); decorationManager->AddMemberDecoration(structTypeId, memberIndex, static_cast(spv::Decoration::XfbStride), stride); decorationManager->AddMemberDecoration(structTypeId, memberIndex, @@ -1486,8 +1468,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // exists to remove. Detected off the module's own debug names, so a // composite built from another program's stage answers for the module it // actually contains. - const auto carrierIt = idsByName.find( - MG_Util::ShaderTranspiler::ShaderCompiler::POINT_SIZE_CAPTURE_CARRIER_NAME); + const auto carrierIt = + idsByName.find(MG_Util::ShaderTranspiler::ShaderCompiler::POINT_SIZE_CAPTURE_CARRIER_NAME); if (carrierIt != idsByName.end()) { decorateForXfb(carrierIt->second, varying.bufferIndex, varying.offsetBytes); modified = true; @@ -1508,7 +1490,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (structTypeId == 0) { if (const auto it = idsByName.find(varying.blockName); it != idsByName.end()) { - const spvtools::opt::Instruction* def = context()->get_def_use_mgr()->GetDef(it->second); + const spvtools::opt::Instruction* def = + context()->get_def_use_mgr()->GetDef(it->second); if (def != nullptr && def->opcode() == spv::Op::OpTypeStruct) { structTypeId = it->second; } else if (def != nullptr && def->opcode() == spv::Op::OpVariable) { @@ -1519,12 +1502,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (structTypeId == 0) { MGLOG_E("XfbCaptureDecoratePass: no SPIR-V interface block '%s' (instance '%s') for " "capture '%s'", - varying.blockName.c_str(), varying.blockInstanceName.c_str(), - varying.name.c_str()); + varying.blockName.c_str(), varying.blockInstanceName.c_str(), varying.name.c_str()); continue; } - auto& group = - memberGroups[{structTypeId, static_cast(varying.blockMemberIndex)}]; + auto& group = memberGroups[{structTypeId, static_cast(varying.blockMemberIndex)}]; if (group.offsets.empty() || varying.offsetBytes < group.minOffset) { group.minOffset = varying.offsetBytes; } @@ -1549,8 +1530,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // decoration, but say so, because the capture layout will not match GL. std::sort(group.offsets.begin(), group.offsets.end()); for (SizeT i = 1; i < group.offsets.size(); ++i) { - if (group.elementBytes == 0 || - group.offsets[i] != group.offsets[i - 1] + group.elementBytes) { + if (group.elementBytes == 0 || group.offsets[i] != group.offsets[i - 1] + group.elementBytes) { MGLOG_D("XfbCaptureDecoratePass: block member %u of type %%%u is captured with a " "non-contiguous element set; the capture layout will differ from GL's", key.second, key.first); @@ -1562,15 +1542,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (needsPositionMirror) { - modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, - spv::BuiltIn::Position, IsVec4Float32, - "gl_Position", positionBufferIndex, positionOffset, - decorateForXfb); + modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, spv::BuiltIn::Position, + IsVec4Float32, "gl_Position", positionBufferIndex, + positionOffset, decorateForXfb); } if (needsPointSizeMirror) { - modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, - spv::BuiltIn::PointSize, IsFloat32Scalar, - "gl_PointSize", pointSizeBufferIndex, + modified |= MirrorPerVertexBuiltInForCapture(entryFunctionId, *entryPoint, spv::BuiltIn::PointSize, + IsFloat32Scalar, "gl_PointSize", pointSizeBufferIndex, pointSizeOffset, decorateForXfb); } @@ -1602,8 +1580,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (const spvtools::opt::Instruction* pointee = defUse->GetDef(pointeeId); pointee != nullptr; pointee = defUse->GetDef(pointeeId)) { if (pointee->opcode() == spv::Op::OpTypeStruct) return pointeeId; - if (pointee->opcode() != spv::Op::OpTypeArray && - pointee->opcode() != spv::Op::OpTypeRuntimeArray) { + if (pointee->opcode() != spv::Op::OpTypeArray && pointee->opcode() != spv::Op::OpTypeRuntimeArray) { return 0; } pointeeId = pointee->GetSingleWordInOperand(0); @@ -1658,8 +1635,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { mirrorLocation = std::max(mirrorLocation, annotation.GetSingleWordInOperand(2) + 1); } auto* decorationManager = context()->get_decoration_mgr(); - decorationManager->AddDecorationVal(mirrorVariableId, - static_cast(spv::Decoration::Location), mirrorLocation); + decorationManager->AddDecorationVal(mirrorVariableId, static_cast(spv::Decoration::Location), + mirrorLocation); decorateForXfb(mirrorVariableId, bufferIndex, offsetBytes); entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {mirrorVariableId}}); @@ -1670,10 +1647,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (auto& block : *function) { for (auto instIter = block.begin(); instIter != block.end(); ++instIter) { // Geometry stages capture per emitted vertex; other stages at return. - const Bool isInjectionSite = - model == spv::ExecutionModel::Geometry - ? instIter->opcode() == spv::Op::OpEmitVertex - : instIter->opcode() == spv::Op::OpReturn; + const Bool isInjectionSite = model == spv::ExecutionModel::Geometry + ? instIter->opcode() == spv::Op::OpEmitVertex + : instIter->opcode() == spv::Op::OpReturn; if (!isInjectionSite) continue; InstructionBuilder builder(context(), &*instIter, IRContext::kAnalysisNone); const Uint32 memberIndexId = builder.GetUintConstantId(target.memberIndex); @@ -1891,10 +1867,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // FINAL module via ValidateTransformedSpirv, which latches instead of rerouting // control flow. options.set_run_validator(false); - optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - MGLOG_E("Vulkan: explicit-LOD0 pass: %s", message != nullptr ? message : ""); - }); + optimizer.SetMessageConsumer( + [](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + MGLOG_E("Vulkan: explicit-LOD0 pass: %s", message != nullptr ? message : ""); + }); optimizer.RegisterPass(CreateForceExplicitLod0SamplePass()); const Bool success = optimizer.Run(input.data(), input.size(), &output, options); @@ -1912,8 +1888,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // namespace - Bool TransformSpirvForFragCoordYFlip(const Vector& input, Vector& output, - Uint32 framebufferHeight) { + Bool TransformSpirvForFragCoordYFlip(const Vector& input, Vector& output, Uint32 framebufferHeight) { if (input.empty()) { output.clear(); return true; @@ -1926,12 +1901,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3); spvtools::OptimizerOptions options; options.set_run_validator(false); // see TransformSpirvForExplicitLod0Sampling - optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { + optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, const char* message) { MGLOG_E("Vulkan: fragcoord y-flip pass: %s", message != nullptr ? message : ""); }); - optimizer.RegisterPass( - spvtools::Optimizer::PassToken(MakeUnique(framebufferHeight))); + optimizer.RegisterPass(spvtools::Optimizer::PassToken(MakeUnique(framebufferHeight))); const Bool success = optimizer.Run(input.data(), input.size(), &output, options); if (!success) { @@ -1951,9 +1924,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector varyings; varyings.reserve(program.GetTransformFeedbackVaryingCount()); for (const auto& varying : program.GetTransformFeedbackVaryings()) { - varyings.push_back({varying.name, varying.bufferIndex, varying.offsetBytes, - varying.blockInstanceName, varying.blockName, varying.blockMemberIndex, - varying.blockMemberElement, varying.byteSize}); + varyings.push_back({varying.name, varying.bufferIndex, varying.offsetBytes, varying.blockInstanceName, + varying.blockName, varying.blockMemberIndex, varying.blockMemberElement, + varying.byteSize}); } Vector strides; strides.reserve(program.GetTransformFeedbackBufferCount()); @@ -1964,12 +1937,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3); spvtools::OptimizerOptions options; options.set_run_validator(false); // see TransformSpirvForExplicitLod0Sampling - optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - MGLOG_E("Vulkan: xfb capture pass: %s", message != nullptr ? message : ""); - }); - optimizer.RegisterPass(spvtools::Optimizer::PassToken( - MakeUnique(Move(varyings), Move(strides)))); + optimizer.SetMessageConsumer( + [](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + MGLOG_E("Vulkan: xfb capture pass: %s", message != nullptr ? message : ""); + }); + optimizer.RegisterPass( + spvtools::Optimizer::PassToken(MakeUnique(Move(varyings), Move(strides)))); const Bool success = optimizer.Run(input.data(), input.size(), &output, options); if (!success) { @@ -1994,10 +1967,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_3); spvtools::OptimizerOptions options; options.set_run_validator(false); // see TransformSpirvForExplicitLod0Sampling - optimizer.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - MGLOG_E("Vulkan: position fix pass: %s", message != nullptr ? message : ""); - }); + optimizer.SetMessageConsumer( + [](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + MGLOG_E("Vulkan: position fix pass: %s", message != nullptr ? message : ""); + }); optimizer.RegisterPass(CreateGlToVulkanPositionFixPass(transformFlags)); const Bool success = optimizer.Run(input.data(), input.size(), &output, options); @@ -2025,7 +1998,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return ShaderStage::Unknown; } - ProgramFactory::DescriptorBindingKind ReflectDescriptorTypeToBindingKind(SpvReflectDescriptorType descriptorType) { + ProgramFactory::DescriptorBindingKind ReflectDescriptorTypeToBindingKind( + SpvReflectDescriptorType descriptorType) { switch (descriptorType) { case SPV_REFLECT_DESCRIPTOR_TYPE_UNIFORM_BUFFER: return ProgramFactory::DescriptorBindingKind::UniformBufferDynamic; @@ -2060,8 +2034,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (name.empty()) { name = std::format("__mg_unnamed_descriptor_set{}_binding{}_id{}", binding.set, binding.binding, binding.spirv_id); - MGLOG_W("ProgramFactory: descriptor has empty name; using generated name '%s' (type=%d)", - name.c_str(), static_cast(binding.descriptor_type)); + MGLOG_W("ProgramFactory: descriptor has empty name; using generated name '%s' (type=%d)", name.c_str(), + static_cast(binding.descriptor_type)); } if (kind == ProgramFactory::DescriptorBindingKind::CombinedImageSampler || kind == ProgramFactory::DescriptorBindingKind::UniformTexelBuffer || @@ -2100,9 +2074,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } - const SpvReflectResult createResult = - spvReflectCreateShaderModule(moduleSpv.size() * sizeof(Uint), moduleSpv.data(), - &reflectModules[moduleIndex]); + const SpvReflectResult createResult = spvReflectCreateShaderModule( + moduleSpv.size() * sizeof(Uint), moduleSpv.data(), &reflectModules[moduleIndex]); MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS, "ProgramFactory: failed to create reflection module for stage %zu (result=%d)", moduleIndex, static_cast(createResult)); @@ -2127,10 +2100,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (bindingCount > 0) { reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModules[moduleIndex], &bindingCount, bindings.data()); - MOBILEGL_ASSERT( - reflectResult == SPV_REFLECT_RESULT_SUCCESS, - "ProgramFactory: failed to fetch descriptor bindings for stage %zu (result=%d)", moduleIndex, - static_cast(reflectResult)); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory: failed to fetch descriptor bindings for stage %zu (result=%d)", + moduleIndex, static_cast(reflectResult)); if (reflectResult != SPV_REFLECT_RESULT_SUCCESS) { destroyReflectModules(); return false; @@ -2204,7 +2176,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (binding->binding != assignedBinding || binding->set != 0) { reflectResult = spvReflectChangeDescriptorBindingNumbers(&reflectModules[moduleIndex], binding, - assignedBinding, 0); + assignedBinding, 0); MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, "ProgramFactory: failed to remap descriptor '%s' in stage %zu (result=%d)", key.name.c_str(), moduleIndex, static_cast(reflectResult)); @@ -2223,8 +2195,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 codeSizeBytes = spvReflectGetCodeSize(&reflectModules[moduleIndex]); MOBILEGL_ASSERT((codeSizeBytes % sizeof(Uint)) == 0, - "ProgramFactory: reflected SPIR-V size is not word aligned for stage %zu", - moduleIndex); + "ProgramFactory: reflected SPIR-V size is not word aligned for stage %zu", moduleIndex); const Uint32* code = spvReflectGetCode(&reflectModules[moduleIndex]); MOBILEGL_ASSERT(code != nullptr, "ProgramFactory: reflected SPIR-V code pointer is null for stage %zu", moduleIndex); @@ -2372,13 +2343,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool ProgramFactory::ReflectedDeclaresOutputBuiltin(const SpvReflectShaderModule& reflectModule, - SpvBuiltIn builtin) { + SpvBuiltIn builtin) { for (Uint32 entryIndex = 0; entryIndex < reflectModule.entry_point_count; ++entryIndex) { const SpvReflectEntryPoint& entryPoint = reflectModule.entry_points[entryIndex]; for (Uint32 variableIndex = 0; variableIndex < entryPoint.output_variable_count; ++variableIndex) { const SpvReflectInterfaceVariable* variable = entryPoint.output_variables[variableIndex]; - if (variable != nullptr && - (variable->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0 && + if (variable != nullptr && (variable->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0 && variable->built_in == builtin) { return true; } @@ -2393,8 +2363,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SpvReflectEntryPoint& entryPoint = reflectModule.entry_points[entryIndex]; for (Uint32 variableIndex = 0; variableIndex < entryPoint.input_variable_count; ++variableIndex) { const SpvReflectInterfaceVariable* variable = entryPoint.input_variables[variableIndex]; - if (variable != nullptr && - (variable->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0 && + if (variable != nullptr && (variable->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0 && variable->built_in == builtin) { return true; } @@ -2424,17 +2393,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormat ProgramFactory::ConvertSpirvImageFormatToVkFormat(SpvImageFormat format) { switch (format) { - case SpvImageFormatUnknown: return VK_FORMAT_UNDEFINED; - case SpvImageFormatRgba32f: return VK_FORMAT_R32G32B32A32_SFLOAT; - case SpvImageFormatRgba16f: return VK_FORMAT_R16G16B16A16_SFLOAT; - case SpvImageFormatR32f: return VK_FORMAT_R32_SFLOAT; - case SpvImageFormatRgba8: return VK_FORMAT_R8G8B8A8_UNORM; - case SpvImageFormatRgba8Snorm: return VK_FORMAT_R8G8B8A8_SNORM; - case SpvImageFormatRg32f: return VK_FORMAT_R32G32_SFLOAT; - case SpvImageFormatRg16f: return VK_FORMAT_R16G16_SFLOAT; - case SpvImageFormatR11fG11fB10f: return VK_FORMAT_B10G11R11_UFLOAT_PACK32; - case SpvImageFormatR16f: return VK_FORMAT_R16_SFLOAT; - case SpvImageFormatRgba16: return VK_FORMAT_R16G16B16A16_UNORM; + case SpvImageFormatUnknown: + return VK_FORMAT_UNDEFINED; + case SpvImageFormatRgba32f: + return VK_FORMAT_R32G32B32A32_SFLOAT; + case SpvImageFormatRgba16f: + return VK_FORMAT_R16G16B16A16_SFLOAT; + case SpvImageFormatR32f: + return VK_FORMAT_R32_SFLOAT; + case SpvImageFormatRgba8: + return VK_FORMAT_R8G8B8A8_UNORM; + case SpvImageFormatRgba8Snorm: + return VK_FORMAT_R8G8B8A8_SNORM; + case SpvImageFormatRg32f: + return VK_FORMAT_R32G32_SFLOAT; + case SpvImageFormatRg16f: + return VK_FORMAT_R16G16_SFLOAT; + case SpvImageFormatR11fG11fB10f: + return VK_FORMAT_B10G11R11_UFLOAT_PACK32; + case SpvImageFormatR16f: + return VK_FORMAT_R16_SFLOAT; + case SpvImageFormatRgba16: + return VK_FORMAT_R16G16B16A16_UNORM; // A2**B**10G10R10, matching MGToVk::ConvertTextureInternalFormatToVkFormat's RGB10A2. // This value becomes the storage image VIEW's format while the image itself was created // from the texture's internal format, so the two must name the same bit layout or the @@ -2443,38 +2423,70 @@ namespace MobileGL::MG_Backend::DirectVulkan { // 20-29 and A in 30-31, which is Vulkan's A2B10G10R10; A2R10G10B10 transposes R and B. // KHR-GL43.shader_image_load_store.basic-allFormats-store read back [2,1,0,3] for an // rgb10_a2ui image stored as [0,1,2,3] while these two converters disagreed. - case SpvImageFormatRgb10A2: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; - case SpvImageFormatRg16: return VK_FORMAT_R16G16_UNORM; - case SpvImageFormatRg8: return VK_FORMAT_R8G8_UNORM; - case SpvImageFormatR16: return VK_FORMAT_R16_UNORM; - case SpvImageFormatR8: return VK_FORMAT_R8_UNORM; - case SpvImageFormatRgba16Snorm: return VK_FORMAT_R16G16B16A16_SNORM; - case SpvImageFormatRg16Snorm: return VK_FORMAT_R16G16_SNORM; - case SpvImageFormatRg8Snorm: return VK_FORMAT_R8G8_SNORM; - case SpvImageFormatR16Snorm: return VK_FORMAT_R16_SNORM; - case SpvImageFormatR8Snorm: return VK_FORMAT_R8_SNORM; - case SpvImageFormatRgba32i: return VK_FORMAT_R32G32B32A32_SINT; - case SpvImageFormatRgba16i: return VK_FORMAT_R16G16B16A16_SINT; - case SpvImageFormatRgba8i: return VK_FORMAT_R8G8B8A8_SINT; - case SpvImageFormatR32i: return VK_FORMAT_R32_SINT; - case SpvImageFormatRg32i: return VK_FORMAT_R32G32_SINT; - case SpvImageFormatRg16i: return VK_FORMAT_R16G16_SINT; - case SpvImageFormatRg8i: return VK_FORMAT_R8G8_SINT; - case SpvImageFormatR16i: return VK_FORMAT_R16_SINT; - case SpvImageFormatR8i: return VK_FORMAT_R8_SINT; - case SpvImageFormatRgba32ui: return VK_FORMAT_R32G32B32A32_UINT; - case SpvImageFormatRgba16ui: return VK_FORMAT_R16G16B16A16_UINT; - case SpvImageFormatRgba8ui: return VK_FORMAT_R8G8B8A8_UINT; - case SpvImageFormatR32ui: return VK_FORMAT_R32_UINT; - case SpvImageFormatRgb10a2ui: return VK_FORMAT_A2B10G10R10_UINT_PACK32; // see Rgb10A2 above - case SpvImageFormatRg32ui: return VK_FORMAT_R32G32_UINT; - case SpvImageFormatRg16ui: return VK_FORMAT_R16G16_UINT; - case SpvImageFormatRg8ui: return VK_FORMAT_R8G8_UINT; - case SpvImageFormatR16ui: return VK_FORMAT_R16_UINT; - case SpvImageFormatR8ui: return VK_FORMAT_R8_UINT; - case SpvImageFormatR64ui: return VK_FORMAT_R64_UINT; - case SpvImageFormatR64i: return VK_FORMAT_R64_SINT; - case SpvImageFormatMax: return VK_FORMAT_UNDEFINED; + case SpvImageFormatRgb10A2: + return VK_FORMAT_A2B10G10R10_UNORM_PACK32; + case SpvImageFormatRg16: + return VK_FORMAT_R16G16_UNORM; + case SpvImageFormatRg8: + return VK_FORMAT_R8G8_UNORM; + case SpvImageFormatR16: + return VK_FORMAT_R16_UNORM; + case SpvImageFormatR8: + return VK_FORMAT_R8_UNORM; + case SpvImageFormatRgba16Snorm: + return VK_FORMAT_R16G16B16A16_SNORM; + case SpvImageFormatRg16Snorm: + return VK_FORMAT_R16G16_SNORM; + case SpvImageFormatRg8Snorm: + return VK_FORMAT_R8G8_SNORM; + case SpvImageFormatR16Snorm: + return VK_FORMAT_R16_SNORM; + case SpvImageFormatR8Snorm: + return VK_FORMAT_R8_SNORM; + case SpvImageFormatRgba32i: + return VK_FORMAT_R32G32B32A32_SINT; + case SpvImageFormatRgba16i: + return VK_FORMAT_R16G16B16A16_SINT; + case SpvImageFormatRgba8i: + return VK_FORMAT_R8G8B8A8_SINT; + case SpvImageFormatR32i: + return VK_FORMAT_R32_SINT; + case SpvImageFormatRg32i: + return VK_FORMAT_R32G32_SINT; + case SpvImageFormatRg16i: + return VK_FORMAT_R16G16_SINT; + case SpvImageFormatRg8i: + return VK_FORMAT_R8G8_SINT; + case SpvImageFormatR16i: + return VK_FORMAT_R16_SINT; + case SpvImageFormatR8i: + return VK_FORMAT_R8_SINT; + case SpvImageFormatRgba32ui: + return VK_FORMAT_R32G32B32A32_UINT; + case SpvImageFormatRgba16ui: + return VK_FORMAT_R16G16B16A16_UINT; + case SpvImageFormatRgba8ui: + return VK_FORMAT_R8G8B8A8_UINT; + case SpvImageFormatR32ui: + return VK_FORMAT_R32_UINT; + case SpvImageFormatRgb10a2ui: + return VK_FORMAT_A2B10G10R10_UINT_PACK32; // see Rgb10A2 above + case SpvImageFormatRg32ui: + return VK_FORMAT_R32G32_UINT; + case SpvImageFormatRg16ui: + return VK_FORMAT_R16G16_UINT; + case SpvImageFormatRg8ui: + return VK_FORMAT_R8G8_UINT; + case SpvImageFormatR16ui: + return VK_FORMAT_R16_UINT; + case SpvImageFormatR8ui: + return VK_FORMAT_R8_UINT; + case SpvImageFormatR64ui: + return VK_FORMAT_R64_UINT; + case SpvImageFormatR64i: + return VK_FORMAT_R64_SINT; + case SpvImageFormatMax: + return VK_FORMAT_UNDEFINED; } return VK_FORMAT_UNDEFINED; } @@ -2587,8 +2599,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Only FragCoordYFlip variants bake the height in, so mixing it unconditionally would // re-key every program in the cache on a resize for no reason. if (flags & CompileOptionBit::FragCoordYFlip) { - XXHASH_VERIFY(XXH64_update(hashState, &m_defaultFramebufferHeight, - sizeof(m_defaultFramebufferHeight))); + XXHASH_VERIFY(XXH64_update(hashState, &m_defaultFramebufferHeight, sizeof(m_defaultFramebufferHeight))); } // Include UBO block bindings in hash so different binding configurations produce different entries @@ -2712,8 +2723,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - void ProgramFactory::ReflectVertexInputs(const Vector& stages, - const Vector>& spirv, + void ProgramFactory::ReflectVertexInputs(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const { entry.activeVertexInputLocationMask = 0; entry.vertexInputTypes.fill(0); @@ -2770,10 +2780,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const GLenum locationType = GetReflectInterfaceLocationType(*input); - MOBILEGL_ASSERT(locationType != GL_FALSE, - "ProgramFactory::ReflectVertexInputs: unsupported vertex input type at location=%u name='%s'", - input->location, - input->name ? input->name : ""); + MOBILEGL_ASSERT( + locationType != GL_FALSE, + "ProgramFactory::ReflectVertexInputs: unsupported vertex input type at location=%u name='%s'", + input->location, input->name ? input->name : ""); const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*input); for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { const Uint32 expandedLocation = input->location + locationOffset; @@ -2797,8 +2807,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // evaluation stages. Rather than guess which one is last, every non-fragment, non-compute // module is asked - one writer anywhere means this program's draws need a multi-viewport // pipeline, and a false positive costs only a wider viewportCount. - void ProgramFactory::ReflectViewportIndexUsage(const Vector& stages, - const Vector>& spirv, + void ProgramFactory::ReflectViewportIndexUsage(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const { entry.writesViewportIndexBuiltin = false; @@ -2831,8 +2840,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - void ProgramFactory::ReflectFragmentOutputs(const Vector& stages, - const Vector>& spirv, + void ProgramFactory::ReflectFragmentOutputs(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const { entry.activeFragmentOutputLocationMask = 0; entry.fragmentOutputTypes.fill(0); @@ -2885,9 +2893,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { const GLenum locationType = GetReflectInterfaceLocationType(*output); MOBILEGL_ASSERT(locationType != GL_FALSE, - "ProgramFactory::ReflectFragmentOutputs: unsupported fragment output type at location=%u name='%s'", - output->location, - output->name ? output->name : ""); + "ProgramFactory::ReflectFragmentOutputs: unsupported fragment output type at " + "location=%u name='%s'", + output->location, output->name ? output->name : ""); const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*output); for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { const Uint32 expandedLocation = output->location + locationOffset; @@ -2942,10 +2950,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // key and every element of an array of arrays seeds texture unit 0. Resolving those elements // would then paint silently-wrong pixels with no diagnostic at all - strictly worse than // declining. The decline goes away together with the seeding fix, not before it. - static Uint32 DescriptorCountForOpaqueUniformArray(const MagmaProgramSource& program, - const String& uniformName, Uint32 binding, Int baseLocation, - Uint32 reflectedCount, Uint32 maxBindings, - const char* kindLabel, Bool& outDeclined) { + static Uint32 DescriptorCountForOpaqueUniformArray(const MagmaProgramSource& program, const String& uniformName, + Uint32 binding, Int baseLocation, Uint32 reflectedCount, + Uint32 maxBindings, const char* kindLabel, Bool& outDeclined) { const Uint32 count = std::max(1u, reflectedCount); if (count == 1) { return 1u; @@ -2978,8 +2985,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return count; } - void ProgramFactory::ReflectLayout(const MagmaProgramSource& program, - const Vector>& spirv, VkProgramObject& entry) const { + void ProgramFactory::ReflectLayout(const MagmaProgramSource& program, const Vector>& spirv, + VkProgramObject& entry) const { // Initialize layout vectors entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None); entry.uniformBlockIndexByBinding.assign(m_maxBindings, -1); @@ -3019,8 +3026,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { SPV_REFLECT_RESULT_SUCCESS && countProbe > 0) { Vector probeBindings(countProbe); - if (spvReflectEnumerateDescriptorBindings(&reflectModule, &countProbe, - probeBindings.data()) == + if (spvReflectEnumerateDescriptorBindings(&reflectModule, &countProbe, probeBindings.data()) == SPV_REFLECT_RESULT_SUCCESS) { for (const auto* probeBinding : probeBindings) { if (probeBinding != nullptr) { @@ -3042,24 +3048,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Check for global UBO if (std::strstr(ubo.name.c_str(), MG_Util::ShaderTranspiler::GLOBAL_UBO_NAME) != nullptr) { - MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || - entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, - "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", - binding, ubo.name.c_str()); + MOBILEGL_ASSERT( + entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", + binding, ubo.name.c_str()); entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; MOBILEGL_ASSERT(entry.globalUboBinding < 0 || entry.globalUboBinding == static_cast(binding), "ProgramFactory::ReflectLayout: global UBO binding mismatch (%d vs %u)", entry.globalUboBinding, binding); - MOBILEGL_ASSERT(entry.uniformBlockIndexByBinding[binding] < 0, - "ProgramFactory::ReflectLayout: global UBO shares binding %u with regular UBO index %d", - binding, entry.uniformBlockIndexByBinding[binding]); + MOBILEGL_ASSERT( + entry.uniformBlockIndexByBinding[binding] < 0, + "ProgramFactory::ReflectLayout: global UBO shares binding %u with regular UBO index %d", + binding, entry.uniformBlockIndexByBinding[binding]); entry.globalUboBinding = static_cast(binding); continue; } const auto countIt = descriptorCountByBinding.find(binding); - const Uint32 descriptorCount = - countIt != descriptorCountByBinding.end() ? countIt->second : 1u; + const Uint32 descriptorCount = countIt != descriptorCountByBinding.end() ? countIt->second : 1u; if (descriptorCount <= 1) { const Uint blockIndex = program.GetUniformBlockIndex(ubo.name.c_str()); @@ -3069,17 +3076,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } - MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || - entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, - "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", - binding, ubo.name.c_str()); + MOBILEGL_ASSERT( + entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", + binding, ubo.name.c_str()); entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; - MOBILEGL_ASSERT(entry.globalUboBinding != static_cast(binding), - "ProgramFactory::ReflectLayout: regular UBO '%s' collides with global UBO binding %u", - ubo.name.c_str(), binding); + MOBILEGL_ASSERT( + entry.globalUboBinding != static_cast(binding), + "ProgramFactory::ReflectLayout: regular UBO '%s' collides with global UBO binding %u", + ubo.name.c_str(), binding); MOBILEGL_ASSERT(entry.uniformBlockIndexByBinding[binding] < 0 || entry.uniformBlockIndexByBinding[binding] == static_cast(blockIndex), - "ProgramFactory::ReflectLayout: descriptor binding %u maps to conflicting UBO blocks (%d vs %u)", + "ProgramFactory::ReflectLayout: descriptor binding %u maps to conflicting UBO " + "blocks (%d vs %u)", binding, entry.uniformBlockIndexByBinding[binding], blockIndex); entry.uniformBlockIndexByBinding[binding] = static_cast(blockIndex); continue; @@ -3124,10 +3134,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } - MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || - entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, - "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", - binding, ubo.name.c_str()); + MOBILEGL_ASSERT( + entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == DescriptorBindingKind::UniformBufferDynamic, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for UBO '%s'", binding, + ubo.name.c_str()); entry.bindingKinds[binding] = DescriptorBindingKind::UniformBufferDynamic; entry.bindingDescriptorCounts[binding] = static_cast(descriptorCount); entry.uniformBlockIndexByBinding[binding] = elementBlockIndices[0]; @@ -3146,10 +3157,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (reflectedBindingCount > 0) { reflectResult = spvReflectEnumerateDescriptorBindings(&reflectModule, &reflectedBindingCount, reflectedBindings.data()); - MOBILEGL_ASSERT( - reflectResult == SPV_REFLECT_RESULT_SUCCESS, - "ProgramFactory::ReflectLayout: failed to fetch descriptor bindings (result=%d)", - static_cast(reflectResult)); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectLayout: failed to fetch descriptor bindings (result=%d)", + static_cast(reflectResult)); } for (const auto* sampler : reflectedBindings) { @@ -3171,15 +3181,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { "ProgramFactory::ReflectLayout: sampler binding %u exceeds maxBindings=%u for '%s'", binding, m_maxBindings, uniformName.c_str()); - MOBILEGL_ASSERT(entry.bindingKinds[binding] == DescriptorBindingKind::None || - entry.bindingKinds[binding] == descriptorKind, - "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for resource '%s'", - binding, uniformName.c_str()); + MOBILEGL_ASSERT( + entry.bindingKinds[binding] == DescriptorBindingKind::None || + entry.bindingKinds[binding] == descriptorKind, + "ProgramFactory::ReflectLayout: descriptor binding %u has conflicting kinds for resource '%s'", + binding, uniformName.c_str()); entry.bindingKinds[binding] = descriptorKind; if (descriptorKind == DescriptorBindingKind::StorageBuffer) { - const GLuint blockIndex = - program.GetShaderStorageBlockIndex(uniformName); + const GLuint blockIndex = program.GetShaderStorageBlockIndex(uniformName); if (blockIndex == GL_INVALID_INDEX) { MGLOG_D("ProgramFactory::ReflectLayout: skipping inactive SSBO '%s' at binding %u", uniformName.c_str(), binding); @@ -3265,11 +3275,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_maxBindings, "image", entry.declinedDescriptors); entry.bindingDescriptorCounts[binding] = static_cast(imageArrayCount); - const VkFormat reflectedFormat = - ConvertSpirvImageFormatToVkFormat(sampler->image.image_format); + const VkFormat reflectedFormat = ConvertSpirvImageFormatToVkFormat(sampler->image.image_format); VkFormat& existingFormat = entry.storageImageFormatByBinding[binding]; - MOBILEGL_ASSERT(existingFormat == VK_FORMAT_UNDEFINED || - reflectedFormat == VK_FORMAT_UNDEFINED || + MOBILEGL_ASSERT(existingFormat == VK_FORMAT_UNDEFINED || reflectedFormat == VK_FORMAT_UNDEFINED || existingFormat == reflectedFormat, "ProgramFactory::ReflectLayout: storage image binding %u ('%s') has " "conflicting reflected formats (%d vs %d)", @@ -3278,8 +3286,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (existingFormat == VK_FORMAT_UNDEFINED) { existingFormat = reflectedFormat; } - if (m_unformattedFloatStorageImagesEnabled && - existingFormat == VK_FORMAT_UNDEFINED && + if (m_unformattedFloatStorageImagesEnabled && existingFormat == VK_FORMAT_UNDEFINED && IsFloatStorageImageUniformType(uniformType)) { entry.storageImageUsesBindingFormatByBinding[binding] = true; } else if (reflectedFormat != VK_FORMAT_UNDEFINED) { @@ -3298,11 +3305,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // exactly as a samplerBuffer array is, so bindingDescriptorCounts stays at the // default 1 and the descriptor write below may take the address of a vector // element without reserving room for extra elements. - const VkFormat reflectedFormat = - ConvertSpirvImageFormatToVkFormat(sampler->image.image_format); + const VkFormat reflectedFormat = ConvertSpirvImageFormatToVkFormat(sampler->image.image_format); VkFormat& existingFormat = entry.storageImageFormatByBinding[binding]; - MOBILEGL_ASSERT(existingFormat == VK_FORMAT_UNDEFINED || - reflectedFormat == VK_FORMAT_UNDEFINED || + MOBILEGL_ASSERT(existingFormat == VK_FORMAT_UNDEFINED || reflectedFormat == VK_FORMAT_UNDEFINED || existingFormat == reflectedFormat, "ProgramFactory::ReflectLayout: storage texel buffer binding %u ('%s') " "has conflicting reflected formats (%d vs %d)", @@ -3337,8 +3342,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { "ProgramFactory::ReflectLayout: failed to resolve sampler numeric domain " "for '%s' (uniformType=0x%x)", uniformName.c_str(), uniformType); - MOBILEGL_ASSERT(entry.samplerNumericDomainByBinding[binding] == - SamplerNumericDomain::Unknown || + MOBILEGL_ASSERT(entry.samplerNumericDomainByBinding[binding] == SamplerNumericDomain::Unknown || entry.samplerNumericDomainByBinding[binding] == numericDomain, "ProgramFactory::ReflectLayout: sampler binding %u ('%s') has conflicting " "numeric domains (%d vs %d)", @@ -3365,8 +3369,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { "ProgramFactory::ReflectLayout: failed to resolve numeric domain for '%s' " "(uniformType=0x%x)", uniformName.c_str(), uniformType); - MOBILEGL_ASSERT(entry.samplerNumericDomainByBinding[binding] == - SamplerNumericDomain::Unknown || + MOBILEGL_ASSERT(entry.samplerNumericDomainByBinding[binding] == SamplerNumericDomain::Unknown || entry.samplerNumericDomainByBinding[binding] == opaqueDomain, "ProgramFactory::ReflectLayout: binding %u ('%s') has conflicting numeric " "domains (%d vs %d)", @@ -3377,17 +3380,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { } MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || location < 0 || entry.samplerUniformLocationByBinding[binding] == location, - "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting uniform locations (%d vs %d)", + "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting uniform " + "locations (%d vs %d)", binding, entry.samplerUniformLocationByBinding[binding], location); - MOBILEGL_ASSERT(entry.samplerUniformLocationByBinding[binding] < 0 || - entry.samplerTextureTargetByBinding[binding] == target, - "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting texture targets (%d vs %d)", - binding, static_cast(entry.samplerTextureTargetByBinding[binding]), - static_cast(target)); - MOBILEGL_ASSERT(entry.samplerNameByBinding[binding].empty() || - entry.samplerNameByBinding[binding] == uniformName, - "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting names ('%s' vs '%s')", - binding, entry.samplerNameByBinding[binding].c_str(), uniformName.c_str()); + MOBILEGL_ASSERT( + entry.samplerUniformLocationByBinding[binding] < 0 || + entry.samplerTextureTargetByBinding[binding] == target, + "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting texture targets (%d vs %d)", + binding, static_cast(entry.samplerTextureTargetByBinding[binding]), static_cast(target)); + MOBILEGL_ASSERT( + entry.samplerNameByBinding[binding].empty() || entry.samplerNameByBinding[binding] == uniformName, + "ProgramFactory::ReflectLayout: texture binding %u maps to conflicting names ('%s' vs '%s')", + binding, entry.samplerNameByBinding[binding].c_str(), uniformName.c_str()); if (location >= 0) { entry.samplerUniformLocationByBinding[binding] = location; @@ -3446,25 +3450,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (Uint32 binding = 0; binding < m_maxBindings; ++binding) { const Uint32 count = entry.bindingDescriptorCounts[binding]; switch (entry.bindingKinds[binding]) { - case DescriptorBindingKind::UniformBufferDynamic: - updateAfterBindUniformBuffers += count; - break; - case DescriptorBindingKind::CombinedImageSampler: - updateAfterBindSamplers += count; - updateAfterBindSampledImages += count; - break; - case DescriptorBindingKind::UniformTexelBuffer: - updateAfterBindSampledImages += count; - break; - case DescriptorBindingKind::StorageBuffer: - case DescriptorBindingKind::StorageTexelBuffer: - updateAfterBindStorageBuffers += count; - break; - case DescriptorBindingKind::StorageImage: - updateAfterBindStorageImages += count; - break; - case DescriptorBindingKind::None: - break; + case DescriptorBindingKind::UniformBufferDynamic: + updateAfterBindUniformBuffers += count; + break; + case DescriptorBindingKind::CombinedImageSampler: + updateAfterBindSamplers += count; + updateAfterBindSampledImages += count; + break; + case DescriptorBindingKind::UniformTexelBuffer: + updateAfterBindSampledImages += count; + break; + case DescriptorBindingKind::StorageBuffer: + case DescriptorBindingKind::StorageTexelBuffer: + updateAfterBindStorageBuffers += count; + break; + case DescriptorBindingKind::StorageImage: + updateAfterBindStorageImages += count; + break; + case DescriptorBindingKind::None: + break; } } const Uint32 updateAfterBindResources = updateAfterBindUniformBuffers + updateAfterBindStorageBuffers + @@ -3475,14 +3479,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // contains a dynamic buffer descriptor (VUID 03001/03011). Wire draws // retain UniformManager's per-frame, content-versioned descriptor sets; // this selects the legal ordinary pool, not in-place mutation of a live set. - entry.dynamicBindings.empty() && - uab.enabled && updateAfterBindSamplers <= uab.maxPerStageSamplers && + entry.dynamicBindings.empty() && uab.enabled && updateAfterBindSamplers <= uab.maxPerStageSamplers && updateAfterBindUniformBuffers <= uab.maxPerStageUniformBuffers && updateAfterBindStorageBuffers <= uab.maxPerStageStorageBuffers && updateAfterBindSampledImages <= uab.maxPerStageSampledImages && updateAfterBindStorageImages <= uab.maxPerStageStorageImages && - updateAfterBindResources <= uab.maxPerStageResources && - updateAfterBindSamplers <= uab.maxSetSamplers && + updateAfterBindResources <= uab.maxPerStageResources && updateAfterBindSamplers <= uab.maxSetSamplers && updateAfterBindUniformBuffers <= uab.maxSetUniformBuffers && updateAfterBindUniformBuffers <= uab.maxSetUniformBuffersDynamic && updateAfterBindStorageBuffers <= uab.maxSetStorageBuffers && @@ -3501,7 +3503,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDescriptorSetLayoutCreateInfo setLayoutInfo{}; setLayoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - setLayoutInfo.flags = entry.usesUpdateAfterBind ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT : 0; + setLayoutInfo.flags = + entry.usesUpdateAfterBind ? VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT : 0; setLayoutInfo.pNext = entry.usesUpdateAfterBind ? &bindingFlagsInfo : nullptr; setLayoutInfo.bindingCount = static_cast(bindings.size()); setLayoutInfo.pBindings = bindings.data(); @@ -3541,8 +3544,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { ++m_cacheStructureEpoch; } - const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram( - const MagmaProgramSource& program, CompileOptionFlags flags) { + const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram(const MagmaProgramSource& program, + CompileOptionFlags flags) { // Hashing the full SPIR-V of every stage is far too expensive to repeat per draw; // reuse the program's memoized hash while its backend state version is unchanged. // The memo keys on the flags word, which ComputeHash is no longer a pure function of: @@ -3645,8 +3648,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Once per program on a cache miss, and only for the single stage that carries the // fixups - not per draw and not per module. SpirvValidationFailure fixupFailure{}; - if (!ValidateTransformedSpirv(moduleSpirvs[i], stages[i], program.GetExternalIndex(), - &fixupFailure)) { + if (!ValidateTransformedSpirv(moduleSpirvs[i], stages[i], program.GetExternalIndex(), &fixupFailure)) { SpirvValidationFailure xfbFailure{}; if (fixupInput != &spv && ValidateTransformedSpirv(*fixupInput, stages[i], program.GetExternalIndex(), &xfbFailure)) { @@ -3668,7 +3670,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { moduleSpirvs[i] = spv; } - if ((flags & ProgramFactory::CompileOptionBit::ExplicitLod0Sampling) && stages[i] == ShaderStage::Fragment) { + if ((flags & ProgramFactory::CompileOptionBit::ExplicitLod0Sampling) && + stages[i] == ShaderStage::Fragment) { Vector explicitLodSpirv; if (TransformSpirvForExplicitLod0Sampling(moduleSpirvs[i], explicitLodSpirv)) { moduleSpirvs[i] = Move(explicitLodSpirv); @@ -3719,8 +3722,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_subgroupPolicy.emulateSubgroups) { Vector emulatedSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::EmulateSubgroupsForVulkan( - moduleSpirvs[i], emulatedSpirv, - m_subgroupPolicy.maxComputeSharedMemoryBytes, enableSpirvValidation)) { + moduleSpirvs[i], emulatedSpirv, m_subgroupPolicy.maxComputeSharedMemoryBytes, + enableSpirvValidation)) { moduleSpirvs[i] = std::move(emulatedSpirv); } else { MGLOG_E("ProgramFactory: subgroup emulation failed for program %u; the " @@ -3735,8 +3738,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector patchedSpirv; if (MG_Util::ShaderTranspiler::ShaderCompiler::FixIterationRPSubgroupScratchForVulkan( moduleSpirvs[i], patchedSpirv, m_subgroupPolicy.nativeSubgroupSize, - m_subgroupPolicy.maxComputeSharedMemoryBytes, - enableSpirvValidation)) { + m_subgroupPolicy.maxComputeSharedMemoryBytes, enableSpirvValidation)) { moduleSpirvs[i] = std::move(patchedSpirv); } else { MGLOG_E("ProgramFactory: iterationRP subgroup scratch patch failed for " @@ -3770,7 +3772,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // stored as - which addresses [0,1] where the application addressed texels. { Vector rectLoweredSpirv; - if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv, enableSpirvValidation) && + if (MG_Util::ShaderTranspiler::ShaderCompiler::LowerRectImages(moduleSpirvs[i], rectLoweredSpirv, + enableSpirvValidation) && !rectLoweredSpirv.empty()) { moduleSpirvs[i] = Move(rectLoweredSpirv); } @@ -3805,8 +3808,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // ReflectVertexInputs, which already reflects this stage. if (stages[i] == ShaderStage::Vertex && m_shaderDrawParametersEnabled) { Vector rebasedSpirv; - if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan(moduleSpirvs[i], - rebasedSpirv, enableSpirvValidation)) { + if (MG_Util::ShaderTranspiler::ShaderCompiler::RebaseInstanceIndexForVulkan( + moduleSpirvs[i], rebasedSpirv, enableSpirvValidation)) { moduleSpirvs[i] = std::move(rebasedSpirv); } else { MGLOG_E("ProgramFactory: failed to rebase gl_InstanceID for program %u; " @@ -3822,8 +3825,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // and for the baseVertex word of an indexed indirect command. if (stages[i] == ShaderStage::Vertex && (flags & CompileOptionBit::ZeroBaseVertex)) { Vector zeroedSpirv; - if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i], - zeroedSpirv, enableSpirvValidation)) { + if (MG_Util::ShaderTranspiler::ShaderCompiler::ZeroBaseVertexForVulkan(moduleSpirvs[i], zeroedSpirv, + enableSpirvValidation)) { moduleSpirvs[i] = std::move(zeroedSpirv); } else { // Failing open keeps the native builtin, which is the pre-fix behavior: @@ -3897,8 +3900,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // bytes over anyway, for diagnosing a driver that copes. for (SizeT i = 0; i < moduleCount; ++i) { String reason; - const DriverModuleVerdict verdict = ScreenModuleForDriver( - moduleSpirvs[i], MG_Config::Features.MagmaAllowInvalidSpirv, &reason); + const DriverModuleVerdict verdict = + ScreenModuleForDriver(moduleSpirvs[i], MG_Config::Features.MagmaAllowInvalidSpirv, &reason); if (verdict == DriverModuleVerdict::DeclinedInvalid) { MGLOG_E_ONCE("ProgramFactory: declining program %u - its stage %d module fails SPIR-V validation (%s); " "no shader module is created and its draws are refused. " @@ -3949,8 +3952,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!m_tessellationAndGeometryPointSizeEnabled && (stages[i] == ShaderStage::TessControl || stages[i] == ShaderStage::TessEval || stages[i] == ShaderStage::Geometry) && - MG_Util::ShaderTranspiler::ShaderCompiler::ModuleDeclaresTessellationOrGeometryPointSize( - moduleSpv)) { + MG_Util::ShaderTranspiler::ShaderCompiler::ModuleDeclaresTessellationOrGeometryPointSize(moduleSpv)) { MGLOG_E_ONCE("ProgramFactory: program %u stage %d accesses gl_PointSize, but this device does not " "support shaderTessellationAndGeometryPointSize; its draws are refused rather than " "built into a pipeline the driver may fault on. Point size from a non-vertex stage " @@ -3985,8 +3987,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (shaderStage == ShaderStage::Compute && m_subgroupPolicy.requireFullSubgroups && !m_subgroupPolicy.emulateSubgroups && m_subgroupPolicy.nativeSubgroupSize != 0) { const ComputeLocalSize localSize = TryGetComputeLocalSize(moduleSpv); - const Uint64 fullSubgroupCount = - localSize.Total() / m_subgroupPolicy.nativeSubgroupSize; + const Uint64 fullSubgroupCount = localSize.Total() / m_subgroupPolicy.nativeSubgroupSize; if (localSize.x != 0 && localSize.x % m_subgroupPolicy.nativeSubgroupSize == 0 && fullSubgroupCount <= m_subgroupPolicy.maxComputeWorkgroupSubgroups) { stage.flags |= VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT; @@ -3995,9 +3996,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.modules.push_back(module); entry.stages.push_back(stage); - entry.stageSpirvDigests.push_back(ShaderStageSpirvDigest{ - static_cast(stage.stage), static_cast(moduleSpv.size()), - XXH64(moduleSpv.data(), moduleSpv.size() * sizeof(Uint), 0)}); + entry.stageSpirvDigests.push_back( + ShaderStageSpirvDigest{static_cast(stage.stage), static_cast(moduleSpv.size()), + XXH64(moduleSpv.data(), moduleSpv.size() * sizeof(Uint), 0)}); // GL ignores gl_Layer when the framebuffer is not layered; Vulkan leaves a Layer at or // past the framebuffer's layer count undefined. A stage that stores it gets a second @@ -4119,8 +4120,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Uint64 ProgramFactory::ComputePassthroughTessControlKey(Uint32 patchVertices, - const FloatVec4& defaultOuterLevel, + Uint64 ProgramFactory::ComputePassthroughTessControlKey(Uint32 patchVertices, const FloatVec4& defaultOuterLevel, const FloatVec2& defaultInnerLevel, Uint32 perVertexMembers) { // A plain 32-byte blob of exactly what the generator reads, hashed once. Deliberately over @@ -4133,8 +4133,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 perVertexMembers; } blob{}; blob.patchVertices = patchVertices; - for (Uint32 i = 0; i < 4; ++i) blob.outerBits[i] = std::bit_cast(defaultOuterLevel[i]); - for (Uint32 i = 0; i < 2; ++i) blob.innerBits[i] = std::bit_cast(defaultInnerLevel[i]); + for (Uint32 i = 0; i < 4; ++i) + blob.outerBits[i] = std::bit_cast(defaultOuterLevel[i]); + for (Uint32 i = 0; i < 2; ++i) + blob.innerBits[i] = std::bit_cast(defaultInnerLevel[i]); blob.perVertexMembers = perVertexMembers; return XXH64(&blob, sizeof(blob), 0); } @@ -4206,11 +4208,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (wordCount >= 5 && words[3] == kDecorationBuiltIn) { Uint32 bit = 0; switch (words[4]) { - case kBuiltInPosition: bit = static_cast(PerVertexMemberBit::Position); break; - case kBuiltInPointSize: bit = static_cast(PerVertexMemberBit::PointSize); break; - case kBuiltInClipDistance: bit = static_cast(PerVertexMemberBit::ClipDistance); break; - case kBuiltInCullDistance: bit = static_cast(PerVertexMemberBit::CullDistance); break; - default: break; + case kBuiltInPosition: + bit = static_cast(PerVertexMemberBit::Position); + break; + case kBuiltInPointSize: + bit = static_cast(PerVertexMemberBit::PointSize); + break; + case kBuiltInClipDistance: + bit = static_cast(PerVertexMemberBit::ClipDistance); + break; + case kBuiltInCullDistance: + bit = static_cast(PerVertexMemberBit::CullDistance); + break; + default: + break; } structMembers[words[1]] |= bit; } @@ -4236,8 +4247,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return 0; } - String ProgramFactory::BuildPassthroughTessControlSource(Uint32 patchVertices, - const FloatVec4& defaultOuterLevel, + String ProgramFactory::BuildPassthroughTessControlSource(Uint32 patchVertices, const FloatVec4& defaultOuterLevel, const FloatVec2& defaultInnerLevel, Uint32 perVertexMembers) { // The stage GL 4.6 core 11.2.2 describes when a program has an evaluation shader and no @@ -4364,10 +4374,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // the GL thread (the draw path), so the live compile env is the right one, and flags=0 // is the Vulkan-targeting form (CompileForOpenGL is what the GLES backend adds). const SharedPtr& env = GetCurrentCompileEnv(); - ShaderAttrib shaderAttrib{.shaderType = GL_TESS_CONTROL_SHADER, - .sourceStr = source, - .flags = 0, - .env = env.get()}; + ShaderAttrib shaderAttrib{ + .shaderType = GL_TESS_CONTROL_SHADER, .sourceStr = source, .flags = 0, .env = env.get()}; auto compiled = ShaderCompiler::CompileShader(shaderAttrib); if (!compiled) { MGLOG_E("ProgramFactory: could not compile the pass-through tessellation control stage for " @@ -4382,7 +4390,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto linked = ShaderCompiler::LinkProgram(programAttrib); if (!linked) { MGLOG_E("ProgramFactory: could not link the pass-through tessellation control stage for " - "patchVertices=%u. %s", patchVertices, linked.error().log.c_str()); + "patchVertices=%u. %s", + patchVertices, linked.error().log.c_str()); m_passthroughTessControlStages.emplace(key, stage); return stage; } @@ -4391,7 +4400,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto binary = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); if (!binary || binary.value().empty() || binary.value().front().empty()) { MGLOG_E("ProgramFactory: could not generate SPIR-V for the pass-through tessellation control stage " - "for patchVertices=%u", patchVertices); + "for patchVertices=%u", + patchVertices); m_passthroughTessControlStages.emplace(key, stage); return stage; } @@ -4412,8 +4422,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (validateThisOne) MG_Util::ShaderTranspiler::ShaderCompiler::PrepareSpirvValidation(); #endif SpirvValidationFailure passthroughFailure{}; - if (validateThisOne && - !ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0, &passthroughFailure)) { + if (validateThisOne && !ValidateTransformedSpirv(spirv, ShaderStage::TessControl, 0, &passthroughFailure)) { MGLOG_E_ONCE("ProgramFactory: the synthesized pass-through tessellation control stage for " "patchVertices=%u does not validate (%s)", patchVertices, passthroughFailure.message.c_str()); @@ -4427,22 +4436,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkResult result = vkCreateShaderModule(m_device, &smci, nullptr, &module); if (result != VK_SUCCESS) { MGLOG_E("ProgramFactory: vkCreateShaderModule failed (%d) for the pass-through tessellation control " - "stage for patchVertices=%u", static_cast(result), patchVertices); + "stage for patchVertices=%u", + static_cast(result), patchVertices); m_passthroughTessControlStages.emplace(key, stage); return stage; } stage.module = module; MGLOG_D("ProgramFactory: built the pass-through tessellation control stage for patchVertices=%u " - "(GL 4.6 11.2.2; Vulkan has no fixed-function equivalent)", patchVertices); + "(GL 4.6 11.2.2; Vulkan has no fixed-function equivalent)", + patchVertices); m_passthroughTessControlStages.emplace(key, stage); return stage; } - void ProgramFactory::ReflectPassthroughTessControlNeed( - const Vector& stages, - const Vector>& spirv, - VkProgramObject& entry) const { + void ProgramFactory::ReflectPassthroughTessControlNeed(const Vector& stages, + const Vector>& spirv, + VkProgramObject& entry) const { entry.needsPassthroughTessControl = false; entry.passthroughTessControlEmulatable = false; entry.passthroughPerVertexMembers = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 710272f7..11e557f9 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -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(PerVertexMemberBit::Position) | static_cast(PerVertexMemberBit::PointSize) | - static_cast(PerVertexMemberBit::ClipDistance); + static constexpr Uint32 kDefaultPerVertexMembers = static_cast(PerVertexMemberBit::Position) | + static_cast(PerVertexMemberBit::PointSize) | + static_cast(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 s_device; // per Magma session (MagmaSession.h) + static inline SessionLocal 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& stages, - const Vector>& spirv, - VkProgramObject& entry) const; - void ReflectViewportIndexUsage(const Vector& stages, - const Vector>& spirv, + void ReflectVertexInputs(const Vector& stages, const Vector>& spirv, + VkProgramObject& entry) const; + void ReflectViewportIndexUsage(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const; - void ReflectFragmentOutputs(const Vector& stages, - const Vector>& spirv, - VkProgramObject& entry) const; + void ReflectFragmentOutputs(const Vector& stages, const Vector>& spirv, + VkProgramObject& entry) const; void ReflectLayout(const MagmaProgramSource& program, const Vector>& 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& stages, - const Vector>& spirv, + void ReflectPassthroughTessControlNeed(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const; VkDevice m_device = VK_NULL_HANDLE; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h index 1154390c..4c4714e1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SubmitFencePrefix.h @@ -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 -bool CollectSubmitFencePrefix(const Records& records, std::uint64_t submitIndex, - Fence nullFence, std::vector& 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 + bool CollectSubmitFencePrefix(const Records& records, std::uint64_t submitIndex, Fence nullFence, + std::vector& 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp index 2bd06253..73c8da16 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp @@ -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& availablePresentModes, Optional swapInterval) { + VkPresentModeKHR SwapchainObject::ChooseSwapchainPresentMode(const Vector& availablePresentModes, + Optional 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 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(minImageCountHint, swapchainCaps.minImageCount + extraImageCount); + Uint32 targetImageCount = std::max(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(defaultFramebufferExtent.width); const Int extentHeight = static_cast(defaultFramebufferExtent.height); - const SizeT defaultAttachmentByteSize = - static_cast(defaultFramebufferExtent.width) * - static_cast(defaultFramebufferExtent.height) * 4; + const SizeT defaultAttachmentByteSize = static_cast(defaultFramebufferExtent.width) * + static_cast(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(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(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(defaultFBOInfo->stencilAttachment.get()); + auto* stencilTex = + static_cast(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) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h index 13a4a8ad..58160c02 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.h @@ -20,9 +20,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector surfaceFormats; Vector 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index e7666eb0..555aef39 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -44,8 +44,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { SizeT UniformManager::WireImageViewKeyHash::operator()(const WireImageViewKey& key) const { SizeT hash = std::hash{}(key.image); const auto mix = [&hash](Uint64 value) { - hash ^= std::hash{}(value) + static_cast(0x9e3779b97f4a7c15ULL) + - (hash << 6) + (hash >> 2); + hash ^= std::hash{}(value) + static_cast(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(offset); const VkDeviceSize remaining = bufferSize - start; - size = declaredSize == MG_Pipe::kMGPipeWholeBuffer - ? remaining : std::min(declaredSize, remaining); + size = + declaredSize == MG_Pipe::kMGPipeWholeBuffer ? remaining : std::min(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(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(element); if (baseLocation < 0 || !program.UniformLocationsAliasSameUniform(baseLocation, location)) return false; const Int unit = program.GetUniformSamplerOrImageUnitIndex(static_cast(location)); - if (unit < 0 || static_cast(unit) >= - (storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false; + if (unit < 0 || + static_cast(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(record.Desc.Levels - localLevel, - record.Params.MaxLevel - localLevel + 1); + levels = std::min(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(record.Desc.InternalFormat)).expandRgbToRgba; + const Bool alphaIsOne = + resource->sharedImageAlphaOne || + ResolveTextureFormatInfo(static_cast(record.Desc.InternalFormat)) + .expandRgbToRgba; const auto swizzle = [&](Uint32 channel) { const auto value = record.Params.Swizzle[channel]; return alphaIsOne && static_cast(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; 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(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(location)); const auto& state = MG_Pipe::MGPipeApplier(); - if (unit < 0 || static_cast(unit) >= - (storage ? MG_Pipe::kMGPipeMaxImageUnits : MG_Pipe::kMGPipeMaxTextureUnits)) return false; + if (unit < 0 || + static_cast(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(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(m_wireMaxTexelElements)) * texelSize; if (size == 0) return false; if (start > std::numeric_limits::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(slice.offset + start), + "(unaligned-texel-buffer-range)", + binding, static_cast(slice.offset + start), static_cast(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_ 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(atomicCounterBinding) - : program.GetShaderStorageBlockBinding(static_cast(blockIndex)) + element; + isAtomicCounterBlock ? static_cast(atomicCounterBinding) + : program.GetShaderStorageBlockBinding(static_cast(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::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(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::max() - source.offset) WireDescriptorFatal("uniform-buffer-offset-overflow"); @@ -1107,7 +1137,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { zero.assign(static_cast(blockSize), 0); BufferSlice padded{}; if (!m_bufferManager->UploadTransient(BufferKind::Uniform, m_wireFrameIndex, zero.data(), blockSize, - std::max(4, m_minDynamicOffsetAlignment), padded) || !padded.IsValid()) return false; + std::max(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::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(ubo.payloadSize)}; + slice.buffer, slice.offset, static_cast(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(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(location)); if (unit < 0 || static_cast(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(write.dstBinding) << 40) ^ - (static_cast(write.descriptorType) << 8) ^ + mix64((static_cast(write.dstBinding) << 40) ^ (static_cast(write.descriptorType) << 8) ^ static_cast(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(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() || diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 7d802726..d07362a3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp index 08f14617..546453ed 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.cpp @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h index 6a3874f4..e1505f2a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateBuilder.h @@ -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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index 48937d22..84be9077 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -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(elements.BackendMemo.get()); if (memo == nullptr) { auto created = std::make_shared(); @@ -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(attr.Size), static_cast(attr.Normalized), - static_cast(attr.IsInteger), static_cast(attr.IsLong), static_cast(attr.IsBgra), - attr.Stride, static_cast(attr.Offset), static_cast(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(attr.Size), + static_cast(attr.Normalized), static_cast(attr.IsInteger), + static_cast(attr.IsLong), static_cast(attr.IsBgra), attr.Stride, + static_cast(attr.Offset), static_cast(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(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(attr.Stride) % alignment)) conversion = VertexStreamConversion::Repack; Uint32 stride = static_cast(attr.Stride); if (stride && conversion != VertexStreamConversion::None) stride = conversion == VertexStreamConversion::Repack ? static_cast(elementSize) - : static_cast(attr.Size) * sizeof(Float); + : static_cast(attr.Size) * sizeof(Float); const Uint32 binding = static_cast(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(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; } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h index 34419200..f234cf2b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -69,11 +69,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // binding uses divisor 1, which is what the plain input rate already means. Vector 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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index a62a9e1b..9363af10 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -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 g_activeBufferManager; // per Magma session (MagmaSession.h) + SessionLocal 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(resource->size)); + MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-storage\"} {slot=%u, gen=%u, size=%llu}", res.Slot, + res.Gen, static_cast(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(std::chrono::duration_cast( - std::chrono::steady_clock::now() - waitStart).count())); + static_cast(std::chrono::duration_cast( + 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(size)); + if (!resource->shadow.empty()) Memcpy(resource->shadow.data() + offset, bytes, static_cast(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(offset), - static_cast(size)); + uploaded = StagedWireRangeCopy(*resource, bytes, static_cast(offset), static_cast(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(offset), - static_cast(size), - (busyBySerial || busyBySubmission) ? "STAGED-or-WAIT" : "HOST-IMMEDIATE", - busyBySerial ? 1 : 0, busyBySubmission ? 1 : 0, - static_cast(probeLastUse), static_cast(probeCompleted), - static_cast(m_frameSerial), + res.Slot, res.Gen, static_cast(offset), static_cast(size), + (busyBySerial || busyBySubmission) ? "STAGED-or-WAIT" : "HOST-IMMEDIATE", busyBySerial ? 1 : 0, + busyBySubmission ? 1 : 0, static_cast(probeLastUse), + static_cast(probeCompleted), static_cast(m_frameSerial), static_cast(m_completedSerialFloor), static_cast(resource->lastUseSubmitIndex)); } - MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage, - static_cast(offset), - static_cast(offset + size)); + MG_Record::StagedShadowStore::CoverageAdd(resource->stagedCoverage, static_cast(offset), + static_cast(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(offset), static_cast(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(offset), + static_cast(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(size), 0)) + }) || + entry.buffer.Map() == nullptr || !entry.buffer.Upload(bytes, static_cast(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(bytes.data()), size, MG_Pipe::kMGHostSpanSegNone, 0}); + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback( + res, offset, {reinterpret_cast(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(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(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 pattern(kBytes / 4); - for (SizeT i = 0; i < pattern.size(); ++i) pattern[i] = 0x5A000000u ^ static_cast(i * 2654435761u); + for (SizeT i = 0; i < pattern.size(); ++i) + pattern[i] = 0x5A000000u ^ static_cast(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(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(i); break; } + if (h.Slot == res.Slot && h.Gen == res.Gen) { + createdAgo = static_cast(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(i); break; } + if (h.Slot == res.Slot && h.Gen == res.Gen) { + destroyedAgo = static_cast(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(size)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::StageBuffer, static_cast(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(size), 16, staging)) { + if (!m_transientUploadArena.Upload(m_currentFrameIndex, data, static_cast(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) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index 024369eb..387053de 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h index 73cca2c7..d44c9d1d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferObject.h @@ -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: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp index 1f510fd2..368c36ba 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.cpp @@ -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() diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h index 7a2688ad..a0375a88 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkClearManager.h @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 1f3675ca..56ee8807 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -18,7 +18,8 @@ #include 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 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(renderPassEntry.extent.x()), - static_cast(renderPassEntry.extent.y())}}; + begin.renderArea = { + {0, 0}, {static_cast(renderPassEntry.extent.x()), static_cast(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 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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index aad7a0ee..c8b24160 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -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 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. diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp index 446ab557..c3aa78b0 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.cpp @@ -261,7 +261,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { template 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 - 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 - 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 - 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h index 2e90d9d3..bcc4cd91 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h @@ -14,117 +14,116 @@ #include 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 + VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format, + Bool forceNearestFiltering, Uint32 viewLevelCount); + template + 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 + 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 + 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 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 - VkSampler GetOrCreateSamplerImpl(const SamplerSource& sampler, TextureInternalFormat format, - Bool forceNearestFiltering, Uint32 viewLevelCount); - template - 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 - 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 - 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 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 1288199a..e46f2b2b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -56,14 +56,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; static SessionLocal s_sampledReadStagesSlot; - static VkImageUsageFlags ResolveTextureUsage(VkImageAspectFlags aspect, - VkFormatFeatureFlags formatFeatures, + static VkImageUsageFlags ResolveTextureUsage(VkImageAspectFlags aspect, VkFormatFeatureFlags formatFeatures, Bool supportsStorageImage) { VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT; if (supportsStorageImage) usage |= VK_IMAGE_USAGE_STORAGE_BIT; // A color aspect does not imply renderability (for example, RGB9_E5). - if ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) && - (formatFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) { + if ((aspect & VK_IMAGE_ASPECT_COLOR_BIT) && (formatFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT)) { usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; } if (aspect & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { @@ -73,8 +71,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { - Int maxDimension = std::max(baseTexelSize.x(), - std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); + Int maxDimension = + std::max(baseTexelSize.x(), std::max(baseTexelSize.y(), std::max(baseTexelSize.z(), 1))); Uint32 mipLevelCount = 1; while (maxDimension > 1) { maxDimension = std::max(maxDimension / 2, 1); @@ -121,8 +119,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VKU_FORMAT_INFO lhsInfo = vkuGetFormatInfo(lhs); const VKU_FORMAT_INFO rhsInfo = vkuGetFormatInfo(rhs); if (lhsInfo.component_count == 0 || lhsInfo.component_count != rhsInfo.component_count || - lhsInfo.texel_block_size != rhsInfo.texel_block_size || - lhsInfo.texels_per_block != 1 || rhsInfo.texels_per_block != 1) { + lhsInfo.texel_block_size != rhsInfo.texel_block_size || lhsInfo.texels_per_block != 1 || + rhsInfo.texels_per_block != 1) { return false; } for (Uint32 component = 0; component < lhsInfo.component_count; ++component) { @@ -167,8 +165,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; } static Uint32 ResolveUploadArrayLayer(TextureUploadTarget target) { @@ -180,12 +177,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { static VkImageLayout ResolveSampledReadOnlyLayout(VkImageAspectFlags aspectMask) { return (aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) != 0 - ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL - : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL + : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; } - static void GetImageTransitionSourceState(VkImageLayout oldLayout, - VkPipelineStageFlags& outSrcStageMask, + static void GetImageTransitionSourceState(VkImageLayout oldLayout, VkPipelineStageFlags& outSrcStageMask, VkAccessFlags& outSrcAccessMask) { switch (oldLayout) { case VK_IMAGE_LAYOUT_UNDEFINED: @@ -202,8 +198,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; 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; + outSrcAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: @@ -228,8 +224,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - static void GetImageTransitionDestinationState(VkImageLayout newLayout, - VkPipelineStageFlags& outDstStageMask, + static void GetImageTransitionDestinationState(VkImageLayout newLayout, VkPipelineStageFlags& outDstStageMask, VkAccessFlags& outDstAccessMask) { switch (newLayout) { case VK_IMAGE_LAYOUT_GENERAL: @@ -242,8 +237,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; 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; + outDstAccessMask = + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; return; case VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: @@ -261,21 +256,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { outDstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; return; default: - MOBILEGL_ASSERT(false, "GetImageTransitionDestinationState: unsupported layout=%d", static_cast(newLayout)); + MOBILEGL_ASSERT(false, "GetImageTransitionDestinationState: unsupported layout=%d", + static_cast(newLayout)); outDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; outDstAccessMask = 0; return; } } - static Bool PreserveTextureContentsOnRecreate(VkDevice device, - VkCommandPool commandPool, - VkQueue graphicsQueue, + static Bool PreserveTextureContentsOnRecreate(VkDevice device, VkCommandPool commandPool, VkQueue graphicsQueue, const VkTextureManager::TextureResource& oldResource, - VkTextureManager::TextureResource& newResource - , Bool splitAspects = false - , VkTextureManager* rawE5Manager = nullptr - ) { + VkTextureManager::TextureResource& newResource, + Bool splitAspects = false, VkTextureManager* rawE5Manager = nullptr) { MOBILEGL_ASSERT(device != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: device is null"); MOBILEGL_ASSERT(commandPool != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: commandPool is null"); MOBILEGL_ASSERT(graphicsQueue != VK_NULL_HANDLE, "PreserveTextureContentsOnRecreate: graphicsQueue is null"); @@ -304,8 +296,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool ok = VkTextureManager::TransitionImageLayout( commandBuffer, newResource.image, newResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, VK_ACCESS_TRANSFER_WRITE_BIT, newResource.aspect, 0, newResource.mipLevels); + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, + newResource.aspect, 0, newResource.mipLevels); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare destination image"); // E5B9G9R9 on a device whose transfer reads canonicalize it: each level leaves through the @@ -319,10 +311,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { GetImageTransitionSourceState(srcTrackedLayout, srcStageMask, srcAccessMask); ok = VkTextureManager::TransitionImageLayout( commandBuffer, oldResource.image, srcTrackedLayout, - rawE5 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, rawE5 ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, rawE5 ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT, - oldResource.aspect, 0, preservedMipLevels); + rawE5 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask, + rawE5 ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, + rawE5 ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT, oldResource.aspect, 0, preservedMipLevels); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to prepare source image"); // Destroyed after the fence wait below, which is the twins' last use. @@ -342,7 +333,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { volume ? std::max(std::min(oldResource.depth, newResource.depth) >> level, 1u) : 1u}; VkTextureManager::TextureResource twin; WireE5RawCopyObjects objects; - if (!rawE5Manager->RecordWireE5RawTwin(commandBuffer, region, VK_IMAGE_LAYOUT_GENERAL, twin, objects)) break; + if (!rawE5Manager->RecordWireE5RawTwin(commandBuffer, region, VK_IMAGE_LAYOUT_GENERAL, twin, objects)) + break; VkImageCopy copy{}; copy.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, region.layerCount}; copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, 0, region.layerCount}; @@ -356,7 +348,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A twin could not be made: the remaining levels go by transfer (canonicalized, // as every level was before the route) rather than not at all. MGLOG_W_ONCE("PreserveTextureContentsOnRecreate: the raw E5B9G9R9 route failed; levels %zu.. " - "are copied by transfer", rawTwins.size()); + "are copied by transfer", + rawTwins.size()); } } const Uint32 firstTransferLevel = static_cast(rawTwins.size()); @@ -389,32 +382,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.extent.height = std::max(oldResource.extent.height >> level, 1u); copy.extent.depth = std::max(oldResource.depth >> level, 1u); if (splitAspects) { - for (const VkImageAspectFlags aspect : {VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_ASPECT_DEPTH_BIT, - VK_IMAGE_ASPECT_STENCIL_BIT}) { + for (const VkImageAspectFlags aspect : + {VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_ASPECT_STENCIL_BIT}) { if ((oldResource.aspect & aspect) == 0) continue; copy.srcSubresource.aspectMask = aspect; copy.dstSubresource.aspectMask = aspect; copyRegions.push_back(copy); } } else - copyRegions.push_back(copy); + copyRegions.push_back(copy); } EndActiveRenderPassOn(commandBuffer); if (!copyRegions.empty()) - vkCmdCopyImage(commandBuffer, - oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - newResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - static_cast(copyRegions.size()), copyRegions.data()); + vkCmdCopyImage(commandBuffer, oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newResource.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(copyRegions.size()), + copyRegions.data()); VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags dstAccessMask = 0; GetImageTransitionDestinationState(oldResource.layout, dstStageMask, dstAccessMask); ok = VkTextureManager::TransitionImageLayout( - commandBuffer, newResource.image, newResource.layout, oldResource.layout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, - VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels); + commandBuffer, newResource.image, newResource.layout, oldResource.layout, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, newResource.aspect, 0, newResource.mipLevels); MOBILEGL_ASSERT(ok, "PreserveTextureContentsOnRecreate: failed to restore destination layout"); VK_VERIFY(vkEndCommandBuffer(commandBuffer), "vkEndCommandBuffer(texture preserve)"); @@ -430,7 +420,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(vkCreateFence(device, &fenceInfo, nullptr, &fence), "vkCreateFence(texture preserve)"); VK_VERIFY(vkQueueSubmit(graphicsQueue, 1, &submitInfo, fence), "vkQueueSubmit(texture preserve)"); VK_VERIFY(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX), "vkWaitForFences(texture preserve)"); - for (WireE5RawCopyObjects& objects : rawObjects) DestroyWireE5RawCopyObjects(device, objects); + for (WireE5RawCopyObjects& objects : rawObjects) + DestroyWireE5RawCopyObjects(device, objects); vkDestroyFence(device, fence, nullptr); vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); @@ -500,17 +491,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { "ExpandRgbSourceToRgba: invalid component size for expanded RGB format"); if (!source || texelSize.x() <= 0 || texelSize.y() <= 0 || !formatInfo.componentByteCount || - formatInfo.componentByteCount > formatInfo.alphaBytes.size()) return false; + formatInfo.componentByteCount > formatInfo.alphaBytes.size()) + return false; const SizeT depth = static_cast(std::max(texelSize.z(), 1)); const SizeT width = static_cast(texelSize.x()); const SizeT height = static_cast(texelSize.y()); if (height > std::numeric_limits::max() / width || - depth > std::numeric_limits::max() / width / height) return false; + depth > std::numeric_limits::max() / width / height) + return false; const SizeT pixelCount = width * height * depth; if (pixelCount > std::numeric_limits::max() / (formatInfo.componentByteCount * 4) || - sourceByteSize != pixelCount * formatInfo.componentByteCount * 3) return false; - MOBILEGL_ASSERT(pixelCount > 0, "ExpandRgbSourceToRgba: invalid texel size (%d, %d, %d)", - texelSize.x(), texelSize.y(), texelSize.z()); + sourceByteSize != pixelCount * formatInfo.componentByteCount * 3) + return false; + MOBILEGL_ASSERT(pixelCount > 0, "ExpandRgbSourceToRgba: invalid texel size (%d, %d, %d)", texelSize.x(), + texelSize.y(), texelSize.z()); MOBILEGL_ASSERT(sourceByteSize == pixelCount * formatInfo.componentByteCount * 3, "ExpandRgbSourceToRgba: unexpected source byte size=%zu for pixelCount=%zu componentBytes=%u", sourceByteSize, pixelCount, formatInfo.componentByteCount); @@ -550,8 +544,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE && m_physicalDevice != VK_NULL_HANDLE && m_allocator != nullptr && m_commandPool != VK_NULL_HANDLE && m_graphicsQueue != VK_NULL_HANDLE, "VkTextureManager::Initialize failed: invalid initialization info"); - MOBILEGL_ASSERT(initInfo.frameCount > 0, - "VkTextureManager::Initialize failed: frameCount must be > 0"); + MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTextureManager::Initialize failed: frameCount must be > 0"); TextureResource::s_device.Get() = m_device; TextureResource::s_allocator.Get() = m_allocator; @@ -562,8 +555,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // allocated/freed there every frame; isolating them keeps both pools' // internal allocators dense. VkCommandPoolCreateInfo uploadPoolInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO}; - uploadPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | - VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; + uploadPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT; uploadPoolInfo.queueFamilyIndex = initInfo.graphicsQueueFamilyIndex; VK_VERIFY(vkCreateCommandPool(m_device, &uploadPoolInfo, nullptr, &m_uploadCommandPool), "vkCreateCommandPool(texture upload batch)"); @@ -586,7 +578,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } DestroyDeferredReleases(); - ++m_resourceEraseEpoch; // every memoized resource pointer dies with the map + ++m_resourceEraseEpoch; // every memoized resource pointer dies with the map // Destroy images while the device/allocator still exist, including after context death. m_wireTextureResources.clear(); m_wireRenderbufferResources.clear(); @@ -602,11 +594,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkTextureManager::BeginFrame(Uint32 frameIndex) { MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), - "VkTextureManager::BeginFrame invalid frame index %u (size=%zu)", - frameIndex, m_deferredReleases.size()); + "VkTextureManager::BeginFrame invalid frame index %u (size=%zu)", frameIndex, + m_deferredReleases.size()); MOBILEGL_ASSERT(frameIndex < m_deferredViewReleases.size(), - "VkTextureManager::BeginFrame invalid deferred-view frame index %u (size=%zu)", - frameIndex, m_deferredViewReleases.size()); + "VkTextureManager::BeginFrame invalid deferred-view frame index %u (size=%zu)", frameIndex, + m_deferredViewReleases.size()); m_currentFrameIndex = frameIndex; CollectDeferredReleases(frameIndex); ReclaimCompletedUploads(); @@ -641,9 +633,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(image != VK_NULL_HANDLE, "TransitionImageLayout: m_image == VK_NULL_HANDLE"); MOBILEGL_ASSERT(!((dstAccessMask & VK_ACCESS_TRANSFER_READ_BIT) != 0 && (dstStageMask & VK_PIPELINE_STAGE_TRANSFER_BIT) == 0), - "TransitionImageLayout: invalid dstAccess/dstStage pair (dstAccess=0x%x, dstStage=0x%x, oldLayout=%d, newLayout=%d)", - static_cast(dstAccessMask), static_cast(dstStageMask), static_cast(trackedLayout), - static_cast(newLayout)); + "TransitionImageLayout: invalid dstAccess/dstStage pair (dstAccess=0x%x, dstStage=0x%x, " + "oldLayout=%d, newLayout=%d)", + static_cast(dstAccessMask), static_cast(dstStageMask), + static_cast(trackedLayout), static_cast(newLayout)); if (trackedLayout == newLayout) { return true; @@ -781,8 +774,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { outShape.viewType = VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; outShape.imageFlags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; outShape.arrayLayers = desc.ArrayLayers; - return desc.Width > 0 && desc.Width == desc.Height && desc.ArrayLayers > 0 && - (desc.ArrayLayers % 6) == 0; + return desc.Width > 0 && desc.Width == desc.Height && desc.ArrayLayers > 0 && (desc.ArrayLayers % 6) == 0; default: // Buffer (a record routing fault on a texture path) and TexBuffer (P7's buffer // twin) have no mipmapped image here. @@ -795,18 +787,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { static TextureTarget WireTextureTargetOfPipeTarget(Uint8 pipeTarget) { using MG_Pipe::MGPipeResourceTarget; switch (static_cast(pipeTarget)) { - case MGPipeResourceTarget::Tex1D: return TextureTarget::Texture1D; - case MGPipeResourceTarget::Tex2D: return TextureTarget::Texture2D; - case MGPipeResourceTarget::Tex3D: return TextureTarget::Texture3D; - case MGPipeResourceTarget::Tex1DArray: return TextureTarget::Texture1DArray; - case MGPipeResourceTarget::Tex2DArray: return TextureTarget::Texture2DArray; - case MGPipeResourceTarget::TexCube: return TextureTarget::TextureCubeMap; - case MGPipeResourceTarget::TexCubeArray: return TextureTarget::TextureCubeMapArray; - case MGPipeResourceTarget::Tex2DMS: return TextureTarget::Texture2DMultisample; - case MGPipeResourceTarget::Tex2DMSArray: return TextureTarget::Texture2DMultisampleArray; - case MGPipeResourceTarget::TexRect: return TextureTarget::TextureRectangle; - case MGPipeResourceTarget::TexBuffer: return TextureTarget::TextureBuffer; - default: return TextureTarget::Unknown; + case MGPipeResourceTarget::Tex1D: + return TextureTarget::Texture1D; + case MGPipeResourceTarget::Tex2D: + return TextureTarget::Texture2D; + case MGPipeResourceTarget::Tex3D: + return TextureTarget::Texture3D; + case MGPipeResourceTarget::Tex1DArray: + return TextureTarget::Texture1DArray; + case MGPipeResourceTarget::Tex2DArray: + return TextureTarget::Texture2DArray; + case MGPipeResourceTarget::TexCube: + return TextureTarget::TextureCubeMap; + case MGPipeResourceTarget::TexCubeArray: + return TextureTarget::TextureCubeMapArray; + case MGPipeResourceTarget::Tex2DMS: + return TextureTarget::Texture2DMultisample; + case MGPipeResourceTarget::Tex2DMSArray: + return TextureTarget::Texture2DMultisampleArray; + case MGPipeResourceTarget::TexRect: + return TextureTarget::TextureRectangle; + case MGPipeResourceTarget::TexBuffer: + return TextureTarget::TextureBuffer; + default: + return TextureTarget::Unknown; } } @@ -817,9 +821,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // false - the upload's decline - when the (format, layout) pair has no conversion here. static Bool ConvertWireLevelBytes(const void* source, SizeT byteSize, const IntVec3& texelSize, VkFormat imageFormat, VkImageAspectFlags aspect, - TextureInternalFormat internalFormat, - const TextureFormatInfo& formatInfo, Vector& outOwned, - const void*& outSource, SizeT& outByteSize) { + TextureInternalFormat internalFormat, const TextureFormatInfo& formatInfo, + Vector& outOwned, const void*& outSource, SizeT& outByteSize) { outSource = static_cast(source); outByteSize = byteSize; if (formatInfo.expandRgbToRgba) { @@ -913,9 +916,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool VkTextureManager::ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target, - Uint32 level, const IntVec3& extent, VkFormat& format, - Vector& bytes) { + Bool VkTextureManager::ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target, Uint32 level, + const IntVec3& extent, VkFormat& format, Vector& bytes) { const auto& records = MG_Pipe::MGPipeApplier().TextureResources; if (!handle.Slot || handle.Slot >= records.size()) return false; const auto& record = records[handle.Slot]; @@ -938,10 +940,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!store.IsCovered(key, upload, level) || store.IsLevelGpuDirty(key, upload, level) || store.LevelExtentOrUndefined(key, upload, level) != extent) { const auto actual = store.LevelExtentOrUndefined(key, upload, level); - MGLOG_E("Magma unbacked read: {%u,%u} target=%u level=%u covered=%d gpuDirty=%d extent=%dx%dx%d requested=%dx%dx%d bytes=%zu", + MGLOG_E("Magma unbacked read: {%u,%u} target=%u level=%u covered=%d gpuDirty=%d extent=%dx%dx%d " + "requested=%dx%dx%d bytes=%zu", handle.Slot, handle.Gen, upload, level, store.IsCovered(key, upload, level), - store.IsLevelGpuDirty(key, upload, level), actual.x(), actual.y(), actual.z(), - extent.x(), extent.y(), extent.z(), store.LevelByteSize(key, upload, level)); + store.IsLevelGpuDirty(key, upload, level), actual.x(), actual.y(), actual.z(), extent.x(), + extent.y(), extent.z(), store.LevelByteSize(key, upload, level)); return false; } const auto logical = static_cast(record.Desc.InternalFormat); @@ -953,8 +956,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { SizeT convertedSize = 0; Vector owned; if (!ConvertWireLevelBytes(source, store.LevelByteSize(key, upload, level), - ToVulkanLevelExtent(WireTextureTargetOfPipeTarget(record.Desc.Target), extent), format, - GetAspectMaskForFormat(format), logical, formatInfo, owned, converted, convertedSize)) return false; + ToVulkanLevelExtent(WireTextureTargetOfPipeTarget(record.Desc.Target), extent), + format, GetAspectMaskForFormat(format), logical, formatInfo, owned, converted, + convertedSize)) + return false; const auto* begin = static_cast(converted); bytes.assign(begin, begin + convertedSize); return true; @@ -976,8 +981,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - Bool VkTextureManager::SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, - TextureResource& resource, Bool requireStorage) { + Bool VkTextureManager::SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, + Bool requireStorage) { const MG_Pipe::MGPResourceDesc& desc = record.Desc; // A YUV shared image is converted INTO the texture's own RGBA8 storage, which the rest of // this function defines as for any texture; the image is only named here, and the @@ -1035,8 +1040,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (desc.Levels == 0) { // A create before the first respecify: no storage, nothing to back. - MGL_WIRE_DECLINE_AT(ShapeNoLevels, "texture {slot=%u, gen=%u} has no defined level yet", - desc.Resource.Slot, desc.Resource.Gen); + MGL_WIRE_DECLINE_AT(ShapeNoLevels, "texture {slot=%u, gen=%u} has no defined level yet", desc.Resource.Slot, + desc.Resource.Gen); return false; } TextureShapeInfo shapeInfo{}; @@ -1075,8 +1080,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkSampleCountFlagBits resolvedSampleCount = VK_SAMPLE_COUNT_1_BIT; if (isMultisample) { if (!TryResolveSampleCountFlagBits(static_cast(desc.Samples), resolvedSampleCount)) { - MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: unsupported sample count %u", - desc.Resource.Slot, desc.Resource.Gen, static_cast(desc.Samples)); + MGLOG_W_ONCE("Magma wire texture {slot=%u, gen=%u}: unsupported sample count %u", desc.Resource.Slot, + desc.Resource.Gen, static_cast(desc.Samples)); WireDeclineTally::Count(WireDeclineSite::ShapeSampleCount); return false; } @@ -1092,8 +1097,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties); // The descriptor's sticky bind mask carries the image-unit hint, so the image is born // with STORAGE usage and no re-mint ever pulls texels back (TextureEmit.h D-A3). - const Bool markedAsStorageImage = requireStorage || resource.storageUsageResolved || - (desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0; + const Bool markedAsStorageImage = + requireStorage || resource.storageUsageResolved || (desc.BindMask & MG_Pipe::kMGPipeBindShaderImage) != 0; const Bool storageImageCapable = !isMultisample && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0; @@ -1111,8 +1116,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } // sRGB color images attach through their UNORM twin while GL_FRAMEBUFFER_SRGB is off. - if (ResolveSrgbAttachmentWriteFormat(format, false) != format && - (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && + if (ResolveSrgbAttachmentWriteFormat(format, false) != format && (aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 && m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) { imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; } @@ -1177,20 +1181,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { static_cast(shapeInfo.depth)}; const Uint32 fullMipLevels = ComputeFullMipLevelCount(mipExtent); const Uint32 backingMipLevels = - isMultisample ? 1u - : (desc.Levels > 1 ? std::min(std::max(desc.Levels, fullMipLevels), fullMipLevels) - : 1u); + isMultisample + ? 1u + : (desc.Levels > 1 ? std::min(std::max(desc.Levels, fullMipLevels), fullMipLevels) : 1u); // A shared image never stands in for allocated storage: the attach is over (or never // resolved), so the texture gets storage of its own, and none of the image's texels. - const Bool compatible = resource.image != VK_NULL_HANDLE && resource.sharedImageId == 0 && - resource.format == format && - resource.extent.width == desc.Width && resource.extent.height == imageHeight && - resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && - resource.viewType == shapeInfo.viewType && - resource.sampleCount == resolvedSampleCount && - resource.imageCreateFlags == imageCreateFlags && - resource.usageFlags == desiredUsage && resource.mipLevels >= backingMipLevels; + const Bool compatible = + resource.image != VK_NULL_HANDLE && resource.sharedImageId == 0 && resource.format == format && + resource.extent.width == desc.Width && resource.extent.height == imageHeight && + resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && + resource.viewType == shapeInfo.viewType && resource.sampleCount == resolvedSampleCount && + resource.imageCreateFlags == imageCreateFlags && resource.usageFlags == desiredUsage && + resource.mipLevels >= backingMipLevels; if (compatible) { resource.storageUsageResolved = markedAsStorageImage; return true; @@ -1199,14 +1202,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A named-level respecify changes ONLY that level; a BindMask update changes no // storage at all. Preserve every compatible old level when growing/upgrading the // allocation. A whole-store redefinition permits (but does not require) old texels. - const Bool preserve = resource.image != VK_NULL_HANDLE && resource.sharedImageId == 0 && - resource.format == format && - resource.extent.width == desc.Width && resource.extent.height == imageHeight && - resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && - resource.viewType == shapeInfo.viewType && - resource.sampleCount == VK_SAMPLE_COUNT_1_BIT && - resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && - resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; + const Bool preserve = + resource.image != VK_NULL_HANDLE && resource.sharedImageId == 0 && resource.format == format && + resource.extent.width == desc.Width && resource.extent.height == imageHeight && + resource.depth == shapeInfo.depth && resource.arrayLayers == shapeInfo.arrayLayers && + resource.viewType == shapeInfo.viewType && resource.sampleCount == VK_SAMPLE_COUNT_1_BIT && + resolvedSampleCount == VK_SAMPLE_COUNT_1_BIT && resource.layout != VK_IMAGE_LAYOUT_UNDEFINED; // Preservation submits its copy separately. Earlier draws/clears of the // old image must reach the queue first; waiting only for the preservation // fence cannot order work that is still in the renderer's open recording. @@ -1236,8 +1237,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.samples = resolvedSampleCount; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - if (isMultisample || (imageInfo.flags & (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | - VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) != 0) { + if (isMultisample || + (imageInfo.flags & (VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT)) != 0) { VkImageFormatProperties imageFormatProperties{}; VkResult imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, @@ -1250,8 +1251,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.flags &= ~VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; imageCreateFlags = imageInfo.flags; imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, - imageInfo.flags, &imageFormatProperties); + m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, + &imageFormatProperties); } if (imageFormatResult != VK_SUCCESS && !isMultisample && (imageInfo.flags & VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT) != 0) { @@ -1259,8 +1260,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { imageInfo.flags &= ~VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; imageCreateFlags = imageInfo.flags; imageFormatResult = vkGetPhysicalDeviceImageFormatProperties( - m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, - imageInfo.flags, &imageFormatProperties); + m_physicalDevice, format, imageInfo.imageType, imageInfo.tiling, imageInfo.usage, imageInfo.flags, + &imageFormatProperties); } if (imageFormatResult != VK_SUCCESS || (isMultisample && (imageFormatProperties.sampleCounts & resolvedSampleCount) == 0)) { @@ -1281,15 +1282,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { allocationInfo.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; // Soft failure, SyncTextureResource's rule: a driver can pass the pre-check yet refuse // the creation; the texture stays unbacked and the caller declines. - const VkResult createImageResult = - vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &replacement.image, &replacement.allocation, nullptr); + const VkResult createImageResult = vmaCreateImage(m_allocator, &imageInfo, &allocationInfo, &replacement.image, + &replacement.allocation, nullptr); if (createImageResult != VK_SUCCESS) { MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: vmaCreateImage failed (%d) extent=%ux%u depth=%u " "layers=%u mips=%u samples=%d format=%d", desc.Resource.Slot, desc.Resource.Gen, createImageResult, imageInfo.extent.width, - imageInfo.extent.height, imageInfo.extent.depth, imageInfo.arrayLayers, - imageInfo.mipLevels, static_cast(imageInfo.samples), - static_cast(imageInfo.format)); + imageInfo.extent.height, imageInfo.extent.depth, imageInfo.arrayLayers, imageInfo.mipLevels, + static_cast(imageInfo.samples), static_cast(imageInfo.format)); WireDeclineTally::Count(WireDeclineSite::ShapeCreateImageFailed); return false; } @@ -1311,8 +1311,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { replacement.storageUsageResolved = markedAsStorageImage; if (preserve) { FlushPendingUploads(); - if (!PreserveTextureContentsOnRecreate(m_device, m_commandPool, m_graphicsQueue, - resource, replacement, true, this)) { + if (!PreserveTextureContentsOnRecreate(m_device, m_commandPool, m_graphicsQueue, resource, replacement, + true, this)) { MGL_WIRE_DECLINE_AT(ShapePreserveCopyFailed, "texture {slot=%u, gen=%u}: its texels could not be copied into the " "reallocated image", @@ -1326,8 +1326,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - Bool VkTextureManager::UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, - MG_Pipe::MGPipeResourceRecord& record, + Bool VkTextureManager::UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource) { if (record.PendingUploads.empty()) { return true; @@ -1374,8 +1373,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // The staged run IS the whole level shadow (StagedTextureStore.h's coverage rule): // it exists because the applier accepted this entry, so a miss is the store's own // Fatal{StageSnapshotTooNarrow}, not a silent zero. - const Uint8* bytes = store.RequireLevelBytes(key, uploadTarget, level, - "VkTextureManager::UploadPendingWireLevels"); + const Uint8* bytes = + store.RequireLevelBytes(key, uploadTarget, level, "VkTextureManager::UploadPendingWireLevels"); // Vulkan geometry, like the image this stages into: a 1D array's layers move out of // the shadow's height into z. const IntVec3 texelSize = ToVulkanLevelExtent(target, glExtent); @@ -1387,7 +1386,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { texelSize.y() != static_cast(std::max(resource.extent.height >> level, 1u)) || (volume && texelSize.z() != static_cast(std::max(resource.depth >> level, 1u))) || (!volume && Uint64(ResolveUploadArrayLayer(static_cast(uploadTarget))) + - static_cast(texelSize.z()) > resource.arrayLayers)) { + static_cast(texelSize.z()) > + resource.arrayLayers)) { // NOT `incomplete`: this arm reports SUCCESS while leaving the entry pending, // so the draw proceeds against texels that were never uploaded. Counted // separately for exactly that reason (B3). @@ -1398,8 +1398,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { continue; } if (texelSize.x() <= 0 || texelSize.y() <= 0 || byteSize == 0) { - MGL_WIRE_DECLINE_AT(UploadEmptyLevel, "texture {slot=%u, gen=%u} level %u is empty", - handle.Slot, handle.Gen, static_cast(level)); + MGL_WIRE_DECLINE_AT(UploadEmptyLevel, "texture {slot=%u, gen=%u} level %u is empty", handle.Slot, + handle.Gen, static_cast(level)); incomplete = true; continue; } @@ -1408,13 +1408,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { item.level = level; item.baseArrayLayer = ResolveUploadArrayLayer(static_cast(uploadTarget)); item.texelSize = texelSize; - if (!ConvertWireLevelBytes(bytes, byteSize, texelSize, resource.format, resource.aspect, - internalFormat, formatInfo, - item.owned, item.source, item.byteSize)) { + if (!ConvertWireLevelBytes(bytes, byteSize, texelSize, resource.format, resource.aspect, internalFormat, + formatInfo, item.owned, item.source, item.byteSize)) { MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: no shadow-to-image conversion for format %d; " "level %u left pending", - handle.Slot, handle.Gen, static_cast(resource.format), - static_cast(level)); + handle.Slot, handle.Gen, static_cast(resource.format), static_cast(level)); WireDeclineTally::Count(WireDeclineSite::UploadConversion); incomplete = true; continue; @@ -1422,12 +1420,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Adoption covers the complete CPU shadow, but only the emitted regions are // authoritative after a GPU write. Uploading the whole run here overwrites // untouched texels of a prior clear/blit/mipmap with stale client bytes. - const SizeT levelTexels = static_cast(texelSize.x()) * texelSize.y() * - std::max(texelSize.z(), 1); - const Bool combined = (resource.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) && - (resource.aspect & VK_IMAGE_ASPECT_STENCIL_BIT); - if (vkuFormatIsCompressed(resource.format) || !levelTexels || - item.byteSize % levelTexels != 0 || item.byteSize / levelTexels == 0) { + const SizeT levelTexels = static_cast(texelSize.x()) * texelSize.y() * std::max(texelSize.z(), 1); + const Bool combined = + (resource.aspect & VK_IMAGE_ASPECT_DEPTH_BIT) && (resource.aspect & VK_IMAGE_ASPECT_STENCIL_BIT); + if (vkuFormatIsCompressed(resource.format) || !levelTexels || item.byteSize % levelTexels != 0 || + item.byteSize / levelTexels == 0) { MGL_WIRE_DECLINE_AT(UploadTexelArithmetic, "texture {slot=%u, gen=%u} level %u: compressed, or its byte size is not a " "whole number of texels", @@ -1441,8 +1438,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; const auto addRegion = [&](const MG_Pipe::MGPBox& glBox) { IntVec3 offset{glBox.X, glBox.Y, glBox.Z}; - IntVec3 extent{static_cast(glBox.W), static_cast(glBox.H), - static_cast(glBox.D)}; + IntVec3 extent{static_cast(glBox.W), static_cast(glBox.H), static_cast(glBox.D)}; if (target == TextureTarget::Texture1DArray) { offset = {glBox.X, 0, glBox.Y}; extent = {static_cast(glBox.W), 1, static_cast(glBox.H)}; @@ -1471,10 +1467,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (Int z = 0; z < extent.z(); ++z) { for (Int y = 0; y < extent.y(); ++y) { const SizeT sourceTexel = - (static_cast(offset.z() + z) * texelSize.y() + offset.y() + y) * - texelSize.x() + offset.x(); - const SizeT destinationTexel = - (static_cast(z) * extent.y() + y) * extent.x(); + (static_cast(offset.z() + z) * texelSize.y() + offset.y() + y) * texelSize.x() + + offset.x(); + const SizeT destinationTexel = (static_cast(z) * extent.y() + y) * extent.x(); std::memcpy(region.owned.data() + destinationTexel * texelBytes, source + sourceTexel * texelBytes, extent.x() * texelBytes); if (combined) { @@ -1503,9 +1498,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { { Uint64 pendingBytes = 0; for (const auto& pending : record.PendingUploads) { - pendingBytes += static_cast( - store.LevelByteSize(MG_Record::StagedTextureStore::KeyForHandle(handle), - pending.UploadTarget, pending.Level)); + pendingBytes += static_cast(store.LevelByteSize( + MG_Record::StagedTextureStore::KeyForHandle(handle), pending.UploadTarget, pending.Level)); } WireDeclineTally::SetPendingGauge(static_cast(record.PendingUploads.size()), pendingBytes); } @@ -1557,10 +1551,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineStageFlags uploadSrcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags uploadSrcAccessMask = 0; GetImageTransitionSourceState(resource.layout, uploadSrcStageMask, uploadSrcAccessMask); - Bool ok = TransitionImageLayout(commandBuffer, resource.image, resource.layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, uploadSrcStageMask, - VK_PIPELINE_STAGE_TRANSFER_BIT, uploadSrcAccessMask, - VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask, 0, resource.mipLevels); + Bool ok = + TransitionImageLayout(commandBuffer, resource.image, resource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + uploadSrcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, uploadSrcAccessMask, + VK_ACCESS_TRANSFER_WRITE_BIT, aspectMask, 0, resource.mipLevels); MOBILEGL_ASSERT(ok, "UploadPendingWireLevels: transition to TRANSFER_DST failed"); const Bool isCombinedDepthStencil = @@ -1569,8 +1563,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { resource.viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY || resource.viewType == VK_IMAGE_VIEW_TYPE_CUBE_ARRAY; for (const auto& item : items) { - const Uint32 depthOrLayers = - item.texelSize.z() > 0 ? static_cast(item.texelSize.z()) : 1u; + const Uint32 depthOrLayers = item.texelSize.z() > 0 ? static_cast(item.texelSize.z()) : 1u; VkBufferImageCopy copy{}; copy.bufferOffset = stagingBase + item.offset; copy.bufferRowLength = 0; @@ -1580,8 +1573,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.imageSubresource.baseArrayLayer = item.baseArrayLayer; copy.imageSubresource.layerCount = depthSelectsArrayLayer ? depthOrLayers : 1; copy.imageOffset = item.imageOffset; - copy.imageExtent = {static_cast(item.texelSize.x()), - static_cast(item.texelSize.y()), + copy.imageExtent = {static_cast(item.texelSize.x()), static_cast(item.texelSize.y()), depthSelectsArrayLayer ? 1u : depthOrLayers}; if (isCombinedDepthStencil) { // Per-aspect copies: VkBufferImageCopy's aspectMask must name exactly one @@ -1627,9 +1619,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } - MG_Pipe::MGPipeHandle VkTextureManager::ResolveWireTextureStorage( - MG_Pipe::MGPipeHandle handle, Uint32& level, Uint32& layer, - VkFormat* viewFormat, Uint32* layerCount, Bool* outsideWindow) { + MG_Pipe::MGPipeHandle VkTextureManager::ResolveWireTextureStorage(MG_Pipe::MGPipeHandle handle, Uint32& level, + Uint32& layer, VkFormat* viewFormat, + Uint32* layerCount, Bool* outsideWindow) { const auto& state = MG_Pipe::MGPipeApplier(); if (viewFormat) *viewFormat = VK_FORMAT_UNDEFINED; if (outsideWindow) *outsideWindow = false; @@ -1650,10 +1642,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (MG_Pipe::MGPipeHandleIsNull(record.Desc.ViewOf)) { // Levels == 0 is a texture with no storage at all: every level is outside it. if (mappedLevel >= record.Desc.Levels) return outside(); - const Uint32 layers = record.Desc.Target == - static_cast(MG_Pipe::MGPipeResourceTarget::Tex3D) - ? std::max(record.Desc.Depth >> mappedLevel, 1u) - : std::max(record.Desc.ArrayLayers, 1u); + const Uint32 layers = record.Desc.Target == static_cast(MG_Pipe::MGPipeResourceTarget::Tex3D) + ? std::max(record.Desc.Depth >> mappedLevel, 1u) + : std::max(record.Desc.ArrayLayers, 1u); if (mappedLayer >= layers) return outside(); level = mappedLevel; layer = mappedLayer; @@ -1681,8 +1672,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { return MG_Pipe::kMGPipeNullHandle; } - Bool VkTextureManager::ImportSharedImage(void* nativeBuffer, Uint32 width, Uint32 height, - ImportedSharedImage& out, String& why) { + Bool VkTextureManager::ImportSharedImage(void* nativeBuffer, Uint32 width, Uint32 height, ImportedSharedImage& out, + String& why) { out = ImportedSharedImage{}; #if defined(__ANDROID__) auto* ahb = static_cast(nativeBuffer); @@ -1706,7 +1697,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { reinterpret_cast(ctx.getAhbProperties); VkAndroidHardwareBufferFormatPropertiesANDROID formatProperties{ VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID}; - VkAndroidHardwareBufferPropertiesANDROID properties{VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID}; + VkAndroidHardwareBufferPropertiesANDROID properties{ + VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID}; properties.pNext = &formatProperties; VkResult result = getProperties(ctx.device, ahb, &properties); if (result != VK_SUCCESS) { @@ -1803,8 +1795,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } #if MOBILEGL_BUILD_DISAGGREGATED - VkTextureManager::SharedImageBind VkTextureManager::SyncWireSharedImage( - const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, Bool requireStorage) { + VkTextureManager::SharedImageBind VkTextureManager::SyncWireSharedImage(const MG_Pipe::MGPipeResourceRecord& record, + TextureResource& resource, + Bool requireStorage) { const MG_Pipe::MGPResourceDesc& desc = record.Desc; const Uint64 id = record.SharedImageId; if (requireStorage) { @@ -1878,14 +1871,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { } #else // P13 W5: a library without a transport allocates no shared image, so no record names one. - VkTextureManager::SharedImageBind VkTextureManager::SyncWireSharedImage( - const MG_Pipe::MGPipeResourceRecord&, TextureResource&, Bool) { + VkTextureManager::SharedImageBind VkTextureManager::SyncWireSharedImage(const MG_Pipe::MGPipeResourceRecord&, + TextureResource&, Bool) { return SharedImageBind::Unresolved; } #endif - VkTextureManager::TextureResource* VkTextureManager::SyncTextureResourceByHandle( - MG_Pipe::MGPipeHandle handle, Bool renderbuffer, Bool requireStorage) { + VkTextureManager::TextureResource* VkTextureManager::SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, + Bool renderbuffer, + Bool requireStorage) { MOBILEGL_ASSERT(m_device != VK_NULL_HANDLE, "SyncTextureResourceByHandle: m_device == VK_NULL_HANDLE"); if (MG_Pipe::MGPipeHandleIsNull(handle)) { return nullptr; @@ -1896,9 +1890,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFormat viewFormat = VK_FORMAT_UNDEFINED; const auto storage = ResolveWireTextureStorage(handle, level, layer, &viewFormat); if (MG_Pipe::MGPipeHandleIsNull(storage)) { - MGL_WIRE_DECLINE_AT(TexResolveStorage, - "texture {slot=%u, gen=%u}: no storage owner resolved", handle.Slot, - handle.Gen); + MGL_WIRE_DECLINE_AT(TexResolveStorage, "texture {slot=%u, gen=%u}: no storage owner resolved", + handle.Slot, handle.Gen); return nullptr; } if (storage != handle) { @@ -1918,8 +1911,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& records = renderbuffer ? applier.RenderbufferResources : applier.TextureResources; auto& resources = renderbuffer ? m_wireRenderbufferResources : m_wireTextureResources; if (handle.Slot >= records.size()) { - MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: no applier record at that slot; declined", - handle.Slot, handle.Gen); + MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: no applier record at that slot; declined", handle.Slot, + handle.Gen); WireDeclineTally::Count(WireDeclineSite::TexNoApplierRecord); return nullptr; } @@ -1927,14 +1920,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!record.Live || record.Gen != handle.Gen) { // FramebufferRecordFor's rule: a dead or recycled slot is a loud refusal, never a // quiet answer from whatever the slot holds today. - MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: the applier's record is %s; declined", - handle.Slot, handle.Gen, - !record.Live ? "dead" : "of another generation"); + MGLOG_E_ONCE("Magma wire texture {slot=%u, gen=%u}: the applier's record is %s; declined", handle.Slot, + handle.Gen, !record.Live ? "dead" : "of another generation"); WireDeclineTally::Count(WireDeclineSite::TexRecordDeadOrStale); return nullptr; } const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(handle); - auto& lookup = (renderbuffer ? m_wireRenderbufferLookup : m_wireTextureLookup)[key % kWireResourceLookupEntries]; + auto& lookup = + (renderbuffer ? m_wireRenderbufferLookup : m_wireTextureLookup)[key % kWireResourceLookupEntries]; if (lookup.resource == nullptr || lookup.key != key || lookup.eraseEpoch != m_resourceEraseEpoch) { auto [it, inserted] = resources.try_emplace(key); (void)inserted; @@ -1951,7 +1944,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_pendingClearHold == 0 || !current) pVulkanRenderer->MaterializeWirePendingClears(resource); } if (resource.image != VK_NULL_HANDLE && resource.syncedWireSerial == record.Serial && - record.PendingUploads.empty() && (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) { + record.PendingUploads.empty() && + (!requireStorage || (resource.usageFlags & VK_IMAGE_USAGE_STORAGE_BIT) != 0)) { return &resource; } if (!SyncWireTextureShape(record, resource, requireStorage)) { @@ -1979,7 +1973,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkTextureManager::MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel, - Uint32 baseArrayLayer, Uint32 layerCount) { + Uint32 baseArrayLayer, Uint32 layerCount) { Uint32 availableLayers = 0; handle = ResolveWireTextureStorage(handle, mipLevel, baseArrayLayer, nullptr, &availableLayers); layerCount = std::min(layerCount, availableLayers); @@ -1990,27 +1984,45 @@ namespace MobileGL::MG_Backend::DirectVulkan { using MG_Pipe::MGPipeResourceTarget; TextureUploadTarget target; switch (static_cast(record.Desc.Target)) { - case MGPipeResourceTarget::Tex1D: target = TextureUploadTarget::Texture1D; break; - case MGPipeResourceTarget::Tex1DArray: target = TextureUploadTarget::Texture1DArray; break; - case MGPipeResourceTarget::Tex3D: target = TextureUploadTarget::Texture3D; break; - case MGPipeResourceTarget::TexRect: target = TextureUploadTarget::TextureRectangle; break; - case MGPipeResourceTarget::Tex2DMS: target = TextureUploadTarget::Texture2DMultisample; break; - case MGPipeResourceTarget::Tex2DMSArray: target = TextureUploadTarget::Texture2DMultisampleArray; break; - case MGPipeResourceTarget::Tex2DArray: target = TextureUploadTarget::Texture2DArray; break; - case MGPipeResourceTarget::TexCubeArray: target = TextureUploadTarget::CubeMapArray; break; + case MGPipeResourceTarget::Tex1D: + target = TextureUploadTarget::Texture1D; + break; + case MGPipeResourceTarget::Tex1DArray: + target = TextureUploadTarget::Texture1DArray; + break; + case MGPipeResourceTarget::Tex3D: + target = TextureUploadTarget::Texture3D; + break; + case MGPipeResourceTarget::TexRect: + target = TextureUploadTarget::TextureRectangle; + break; + case MGPipeResourceTarget::Tex2DMS: + target = TextureUploadTarget::Texture2DMultisample; + break; + case MGPipeResourceTarget::Tex2DMSArray: + target = TextureUploadTarget::Texture2DMultisampleArray; + break; + case MGPipeResourceTarget::Tex2DArray: + target = TextureUploadTarget::Texture2DArray; + break; + case MGPipeResourceTarget::TexCubeArray: + target = TextureUploadTarget::CubeMapArray; + break; case MGPipeResourceTarget::TexCube: { const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(handle); for (Uint32 face = baseArrayLayer; face < 6 && face - baseArrayLayer < layerCount; ++face) - MG_Record::ServerStagedTexture().MarkLevelGpuDirty(key, - static_cast(TextureUploadTarget::CubeMapPositiveX) + face, + MG_Record::ServerStagedTexture().MarkLevelGpuDirty( + key, static_cast(TextureUploadTarget::CubeMapPositiveX) + face, static_cast(mipLevel), true); return; } - default: target = TextureUploadTarget::Texture2D; break; + default: + target = TextureUploadTarget::Texture2D; + break; } - MG_Record::ServerStagedTexture().MarkLevelGpuDirty( - MG_Record::StagedTextureStore::KeyForHandle(handle), - static_cast(target), static_cast(mipLevel), true); + MG_Record::ServerStagedTexture().MarkLevelGpuDirty(MG_Record::StagedTextureStore::KeyForHandle(handle), + static_cast(target), static_cast(mipLevel), + true); } Bool VkTextureManager::WireE5RawReadApplies(const TextureResource& resource) const { @@ -2040,8 +2052,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { twin.aspect = VK_IMAGE_ASPECT_COLOR_BIT; twin.viewType = region.volume ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY; EndActiveRenderPassOn(commandBuffer); - const VkResult result = RecordWireE5RawCopy(m_device, commandBuffer, *m_wireE5RawCopyKit, region, - sourceLayout, twin.image, objects); + const VkResult result = RecordWireE5RawCopy(m_device, commandBuffer, *m_wireE5RawCopyKit, region, sourceLayout, + twin.image, objects); if (result != VK_SUCCESS) { MGLOG_E_ONCE("RecordWireE5RawTwin: recording the raw copy failed (%d)", static_cast(result)); DestroyWireE5RawCopyObjects(m_device, objects); @@ -2111,18 +2123,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags srcAccessMask = 0; GetImageTransitionSourceState(oldLayout, srcStageMask, srcAccessMask); - Bool ok = TransitionImageLayout(commandBuffer, old.image, old.layout, - rawE5 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - srcStageMask, - rawE5 ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, - srcAccessMask, rawE5 ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT, - aspect, 0, old.mipLevels); + Bool ok = TransitionImageLayout( + commandBuffer, old.image, old.layout, + rawE5 ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, srcStageMask, + rawE5 ? VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, + rawE5 ? VK_ACCESS_SHADER_READ_BIT : VK_ACCESS_TRANSFER_READ_BIT, aspect, 0, old.mipLevels); MOBILEGL_ASSERT(ok, "GrowWireTextureMipChain: source transition failed"); VkImageLayout grownLayout = VK_IMAGE_LAYOUT_UNDEFINED; - ok = TransitionImageLayout(commandBuffer, grown.image, grownLayout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT, aspect, 0, - requiredMipLevels); + ok = TransitionImageLayout(commandBuffer, grown.image, grownLayout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + VK_ACCESS_TRANSFER_WRITE_BIT, aspect, 0, requiredMipLevels); MOBILEGL_ASSERT(ok, "GrowWireTextureMipChain: destination transition failed"); for (Uint32 level = 0; level < old.mipLevels; ++level) { @@ -2147,7 +2157,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Nothing was recorded for this level; the plain copy below reads the // canonicalized words, which is what every level got before the route. MGLOG_W_ONCE("GrowWireTextureMipChain: the raw E5B9G9R9 route failed; level %u is copied " - "by transfer", level); + "by transfer", + level); VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; toTransfer.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; @@ -2171,9 +2182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { DeferResourceRelease(Move(twin)); continue; } - for (const VkImageAspectFlags copyAspect : {VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_ASPECT_DEPTH_BIT, - VK_IMAGE_ASPECT_STENCIL_BIT}) { + for (const VkImageAspectFlags copyAspect : + {VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_ASPECT_DEPTH_BIT, VK_IMAGE_ASPECT_STENCIL_BIT}) { if ((aspect & copyAspect) == 0) continue; copy.srcSubresource.aspectMask = copyAspect; copy.dstSubresource.aspectMask = copyAspect; @@ -2186,9 +2196,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags dstAccessMask = 0; GetImageTransitionDestinationState(oldLayout, dstStageMask, dstAccessMask); - ok = TransitionImageLayout(commandBuffer, grown.image, grownLayout, oldLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, dstStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, - dstAccessMask, aspect, 0, requiredMipLevels); + ok = TransitionImageLayout(commandBuffer, grown.image, grownLayout, oldLayout, VK_PIPELINE_STAGE_TRANSFER_BIT, + dstStageMask, VK_ACCESS_TRANSFER_WRITE_BIT, dstAccessMask, aspect, 0, + requiredMipLevels); MOBILEGL_ASSERT(ok, "GrowWireTextureMipChain: restore transition failed"); grown.layout = oldLayout; @@ -2225,12 +2235,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { FlushPendingUploads(); } if (resource.image == VK_NULL_HANDLE && resource.fullView == VK_NULL_HANDLE && - resource.sampledView == VK_NULL_HANDLE && - resource.perMipViews.empty() && resource.perMipSampledViews.empty() && - resource.attachmentViews.empty() && resource.alternateSampledViews.empty() && - resource.storageImageViews.empty() - && resource.yuvSource == nullptr - ) { + resource.sampledView == VK_NULL_HANDLE && resource.perMipViews.empty() && + resource.perMipSampledViews.empty() && resource.attachmentViews.empty() && + resource.alternateSampledViews.empty() && resource.storageImageViews.empty() && + resource.yuvSource == nullptr) { return; } @@ -2247,8 +2255,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkTextureManager::CollectDeferredReleases(Uint32 frameIndex) { MOBILEGL_ASSERT(frameIndex < m_deferredReleases.size(), - "VkTextureManager::CollectDeferredReleases invalid frame index %u (size=%zu)", - frameIndex, m_deferredReleases.size()); + "VkTextureManager::CollectDeferredReleases invalid frame index %u (size=%zu)", frameIndex, + m_deferredReleases.size()); if (frameIndex < m_deferredE5Releases.size()) { for (WireE5RawCopyObjects& objects : m_deferredE5Releases[frameIndex]) DestroyWireE5RawCopyObjects(m_device, objects); @@ -2369,8 +2377,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_freeUploadStagingBlocks[i].capacity >= size) { block = Move(m_freeUploadStagingBlocks[i]); m_freeUploadStagingBytes -= block.capacity; - m_freeUploadStagingBlocks.erase(m_freeUploadStagingBlocks.begin() + - static_cast(i)); + m_freeUploadStagingBlocks.erase(m_freeUploadStagingBlocks.begin() + static_cast(i)); break; } } @@ -2382,8 +2389,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VmaAllocationCreateInfo stagingAllocationInfo{}; stagingAllocationInfo.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; - stagingAllocationInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | - VMA_ALLOCATION_CREATE_MAPPED_BIT; + stagingAllocationInfo.flags = + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT; stagingAllocationInfo.requiredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; VmaAllocationInfo allocationResult{}; @@ -2496,7 +2503,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VkTextureManager::DestroyDeferredReleases() { for (auto& deferredE5 : m_deferredE5Releases) { - for (WireE5RawCopyObjects& objects : deferredE5) DestroyWireE5RawCopyObjects(m_device, objects); + for (WireE5RawCopyObjects& objects : deferredE5) + DestroyWireE5RawCopyObjects(m_device, objects); } m_deferredE5Releases.clear(); for (auto& deferredReleases : m_deferredReleases) { @@ -2554,8 +2562,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return imageAspect; } - VkFormat VkTextureManager::ResolveSampledImageViewFormat(VkFormat imageFormat, - SamplerNumericDomain numericDomain) { + VkFormat VkTextureManager::ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain) { // Depth/stencil images always sample through the existing depth-aspect sampledView. // Combined formats (D24S8, D32FS8) are multi-numeric, so vkuFormatIsSampledFloat is // false for them by design, yet their depth aspect reads as float in every GL depth @@ -2577,8 +2584,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // texture sampled by a usampler rather than an arbitrary member (such as // R8G8B8A8_UINT) of Vulkan's broad 32-bit compatibility class. for (Int candidateValue = static_cast(VK_FORMAT_R4G4_UNORM_PACK8); - candidateValue <= static_cast(VK_FORMAT_ASTC_12x12_SRGB_BLOCK); - ++candidateValue) { + candidateValue <= static_cast(VK_FORMAT_ASTC_12x12_SRGB_BLOCK); ++candidateValue) { const VkFormat candidate = static_cast(candidateValue); if (!IsMutableStorageImageFormat(candidate) || !FormatMatchesSamplerNumericDomain(candidate, numericDomain) || diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index 75de98dc..cda4b33d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -25,791 +25,787 @@ #include "WireE5RawCopy.h" namespace MobileGL::MG_State::GLState { -class ITextureObject; -} + class ITextureObject; +} // namespace MobileGL::MG_State::GLState namespace MobileGL::MG_Pipe { -struct MGPipeResourceRecord; -} + struct MGPipeResourceRecord; +} // namespace MobileGL::MG_Pipe namespace MobileGL::MG_Backend::DirectVulkan { -enum class SamplerNumericDomain : Uint8; + enum class SamplerNumericDomain : Uint8; -// What VkFormat a GL internal format is BACKED with, and how a shadow upload has to be reshaped to -// fit it. This is not the same question as "is there an exact VkFormat for this GL format", which is -// what ConvertTextureInternalFormatToVkEnum answers: several GL formats have no Vulkan twin at all -// (RGBA2, RGBA12) and several three-channel ones are deliberately widened to their four-channel twin -// because Vulkan devices rarely support the 3-channel layouts. -// -// SHARED, and it must stay the only answer to that question. A renderbuffer and a texture of the -// same GL format have to resolve to the SAME VkFormat or every blit, resolve and glCopyImageSubData -// between them crosses a size-incompatible pair, which vkCmdCopyImage leaves undefined -// (VUID-vkCmdCopyImage-srcImage-01548). The renderbuffer path used to carry a hand-maintained second -// copy of this table that was missing four rows - RGBA2, RGBA4, RGB5A1 and RGBA12 - so those four -// renderbuffer formats either got no image at all or a 16-bit-packed one facing a 32-bit texture. -struct TextureFormatInfo { - VkFormat format = VK_FORMAT_UNDEFINED; - // The GL format has three channels and is carried in a four-channel image; a shadow upload has - // to be expanded, inserting `alphaBytes` after every `componentByteCount * 3` source bytes. - Bool expandRgbToRgba = false; - Uint32 componentByteCount = 0; - Array alphaBytes = {0, 0, 0, 0}; -}; + // What VkFormat a GL internal format is BACKED with, and how a shadow upload has to be reshaped to + // fit it. This is not the same question as "is there an exact VkFormat for this GL format", which is + // what ConvertTextureInternalFormatToVkEnum answers: several GL formats have no Vulkan twin at all + // (RGBA2, RGBA12) and several three-channel ones are deliberately widened to their four-channel twin + // because Vulkan devices rarely support the 3-channel layouts. + // + // SHARED, and it must stay the only answer to that question. A renderbuffer and a texture of the + // same GL format have to resolve to the SAME VkFormat or every blit, resolve and glCopyImageSubData + // between them crosses a size-incompatible pair, which vkCmdCopyImage leaves undefined + // (VUID-vkCmdCopyImage-srcImage-01548). The renderbuffer path used to carry a hand-maintained second + // copy of this table that was missing four rows - RGBA2, RGBA4, RGB5A1 and RGBA12 - so those four + // renderbuffer formats either got no image at all or a 16-bit-packed one facing a 32-bit texture. + struct TextureFormatInfo { + VkFormat format = VK_FORMAT_UNDEFINED; + // The GL format has three channels and is carried in a four-channel image; a shadow upload has + // to be expanded, inserting `alphaBytes` after every `componentByteCount * 3` source bytes. + Bool expandRgbToRgba = false; + Uint32 componentByteCount = 0; + Array alphaBytes = {0, 0, 0, 0}; + }; -// Callers that only need the backing VkFormat (a renderbuffer has no shadow upload to reshape) take -// `.format` and ignore the rest. -TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format); + // Callers that only need the backing VkFormat (a renderbuffer has no shadow upload to reshape) take + // `.format` and ignore the rest. + TextureFormatInfo ResolveTextureFormatInfo(TextureInternalFormat format); -// A GL 1D-ARRAY level keeps its LAYER COUNT in the state-side HEIGHT: that is what -// glTexImage2D(GL_TEXTURE_1D_ARRAY, width, layers) means, and the frontend records the level -// as {width, layers, 1} (see GL_Texture.cpp's AllocateStorage and the completeness walk in -// TextureObject.cpp, which shrinks only x down the chain). Vulkan packs it the other way: a -// 1D array is a VK_IMAGE_TYPE_1D image whose extent.height MUST be 1 and whose layers live in -// arrayLayers - i.e. in the slot this backend reads out of z. So every place that turns a GL -// level size into Vulkan image geometry has to move the count across first, and every GL-space -// sub-box that rides along with it has to move its y the same way. DirectGLES performs the -// identical remap onto the ES 2D array it maps 1D arrays to (GetBackendUploadSize). -// -// Applied to nothing else: a 2D array, a cube array and a 3D texture all already carry their -// depth/layer count in z, which is where the Vulkan side expects it. -inline IntVec3 ToVulkanLevelExtent(TextureTarget stateTarget, const IntVec3& glTexelSize) { - if (stateTarget == TextureTarget::Texture1DArray) { - return {glTexelSize.x(), 1, glTexelSize.y()}; + // A GL 1D-ARRAY level keeps its LAYER COUNT in the state-side HEIGHT: that is what + // glTexImage2D(GL_TEXTURE_1D_ARRAY, width, layers) means, and the frontend records the level + // as {width, layers, 1} (see GL_Texture.cpp's AllocateStorage and the completeness walk in + // TextureObject.cpp, which shrinks only x down the chain). Vulkan packs it the other way: a + // 1D array is a VK_IMAGE_TYPE_1D image whose extent.height MUST be 1 and whose layers live in + // arrayLayers - i.e. in the slot this backend reads out of z. So every place that turns a GL + // level size into Vulkan image geometry has to move the count across first, and every GL-space + // sub-box that rides along with it has to move its y the same way. DirectGLES performs the + // identical remap onto the ES 2D array it maps 1D arrays to (GetBackendUploadSize). + // + // Applied to nothing else: a 2D array, a cube array and a 3D texture all already carry their + // depth/layer count in z, which is where the Vulkan side expects it. + inline IntVec3 ToVulkanLevelExtent(TextureTarget stateTarget, const IntVec3& glTexelSize) { + if (stateTarget == TextureTarget::Texture1DArray) { + return {glTexelSize.x(), 1, glTexelSize.y()}; + } + return glTexelSize; } - return glTexelSize; -} -// How many Vulkan array layers (or, for a 3D image, z slices) a GL framebuffer attachment spans. -// -// THE ONE COPY, deliberately. This used to exist twice - privately in VkRenderPassManager.cpp and -// again in VkClearManager.cpp - and the two are not independent: the render pass builds the -// attachment view and VkFramebufferCreateInfo::layers from one, while the CLEAR key built from the -// other is written verbatim into VkImageSubresourceRange::layerCount when a queued glClear is -// materialised outside a render pass (MaterializePendingClearForTexture). They are two consumers -// of the same GL clear, so any disagreement means the same glClear produces two different pictures -// depending only on which path happens to consume it first - and the materialise path then POPS -// the entry, so the other one never runs. Fixing one copy and leaving the other is exactly how -// that split gets introduced; keep them the same function. -// -// Two shapes make this more than `size.z()`: -// * GL_TEXTURE_1D_ARRAY keeps its layer count in the state-side HEIGHT (see ToVulkanLevelExtent -// just above), so z reads 1 and every layer above the first was silently dropped. -// * GL_TEXTURE_CUBE_MAP is attached layered as its REPRESENTATIVE upload target, the +X face -// (ResolveRepresentableFramebufferTextureUploadTarget), and one face's level size has z = 1 - -// but a layered cube attachment names all six faces (GL 4.6 core 9.2.8), which are the image's -// six array layers. A cube ARRAY needs no such arm: its representative target carries 6n in z. -inline Uint32 ResolveAttachmentLayerCount(const MG_State::GLState::FramebufferAttachmentObject& attachment) { - if (!attachment.IsLayered()) { - return 1u; + // How many Vulkan array layers (or, for a 3D image, z slices) a GL framebuffer attachment spans. + // + // THE ONE COPY, deliberately. This used to exist twice - privately in VkRenderPassManager.cpp and + // again in VkClearManager.cpp - and the two are not independent: the render pass builds the + // attachment view and VkFramebufferCreateInfo::layers from one, while the CLEAR key built from the + // other is written verbatim into VkImageSubresourceRange::layerCount when a queued glClear is + // materialised outside a render pass (MaterializePendingClearForTexture). They are two consumers + // of the same GL clear, so any disagreement means the same glClear produces two different pictures + // depending only on which path happens to consume it first - and the materialise path then POPS + // the entry, so the other one never runs. Fixing one copy and leaving the other is exactly how + // that split gets introduced; keep them the same function. + // + // Two shapes make this more than `size.z()`: + // * GL_TEXTURE_1D_ARRAY keeps its layer count in the state-side HEIGHT (see ToVulkanLevelExtent + // just above), so z reads 1 and every layer above the first was silently dropped. + // * GL_TEXTURE_CUBE_MAP is attached layered as its REPRESENTATIVE upload target, the +X face + // (ResolveRepresentableFramebufferTextureUploadTarget), and one face's level size has z = 1 - + // but a layered cube attachment names all six faces (GL 4.6 core 9.2.8), which are the image's + // six array layers. A cube ARRAY needs no such arm: its representative target carries 6n in z. + inline Uint32 ResolveAttachmentLayerCount(const MG_State::GLState::FramebufferAttachmentObject& attachment) { + if (!attachment.IsLayered()) { + return 1u; + } + const auto& texture = attachment.GetTexture(); + const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; + if (target == TextureTarget::TextureCubeMap) { + return 6u; + } + return static_cast(std::max(ToVulkanLevelExtent(target, attachment.GetSize()).z(), 1)); } - const auto& texture = attachment.GetTexture(); - const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; - if (target == TextureTarget::TextureCubeMap) { - return 6u; - } - return static_cast(std::max(ToVulkanLevelExtent(target, attachment.GetSize()).z(), 1)); -} -class VkTextureManager { -public: - // Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass - // manager keys its per-draw fast path on this so an attachment's image recreation - // invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1). - Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; } - // P15 (wire deferred clears): while one of these lives, SyncTextureResourceByHandle leaves an - // image's pending clears pending - its caller is resolving the image as an ATTACHMENT, and - // either folds the clears into the pass it begins (LOAD_OP_CLEAR) or replaces them with a - // newer clear. Every other caller (sampling, blits, copies, readbacks, uploads, mip - // generation) materializes them first, so a use that forgets the hold is merely slower. - class PendingClearHold { + class VkTextureManager { public: - explicit PendingClearHold(VkTextureManager& manager) : m_manager(manager) { ++m_manager.m_pendingClearHold; } - ~PendingClearHold() { --m_manager.m_pendingClearHold; } - PendingClearHold(const PendingClearHold&) = delete; - PendingClearHold& operator=(const PendingClearHold&) = delete; - private: - VkTextureManager& m_manager; - }; - // A whole-subresource clear that was recorded but has not run yet (VulkanRenderer::DeferWireClear). - struct WirePendingClear { - Uint32 level = 0, layer = 0, layers = 1; - VkImageAspectFlags aspects = 0; - VkClearValue value{}; - }; - // Bumped whenever any tracked texture resource is erased; cached - // TextureResource pointers are valid only while this is unchanged. - Uint64 GetResourceEraseEpoch() const { return m_resourceEraseEpoch; } - - struct InitInfo { - VkDevice device = VK_NULL_HANDLE; - VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; - VmaAllocator allocator = nullptr; - VkCommandPool commandPool = VK_NULL_HANDLE; - VkQueue graphicsQueue = VK_NULL_HANDLE; - Uint32 frameCount = 0; - // VK_KHR_image_format_list is enabled: MUTABLE_FORMAT images can name the exact set of - // formats they will be viewed as, which is what lets a tiler keep them compressed. - Bool imageFormatListSupported = false; - // Union of shader stages sampled-read barriers may name on this device; the renderer - // builds it from the enabled features because geometry/tessellation stage bits are - // invalid in a barrier when their feature is off. - VkPipelineStageFlags sampledReadStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; - // Family of `graphicsQueue`; the manager creates its own command pool - // on it for the recycled upload-batch command buffers, so their parked - // allocations never sit in (and fragment) the renderer's shared pool - // that frame command buffers churn through every frame. - Uint32 graphicsQueueFamilyIndex = 0; - }; - - struct TextureResource { - struct AttachmentViewKey { - Uint32 mipLevel = 0; - Uint32 baseArrayLayer = 0; - Uint32 layerCount = 1; - VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; - // May differ from the image format: sRGB images attach through their UNORM - // twin while GL_FRAMEBUFFER_SRGB is disabled. - VkFormat viewFormat = VK_FORMAT_UNDEFINED; - - Bool operator==(const AttachmentViewKey& other) const { - return mipLevel == other.mipLevel && - baseArrayLayer == other.baseArrayLayer && - layerCount == other.layerCount && - viewType == other.viewType && - viewFormat == other.viewFormat; + // Monotonic epoch bumped whenever a texture VkImage is (re)created. The render-pass + // manager keys its per-draw fast path on this so an attachment's image recreation + // invalidates the cached render pass (dirty-flag tracking; portable to Vulkan 1.1). + Uint64 GetTextureImageEpoch() const { return m_textureImageEpoch; } + // P15 (wire deferred clears): while one of these lives, SyncTextureResourceByHandle leaves an + // image's pending clears pending - its caller is resolving the image as an ATTACHMENT, and + // either folds the clears into the pass it begins (LOAD_OP_CLEAR) or replaces them with a + // newer clear. Every other caller (sampling, blits, copies, readbacks, uploads, mip + // generation) materializes them first, so a use that forgets the hold is merely slower. + class PendingClearHold { + public: + explicit PendingClearHold(VkTextureManager& manager) : m_manager(manager) { + ++m_manager.m_pendingClearHold; } + ~PendingClearHold() { --m_manager.m_pendingClearHold; } + PendingClearHold(const PendingClearHold&) = delete; + PendingClearHold& operator=(const PendingClearHold&) = delete; + + private: + VkTextureManager& m_manager; + }; + // A whole-subresource clear that was recorded but has not run yet (VulkanRenderer::DeferWireClear). + struct WirePendingClear { + Uint32 level = 0, layer = 0, layers = 1; + VkImageAspectFlags aspects = 0; + VkClearValue value{}; + }; + // Bumped whenever any tracked texture resource is erased; cached + // TextureResource pointers are valid only while this is unchanged. + Uint64 GetResourceEraseEpoch() const { return m_resourceEraseEpoch; } + + struct InitInfo { + VkDevice device = VK_NULL_HANDLE; + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VmaAllocator allocator = nullptr; + VkCommandPool commandPool = VK_NULL_HANDLE; + VkQueue graphicsQueue = VK_NULL_HANDLE; + Uint32 frameCount = 0; + // VK_KHR_image_format_list is enabled: MUTABLE_FORMAT images can name the exact set of + // formats they will be viewed as, which is what lets a tiler keep them compressed. + Bool imageFormatListSupported = false; + // Union of shader stages sampled-read barriers may name on this device; the renderer + // builds it from the enabled features because geometry/tessellation stage bits are + // invalid in a barrier when their feature is off. + VkPipelineStageFlags sampledReadStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + // Family of `graphicsQueue`; the manager creates its own command pool + // on it for the recycled upload-batch command buffers, so their parked + // allocations never sit in (and fragment) the renderer's shared pool + // that frame command buffers churn through every frame. + Uint32 graphicsQueueFamilyIndex = 0; }; - struct AttachmentViewKeyHash { - SizeT operator()(const AttachmentViewKey& key) const { - SizeT hash = std::hash{}(key.mipLevel); - hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.viewType)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.viewFormat)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - return hash; - } - }; + struct TextureResource { + struct AttachmentViewKey { + Uint32 mipLevel = 0; + Uint32 baseArrayLayer = 0; + Uint32 layerCount = 1; + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + // May differ from the image format: sRGB images attach through their UNORM + // twin while GL_FRAMEBUFFER_SRGB is disabled. + VkFormat viewFormat = VK_FORMAT_UNDEFINED; - struct StorageImageViewKey { - Uint32 mipLevel = 0; - Uint32 baseArrayLayer = 0; - Uint32 layerCount = 1; - VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + Bool operator==(const AttachmentViewKey& other) const { + return mipLevel == other.mipLevel && baseArrayLayer == other.baseArrayLayer && + layerCount == other.layerCount && viewType == other.viewType && + viewFormat == other.viewFormat; + } + }; + + struct AttachmentViewKeyHash { + SizeT operator()(const AttachmentViewKey& key) const { + SizeT hash = std::hash{}(key.mipLevel); + hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(static_cast(key.viewType)) + 0x9e3779b9u + (hash << 6) + + (hash >> 2); + hash ^= std::hash{}(static_cast(key.viewFormat)) + 0x9e3779b9u + (hash << 6) + + (hash >> 2); + return hash; + } + }; + + struct StorageImageViewKey { + Uint32 mipLevel = 0; + Uint32 baseArrayLayer = 0; + Uint32 layerCount = 1; + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + VkFormat format = VK_FORMAT_UNDEFINED; + + Bool operator==(const StorageImageViewKey& other) const { + return mipLevel == other.mipLevel && baseArrayLayer == other.baseArrayLayer && + layerCount == other.layerCount && viewType == other.viewType && format == other.format; + } + }; + + // Layer range and aspect join the key because a GL texture view (ARB_texture_view) can + // differ from its storage on either: the Better Clouds shape samples ONE D24S8 image + // through two GL names in one draw, the parent with the stencil aspect and the view with + // the depth aspect, and a layer-sliced view of an array texture names a sub-range of the + // same image. Without these two fields those views would alias each other in the cache. + struct SampledImageViewKey { + Uint32 baseMipLevel = 0; + Uint32 levelCount = 1; + Uint32 baseArrayLayer = 0; + Uint32 layerCount = 1; + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; + // GL_TEXTURE_SWIZZLE_* is per-texture state, so two views over one storage with the + // same window but different swizzles are different views. Baked into the key because + // a GL texture view's ONLY sampled view lives in this cache: unlike the storage + // texture's own sampledView, which SyncTextureViews rebuilds whenever the params + // version moves, nothing else would ever notice a swizzle change on a view. + Uint32 componentSwizzle = 0; + + Bool operator==(const SampledImageViewKey& other) const { + return baseMipLevel == other.baseMipLevel && levelCount == other.levelCount && + baseArrayLayer == other.baseArrayLayer && layerCount == other.layerCount && + viewType == other.viewType && format == other.format && aspect == other.aspect && + componentSwizzle == other.componentSwizzle; + } + }; + + struct SampledImageViewKeyHash { + SizeT operator()(const SampledImageViewKey& key) const { + SizeT hash = std::hash{}(key.baseMipLevel); + hash ^= std::hash{}(key.levelCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(static_cast(key.viewType)) + 0x9e3779b9u + (hash << 6) + + (hash >> 2); + hash ^= + std::hash{}(static_cast(key.format)) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= + std::hash{}(static_cast(key.aspect)) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.componentSwizzle) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + struct StorageImageViewKeyHash { + SizeT operator()(const StorageImageViewKey& key) const { + SizeT hash = std::hash{}(key.mipLevel); + hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + hash ^= std::hash{}(static_cast(key.viewType)) + 0x9e3779b9u + (hash << 6) + + (hash >> 2); + hash ^= + std::hash{}(static_cast(key.format)) + 0x9e3779b9u + (hash << 6) + (hash >> 2); + return hash; + } + }; + + VkImage image = VK_NULL_HANDLE; + VmaAllocation allocation = nullptr; + VkImageView fullView = VK_NULL_HANDLE; + VkImageView sampledView = VK_NULL_HANDLE; + Vector perMipViews; + Vector perMipSampledViews; + UnorderedMap attachmentViews; + UnorderedMap alternateSampledViews; + UnorderedMap storageImageViews; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + VkExtent2D extent = {0, 0}; + Uint32 depth = 1; + Uint32 arrayLayers = 1; + Uint32 mipLevels = 1; + Uint32 sampledBaseMipLevel = 0; + Uint32 sampledLevelCount = 1; VkFormat format = VK_FORMAT_UNDEFINED; + VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; + VkImageCreateFlags imageCreateFlags = 0; + // Usage the live image was created with. STORAGE is only requested for textures that + // have actually been bound to a GL image unit, because on Adreno a storage-capable + // image loses UBWC bandwidth compression; a later image binding upgrades the usage + // and recreates the image, so the resolved usage has to be part of the compatibility + // check that decides whether the existing image can be kept. + VkImageUsageFlags usageFlags = 0; + // True once this image was (re)resolved while the texture was already marked as an + // image-unit texture. Distinguishes "not upgraded yet" from "cannot be upgraded" + // (a format whose optimalTilingFeatures lack STORAGE_IMAGE never gains the bit), so + // NeedsStorageImagePreparation cannot ask for a recreate that will never happen. + Bool storageUsageResolved = false; + Uint16 syncedTextureParamsVersion = 0; + // Recording generation (VkTextureManager::GetRecordingGeneration) of the last + // command referencing this image that was recorded into the CURRENT frame + // command buffer. An image untouched by the open recording may have its + // out-of-pass work (deferred clears, sampled-layout transitions) recorded + // into the frame's PRE command buffer - which executes strictly before the + // frame's commands - instead of splitting the active render pass. + Uint64 lastRecordingGeneration = 0; + // The same, for the last recorded command that WRITES the image (a render pass it is an + // attachment of, a clear, a blit/copy into it, a mip generation, a storage-image binding). + // An upload into an image written this recording has to be ordered after that write + // (VulkanRenderer::OrderPendingUploadAfterRecording). + Uint64 lastRecordingWriteGeneration = 0; + // Snapshot of ITextureObject::GetContentVersion() at the last successful sync; + // lets SyncTexture skip the whole re-check/re-upload when content is unchanged. + Uint64 syncedContentVersion = 0; + // Snapshot of the defined mip-level count at the last sync. Folded into the early-out key + // as defense-in-depth: any path that grows the level set (which resizes the sampled view) + // busts the skip even if it failed to bump the content version. + Uint32 syncedMipLevelCount = 0; + // Snapshot of ITextureObject::GetShapeVersion() at the last successful sync. The content + // version alone does NOT cover a re-specification: glTexImage2D(..., nullptr) on an + // already-defined level changes its size or format and dirties no texel, so it moves the + // shape version and nothing else. Without this in the early-out key the image, its views + // and therefore imageSize() all keep answering with the texture's PREVIOUS shape. + Uint64 syncedShapeVersion = 0; + // The handle-keyed arm's (P5f fm) early-out key: the applier record's server-owned + // Serial at the last successful sync. 0 also means "this resource has only ever been + // synced by the frontend arm" - a wire record's Serial starts at 0 on a create and moves + // on every mutation, so the first handle sync of a fresh image always re-checks. + Uint64 syncedWireSerial = 0; + // SHARED IMAGES: level 0 is a server shared image (an imported AHardwareBuffer), not a + // VMA allocation. The resource owns the image and its dedicated imported memory, and + // holds the registry's reference so the buffer outlives every use (deferred releases + // included). `sharedImageAlphaOne`: an X-format image, whose alpha bytes are undefined. + VkDeviceMemory importedMemory = VK_NULL_HANDLE; + Uint64 sharedImageId = 0; + SharedPtr sharedImageOwner; + Bool sharedImageAlphaOne = false; + // The reading side's ownership of the image (VulkanRenderer::AcquireSharedImage): whether + // this device holds it now - acquired from the foreign family and not yet released back at + // the frame boundary - the writer's generation it last acquired, and the + // m_wireTextureResources key it lives under, by which the frame boundary finds it again. + Bool sharedImageHeld = false; + Uint64 sharedImageGeneration = 0; + Uint64 sharedImageKey = 0; + // A YUV shared image (NV12/P010) is never the texture's image: the texture keeps its own + // RGBA8 level 0, into which the renderer converts the image at its first use in each frame + // (WireYuvImage.cpp) - `yuvSource` being that conversion's import of the buffer, made on + // first use, and `yuvConvertedFrame` the frame it last ran in. `yuvOwner` holds the + // registry's image for as long as the texture names it. + Uint64 yuvImageId = 0; + SharedPtr yuvOwner; + SharedPtr yuvSource; + Uint64 yuvConvertedFrame = 0; + // P15: clears of this image still to run (WirePendingClear). Part of the image, so a + // respecify (which moves a new image in, below) drops them with the old one. + Vector pendingClears; - Bool operator==(const StorageImageViewKey& other) const { - return mipLevel == other.mipLevel && - baseArrayLayer == other.baseArrayLayer && - layerCount == other.layerCount && - viewType == other.viewType && - format == other.format; + TextureResource() = default; + TextureResource(const TextureResource&) = delete; + TextureResource(TextureResource&& that) noexcept { + std::swap(this->image, that.image); + std::swap(this->allocation, that.allocation); + std::swap(this->fullView, that.fullView); + std::swap(this->sampledView, that.sampledView); + std::swap(this->perMipViews, that.perMipViews); + std::swap(this->perMipSampledViews, that.perMipSampledViews); + std::swap(this->attachmentViews, that.attachmentViews); + std::swap(this->alternateSampledViews, that.alternateSampledViews); + std::swap(this->storageImageViews, that.storageImageViews); + std::swap(this->layout, that.layout); + std::swap(this->extent, that.extent); + std::swap(this->depth, that.depth); + std::swap(this->arrayLayers, that.arrayLayers); + std::swap(this->mipLevels, that.mipLevels); + std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel); + std::swap(this->sampledLevelCount, that.sampledLevelCount); + std::swap(this->format, that.format); + std::swap(this->aspect, that.aspect); + std::swap(this->viewType, that.viewType); + std::swap(this->sampleCount, that.sampleCount); + std::swap(this->imageCreateFlags, that.imageCreateFlags); + std::swap(this->usageFlags, that.usageFlags); + std::swap(this->storageUsageResolved, that.storageUsageResolved); + std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion); + std::swap(this->lastRecordingGeneration, that.lastRecordingGeneration); + std::swap(this->lastRecordingWriteGeneration, that.lastRecordingWriteGeneration); + std::swap(this->syncedContentVersion, that.syncedContentVersion); + std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount); + std::swap(this->syncedShapeVersion, that.syncedShapeVersion); + std::swap(this->syncedWireSerial, that.syncedWireSerial); + std::swap(this->importedMemory, that.importedMemory); + std::swap(this->sharedImageId, that.sharedImageId); + std::swap(this->sharedImageOwner, that.sharedImageOwner); + std::swap(this->sharedImageAlphaOne, that.sharedImageAlphaOne); + std::swap(this->sharedImageHeld, that.sharedImageHeld); + std::swap(this->sharedImageGeneration, that.sharedImageGeneration); + std::swap(this->sharedImageKey, that.sharedImageKey); + std::swap(this->yuvImageId, that.yuvImageId); + std::swap(this->yuvOwner, that.yuvOwner); + std::swap(this->yuvSource, that.yuvSource); + std::swap(this->yuvConvertedFrame, that.yuvConvertedFrame); + std::swap(this->pendingClears, that.pendingClears); } + + void Reset() { + if (fullView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), fullView, nullptr); + } + if (sampledView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), sampledView, nullptr); + } + for (const auto attachmentView : perMipViews) { + if (attachmentView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), attachmentView, nullptr); + } + } + for (const auto sampledView : perMipSampledViews) { + if (sampledView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), sampledView, nullptr); + } + } + for (const auto& [_, attachmentView] : attachmentViews) { + if (attachmentView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), attachmentView, nullptr); + } + } + for (const auto& [_, sampledView] : alternateSampledViews) { + if (sampledView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), sampledView, nullptr); + } + } + for (const auto& [_, storageImageView] : storageImageViews) { + if (storageImageView != VK_NULL_HANDLE) { + vkDestroyImageView(s_device.Get(), storageImageView, nullptr); + } + } + if (image != VK_NULL_HANDLE && allocation != nullptr) { + vmaDestroyImage(s_allocator.Get(), image, allocation); + } + if (importedMemory != VK_NULL_HANDLE) { + if (image != VK_NULL_HANDLE) vkDestroyImage(s_device.Get(), image, nullptr); + vkFreeMemory(s_device.Get(), importedMemory, nullptr); + } + importedMemory = VK_NULL_HANDLE; + sharedImageId = 0; + sharedImageOwner.reset(); + sharedImageAlphaOne = false; + sharedImageHeld = false; + sharedImageGeneration = 0; + sharedImageKey = 0; + yuvImageId = 0; + yuvOwner.reset(); + yuvSource.reset(); + yuvConvertedFrame = 0; + fullView = VK_NULL_HANDLE; + sampledView = VK_NULL_HANDLE; + perMipViews.clear(); + perMipSampledViews.clear(); + attachmentViews.clear(); + alternateSampledViews.clear(); + storageImageViews.clear(); + image = VK_NULL_HANDLE; + allocation = nullptr; + layout = VK_IMAGE_LAYOUT_UNDEFINED; + extent = {0, 0}; + depth = 1; + arrayLayers = 1; + mipLevels = 1; + sampledBaseMipLevel = 0; + sampledLevelCount = 1; + format = VK_FORMAT_UNDEFINED; + aspect = VK_IMAGE_ASPECT_NONE; + viewType = VK_IMAGE_VIEW_TYPE_2D; + sampleCount = VK_SAMPLE_COUNT_1_BIT; + imageCreateFlags = 0; + usageFlags = 0; + storageUsageResolved = false; + syncedTextureParamsVersion = 0; + syncedContentVersion = 0; + syncedMipLevelCount = 0; + syncedShapeVersion = 0; + syncedWireSerial = 0; + } + + ~TextureResource() { Reset(); } + + static inline SessionLocal s_device; // per Magma session (MagmaSession.h) + static inline SessionLocal s_allocator; }; - // Layer range and aspect join the key because a GL texture view (ARB_texture_view) can - // differ from its storage on either: the Better Clouds shape samples ONE D24S8 image - // through two GL names in one draw, the parent with the stencil aspect and the view with - // the depth aspect, and a layer-sliced view of an array texture names a sub-range of the - // same image. Without these two fields those views would alias each other in the cache. - struct SampledImageViewKey { + struct SampledTextureSnapshot { + VkImageView imageView = VK_NULL_HANDLE; + VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; + }; + + Bool Initialize(const InitInfo& initInfo); + void Shutdown(); + void BeginFrame(Uint32 frameIndex); + // Submits the accumulated texture-upload batch (one command buffer, one + // vkQueueSubmit, one pooled fence) if any uploads are pending. MUST run + // before any other vkQueueSubmit on the shared graphics queue whose + // commands may consume an image the batch writes - the frame command + // buffer submit (mid-frame flush, readback, Present) and the + // preserve-on-recreate copy are the existing callers. No-op when the + // batch is empty. + void FlushPendingUploads(); + // Non-blocking idle proof for wire-object retirement. Independent texture + // submissions are not represented by VulkanRenderer's submit counter. + Bool WireUploadsAreIdle(); + + // Drains every frame slot's deferred image/view releases. Only valid when + // the caller has proven every queue submission complete; used by the + // present-less frame-boundary drain. + void CollectAllDeferredReleases(); + + // The window a GL texture object opens onto its storage image. For a plain texture this is + // the resource's own full extent; for a view it is the sub-range, format and aspect + // glTextureView gave it. Views built from a non-default window must live in the KEYED caches + // (attachmentViews / alternateSampledViews), never in the per-mip vectors, which belong to + // the storage texture's own defaults. + struct TextureViewWindow { Uint32 baseMipLevel = 0; Uint32 levelCount = 1; Uint32 baseArrayLayer = 0; Uint32 layerCount = 1; - VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; VkFormat format = VK_FORMAT_UNDEFINED; - VkImageAspectFlags aspect = VK_IMAGE_ASPECT_COLOR_BIT; - // GL_TEXTURE_SWIZZLE_* is per-texture state, so two views over one storage with the - // same window but different swizzles are different views. Baked into the key because - // a GL texture view's ONLY sampled view lives in this cache: unlike the storage - // texture's own sampledView, which SyncTextureViews rebuilds whenever the params - // version moves, nothing else would ever notice a swizzle change on a view. - Uint32 componentSwizzle = 0; - - Bool operator==(const SampledImageViewKey& other) const { - return baseMipLevel == other.baseMipLevel && - levelCount == other.levelCount && - baseArrayLayer == other.baseArrayLayer && - layerCount == other.layerCount && - viewType == other.viewType && - format == other.format && - aspect == other.aspect && - componentSwizzle == other.componentSwizzle; - } + VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; + VkImageAspectFlags sampledAspect = VK_IMAGE_ASPECT_COLOR_BIT; + VkComponentMapping components{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_A}; + Bool isTextureView = false; }; - struct SampledImageViewKeyHash { - SizeT operator()(const SampledImageViewKey& key) const { - SizeT hash = std::hash{}(key.baseMipLevel); - hash ^= std::hash{}(key.levelCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.viewType)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.format)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.aspect)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(key.componentSwizzle) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - return hash; - } + // P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport + // the apply thread may not name the client's ITextureObject (rule E), so the clear / blit / + // readback / mipmap verbs resolve their attachment textures from the wire handle the + // framebuffer or mip record carried: shape from the applier's resource record + // (MGPipeApplier().TextureResources), texels from the server's staged-texture store + // (P5c tx), pending-upload bookkeeping consumed from the same record. Returns null - never + // a frontend fallback - when the record or the shape does not resolve; the caller declines + // loudly, which is the honest answer a missing record deserves. + // + // The resource lives in m_wireTextureResources keyed by StagedTextureStore::KeyForHandle - + // keyed by the handle's {slot, gen}, so a recycled slot can never inherit its + // predecessor's image. Texture views resolve the storage owner from + // Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm. + Bool ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target, Uint32 level, + const IntVec3& extent, VkFormat& format, Vector& bytes); + TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false, + Bool requireStorage = false); + // Map a texture/view-relative subresource to its live storage owner. layerCount, when + // supplied, receives the number of accessible layers starting at the input layer. + // viewFormat is UNDEFINED for an ordinary texture, or the outer view's format. + // outsideWindow, when supplied, says WHY a null came back: true only when every record on + // the way was live and consistent and the (level, layer) simply is not there - past the + // owner's levels or layers, or past a view's own window. A dead record, a missing or stale + // view CSO and an unresolvable view format leave it false. + MG_Pipe::MGPipeHandle ResolveWireTextureStorage(MG_Pipe::MGPipeHandle handle, Uint32& level, Uint32& layer, + VkFormat* viewFormat = nullptr, Uint32* layerCount = nullptr, + Bool* outsideWindow = nullptr); + // Grows the live image of a handle-keyed resource to `requiredMipLevels`, carrying the + // existing levels' content across with an in-command-buffer copy. The caller + // (GenerateMipmap's record arm) has flushed every pending submission first, so the old + // image is GPU-idle and the copy may be recorded into `commandBuffer` directly; the old + // image goes to the deferred-release ring. False when the chain cannot grow (the resource + // stays as it was). + Bool GrowWireTextureMipChain(MG_Pipe::MGPipeHandle handle, Uint32 requiredMipLevels, + VkCommandBuffer commandBuffer); + // THE RAW E5B9G9R9 READ ROUTE (WireE5RawCopy.h). The renderer hands its kit over once + // ArmWireE5RawReads has armed the route; nullptr keeps every transfer read plain. + void SetWireE5RawCopyKit(const WireE5RawCopyKit* kit) { m_wireE5RawCopyKit = kit; } + // True when a transfer read of `resource` must go through the route: armed, and a + // single-sample E5B9G9R9 MUTABLE_FORMAT image that is not 1D. + Bool WireE5RawReadApplies(const TextureResource& resource) const; + // Records `region` of such an image (in GENERAL or SHADER_READ_ONLY_OPTIMAL) into a fresh + // R32_UINT twin, left in TRANSFER_SRC_OPTIMAL. On success `twin` owns the image and `objects` + // the views and the pool, and both must outlive the recording's submission. On failure + // nothing was recorded and everything created was destroyed. + Bool RecordWireE5RawTwin(VkCommandBuffer commandBuffer, const WireE5RawCopyRegion& region, + VkImageLayout sourceLayout, TextureResource& twin, WireE5RawCopyObjects& objects); + // Records that GPU writes superseded the staged CPU snapshot of this level. Layer + // ranges select cube faces; array and 3D levels share one staged level shadow. + void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel, Uint32 baseArrayLayer = 0, + Uint32 layerCount = 1); + + // SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md): a server shared image's + // AHardwareBuffer bound as a single-level 2D colour image on THIS session's device, on dedicated + // memory imported from the buffer. Android only (false with `why` elsewhere, or when the device + // did not take the AHardwareBuffer extension). The image is born UNDEFINED; whoever uses it + // acquires it from the foreign queue family first, and a writer releases it back after. + struct ImportedSharedImage { + VkImage image = VK_NULL_HANDLE; + VkDeviceMemory memory = VK_NULL_HANDLE; + VkFormat format = VK_FORMAT_UNDEFINED; + VkImageUsageFlags usage = 0; + VkFormatFeatureFlags features = 0; + Uint32 queueFamily = 0; }; + static Bool ImportSharedImage(void* nativeBuffer, Uint32 width, Uint32 height, ImportedSharedImage& out, + String& why); + static void DestroyImportedSharedImage(VkDevice device, ImportedSharedImage& image); + // The reading side's per-use hook: PrepareWireTextureResources notes every shared-image + // texture a draw or dispatch samples, and the renderer takes the list right after + // (VulkanRenderer::AcquireNotedSharedImages) to acquire each one ahead of the sampling. + void NoteSharedImageUse(const TextureResource& resource) { + if (std::find(m_sharedImageUses.begin(), m_sharedImageUses.end(), resource.sharedImageKey) == + m_sharedImageUses.end()) + m_sharedImageUses.push_back(resource.sharedImageKey); + } + Bool HasNotedSharedImageUses() const { return !m_sharedImageUses.empty(); } + void TakeNotedSharedImageUses(Vector& out) { + out.clear(); + std::swap(out, m_sharedImageUses); + } + // The live handle-keyed texture resource under `key`, or null. + TextureResource* FindWireTextureResource(Uint64 key) { + const auto it = m_wireTextureResources.find(key); + return it != m_wireTextureResources.end() && it->second.image != VK_NULL_HANDLE ? &it->second : nullptr; + } + // Recording-generation bookkeeping for the pre-pass command stream. The + // generation advances every time the frame command buffer (re)begins + // recording; a resource whose stamp does not match was not referenced by + // any command in the open recording, so its out-of-pass work may safely + // execute ahead of the whole recording (in the pre command buffer). + void AdvanceRecordingGeneration() { ++m_recordingGeneration; } + void StampResourceRecordingUse(TextureResource& resource) const { + resource.lastRecordingGeneration = m_recordingGeneration; + } + Bool WasTouchedThisRecording(const TextureResource& resource) const { + return resource.lastRecordingGeneration == m_recordingGeneration; + } + // A write is a use too. See TextureResource::lastRecordingWriteGeneration. + void StampResourceRecordingWrite(TextureResource& resource) const { + resource.lastRecordingGeneration = m_recordingGeneration; + resource.lastRecordingWriteGeneration = m_recordingGeneration; + } + // `depthStencilTextureMode` is the texture's GL_DEPTH_STENCIL_TEXTURE_MODE; it only decides + // anything for an image that carries both aspects. Defaulted so the call sites that have no + // texture in hand keep the depth-aspect answer they have always given. + static VkImageAspectFlags ResolveSampledImageViewAspectMask( + VkImageAspectFlags imageAspect, GLenum depthStencilTextureMode = GL_DEPTH_COMPONENT); + static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain); + static Bool AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat); + // Moves `image` to `newLayout` and writes the new layout back through `trackedLayout`. + // + // The barrier covers EVERY array layer of the image, and there is deliberately no layer + // parameter to say otherwise: layout here is tracked per IMAGE (one `TextureResource::layout`, + // or one caller-owned variable), so a barrier narrower than the image would leave the layers it + // skipped in the old layout while the tracker claims they moved. Every transfer against a + // framebuffer attachment above layer 0 - glReadPixels, glBlitFramebuffer, glCopyTexSubImage, + // glCopyImageSubData - then ran its copy on a layer no barrier had transitioned. + // + // The mip range IS a parameter, because mip levels really are transitioned piecewise (see + // UpdateTrackedImageLayoutAfterAttachmentWrite and the mipmap generation loops): those callers + // move the complement of the level they wrote so the whole image converges on one layout again. + // Nothing does, or can, do that per layer. + static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, + VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, + VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, + VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask, + Uint32 baseMipLevel = 0, Uint32 levelCount = 1); - struct StorageImageViewKeyHash { - SizeT operator()(const StorageImageViewKey& key) const { - SizeT hash = std::hash{}(key.mipLevel); - hash ^= std::hash{}(key.baseArrayLayer) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(key.layerCount) + 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.viewType)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - hash ^= std::hash{}(static_cast(key.format)) + - 0x9e3779b9u + (hash << 6) + (hash >> 2); - return hash; - } + SizeT CollectGarbage(); + + private: + // Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch(). + Uint64 m_textureImageEpoch = 1; + Uint32 m_pendingClearHold = 0; // PendingClearHold's depth + // See AdvanceRecordingGeneration. Starts above every resource's default + // stamp of 0 so a fresh resource counts as untouched. + Uint64 m_recordingGeneration = 1; + + static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format); + void DeferResourceRelease(TextureResource&& resource); + void CollectDeferredReleases(Uint32 frameIndex); + void DestroyDeferredReleases(); + // Frees the fence/command buffer/staging buffer of every in-flight texture + // upload whose fence has signaled (submission order = completion order on + // the single queue, so the scan stops at the first still-pending entry). + // waitAll blocks on every entry - Shutdown's drain. + void ReclaimCompletedUploads(Bool waitAll = false); + SizeT PruneDeadTextures(); + // The two halves of SyncTextureResourceByHandle: shape from the resource record's + // descriptor (create / recreate / compatibility), then the pending-upload walk against the + // server's staged store. UploadPendingWireLevels mutates the applier's record (consumed + // entries are dropped), which is why the record is a non-const reference. + Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, + Bool requireStorage); + Bool UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record, + TextureResource& resource); + // SyncWireTextureShape's arm for a record whose level 0 IS a shared image (SharedImageId): + // binds the imported buffer instead of allocating. Unresolved = the image is gone or cannot be + // imported here; `resource` is then untouched and the caller allocates as for any texture. + enum class SharedImageBind : Uint8 { + Bound, + Failed, + Unresolved }; + SharedImageBind SyncWireSharedImage(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, + Bool requireStorage); + // The handle-keyed twin of m_textureResources, keyed by StagedTextureStore::KeyForHandle. + // Node-based for the same reason m_renderbufferResources is: callers hold TextureResource* + // across map-touching calls. + std::unordered_map m_wireTextureResources; + // Shared-image textures sampled since the renderer last took the list (NoteSharedImageUse). + Vector m_sharedImageUses; + // Handles are per-kind; a renderbuffer and texture may have identical slot/gen. + std::unordered_map m_wireRenderbufferResources; - VkImage image = VK_NULL_HANDLE; - VmaAllocation allocation = nullptr; - VkImageView fullView = VK_NULL_HANDLE; - VkImageView sampledView = VK_NULL_HANDLE; - Vector perMipViews; - Vector perMipSampledViews; - UnorderedMap attachmentViews; - UnorderedMap alternateSampledViews; - UnorderedMap storageImageViews; - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; - VkExtent2D extent = {0, 0}; - Uint32 depth = 1; - Uint32 arrayLayers = 1; - Uint32 mipLevels = 1; - Uint32 sampledBaseMipLevel = 0; - Uint32 sampledLevelCount = 1; - VkFormat format = VK_FORMAT_UNDEFINED; - VkImageAspectFlags aspect = VK_IMAGE_ASPECT_NONE; - VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; - VkSampleCountFlagBits sampleCount = VK_SAMPLE_COUNT_1_BIT; - VkImageCreateFlags imageCreateFlags = 0; - // Usage the live image was created with. STORAGE is only requested for textures that - // have actually been bound to a GL image unit, because on Adreno a storage-capable - // image loses UBWC bandwidth compression; a later image binding upgrades the usage - // and recreates the image, so the resolved usage has to be part of the compatibility - // check that decides whether the existing image can be kept. - VkImageUsageFlags usageFlags = 0; - // True once this image was (re)resolved while the texture was already marked as an - // image-unit texture. Distinguishes "not upgraded yet" from "cannot be upgraded" - // (a format whose optimalTilingFeatures lack STORAGE_IMAGE never gains the bit), so - // NeedsStorageImagePreparation cannot ask for a recreate that will never happen. - Bool storageUsageResolved = false; - Uint16 syncedTextureParamsVersion = 0; - // Recording generation (VkTextureManager::GetRecordingGeneration) of the last - // command referencing this image that was recorded into the CURRENT frame - // command buffer. An image untouched by the open recording may have its - // out-of-pass work (deferred clears, sampled-layout transitions) recorded - // into the frame's PRE command buffer - which executes strictly before the - // frame's commands - instead of splitting the active render pass. - Uint64 lastRecordingGeneration = 0; - // The same, for the last recorded command that WRITES the image (a render pass it is an - // attachment of, a clear, a blit/copy into it, a mip generation, a storage-image binding). - // An upload into an image written this recording has to be ordered after that write - // (VulkanRenderer::OrderPendingUploadAfterRecording). - Uint64 lastRecordingWriteGeneration = 0; - // Snapshot of ITextureObject::GetContentVersion() at the last successful sync; - // lets SyncTexture skip the whole re-check/re-upload when content is unchanged. - Uint64 syncedContentVersion = 0; - // Snapshot of the defined mip-level count at the last sync. Folded into the early-out key - // as defense-in-depth: any path that grows the level set (which resizes the sampled view) - // busts the skip even if it failed to bump the content version. - Uint32 syncedMipLevelCount = 0; - // Snapshot of ITextureObject::GetShapeVersion() at the last successful sync. The content - // version alone does NOT cover a re-specification: glTexImage2D(..., nullptr) on an - // already-defined level changes its size or format and dirties no texel, so it moves the - // shape version and nothing else. Without this in the early-out key the image, its views - // and therefore imageSize() all keep answering with the texture's PREVIOUS shape. - Uint64 syncedShapeVersion = 0; - // The handle-keyed arm's (P5f fm) early-out key: the applier record's server-owned - // Serial at the last successful sync. 0 also means "this resource has only ever been - // synced by the frontend arm" - a wire record's Serial starts at 0 on a create and moves - // on every mutation, so the first handle sync of a fresh image always re-checks. - Uint64 syncedWireSerial = 0; - // SHARED IMAGES: level 0 is a server shared image (an imported AHardwareBuffer), not a - // VMA allocation. The resource owns the image and its dedicated imported memory, and - // holds the registry's reference so the buffer outlives every use (deferred releases - // included). `sharedImageAlphaOne`: an X-format image, whose alpha bytes are undefined. - VkDeviceMemory importedMemory = VK_NULL_HANDLE; - Uint64 sharedImageId = 0; - SharedPtr sharedImageOwner; - Bool sharedImageAlphaOne = false; - // The reading side's ownership of the image (VulkanRenderer::AcquireSharedImage): whether - // this device holds it now - acquired from the foreign family and not yet released back at - // the frame boundary - the writer's generation it last acquired, and the - // m_wireTextureResources key it lives under, by which the frame boundary finds it again. - Bool sharedImageHeld = false; - Uint64 sharedImageGeneration = 0; - Uint64 sharedImageKey = 0; - // A YUV shared image (NV12/P010) is never the texture's image: the texture keeps its own - // RGBA8 level 0, into which the renderer converts the image at its first use in each frame - // (WireYuvImage.cpp) - `yuvSource` being that conversion's import of the buffer, made on - // first use, and `yuvConvertedFrame` the frame it last ran in. `yuvOwner` holds the - // registry's image for as long as the texture names it. - Uint64 yuvImageId = 0; - SharedPtr yuvOwner; - SharedPtr yuvSource; - Uint64 yuvConvertedFrame = 0; - // P15: clears of this image still to run (WirePendingClear). Part of the image, so a - // respecify (which moves a new image in, below) drops them with the old one. - Vector pendingClears; + VkDevice m_device = VK_NULL_HANDLE; + VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; + VmaAllocator m_allocator = nullptr; + VkCommandPool m_commandPool = VK_NULL_HANDLE; + // Dedicated pool for the recycled upload-batch command buffers (see + // InitInfo::graphicsQueueFamilyIndex). + VkCommandPool m_uploadCommandPool = VK_NULL_HANDLE; + VkQueue m_graphicsQueue = VK_NULL_HANDLE; + Bool m_imageFormatListSupported = false; + const WireE5RawCopyKit* m_wireE5RawCopyKit = nullptr; + Uint32 m_currentFrameIndex = 0; - TextureResource() = default; - TextureResource(const TextureResource&) = delete; - TextureResource(TextureResource&& that) noexcept { - std::swap(this->image, that.image); - std::swap(this->allocation, that.allocation); - std::swap(this->fullView, that.fullView); - std::swap(this->sampledView, that.sampledView); - std::swap(this->perMipViews, that.perMipViews); - std::swap(this->perMipSampledViews, that.perMipSampledViews); - std::swap(this->attachmentViews, that.attachmentViews); - std::swap(this->alternateSampledViews, that.alternateSampledViews); - std::swap(this->storageImageViews, that.storageImageViews); - std::swap(this->layout, that.layout); - std::swap(this->extent, that.extent); - std::swap(this->depth, that.depth); - std::swap(this->arrayLayers, that.arrayLayers); - std::swap(this->mipLevels, that.mipLevels); - std::swap(this->sampledBaseMipLevel, that.sampledBaseMipLevel); - std::swap(this->sampledLevelCount, that.sampledLevelCount); - std::swap(this->format, that.format); - std::swap(this->aspect, that.aspect); - std::swap(this->viewType, that.viewType); - std::swap(this->sampleCount, that.sampleCount); - std::swap(this->imageCreateFlags, that.imageCreateFlags); - std::swap(this->usageFlags, that.usageFlags); - std::swap(this->storageUsageResolved, that.storageUsageResolved); - std::swap(this->syncedTextureParamsVersion, that.syncedTextureParamsVersion); - std::swap(this->lastRecordingGeneration, that.lastRecordingGeneration); - std::swap(this->lastRecordingWriteGeneration, that.lastRecordingWriteGeneration); - std::swap(this->syncedContentVersion, that.syncedContentVersion); - std::swap(this->syncedMipLevelCount, that.syncedMipLevelCount); - std::swap(this->syncedShapeVersion, that.syncedShapeVersion); - std::swap(this->syncedWireSerial, that.syncedWireSerial); - std::swap(this->importedMemory, that.importedMemory); - std::swap(this->sharedImageId, that.sharedImageId); - std::swap(this->sharedImageOwner, that.sharedImageOwner); - std::swap(this->sharedImageAlphaOne, that.sharedImageAlphaOne); - std::swap(this->sharedImageHeld, that.sharedImageHeld); - std::swap(this->sharedImageGeneration, that.sharedImageGeneration); - std::swap(this->sharedImageKey, that.sharedImageKey); - std::swap(this->yuvImageId, that.yuvImageId); - std::swap(this->yuvOwner, that.yuvOwner); - std::swap(this->yuvSource, that.yuvSource); - std::swap(this->yuvConvertedFrame, that.yuvConvertedFrame); - std::swap(this->pendingClears, that.pendingClears); - } + Uint8 m_gcCounter = 0; + // Frame-boundary GC gate: counts BeginFrame calls, not draws, so texture churn + // through non-draw paths (FBO clears, readbacks) still reaches the prune. + Uint32 m_gcFrameCounter = 0; + // SyncWireTextureShape defining the RGBA8 storage a YUV shared image is converted into. + Bool m_syncingYuvTextureStorage = false; + Uint64 m_resourceEraseEpoch = 1; + // P14: a direct-mapped front for the two wire resource maps, keyed on the handle key + // ({slot, gen}, never reused) and believed only at the erase epoch it was taken at - an + // unordered_map node never moves until it is erased, and every erase moves the epoch. + struct WireResourceLookup { + Uint64 key = 0; + Uint64 eraseEpoch = 0; + TextureResource* resource = nullptr; + }; + static constexpr SizeT kWireResourceLookupEntries = 256; + Array m_wireTextureLookup{}; + Array m_wireRenderbufferLookup{}; + // Formats whose mutable-image probe failed on this device; their images are created + // without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch. + std::unordered_set m_mutableFormatUnsupported; + // Formats whose 3D images refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT. Per format+usage, + // exactly like the mutable-format verdict above, so it is answered at image creation and + // remembered rather than probed once globally. + std::unordered_set m_2dArrayCompatibleUnsupported; + // Supported multisample counts per format, so repeat texture syncs do not + // re-query vkGetPhysicalDeviceImageFormatProperties. + std::unordered_map m_multisampleCountsByFormat; + Vector> m_deferredReleases; + Vector> m_deferredViewReleases; + // The raw E5B9G9R9 route's views and descriptor pools, on the same ring as the images they name. + Vector> m_deferredE5Releases; - void Reset() { - if (fullView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), fullView, nullptr); - } - if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), sampledView, nullptr); - } - for (const auto attachmentView : perMipViews) { - if (attachmentView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), attachmentView, nullptr); - } - } - for (const auto sampledView : perMipSampledViews) { - if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), sampledView, nullptr); - } - } - for (const auto& [_, attachmentView] : attachmentViews) { - if (attachmentView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), attachmentView, nullptr); - } - } - for (const auto& [_, sampledView] : alternateSampledViews) { - if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), sampledView, nullptr); - } - } - for (const auto& [_, storageImageView] : storageImageViews) { - if (storageImageView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device.Get(), storageImageView, nullptr); - } - } - if (image != VK_NULL_HANDLE && allocation != nullptr) { - vmaDestroyImage(s_allocator.Get(), image, allocation); - } - if (importedMemory != VK_NULL_HANDLE) { - if (image != VK_NULL_HANDLE) vkDestroyImage(s_device.Get(), image, nullptr); - vkFreeMemory(s_device.Get(), importedMemory, nullptr); - } - importedMemory = VK_NULL_HANDLE; - sharedImageId = 0; - sharedImageOwner.reset(); - sharedImageAlphaOne = false; - sharedImageHeld = false; - sharedImageGeneration = 0; - sharedImageKey = 0; - yuvImageId = 0; - yuvOwner.reset(); - yuvSource.reset(); - yuvConvertedFrame = 0; - fullView = VK_NULL_HANDLE; - sampledView = VK_NULL_HANDLE; - perMipViews.clear(); - perMipSampledViews.clear(); - attachmentViews.clear(); - alternateSampledViews.clear(); - storageImageViews.clear(); - image = VK_NULL_HANDLE; - allocation = nullptr; - layout = VK_IMAGE_LAYOUT_UNDEFINED; - extent = {0, 0}; - depth = 1; - arrayLayers = 1; - mipLevels = 1; - sampledBaseMipLevel = 0; - sampledLevelCount = 1; - format = VK_FORMAT_UNDEFINED; - aspect = VK_IMAGE_ASPECT_NONE; - viewType = VK_IMAGE_VIEW_TYPE_2D; - sampleCount = VK_SAMPLE_COUNT_1_BIT; - imageCreateFlags = 0; - usageFlags = 0; - storageUsageResolved = false; - syncedTextureParamsVersion = 0; - syncedContentVersion = 0; - syncedMipLevelCount = 0; - syncedShapeVersion = 0; - syncedWireSerial = 0; - } + // --- Batched upload machinery --- + // Uploads within a frame are recorded into ONE shared command buffer and + // submitted with ONE vkQueueSubmit at FlushPendingUploads (the renderer + // flushes before every frame-command-buffer submit). Staging memory comes + // from a pool of persistently-mapped, reusable blocks instead of a + // vmaCreateBuffer per upload. + struct UploadStagingBlock { + VkBuffer buffer = VK_NULL_HANDLE; + VmaAllocation allocation = nullptr; + Uint8* mapped = nullptr; // persistently mapped for the block's lifetime + VkDeviceSize capacity = 0; + VkDeviceSize cursor = 0; // bump cursor while the block backs the open batch + }; + // Opens the batch command buffer lazily (allocates/reuses + begins recording). + VkCommandBuffer EnsureUploadBatchOpen(); + // Bump-allocates `size` staging bytes for the open batch, growing onto a + // new/pooled block when the current one cannot fit. Returns the write + // pointer; outBuffer/outBaseOffset locate the space for copy commands. + Uint8* AcquireUploadStagingSpace(VkDeviceSize size, VkBuffer& outBuffer, VkDeviceSize& outBaseOffset); + void RecycleUploadStagingBlock(UploadStagingBlock&& block); + // Drops a recorded-but-unsubmitted batch on the floor. Shutdown only: the + // device is being torn down, so the lost texel data is unobservable. + void DiscardPendingUploadBatch(); + void DestroyUploadPools(); - ~TextureResource() { - Reset(); - } + Vector m_freeUploadStagingBlocks; + VkDeviceSize m_freeUploadStagingBytes = 0; + Vector m_freeUploadCommandBuffers; + Vector m_freeUploadFences; + Bool m_uploadBatchOpen = false; + VkCommandBuffer m_uploadBatchCommandBuffer = VK_NULL_HANDLE; + // Blocks whose staging bytes the open batch's copies reference (last = + // the block the bump cursor is currently allocating from). + Vector m_uploadBatchBlocks; + // Images the open batch writes; consulted for the rare re-upload-after- + // draw flush and by DeferResourceRelease (an unsubmitted command buffer + // referencing a deferred-released image would escape every fence-based + // destruction proof, so the batch is flushed before the image is parked). + Vector m_uploadBatchImages; + VkDeviceSize m_uploadBatchStagingBytes = 0; - static inline SessionLocal s_device; // per Magma session (MagmaSession.h) - static inline SessionLocal s_allocator; + // Texture uploads are submitted out-of-band but NOT waited on (waiting + // behind the queue serialized the CPU against the previous frame's GPU + // work every time an animated atlas re-uploaded). Each flushed batch's + // transients are parked here and RECYCLED (fence reset to the fence pool, + // command buffer reset to the CB pool, staging blocks back to the block + // pool) once the batch fence signals. + struct PendingUploadReclaim { + VkFence fence = VK_NULL_HANDLE; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + Vector stagingBlocks; + }; + Vector m_pendingUploadReclaims; }; - - struct SampledTextureSnapshot { - VkImageView imageView = VK_NULL_HANDLE; - VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED; - }; - - Bool Initialize(const InitInfo& initInfo); - void Shutdown(); - void BeginFrame(Uint32 frameIndex); - // Submits the accumulated texture-upload batch (one command buffer, one - // vkQueueSubmit, one pooled fence) if any uploads are pending. MUST run - // before any other vkQueueSubmit on the shared graphics queue whose - // commands may consume an image the batch writes - the frame command - // buffer submit (mid-frame flush, readback, Present) and the - // preserve-on-recreate copy are the existing callers. No-op when the - // batch is empty. - void FlushPendingUploads(); - // Non-blocking idle proof for wire-object retirement. Independent texture - // submissions are not represented by VulkanRenderer's submit counter. - Bool WireUploadsAreIdle(); - - // Drains every frame slot's deferred image/view releases. Only valid when - // the caller has proven every queue submission complete; used by the - // present-less frame-boundary drain. - void CollectAllDeferredReleases(); - - // The window a GL texture object opens onto its storage image. For a plain texture this is - // the resource's own full extent; for a view it is the sub-range, format and aspect - // glTextureView gave it. Views built from a non-default window must live in the KEYED caches - // (attachmentViews / alternateSampledViews), never in the per-mip vectors, which belong to - // the storage texture's own defaults. - struct TextureViewWindow { - Uint32 baseMipLevel = 0; - Uint32 levelCount = 1; - Uint32 baseArrayLayer = 0; - Uint32 layerCount = 1; - VkFormat format = VK_FORMAT_UNDEFINED; - VkImageViewType viewType = VK_IMAGE_VIEW_TYPE_2D; - VkImageAspectFlags sampledAspect = VK_IMAGE_ASPECT_COLOR_BIT; - VkComponentMapping components{VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, - VK_COMPONENT_SWIZZLE_A}; - Bool isTextureView = false; - }; - - // P5f (fm): THE HANDLE-KEYED ARM of SyncTextureAndGetDescriptor. Under an active transport - // the apply thread may not name the client's ITextureObject (rule E), so the clear / blit / - // readback / mipmap verbs resolve their attachment textures from the wire handle the - // framebuffer or mip record carried: shape from the applier's resource record - // (MGPipeApplier().TextureResources), texels from the server's staged-texture store - // (P5c tx), pending-upload bookkeeping consumed from the same record. Returns null - never - // a frontend fallback - when the record or the shape does not resolve; the caller declines - // loudly, which is the honest answer a missing record deserves. - // - // The resource lives in m_wireTextureResources keyed by StagedTextureStore::KeyForHandle - - // keyed by the handle's {slot, gen}, so a recycled slot can never inherit its - // predecessor's image. Texture views resolve the storage owner from - // Desc.ViewOf and the window from ViewCso; buffer-backed textures remain P7's buffer arm. - Bool ReadUnbackedWireLevel(MG_Pipe::MGPipeHandle handle, TextureUploadTarget target, Uint32 level, - const IntVec3& extent, VkFormat& format, Vector& bytes); - TextureResource* SyncTextureResourceByHandle(MG_Pipe::MGPipeHandle handle, Bool renderbuffer = false, - Bool requireStorage = false); - // Map a texture/view-relative subresource to its live storage owner. layerCount, when - // supplied, receives the number of accessible layers starting at the input layer. - // viewFormat is UNDEFINED for an ordinary texture, or the outer view's format. - // outsideWindow, when supplied, says WHY a null came back: true only when every record on - // the way was live and consistent and the (level, layer) simply is not there - past the - // owner's levels or layers, or past a view's own window. A dead record, a missing or stale - // view CSO and an unresolvable view format leave it false. - MG_Pipe::MGPipeHandle ResolveWireTextureStorage(MG_Pipe::MGPipeHandle handle, Uint32& level, - Uint32& layer, VkFormat* viewFormat = nullptr, Uint32* layerCount = nullptr, - Bool* outsideWindow = nullptr); - // Grows the live image of a handle-keyed resource to `requiredMipLevels`, carrying the - // existing levels' content across with an in-command-buffer copy. The caller - // (GenerateMipmap's record arm) has flushed every pending submission first, so the old - // image is GPU-idle and the copy may be recorded into `commandBuffer` directly; the old - // image goes to the deferred-release ring. False when the chain cannot grow (the resource - // stays as it was). - Bool GrowWireTextureMipChain(MG_Pipe::MGPipeHandle handle, Uint32 requiredMipLevels, - VkCommandBuffer commandBuffer); - // THE RAW E5B9G9R9 READ ROUTE (WireE5RawCopy.h). The renderer hands its kit over once - // ArmWireE5RawReads has armed the route; nullptr keeps every transfer read plain. - void SetWireE5RawCopyKit(const WireE5RawCopyKit* kit) { m_wireE5RawCopyKit = kit; } - // True when a transfer read of `resource` must go through the route: armed, and a - // single-sample E5B9G9R9 MUTABLE_FORMAT image that is not 1D. - Bool WireE5RawReadApplies(const TextureResource& resource) const; - // Records `region` of such an image (in GENERAL or SHADER_READ_ONLY_OPTIMAL) into a fresh - // R32_UINT twin, left in TRANSFER_SRC_OPTIMAL. On success `twin` owns the image and `objects` - // the views and the pool, and both must outlive the recording's submission. On failure - // nothing was recorded and everything created was destroyed. - Bool RecordWireE5RawTwin(VkCommandBuffer commandBuffer, const WireE5RawCopyRegion& region, - VkImageLayout sourceLayout, TextureResource& twin, WireE5RawCopyObjects& objects); - // Records that GPU writes superseded the staged CPU snapshot of this level. Layer - // ranges select cube faces; array and 3D levels share one staged level shadow. - void MarkWireTextureGpuWritten(MG_Pipe::MGPipeHandle handle, Uint32 mipLevel, - Uint32 baseArrayLayer = 0, Uint32 layerCount = 1); - - // SHARED IMAGES (docs/archive/notes/anland/plan-ahb-dmabuf.md): a server shared image's - // AHardwareBuffer bound as a single-level 2D colour image on THIS session's device, on dedicated - // memory imported from the buffer. Android only (false with `why` elsewhere, or when the device - // did not take the AHardwareBuffer extension). The image is born UNDEFINED; whoever uses it - // acquires it from the foreign queue family first, and a writer releases it back after. - struct ImportedSharedImage { - VkImage image = VK_NULL_HANDLE; - VkDeviceMemory memory = VK_NULL_HANDLE; - VkFormat format = VK_FORMAT_UNDEFINED; - VkImageUsageFlags usage = 0; - VkFormatFeatureFlags features = 0; - Uint32 queueFamily = 0; - }; - static Bool ImportSharedImage(void* nativeBuffer, Uint32 width, Uint32 height, ImportedSharedImage& out, - String& why); - static void DestroyImportedSharedImage(VkDevice device, ImportedSharedImage& image); - // The reading side's per-use hook: PrepareWireTextureResources notes every shared-image - // texture a draw or dispatch samples, and the renderer takes the list right after - // (VulkanRenderer::AcquireNotedSharedImages) to acquire each one ahead of the sampling. - void NoteSharedImageUse(const TextureResource& resource) { - if (std::find(m_sharedImageUses.begin(), m_sharedImageUses.end(), resource.sharedImageKey) == - m_sharedImageUses.end()) - m_sharedImageUses.push_back(resource.sharedImageKey); - } - Bool HasNotedSharedImageUses() const { return !m_sharedImageUses.empty(); } - void TakeNotedSharedImageUses(Vector& out) { - out.clear(); - std::swap(out, m_sharedImageUses); - } - // The live handle-keyed texture resource under `key`, or null. - TextureResource* FindWireTextureResource(Uint64 key) { - const auto it = m_wireTextureResources.find(key); - return it != m_wireTextureResources.end() && it->second.image != VK_NULL_HANDLE ? &it->second : nullptr; - } - // Recording-generation bookkeeping for the pre-pass command stream. The - // generation advances every time the frame command buffer (re)begins - // recording; a resource whose stamp does not match was not referenced by - // any command in the open recording, so its out-of-pass work may safely - // execute ahead of the whole recording (in the pre command buffer). - void AdvanceRecordingGeneration() { ++m_recordingGeneration; } - void StampResourceRecordingUse(TextureResource& resource) const { - resource.lastRecordingGeneration = m_recordingGeneration; - } - Bool WasTouchedThisRecording(const TextureResource& resource) const { - return resource.lastRecordingGeneration == m_recordingGeneration; - } - // A write is a use too. See TextureResource::lastRecordingWriteGeneration. - void StampResourceRecordingWrite(TextureResource& resource) const { - resource.lastRecordingGeneration = m_recordingGeneration; - resource.lastRecordingWriteGeneration = m_recordingGeneration; - } - // `depthStencilTextureMode` is the texture's GL_DEPTH_STENCIL_TEXTURE_MODE; it only decides - // anything for an image that carries both aspects. Defaulted so the call sites that have no - // texture in hand keep the depth-aspect answer they have always given. - static VkImageAspectFlags ResolveSampledImageViewAspectMask(VkImageAspectFlags imageAspect, - GLenum depthStencilTextureMode = GL_DEPTH_COMPONENT); - static VkFormat ResolveSampledImageViewFormat(VkFormat imageFormat, SamplerNumericDomain numericDomain); - static Bool AreSampledImageViewFormatsCompatible(VkFormat imageFormat, VkFormat viewFormat); - // Moves `image` to `newLayout` and writes the new layout back through `trackedLayout`. - // - // The barrier covers EVERY array layer of the image, and there is deliberately no layer - // parameter to say otherwise: layout here is tracked per IMAGE (one `TextureResource::layout`, - // or one caller-owned variable), so a barrier narrower than the image would leave the layers it - // skipped in the old layout while the tracker claims they moved. Every transfer against a - // framebuffer attachment above layer 0 - glReadPixels, glBlitFramebuffer, glCopyTexSubImage, - // glCopyImageSubData - then ran its copy on a layer no barrier had transitioned. - // - // The mip range IS a parameter, because mip levels really are transitioned piecewise (see - // UpdateTrackedImageLayoutAfterAttachmentWrite and the mipmap generation loops): those callers - // move the complement of the level they wrote so the whole image converges on one layout again. - // Nothing does, or can, do that per layer. - static Bool TransitionImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout& trackedLayout, - VkImageLayout newLayout, VkPipelineStageFlags srcStageMask, - VkPipelineStageFlags dstStageMask, VkAccessFlags srcAccessMask, - VkAccessFlags dstAccessMask, VkImageAspectFlags aspectMask, - Uint32 baseMipLevel = 0, Uint32 levelCount = 1); - - SizeT CollectGarbage(); - -private: - // Bumped in SyncTextureResource right after vmaCreateImage(texture). See GetTextureImageEpoch(). - Uint64 m_textureImageEpoch = 1; - Uint32 m_pendingClearHold = 0; // PendingClearHold's depth - // See AdvanceRecordingGeneration. Starts above every resource's default - // stamp of 0 so a fresh resource counts as untouched. - Uint64 m_recordingGeneration = 1; - - static VkImageAspectFlags GetAspectMaskForFormat(VkFormat format); - void DeferResourceRelease(TextureResource&& resource); - void CollectDeferredReleases(Uint32 frameIndex); - void DestroyDeferredReleases(); - // Frees the fence/command buffer/staging buffer of every in-flight texture - // upload whose fence has signaled (submission order = completion order on - // the single queue, so the scan stops at the first still-pending entry). - // waitAll blocks on every entry - Shutdown's drain. - void ReclaimCompletedUploads(Bool waitAll = false); - SizeT PruneDeadTextures(); - // The two halves of SyncTextureResourceByHandle: shape from the resource record's - // descriptor (create / recreate / compatibility), then the pending-upload walk against the - // server's staged store. UploadPendingWireLevels mutates the applier's record (consumed - // entries are dropped), which is why the record is a non-const reference. - Bool SyncWireTextureShape(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, - Bool requireStorage); - Bool UploadPendingWireLevels(MG_Pipe::MGPipeHandle handle, MG_Pipe::MGPipeResourceRecord& record, - TextureResource& resource); - // SyncWireTextureShape's arm for a record whose level 0 IS a shared image (SharedImageId): - // binds the imported buffer instead of allocating. Unresolved = the image is gone or cannot be - // imported here; `resource` is then untouched and the caller allocates as for any texture. - enum class SharedImageBind : Uint8 { Bound, Failed, Unresolved }; - SharedImageBind SyncWireSharedImage(const MG_Pipe::MGPipeResourceRecord& record, TextureResource& resource, - Bool requireStorage); - // The handle-keyed twin of m_textureResources, keyed by StagedTextureStore::KeyForHandle. - // Node-based for the same reason m_renderbufferResources is: callers hold TextureResource* - // across map-touching calls. - std::unordered_map m_wireTextureResources; - // Shared-image textures sampled since the renderer last took the list (NoteSharedImageUse). - Vector m_sharedImageUses; - // Handles are per-kind; a renderbuffer and texture may have identical slot/gen. - std::unordered_map m_wireRenderbufferResources; - - VkDevice m_device = VK_NULL_HANDLE; - VkPhysicalDevice m_physicalDevice = VK_NULL_HANDLE; - VmaAllocator m_allocator = nullptr; - VkCommandPool m_commandPool = VK_NULL_HANDLE; - // Dedicated pool for the recycled upload-batch command buffers (see - // InitInfo::graphicsQueueFamilyIndex). - VkCommandPool m_uploadCommandPool = VK_NULL_HANDLE; - VkQueue m_graphicsQueue = VK_NULL_HANDLE; - Bool m_imageFormatListSupported = false; - const WireE5RawCopyKit* m_wireE5RawCopyKit = nullptr; - Uint32 m_currentFrameIndex = 0; - - Uint8 m_gcCounter = 0; - // Frame-boundary GC gate: counts BeginFrame calls, not draws, so texture churn - // through non-draw paths (FBO clears, readbacks) still reaches the prune. - Uint32 m_gcFrameCounter = 0; - // SyncWireTextureShape defining the RGBA8 storage a YUV shared image is converted into. - Bool m_syncingYuvTextureStorage = false; - Uint64 m_resourceEraseEpoch = 1; - // P14: a direct-mapped front for the two wire resource maps, keyed on the handle key - // ({slot, gen}, never reused) and believed only at the erase epoch it was taken at - an - // unordered_map node never moves until it is erased, and every erase moves the epoch. - struct WireResourceLookup { - Uint64 key = 0; - Uint64 eraseEpoch = 0; - TextureResource* resource = nullptr; - }; - static constexpr SizeT kWireResourceLookupEntries = 256; - Array m_wireTextureLookup{}; - Array m_wireRenderbufferLookup{}; - // Formats whose mutable-image probe failed on this device; their images are created - // without MUTABLE_FORMAT_BIT so repeat syncs neither re-probe nor flag-mismatch. - std::unordered_set m_mutableFormatUnsupported; - // Formats whose 3D images refused VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT. Per format+usage, - // exactly like the mutable-format verdict above, so it is answered at image creation and - // remembered rather than probed once globally. - std::unordered_set m_2dArrayCompatibleUnsupported; - // Supported multisample counts per format, so repeat texture syncs do not - // re-query vkGetPhysicalDeviceImageFormatProperties. - std::unordered_map m_multisampleCountsByFormat; - Vector> m_deferredReleases; - Vector> m_deferredViewReleases; - // The raw E5B9G9R9 route's views and descriptor pools, on the same ring as the images they name. - Vector> m_deferredE5Releases; - - // --- Batched upload machinery --- - // Uploads within a frame are recorded into ONE shared command buffer and - // submitted with ONE vkQueueSubmit at FlushPendingUploads (the renderer - // flushes before every frame-command-buffer submit). Staging memory comes - // from a pool of persistently-mapped, reusable blocks instead of a - // vmaCreateBuffer per upload. - struct UploadStagingBlock { - VkBuffer buffer = VK_NULL_HANDLE; - VmaAllocation allocation = nullptr; - Uint8* mapped = nullptr; // persistently mapped for the block's lifetime - VkDeviceSize capacity = 0; - VkDeviceSize cursor = 0; // bump cursor while the block backs the open batch - }; - // Opens the batch command buffer lazily (allocates/reuses + begins recording). - VkCommandBuffer EnsureUploadBatchOpen(); - // Bump-allocates `size` staging bytes for the open batch, growing onto a - // new/pooled block when the current one cannot fit. Returns the write - // pointer; outBuffer/outBaseOffset locate the space for copy commands. - Uint8* AcquireUploadStagingSpace(VkDeviceSize size, VkBuffer& outBuffer, VkDeviceSize& outBaseOffset); - void RecycleUploadStagingBlock(UploadStagingBlock&& block); - // Drops a recorded-but-unsubmitted batch on the floor. Shutdown only: the - // device is being torn down, so the lost texel data is unobservable. - void DiscardPendingUploadBatch(); - void DestroyUploadPools(); - - Vector m_freeUploadStagingBlocks; - VkDeviceSize m_freeUploadStagingBytes = 0; - Vector m_freeUploadCommandBuffers; - Vector m_freeUploadFences; - Bool m_uploadBatchOpen = false; - VkCommandBuffer m_uploadBatchCommandBuffer = VK_NULL_HANDLE; - // Blocks whose staging bytes the open batch's copies reference (last = - // the block the bump cursor is currently allocating from). - Vector m_uploadBatchBlocks; - // Images the open batch writes; consulted for the rare re-upload-after- - // draw flush and by DeferResourceRelease (an unsubmitted command buffer - // referencing a deferred-released image would escape every fence-based - // destruction proof, so the batch is flushed before the image is parked). - Vector m_uploadBatchImages; - VkDeviceSize m_uploadBatchStagingBytes = 0; - - // Texture uploads are submitted out-of-band but NOT waited on (waiting - // behind the queue serialized the CPU against the previous frame's GPU - // work every time an animated atlas re-uploaded). Each flushed batch's - // transients are parked here and RECYCLED (fence reset to the fence pool, - // command buffer reset to the CB pool, staging blocks back to the block - // pool) once the batch fence signals. - struct PendingUploadReclaim { - VkFence fence = VK_NULL_HANDLE; - VkCommandBuffer commandBuffer = VK_NULL_HANDLE; - Vector stagingBlocks; - }; - Vector m_pendingUploadReclaims; -}; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp index b812e89d..857d8514 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.cpp @@ -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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.h index 68964ee9..02693852 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTimerQueryManager.h @@ -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 WriteTimestamp(VkCommandBuffer commandBuffer, Uint32 frameIndex, - Uint64 frameSerial); + SharedPtr 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 39d589aa..e7f1b1af 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -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(capsResult), requestedFramesInFlight); + "keeping requested %u", + static_cast(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(); 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(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(m_physicalDevice.queueFamilies.graphicsFamily)}); MOBILEGL_ASSERT(succeeded, "VkTextureManager initialization failed."); m_textureManager->SetWireE5RawCopyKit(GetWireE5RawCopyKit()); - m_renderPassManager = - MakeUnique(m_device, m_physicalDevice.handle, m_allocator, m_config, - *m_textureManager, m_swapchainObject); + m_renderPassManager = MakeUnique(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(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( + 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(m_config, m_physicalDevice.handle); + m_vertexInputStateFactory = MakeUnique(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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index ab17c39b..ee7925cc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -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 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 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 aspects, const DrawCmdParam& drawParams, const IndexBufferView* indices); @@ -996,12 +978,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { UniquePtr m_wireDrawPass; Vector 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& outSlots, Vector& outRerouteSlots); - Bool ResolveXfbQueryResult(const Vector& slots, const Vector& rerouteSlots, - Bool wantGenerated, Uint64& outPrimitives); + Bool ResolveXfbQueryResult(const Vector& slots, const Vector& 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 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 diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererCopy.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererCopy.cpp index 6d6fefa1..31c58df2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererCopy.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererCopy.cpp @@ -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(source[0]) / 255.0f; - rgba[1] = static_cast(source[1]) / 255.0f; - rgba[2] = static_cast(source[2]) / 255.0f; - rgba[3] = static_cast(source[3]) / 255.0f; - return true; - case VK_FORMAT_B8G8R8A8_UNORM: - case VK_FORMAT_B8G8R8A8_SRGB: - rgba[0] = static_cast(source[2]) / 255.0f; - rgba[1] = static_cast(source[1]) / 255.0f; - rgba[2] = static_cast(source[0]) / 255.0f; - rgba[3] = static_cast(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(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(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(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(source[0]) / 255.0f; + rgba[1] = static_cast(source[1]) / 255.0f; + rgba[2] = static_cast(source[2]) / 255.0f; + rgba[3] = static_cast(source[3]) / 255.0f; + return true; + case VK_FORMAT_B8G8R8A8_UNORM: + case VK_FORMAT_B8G8R8A8_SRGB: + rgba[0] = static_cast(source[2]) / 255.0f; + rgba[1] = static_cast(source[1]) / 255.0f; + rgba[2] = static_cast(source[0]) / 255.0f; + rgba[3] = static_cast(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(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(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(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(mappedX) + mappedWidth > imageExtent.width || static_cast(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(outY) * logicalWidth + outX) * texelSize, - rawPixels + (static_cast(srcY) * rawWidth + srcX) * texelSize, - texelSize); + rawPixels + (static_cast(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(row) * static_cast(width) * sourceTexelSize; + const Uint8* sourceRow = + sourcePixels + static_cast(row) * static_cast(width) * sourceTexelSize; Uint8* destinationRow = destinationPixels + static_cast(row) * destinationRowStride; for (GLsizei column = 0; column < width; ++column) { const Uint8* source = sourceRow + static_cast(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(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(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(mip), mappedLayer = static_cast(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(mappedMip); layer = static_cast(mappedLayer); + mip = static_cast(mappedMip); + layer = static_cast(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(srcLevel); const Uint32 dstMipLevel = static_cast(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(srcX + srcWidth) > srcMipWidth || + if (srcX < 0 || srcY < 0 || dstX < 0 || dstY < 0 || static_cast(srcX + srcWidth) > srcMipWidth || static_cast(srcY + srcHeight) > srcMipHeight || static_cast(dstX + srcWidth) > dstMipWidth || static_cast(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(slice); - else one.srcSubresource = {copyRegion.srcSubresource.aspectMask, copyRegion.srcSubresource.mipLevel, - copyRegion.srcSubresource.baseArrayLayer + slice, 1}; - if (dstSlices.slicesAreDepth) one.dstOffset.z += static_cast(slice); - else one.dstSubresource = {copyRegion.dstSubresource.aspectMask, copyRegion.dstSubresource.mipLevel, - copyRegion.dstSubresource.baseArrayLayer + slice, 1}; + if (srcSlices.slicesAreDepth) + one.srcOffset.z += static_cast(slice); + else + one.srcSubresource = {copyRegion.srcSubresource.aspectMask, copyRegion.srcSubresource.mipLevel, + copyRegion.srcSubresource.baseArrayLayer + slice, 1}; + if (dstSlices.slicesAreDepth) + one.dstOffset.z += static_cast(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(readbackFences.size()), - readbackFences.data(), VK_TRUE, UINT64_MAX); + VkResult result = vkWaitForFences(m_device, static_cast(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(srcFormat), static_cast(dstFormat)); + static_cast(srcFormat), static_cast(dstFormat)); return false; } const SizeT pixelCount = static_cast(width) * static_cast(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(vkFormat)); + static_cast(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(width), - static_cast(height), preTransform, - depthCopyBytes, remappedDepth.data()); + remapped = + RemapDefaultFramebufferReadback(depthSrc, static_cast(width), static_cast(height), + preTransform, depthCopyBytes, remappedDepth.data()); } if (remapped && wantStencil) { remappedStencil.resize(pixelCount); - remapped = RemapDefaultFramebufferReadback(stencilSrc, static_cast(width), - static_cast(height), preTransform, 1, - remappedStencil.data()); + remapped = + RemapDefaultFramebufferReadback(stencilSrc, static_cast(width), static_cast(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 wireReplyHasNoPackBuffer; - const auto& pixelPackBufferObject = - wireReplyHasNoPackBuffer; + const auto& pixelPackBufferObject = wireReplyHasNoPackBuffer; const auto packParams = MG_Pipe::gPipeInputs->GetPixelStoreParameters(false); const SizeT rowPixels = static_cast(packParams.RowLength > 0 ? packParams.RowLength : width); const SizeT packAlignment = packParams.Alignment > 0 ? static_cast(packParams.Alignment) : 1; const SizeT dstRowStride = ((rowPixels * dstPixelBytes) + packAlignment - 1) / packAlignment * packAlignment; const SizeT dstSkipOffset = static_cast(std::max(packParams.SkipRows, 0)) * dstRowStride + - static_cast(std::max(packParams.SkipPixels, 0)) * dstPixelBytes; + static_cast(std::max(packParams.SkipPixels, 0)) * dstPixelBytes; const SizeT dstRowBytes = static_cast(width) * dstPixelBytes; const SizeT pboBaseOffset = reinterpret_cast(pixels); if (pixelPackBufferObject != nullptr) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDevice.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDevice.cpp index fb8ed76e..5efd01be 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDevice.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDevice.cpp @@ -49,8 +49,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { CGRect frame = {{0.0, 0.0}, {surfaceWidth, surfaceHeight}}; id window = SendId(windowClass, "alloc"); window = ObjcMsgSend()( - 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( - vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphoreProperties")); + auto getExternalSemaphoreProperties = + reinterpret_cast( + vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphoreProperties")); if (getExternalSemaphoreProperties == nullptr) { - getExternalSemaphoreProperties = reinterpret_cast( - vkGetInstanceProcAddr(m_instance, "vkGetPhysicalDeviceExternalSemaphorePropertiesKHR")); + getExternalSemaphoreProperties = + reinterpret_cast( + 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(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(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(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(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( vkGetDeviceProcAddr(m_device, "vkCmdBeginQueryIndexedEXT")); - s_vkCmdEndQueryIndexedEXT = reinterpret_cast( - vkGetDeviceProcAddr(m_device, "vkCmdEndQueryIndexedEXT")); + s_vkCmdEndQueryIndexedEXT = + reinterpret_cast(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(m_physicalDevice.queueFamilies.graphicsFamily); + probeContext.queueFamilyIndex = static_cast(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(shape.streamGenerated), + name, shape.drawn ? 1 : 0, static_cast(shape.streamGenerated), static_cast(shape.expectedPrimitives), static_cast(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( - vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT")) - : nullptr; + auto* createHeadlessSurface = m_headlessSurfaceSupported + ? reinterpret_cast( + 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( - vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT")); + auto* createHeadlessSurface = reinterpret_cast( + 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( - vkGetInstanceProcAddr(m_instance, "vkCreateHeadlessSurfaceEXT")); + auto* createHeadlessSurface = reinterpret_cast( + 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& 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); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDraw.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDraw.cpp index 42b567a8..6439958d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDraw.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererDraw.cpp @@ -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(counterSlot) * 16 + static_cast(i) * 4; } if (resume) { - s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast(bufferCount), - counterBuffers, counterOffsets); + s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast(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& outSlots, - Vector& outRerouteSlots) { + void VulkanRenderer::StopXfbQueryCapture(Uint32 kind, Vector& outSlots, Vector& 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(payload.params.vertexOffset) - : 0); + vertexRange.vertexCount = + payload.params.indexCount + + (payload.params.vertexOffset > 0 ? static_cast(payload.params.vertexOffset) : 0); vertexRange.instanceCount = payload.params.instanceCount; vertexRange.firstVertex = 0; vertexRange.firstInstance = static_cast(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(drawCount) * sizeof(DrawCmdParam), - sizeof(Uint32), commandSlice)) { + if (m_bufferManager.UploadTransient( + BufferKind::Indirect, m_frameContext.GetCurrentFrameIndex(), pParams, + static_cast(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(base) * sizeof(DrawCmdParam), + commandSlice.offset + static_cast(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(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(drawCount) * - sizeof(DrawIndexedCmdParam), + static_cast(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(payload.pParams[idraw].firstInstance)); + vertexRange.firstInstance = + std::max(vertexRange.firstInstance, static_cast(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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererInternal.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererInternal.h index b79d61cb..ec6abe24 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererInternal.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererInternal.h @@ -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(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(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(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(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(std::llround(std::clamp(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( - std::llround(std::clamp(d, 0.0, 1.0) * static_cast(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(std::llround(std::clamp(d, 0.0, 1.0) * + static_cast(maximum))); + Memcpy(dst, &value, size); + break; } } } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPipeline.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPipeline.cpp index 2cdc3aae..f70887c6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPipeline.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPipeline.cpp @@ -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(std::lround(stored.x())), - static_cast(std::lround(stored.y())), - static_cast(std::lround(stored.z())), - static_cast(std::lround(stored.w()))); + const IntVec4 viewportState( + static_cast(std::lround(stored.x())), static_cast(std::lround(stored.y())), + static_cast(std::lround(stored.z())), static_cast(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(stage.stage)); + program.GetExternalIndex(), static_cast(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(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(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(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(attribute.format), - static_cast(shaderInputType), + attribute.location, static_cast(attribute.format), static_cast(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(attribute.format), - static_cast(patchedFormat), - static_cast(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(attribute.format), static_cast(patchedFormat), + static_cast(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 drawBuffers{}; for (SizeT i = 0; i < drawBuffers.size(); ++i) - drawBuffers[i] = wireFbo->DrawBuffers[i] < 0 ? FramebufferAttachmentType::None : - static_cast(static_cast(FramebufferAttachmentType::Color0) + wireFbo->DrawBuffers[i]); + drawBuffers[i] = wireFbo->DrawBuffers[i] < 0 + ? FramebufferAttachmentType::None + : static_cast( + static_cast(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( - (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(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(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(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(colorAttachmentFormat)); + "depth-write quirk cannot apply to it; depth-equality chains " + "accumulating into this format may flicker", + static_cast(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 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; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp index a8af1d3d..d0e50727 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererPresent.cpp @@ -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(m_swapchainObject.GetPreTransform()), + MGLOG_D("Swapchain out of date: surface %ux%u transform %u -> %ux%u transform %u", builtFrom.width, + builtFrom.height, static_cast(m_swapchainObject.GetPreTransform()), surfaceCaps.currentExtent.width, surfaceCaps.currentExtent.height, static_cast(surfaceCaps.currentTransform)); return true; @@ -529,8 +528,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_device, static_cast(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; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererSubmit.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererSubmit.cpp index 007e366b..7f42de7b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererSubmit.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRendererSubmit.cpp @@ -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(through), - UniformManager::kWireDescriptorSetBudget, cachedSets); + MGLOG_I("Magma descriptor rewind: frame=%u retiredSubmit=%llu budget=%u cached=%zu", frameIndex, + static_cast(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(std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count()); + const Uint64 now = static_cast( + std::chrono::duration_cast(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 fences; - if (!CollectSubmitFencePrefix(m_inFlightSubmits, submitIndex, VkFence{}, fences)) - return false; - const VkResult result = vkWaitForFences(m_device, static_cast(fences.size()), - fences.data(), VK_TRUE, timeoutNs); + if (!CollectSubmitFencePrefix(m_inFlightSubmits, submitIndex, VkFence{}, fences)) return false; + const VkResult result = + vkWaitForFences(m_device, static_cast(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(submitIndex), - static_cast(advanceTo), m_inFlightSubmits.size(), + static_cast(submitIndex), static_cast(advanceTo), + m_inFlightSubmits.size(), m_inFlightSubmits.empty() ? -1LL : static_cast(minRemaining), static_cast(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(m_progressMarkers.HungRunningMs()), - site != nullptr ? site : "?"); + static_cast(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()) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.cpp index d7a94f4b..6e0d7173 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.cpp @@ -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(sx1) - sx0); const Int64 height = std::abs(static_cast(sy1) - sy0); if (!width || !height || dx0 == dx1 || dy0 == dy1) return true; if (std::min(sx0, sx1) < 0 || std::min(sy0, sy1) < 0 || static_cast(std::max(sx0, sx1)) > source.extent.width || - static_cast(std::max(sy0, sy1)) > source.extent.height) return false; + static_cast(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(source.layer) : 0}; + copy.srcOffset = {std::min(sx0, sx1), std::min(sy0, sy1), source.is3D ? static_cast(source.layer) : 0}; copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; copy.dstOffset = {0, 0, 0}; copy.extent = {static_cast(width), static_cast(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(width) : 0; const GLint ry0 = sy1 > sy0 ? 0 : static_cast(height); const GLint ry1 = sy1 > sy0 ? static_cast(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(source.extent.width), sh = static_cast(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(quarterTurn ? destination.extent.height : destination.extent.width); const float dh = static_cast(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(destination.extent.height) - dy0; dy1 = static_cast(destination.extent.height) - dy1; } - const Constants constants{{sx0 / sw, sy0 / sh, (static_cast(sx1) - sx0) / sw, - (static_cast(sy1) - sy0) / sh}, - {dx0 / dw, dy0 / dh, (static_cast(dx1) - dx0) / dw, - (static_cast(dy1) - dy0) / dh}, - static_cast(ToBlitSurfaceTransform(transform))}; + const Constants constants{ + {sx0 / sw, sy0 / sh, (static_cast(sx1) - sx0) / sw, (static_cast(sy1) - sy0) / sh}, + {dx0 / dw, dy0 / dh, (static_cast(dx1) - dx0) / dw, (static_cast(dy1) - dy0) / dh}, + static_cast(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(destination.extent.width), - static_cast(destination.extent.height), 0.0f, 1.0f}; + const VkViewport viewport{ + 0.0f, 0.0f, static_cast(destination.extent.width), static_cast(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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitFilter.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitFilter.h index fa4efc8e..99d5b194 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitFilter.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitFilter.h @@ -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(b) - static_cast(a)); - }; + const auto extent = [](GLint a, GLint b) { return std::abs(static_cast(b) - static_cast(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 || diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h index 1ca4d4ea..0581b115 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h @@ -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, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.cpp index 806f482a..ba1546a3 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.cpp @@ -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 type = PickMemoryType(memoryProperties, requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0); + const Optional 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(getInstanceProcAddr(instance, "vkCreateDevice")); + const auto createDevice = reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); if (createDevice == nullptr) { measurement.failureReason = "no vkCreateDevice"; return measurement; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.h index b332ea9d..d6e55c74 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireCopySliceProbe.h @@ -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 "?"; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h index be3160a3..640b280e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDeclineTally.h @@ -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(WireDeclineSite::Count); - - static void Count(WireDeclineSite site) { - Slots()[static_cast(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(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(UnsoundSerialCompleteEvents())); - if (TotalDeclines() == 0 && PeakPendingUploadBytes() == 0) return; - MGLOG_I("MGWIRE-DECLINES[%s] total=%llu pendingEntries=%llu pendingBytes=%llu peakPendingBytes=%llu", tag, - static_cast(TotalDeclines()), - static_cast(PendingUploadEntries()), - static_cast(PendingUploadBytes()), - static_cast(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(i)), - static_cast(value)); + class WireDeclineTally { + public: + static constexpr SizeT kSiteCount = static_cast(WireDeclineSite::Count); + + static void Count(WireDeclineSite site) { + Slots()[static_cast(site)].fetch_add(1, std::memory_order_relaxed); + Total().fetch_add(1, std::memory_order_relaxed); } - } -private: - static std::atomic* Slots() { - static std::atomic slots[kSiteCount]{}; - return slots; - } - static std::atomic& Total() { - static std::atomic total{0}; - return total; - } - static std::atomic& PendingEntries() { - static std::atomic value{0}; - return value; - } - static std::atomic& PendingBytes() { - static std::atomic value{0}; - return value; - } - static std::atomic& PeakPendingBytes() { - static std::atomic value{0}; - return value; - } - static std::atomic& UnsoundSerial() { - static std::atomic value{0}; - return value; - } -}; + static Uint64 Get(WireDeclineSite site) { + return Slots()[static_cast(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(UnsoundSerialCompleteEvents())); + if (TotalDeclines() == 0 && PeakPendingUploadBytes() == 0) return; + MGLOG_I("MGWIRE-DECLINES[%s] total=%llu pendingEntries=%llu pendingBytes=%llu peakPendingBytes=%llu", tag, + static_cast(TotalDeclines()), + static_cast(PendingUploadEntries()), + static_cast(PendingUploadBytes()), + static_cast(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(i)), + static_cast(value)); + } + } + + private: + static std::atomic* Slots() { + static std::atomic slots[kSiteCount]{}; + return slots; + } + static std::atomic& Total() { + static std::atomic total{0}; + return total; + } + static std::atomic& PendingEntries() { + static std::atomic value{0}; + return value; + } + static std::atomic& PendingBytes() { + static std::atomic value{0}; + return value; + } + static std::atomic& PeakPendingBytes() { + static std::atomic value{0}; + return value; + } + static std::atomic& UnsoundSerial() { + static std::atomic 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 - diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.cpp index 969fcfa0..02d548fc 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.cpp @@ -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(source.extent.width), static_cast(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(source.extent.width), static_cast(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(destination.extent.width), - static_cast(destination.extent.height), 0.0f, 1.0f}; + const VkViewport viewport{ + 0.0f, 0.0f, static_cast(destination.extent.width), static_cast(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(); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h index 3b0cc3d9..d35892ef 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h @@ -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, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h index 90ec79b2..5909d25f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveArm.h @@ -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) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp index 91629a72..c0cf1b8a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp @@ -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(attachmentExtent.width), - static_cast(attachmentExtent.height), 0.0f, 1.0f}; + const VkViewport viewport{ + 0.0f, 0.0f, static_cast(attachmentExtent.width), static_cast(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(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 type = PickMemoryType(memoryProperties, requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0); + const Optional 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 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( getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR")); - const auto createDevice = - reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); + const auto createDevice = reinterpret_cast(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"; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h index be4d6fc5..20ba198f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.h @@ -69,9 +69,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkImageAspectFlags aspect, VkPipeline* pipeline); // Records the draw: the render area and the scissor are `rect`, the viewport is the WHOLE // attachment (gl_FragCoord is what the fragment stage fetches by), one triangle. - 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); // What the probe runs on. The device must have been created with VK_KHR_create_renderpass2 and // VK_KHR_depth_stencil_resolve (or Vulkan 1.2 core), and with VK_EXT_shader_stencil_export where diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp index 75ae6860..17f67d50 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.cpp @@ -45,15 +45,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkFence fence = VK_NULL_HANDLE; void Destroy() { - for (VkPipeline pipeline : pipelines) vkDestroyPipeline(device, pipeline, nullptr); - for (VkShaderModule module : modules) vkDestroyShaderModule(device, module, nullptr); + for (VkPipeline pipeline : pipelines) + vkDestroyPipeline(device, pipeline, nullptr); + for (VkShaderModule module : modules) + vkDestroyShaderModule(device, module, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - for (VkFramebuffer framebuffer : framebuffers) vkDestroyFramebuffer(device, framebuffer, nullptr); + for (VkFramebuffer framebuffer : framebuffers) + vkDestroyFramebuffer(device, framebuffer, nullptr); vkDestroyRenderPass(device, pass, nullptr); - for (VkImageView view : views) vkDestroyImageView(device, view, nullptr); - for (VkImage image : images) vkDestroyImage(device, image, nullptr); - for (VkBuffer buffer : buffers) vkDestroyBuffer(device, buffer, nullptr); - for (VkDeviceMemory memory : memories) vkFreeMemory(device, memory, nullptr); + for (VkImageView view : views) + vkDestroyImageView(device, view, nullptr); + for (VkImage image : images) + vkDestroyImage(device, image, nullptr); + for (VkBuffer buffer : buffers) + vkDestroyBuffer(device, buffer, nullptr); + for (VkDeviceMemory memory : memories) + vkFreeMemory(device, memory, nullptr); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyFence(device, fence, nullptr); } @@ -106,8 +113,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { PickMemoryType(memoryProperties, requirements.memoryTypeBits, wanted, required); if (!type) return false; if (coherent) - *coherent = (memoryProperties.memoryTypes[*type].propertyFlags & - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; + *coherent = + (memoryProperties.memoryTypes[*type].propertyFlags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) != 0; VkMemoryAllocateInfo info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; info.allocationSize = requirements.size; info.memoryTypeIndex = *type; @@ -242,7 +249,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; stages[1].module = fragment; VkPipelineVertexInputStateCreateInfo vertexInput{VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO}; - VkPipelineInputAssemblyStateCreateInfo assembly{VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; + VkPipelineInputAssemblyStateCreateInfo assembly{ + VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO}; assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; VkPipelineViewportStateCreateInfo viewportState{VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO}; viewportState.viewportCount = viewportState.scissorCount = 1; @@ -378,8 +386,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice, Uint32 graphicsQueueFamilyIndex) { WireDiscardStateProbeMeasurement measurement; - const auto createDevice = - reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); + const auto createDevice = reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); if (createDevice == nullptr) { measurement.failureReason = "no vkCreateDevice"; return measurement; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h index 43eadb42..d60a8cab 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.h @@ -58,10 +58,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { inline const char* WireDiscardStateProbeVerdictName(WireDiscardStateProbeVerdict verdict) { switch (verdict) { - case WireDiscardStateProbeVerdict::NotRun: return "not-run"; - case WireDiscardStateProbeVerdict::Inconclusive: return "inconclusive"; - case WireDiscardStateProbeVerdict::Clean: return "clean"; - case WireDiscardStateProbeVerdict::DiscardDrawDropsState: return "discard-draw-drops-state"; + case WireDiscardStateProbeVerdict::NotRun: + return "not-run"; + case WireDiscardStateProbeVerdict::Inconclusive: + return "inconclusive"; + case WireDiscardStateProbeVerdict::Clean: + return "clean"; + case WireDiscardStateProbeVerdict::DiscardDrawDropsState: + return "discard-draw-drops-state"; } return "?"; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h index 8020ca1d..2f3afae5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h @@ -14,442 +14,56 @@ #pragma once static constexpr Uint32 kWireDiscardStateProbeVertexSpirv[] = { - 0x07230203u, - 0x00010000u, - 0x0008000bu, - 0x00000037u, - 0x00000000u, - 0x00020011u, - 0x00000001u, - 0x0006000bu, - 0x00000001u, - 0x4c534c47u, - 0x6474732eu, - 0x3035342eu, - 0x00000000u, - 0x0003000eu, - 0x00000000u, - 0x00000001u, - 0x0007000fu, - 0x00000000u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x0000000cu, - 0x0000001eu, - 0x00030003u, - 0x00000002u, - 0x000001c2u, - 0x00040005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x00040005u, - 0x00000009u, - 0x7366666fu, - 0x00007465u, - 0x00060005u, - 0x0000000cu, - 0x565f6c67u, - 0x65747265u, - 0x646e4978u, - 0x00007865u, - 0x00060005u, - 0x0000001cu, - 0x505f6c67u, - 0x65567265u, - 0x78657472u, - 0x00000000u, - 0x00060006u, - 0x0000001cu, - 0x00000000u, - 0x505f6c67u, - 0x7469736fu, - 0x006e6f69u, - 0x00070006u, - 0x0000001cu, - 0x00000001u, - 0x505f6c67u, - 0x746e696fu, - 0x657a6953u, - 0x00000000u, - 0x00070006u, - 0x0000001cu, - 0x00000002u, - 0x435f6c67u, - 0x4470696cu, - 0x61747369u, - 0x0065636eu, - 0x00070006u, - 0x0000001cu, - 0x00000003u, - 0x435f6c67u, - 0x446c6c75u, - 0x61747369u, - 0x0065636eu, - 0x00030005u, - 0x0000001eu, - 0x00000000u, - 0x00050005u, - 0x0000002du, - 0x65646e69u, - 0x6c626178u, - 0x00000065u, - 0x00040047u, - 0x0000000cu, - 0x0000000bu, - 0x0000002au, - 0x00030047u, - 0x0000001cu, - 0x00000002u, - 0x00050048u, - 0x0000001cu, - 0x00000000u, - 0x0000000bu, - 0x00000000u, - 0x00050048u, - 0x0000001cu, - 0x00000001u, - 0x0000000bu, - 0x00000001u, - 0x00050048u, - 0x0000001cu, - 0x00000002u, - 0x0000000bu, - 0x00000003u, - 0x00050048u, - 0x0000001cu, - 0x00000003u, - 0x0000000bu, - 0x00000004u, - 0x00020013u, - 0x00000002u, - 0x00030021u, - 0x00000003u, - 0x00000002u, - 0x00030016u, - 0x00000006u, - 0x00000020u, - 0x00040017u, - 0x00000007u, - 0x00000006u, - 0x00000002u, - 0x00040020u, - 0x00000008u, - 0x00000007u, - 0x00000007u, - 0x00040015u, - 0x0000000au, - 0x00000020u, - 0x00000001u, - 0x00040020u, - 0x0000000bu, - 0x00000001u, - 0x0000000au, - 0x0004003bu, - 0x0000000bu, - 0x0000000cu, - 0x00000001u, - 0x0004002bu, - 0x0000000au, - 0x0000000eu, - 0x00000004u, - 0x00020014u, - 0x0000000fu, - 0x0004002bu, - 0x00000006u, - 0x00000011u, - 0x41000000u, - 0x0005002cu, - 0x00000007u, - 0x00000012u, - 0x00000011u, - 0x00000011u, - 0x0004002bu, - 0x00000006u, - 0x00000013u, - 0x00000000u, - 0x0005002cu, - 0x00000007u, - 0x00000014u, - 0x00000013u, - 0x00000013u, - 0x00040017u, - 0x00000015u, - 0x0000000fu, - 0x00000002u, - 0x00040017u, - 0x00000018u, - 0x00000006u, - 0x00000004u, - 0x00040015u, - 0x00000019u, - 0x00000020u, - 0x00000000u, - 0x0004002bu, - 0x00000019u, - 0x0000001au, - 0x00000001u, - 0x0004001cu, - 0x0000001bu, - 0x00000006u, - 0x0000001au, - 0x0006001eu, - 0x0000001cu, - 0x00000018u, - 0x00000006u, - 0x0000001bu, - 0x0000001bu, - 0x00040020u, - 0x0000001du, - 0x00000003u, - 0x0000001cu, - 0x0004003bu, - 0x0000001du, - 0x0000001eu, - 0x00000003u, - 0x0004002bu, - 0x0000000au, - 0x0000001fu, - 0x00000000u, - 0x0004002bu, - 0x00000019u, - 0x00000020u, - 0x00000004u, - 0x0004001cu, - 0x00000021u, - 0x00000007u, - 0x00000020u, - 0x0004002bu, - 0x00000006u, - 0x00000022u, - 0xbf800000u, - 0x0005002cu, - 0x00000007u, - 0x00000023u, - 0x00000022u, - 0x00000022u, - 0x0004002bu, - 0x00000006u, - 0x00000024u, - 0x3f800000u, - 0x0005002cu, - 0x00000007u, - 0x00000025u, - 0x00000024u, - 0x00000022u, - 0x0005002cu, - 0x00000007u, - 0x00000026u, - 0x00000022u, - 0x00000024u, - 0x0005002cu, - 0x00000007u, - 0x00000027u, - 0x00000024u, - 0x00000024u, - 0x0007002cu, - 0x00000021u, - 0x00000028u, - 0x00000023u, - 0x00000025u, - 0x00000026u, - 0x00000027u, - 0x0004002bu, - 0x0000000au, - 0x0000002au, - 0x00000003u, - 0x00040020u, - 0x0000002cu, - 0x00000007u, - 0x00000021u, - 0x00040020u, - 0x00000035u, - 0x00000003u, - 0x00000018u, - 0x00050036u, - 0x00000002u, - 0x00000004u, - 0x00000000u, - 0x00000003u, - 0x000200f8u, - 0x00000005u, - 0x0004003bu, - 0x00000008u, - 0x00000009u, - 0x00000007u, - 0x0004003bu, - 0x0000002cu, - 0x0000002du, - 0x00000007u, - 0x0004003du, - 0x0000000au, - 0x0000000du, - 0x0000000cu, - 0x000500afu, - 0x0000000fu, - 0x00000010u, - 0x0000000du, - 0x0000000eu, - 0x00050050u, - 0x00000015u, - 0x00000016u, - 0x00000010u, - 0x00000010u, - 0x000600a9u, - 0x00000007u, - 0x00000017u, - 0x00000016u, - 0x00000012u, - 0x00000014u, - 0x0003003eu, - 0x00000009u, - 0x00000017u, - 0x0004003du, - 0x0000000au, - 0x00000029u, - 0x0000000cu, - 0x000500c7u, - 0x0000000au, - 0x0000002bu, - 0x00000029u, - 0x0000002au, - 0x0003003eu, - 0x0000002du, - 0x00000028u, - 0x00050041u, - 0x00000008u, - 0x0000002eu, - 0x0000002du, - 0x0000002bu, - 0x0004003du, - 0x00000007u, - 0x0000002fu, - 0x0000002eu, - 0x0004003du, - 0x00000007u, - 0x00000030u, - 0x00000009u, - 0x00050081u, - 0x00000007u, - 0x00000031u, - 0x0000002fu, - 0x00000030u, - 0x00050051u, - 0x00000006u, - 0x00000032u, - 0x00000031u, - 0x00000000u, - 0x00050051u, - 0x00000006u, - 0x00000033u, - 0x00000031u, - 0x00000001u, - 0x00070050u, - 0x00000018u, - 0x00000034u, - 0x00000032u, - 0x00000033u, - 0x00000013u, - 0x00000024u, - 0x00050041u, - 0x00000035u, - 0x00000036u, - 0x0000001eu, - 0x0000001fu, - 0x0003003eu, - 0x00000036u, - 0x00000034u, - 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000037u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000cu, 0x0000001eu, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00040005u, 0x00000009u, 0x7366666fu, 0x00007465u, 0x00060005u, 0x0000000cu, + 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00060005u, 0x0000001cu, 0x505f6c67u, 0x65567265u, 0x78657472u, + 0x00000000u, 0x00060006u, 0x0000001cu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000001cu, + 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000001cu, 0x00000002u, 0x435f6c67u, + 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000001cu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, + 0x0065636eu, 0x00030005u, 0x0000001eu, 0x00000000u, 0x00050005u, 0x0000002du, 0x65646e69u, 0x6c626178u, 0x00000065u, + 0x00040047u, 0x0000000cu, 0x0000000bu, 0x0000002au, 0x00030047u, 0x0000001cu, 0x00000002u, 0x00050048u, 0x0000001cu, + 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000001cu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, + 0x0000001cu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000001cu, 0x00000003u, 0x0000000bu, 0x00000004u, + 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, + 0x00000007u, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000008u, 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, + 0x00000020u, 0x00000001u, 0x00040020u, 0x0000000bu, 0x00000001u, 0x0000000au, 0x0004003bu, 0x0000000bu, 0x0000000cu, + 0x00000001u, 0x0004002bu, 0x0000000au, 0x0000000eu, 0x00000004u, 0x00020014u, 0x0000000fu, 0x0004002bu, 0x00000006u, + 0x00000011u, 0x41000000u, 0x0005002cu, 0x00000007u, 0x00000012u, 0x00000011u, 0x00000011u, 0x0004002bu, 0x00000006u, + 0x00000013u, 0x00000000u, 0x0005002cu, 0x00000007u, 0x00000014u, 0x00000013u, 0x00000013u, 0x00040017u, 0x00000015u, + 0x0000000fu, 0x00000002u, 0x00040017u, 0x00000018u, 0x00000006u, 0x00000004u, 0x00040015u, 0x00000019u, 0x00000020u, + 0x00000000u, 0x0004002bu, 0x00000019u, 0x0000001au, 0x00000001u, 0x0004001cu, 0x0000001bu, 0x00000006u, 0x0000001au, + 0x0006001eu, 0x0000001cu, 0x00000018u, 0x00000006u, 0x0000001bu, 0x0000001bu, 0x00040020u, 0x0000001du, 0x00000003u, + 0x0000001cu, 0x0004003bu, 0x0000001du, 0x0000001eu, 0x00000003u, 0x0004002bu, 0x0000000au, 0x0000001fu, 0x00000000u, + 0x0004002bu, 0x00000019u, 0x00000020u, 0x00000004u, 0x0004001cu, 0x00000021u, 0x00000007u, 0x00000020u, 0x0004002bu, + 0x00000006u, 0x00000022u, 0xbf800000u, 0x0005002cu, 0x00000007u, 0x00000023u, 0x00000022u, 0x00000022u, 0x0004002bu, + 0x00000006u, 0x00000024u, 0x3f800000u, 0x0005002cu, 0x00000007u, 0x00000025u, 0x00000024u, 0x00000022u, 0x0005002cu, + 0x00000007u, 0x00000026u, 0x00000022u, 0x00000024u, 0x0005002cu, 0x00000007u, 0x00000027u, 0x00000024u, 0x00000024u, + 0x0007002cu, 0x00000021u, 0x00000028u, 0x00000023u, 0x00000025u, 0x00000026u, 0x00000027u, 0x0004002bu, 0x0000000au, + 0x0000002au, 0x00000003u, 0x00040020u, 0x0000002cu, 0x00000007u, 0x00000021u, 0x00040020u, 0x00000035u, 0x00000003u, + 0x00000018u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, + 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x0000002cu, 0x0000002du, 0x00000007u, 0x0004003du, 0x0000000au, + 0x0000000du, 0x0000000cu, 0x000500afu, 0x0000000fu, 0x00000010u, 0x0000000du, 0x0000000eu, 0x00050050u, 0x00000015u, + 0x00000016u, 0x00000010u, 0x00000010u, 0x000600a9u, 0x00000007u, 0x00000017u, 0x00000016u, 0x00000012u, 0x00000014u, + 0x0003003eu, 0x00000009u, 0x00000017u, 0x0004003du, 0x0000000au, 0x00000029u, 0x0000000cu, 0x000500c7u, 0x0000000au, + 0x0000002bu, 0x00000029u, 0x0000002au, 0x0003003eu, 0x0000002du, 0x00000028u, 0x00050041u, 0x00000008u, 0x0000002eu, + 0x0000002du, 0x0000002bu, 0x0004003du, 0x00000007u, 0x0000002fu, 0x0000002eu, 0x0004003du, 0x00000007u, 0x00000030u, + 0x00000009u, 0x00050081u, 0x00000007u, 0x00000031u, 0x0000002fu, 0x00000030u, 0x00050051u, 0x00000006u, 0x00000032u, + 0x00000031u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000033u, 0x00000031u, 0x00000001u, 0x00070050u, 0x00000018u, + 0x00000034u, 0x00000032u, 0x00000033u, 0x00000013u, 0x00000024u, 0x00050041u, 0x00000035u, 0x00000036u, 0x0000001eu, + 0x0000001fu, 0x0003003eu, 0x00000036u, 0x00000034u, 0x000100fdu, 0x00010038u, }; static constexpr Uint32 kWireDiscardStateProbeFragmentSpirv[] = { - 0x07230203u, - 0x00010000u, - 0x0008000bu, - 0x0000000du, - 0x00000000u, - 0x00020011u, - 0x00000001u, - 0x0006000bu, - 0x00000001u, - 0x4c534c47u, - 0x6474732eu, - 0x3035342eu, - 0x00000000u, - 0x0003000eu, - 0x00000000u, - 0x00000001u, - 0x0006000fu, - 0x00000004u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x00000009u, - 0x00030010u, - 0x00000004u, - 0x00000007u, - 0x00030003u, - 0x00000002u, - 0x000001c2u, - 0x00040005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x00040005u, - 0x00000009u, - 0x6f6c6f63u, - 0x00000072u, - 0x00040047u, - 0x00000009u, - 0x0000001eu, - 0x00000000u, - 0x00020013u, - 0x00000002u, - 0x00030021u, - 0x00000003u, - 0x00000002u, - 0x00030016u, - 0x00000006u, - 0x00000020u, - 0x00040017u, - 0x00000007u, - 0x00000006u, - 0x00000004u, - 0x00040020u, - 0x00000008u, - 0x00000003u, - 0x00000007u, - 0x0004003bu, - 0x00000008u, - 0x00000009u, - 0x00000003u, - 0x0004002bu, - 0x00000006u, - 0x0000000au, - 0x00000000u, - 0x0004002bu, - 0x00000006u, - 0x0000000bu, - 0x3f800000u, - 0x0007002cu, - 0x00000007u, - 0x0000000cu, - 0x0000000au, - 0x0000000bu, - 0x0000000au, - 0x0000000bu, - 0x00050036u, - 0x00000002u, - 0x00000004u, - 0x00000000u, - 0x00000003u, - 0x000200f8u, - 0x00000005u, - 0x0003003eu, - 0x00000009u, - 0x0000000cu, - 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000000du, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0006000fu, 0x00000004u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030003u, 0x00000002u, + 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00040005u, 0x00000009u, 0x6f6c6f63u, 0x00000072u, + 0x00040047u, 0x00000009u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, + 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, 0x00000008u, + 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000008u, 0x00000009u, 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000000au, + 0x00000000u, 0x0004002bu, 0x00000006u, 0x0000000bu, 0x3f800000u, 0x0007002cu, 0x00000007u, 0x0000000cu, 0x0000000au, + 0x0000000bu, 0x0000000au, 0x0000000bu, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, + 0x00000005u, 0x0003003eu, 0x00000009u, 0x0000000cu, 0x000100fdu, 0x00010038u, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp index de22f290..3f0af3e6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.cpp @@ -38,11 +38,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } static Bool TryGetCurrentVertexAttributeUploadPayload( - const MG_State::GLState::CurrentVertexAttributeValue& currentValue, - GLenum glType, - VkFormat& outFormat, - const void*& outData, - VkDeviceSize& outSize) { + const MG_State::GLState::CurrentVertexAttributeValue& currentValue, GLenum glType, VkFormat& outFormat, + const void*& outData, VkDeviceSize& outSize) { switch (glType) { case GL_FLOAT: outFormat = VK_FORMAT_R32_SFLOAT; @@ -109,14 +106,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } -// Server-owned draw/dispatch setup. The legacy setup remains the monolith implementation. + // Server-owned draw/dispatch setup. The legacy setup remains the monolith implementation. static MagmaProgramSource WireProgramSource(MG_Pipe::MGPipeHandle handle) { auto& state = MG_Pipe::MGPipeApplier(); const Bool composite = handle.Slot >= MG_Pipe::kMGPipeShaderCsoCompositeSlotBase; const auto& records = composite ? state.CompositeShaderCsos : state.ShaderCsos; const SizeT index = composite ? handle.Slot - MG_Pipe::kMGPipeShaderCsoCompositeSlotBase : handle.Slot; - if (MG_Pipe::MGPipeHandleIsNull(handle) || index >= records.size() || - !records[index].Live || records[index].Gen != handle.Gen || !records[index].Archive) + if (MG_Pipe::MGPipeHandleIsNull(handle) || index >= records.size() || !records[index].Live || + records[index].Gen != handle.Gen || !records[index].Archive) MagmaWireFatal("program-record"); return MagmaProgramSource(handle, records[index]); } @@ -138,9 +135,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } [[noreturn]] static void WireBufferLegacyFatal() { - MG_Pipe::MGPipeSessionFail( - MG_Pipe::MGPipeFatalFamily::RoleViolation, - "MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)"); + MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"buffer-legacy-arm\"} (Magma P7 buffer consumer)"); } static const MG_Pipe::MGPStreamOutputBegin& WireXfbSpan() { @@ -170,7 +166,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { MagmaWireFatal("xfb-buffer-resource"); if (target.Offset >= slice.size) return false; const Uint64 size = target.Size == 0 || target.Size == MG_Pipe::kMGPipeWholeBuffer - ? slice.size - target.Offset : target.Size; + ? slice.size - target.Offset + : target.Size; if (!size || size > slice.size - target.Offset) return false; buffers[i] = slice.buffer; offsets[i] = slice.offset + target.Offset; @@ -185,8 +182,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } static Bool ConvertWireVertexStream(const MGPVertexAttribWire& attr, - VertexInputStateFactory::VertexStreamConversion conversion, const Uint8* source, - SizeT stride, SizeT count, Vector& output) { + VertexInputStateFactory::VertexStreamConversion conversion, const Uint8* source, + SizeT stride, SizeT count, Vector& output) { using Conversion = VertexInputStateFactory::VertexStreamConversion; const auto type = static_cast(attr.Type); const SizeT elementBytes = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra); @@ -210,8 +207,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (normalize) { constexpr double kMax = static_cast(std::numeric_limits::max()); converted = static_cast(std::is_signed_v - ? std::max(static_cast(value) / kMax, -1.0) - : static_cast(value) / kMax); + ? std::max(static_cast(value) / kMax, -1.0) + : static_cast(value) / kMax); } } Memcpy(output.data() + (i * attr.Size + component) * sizeof(Float), &converted, sizeof(Float)); @@ -232,7 +229,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 bits = component < 3 ? 10u : 2u; const Uint32 field = (word >> (component * 10u)) & ((1u << bits) - 1u); const Int32 value = isSigned && (field >> (bits - 1u)) - ? static_cast(field) - static_cast(1u << bits) : static_cast(field); + ? static_cast(field) - static_cast(1u << bits) + : static_cast(field); Float converted = static_cast(value); if (normalize) converted = isSigned ? std::max(converted / static_cast((1u << (bits - 1u)) - 1u), -1.0f) @@ -242,14 +240,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { } return true; } - case DataType::Float64: return convert.template operator()(); - case DataType::Int8: return convert.template operator()(); - case DataType::Uint8: return convert.template operator()(); - case DataType::Int16: return convert.template operator()(); - case DataType::Uint16: return convert.template operator()(); - case DataType::Int32: return convert.template operator()(); - case DataType::Uint32: return convert.template operator()(); - default: return false; + case DataType::Float64: + return convert.template operator()(); + case DataType::Int8: + return convert.template operator()(); + case DataType::Uint8: + return convert.template operator()(); + case DataType::Int16: + return convert.template operator()(); + case DataType::Uint16: + return convert.template operator()(); + case DataType::Int32: + return convert.template operator()(); + case DataType::Uint32: + return convert.template operator()(); + default: + return false; } } @@ -264,7 +270,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wireDrawPassCompatibilityId = 0; m_wirePassMarksValid = false; m_wirePassMemo.valid = false; - for (const auto view : m_wireDrawViews) vkDestroyImageView(m_device, view, nullptr); + for (const auto view : m_wireDrawViews) + vkDestroyImageView(m_device, view, nullptr); m_wireDrawViews.clear(); m_wireDrawPassKey = {}; } @@ -318,7 +325,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::DestroyWireDrawPassCacheEntry(WireDrawPassCacheEntry& entry) { entry.pass.reset(); - for (const auto view : entry.views) vkDestroyImageView(m_device, view, nullptr); + for (const auto view : entry.views) + vkDestroyImageView(m_device, view, nullptr); entry.views.clear(); } @@ -356,7 +364,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Idle points only (shutdown, swapchain replacement, the device-idle drain): nothing in flight. void VulkanRenderer::ClearAllWireDrawPassCaches() { - for (auto& entry : m_wireDrawPassCache) DestroyWireDrawPassCacheEntry(entry); + for (auto& entry : m_wireDrawPassCache) + DestroyWireDrawPassCacheEntry(entry); m_wireDrawPassCache.clear(); if (m_wireColorBlitResources) { for (Uint32 frameIndex = 0; frameIndex < m_frameContext.GetFrameCount(); ++frameIndex) @@ -376,10 +385,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // Framebuffers/descriptor sets stop naming their views first. entry.drawPass.reset(); - for (const auto pool : entry.descriptorPools) vkDestroyDescriptorPool(m_device, pool, nullptr); - for (const auto framebuffer : entry.framebuffers) vkDestroyFramebuffer(m_device, framebuffer, nullptr); - for (const auto pass : entry.renderPasses) vkDestroyRenderPass(m_device, pass, nullptr); - for (const auto view : entry.imageViews) vkDestroyImageView(m_device, view, nullptr); + for (const auto pool : entry.descriptorPools) + vkDestroyDescriptorPool(m_device, pool, nullptr); + for (const auto framebuffer : entry.framebuffers) + vkDestroyFramebuffer(m_device, framebuffer, nullptr); + for (const auto pass : entry.renderPasses) + vkDestroyRenderPass(m_device, pass, nullptr); + for (const auto view : entry.imageViews) + vkDestroyImageView(m_device, view, nullptr); } // Erase once: repeated front erases move the whole remaining cohort for // every draw retired by a fence, turning a batch retirement quadratic. @@ -428,9 +441,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // clear (WireFramebuffer.cpp): a clear into the attachments of the open pass is recorded // inside it instead of closing it and building a pass of its own. Leaves m_wireDrawPass and // m_wireDrawPassKey describing the pass, `continuing` true when it is the open one. - Bool VulkanRenderer::PrepareWireDrawPass(FrameContext::FrameData& frame, - const MG_Pipe::MGPFramebufferState& fbo, WireDrawScratch& scratch, Bool passContinues, - Bool& continuing) { + Bool VulkanRenderer::PrepareWireDrawPass(FrameContext::FrameData& frame, const MG_Pipe::MGPFramebufferState& fbo, + WireDrawScratch& scratch, Bool passContinues, Bool& continuing) { continuing = true; m_wireFoldClears.clear(); if (passContinues) { @@ -442,246 +454,253 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration(); } } else { - m_wirePassMemo.valid = false; - auto& attachments = scratch.attachments; - auto& viewInfos = scratch.viewInfos; - auto& colors = scratch.colors; - attachments.clear(); - viewInfos.clear(); - colors.clear(); - VkAttachmentReference depth{VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_GENERAL}; - VkExtent2D extent{~0u, ~0u}; - Uint32 layers = ~0u; - VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; - Uint64 passHash = 0; - auto& passKey = scratch.passKey; - passKey.ResetKeepingCapacity(); - passKey.isDefault = fbo.IsDefault; - passKey.framebufferSrgb = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb); - if (fbo.IsDefault) { - passKey.swapchain = m_swapchainObject.GetHandle(); - // A drawing pass on the default framebuffer is what makes the freshly acquired - // swapchain image the one GL addresses (see m_defaultFramebufferImageIndex); the - // attachments below resolve the same index, so this key has to be taken after it. - passKey.swapchainImageIndex = DefaultFramebufferWriteIndex(true); - } - auto& sameLayoutAttachments = scratch.sameLayoutAttachments; - sameLayoutAttachments.clear(); - scratch.gpuWriteMarks.clear(); - m_wirePassMemo.attachments.clear(); - const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 { - // An attachment resolve: its deferred clears stay pending, to become this pass's load - // op (BeginWireDrawPassFolding) - WireFramebuffer.cpp's deferred-clear section. - WireImage image; - { - VkTextureManager::PendingClearHold hold(*m_textureManager); - image = ResolveWireImage(fbo, surface, aspect, /*isWriteTarget=*/true); + m_wirePassMemo.valid = false; + auto& attachments = scratch.attachments; + auto& viewInfos = scratch.viewInfos; + auto& colors = scratch.colors; + attachments.clear(); + viewInfos.clear(); + colors.clear(); + VkAttachmentReference depth{VK_ATTACHMENT_UNUSED, VK_IMAGE_LAYOUT_GENERAL}; + VkExtent2D extent{~0u, ~0u}; + Uint32 layers = ~0u; + VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT; + Uint64 passHash = 0; + auto& passKey = scratch.passKey; + passKey.ResetKeepingCapacity(); + passKey.isDefault = fbo.IsDefault; + passKey.framebufferSrgb = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb); + if (fbo.IsDefault) { + passKey.swapchain = m_swapchainObject.GetHandle(); + // A drawing pass on the default framebuffer is what makes the freshly acquired + // swapchain image the one GL addresses (see m_defaultFramebufferImageIndex); the + // attachments below resolve the same index, so this key has to be taken after it. + passKey.swapchainImageIndex = DefaultFramebufferWriteIndex(true); } - if (!image.image) return VK_ATTACHMENT_UNUSED; - m_textureManager->FlushPendingUploads(); - m_wirePassMemo.attachments.push_back( - {image.resource, image.storage, surface.Kind == MG_Pipe::kMGPipeSurfaceKindRenderbuffer}); - if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) - image.format = ResolveSrgbAttachmentWriteFormat(image.format, - MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); - const VkImageLayout layout = aspect == VK_IMAGE_ASPECT_COLOR_BIT ? - VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_GENERAL; - // Already in its attachment layout: its barrier (which closes any open pass) waits until - // it is known whether the open pass continues with this very attachment. - if (image.layout == layout) - sameLayoutAttachments.push_back(image); - else - TransitionWireImage(image, layout); - VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; - info.image = image.image; - info.viewType = image.viewType; - info.format = image.format; - info.subresourceRange = {aspect, image.level, 1, image.layer, image.layers}; - if (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT) - info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - viewInfos.push_back(info); - passKey.attachments.push_back({image.storage, static_cast(surface.Kind), image.image, - image.viewType, image.format, info.subresourceRange.aspectMask, aspect, - image.level, image.layer, image.layers, image.levels, image.extent.width, image.extent.height, - image.samples}); - VkAttachmentDescription description{}; - description.format = image.format; - description.samples = image.samples; - description.loadOp = description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; - description.storeOp = description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; - description.initialLayout = description.finalLayout = layout; - attachments.push_back(description); - if (image.resource != nullptr && !image.resource->pendingClears.empty()) - m_wireFoldClears.push_back({static_cast(attachments.size() - 1), info.subresourceRange.aspectMask, - image.resource, image.level, image.layer, image.layers}); - extent.width = std::min(extent.width, image.extent.width); - extent.height = std::min(extent.height, image.extent.height); - layers = std::min(layers, image.layers); - samples = image.samples; - passHash = XXH64(&description, sizeof(description), passHash); - // Marked below, once the pass is known: a continued pass repeats the same marks. - if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) - scratch.gpuWriteMarks.push_back({image.storage, image.level, image.layer, image.layers}); - if (image.isDefault) { + auto& sameLayoutAttachments = scratch.sameLayoutAttachments; + sameLayoutAttachments.clear(); + scratch.gpuWriteMarks.clear(); + m_wirePassMemo.attachments.clear(); + const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 { + // An attachment resolve: its deferred clears stay pending, to become this pass's load + // op (BeginWireDrawPassFolding) - WireFramebuffer.cpp's deferred-clear section. + WireImage image; + { + VkTextureManager::PendingClearHold hold(*m_textureManager); + image = ResolveWireImage(fbo, surface, aspect, /*isWriteTarget=*/true); + } + if (!image.image) return VK_ATTACHMENT_UNUSED; + m_textureManager->FlushPendingUploads(); + m_wirePassMemo.attachments.push_back( + {image.resource, image.storage, surface.Kind == MG_Pipe::kMGPipeSurfaceKindRenderbuffer}); if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) - m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true); + image.format = ResolveSrgbAttachmentWriteFormat( + image.format, MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); + const VkImageLayout layout = + aspect == VK_IMAGE_ASPECT_COLOR_BIT ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_GENERAL; + // Already in its attachment layout: its barrier (which closes any open pass) waits until + // it is known whether the open pass continues with this very attachment. + if (image.layout == layout) + sameLayoutAttachments.push_back(image); else - m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true); + TransitionWireImage(image, layout); + VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + info.image = image.image; + info.viewType = image.viewType; + info.format = image.format; + info.subresourceRange = {aspect, image.level, 1, image.layer, image.layers}; + if (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT) + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + viewInfos.push_back(info); + passKey.attachments.push_back({image.storage, static_cast(surface.Kind), image.image, + image.viewType, image.format, info.subresourceRange.aspectMask, aspect, + image.level, image.layer, image.layers, image.levels, image.extent.width, + image.extent.height, image.samples}); + VkAttachmentDescription description{}; + description.format = image.format; + description.samples = image.samples; + description.loadOp = description.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + description.storeOp = description.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + description.initialLayout = description.finalLayout = layout; + attachments.push_back(description); + if (image.resource != nullptr && !image.resource->pendingClears.empty()) + m_wireFoldClears.push_back({static_cast(attachments.size() - 1), + info.subresourceRange.aspectMask, image.resource, image.level, + image.layer, image.layers}); + extent.width = std::min(extent.width, image.extent.width); + extent.height = std::min(extent.height, image.extent.height); + layers = std::min(layers, image.layers); + samples = image.samples; + passHash = XXH64(&description, sizeof(description), passHash); + // Marked below, once the pass is known: a continued pass repeats the same marks. + if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) + scratch.gpuWriteMarks.push_back({image.storage, image.level, image.layer, image.layers}); + if (image.isDefault) { + if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) + m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true); + else + m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true); + } + return static_cast(attachments.size() - 1); + }; + for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) { + const Int32 slot = fbo.DrawBuffers[i]; + const Uint32 index = slot < 0 ? VK_ATTACHMENT_UNUSED : add(fbo.Color[slot], VK_IMAGE_ASPECT_COLOR_BIT); + colors.push_back({index, VK_IMAGE_LAYOUT_GENERAL}); } - return static_cast(attachments.size() - 1); - }; - for (Uint32 i = 0; i < MG_Pipe::kMGPipeMaxColorAttachments; ++i) { - const Int32 slot = fbo.DrawBuffers[i]; - const Uint32 index = slot < 0 ? VK_ATTACHMENT_UNUSED : add(fbo.Color[slot], VK_IMAGE_ASPECT_COLOR_BIT); - colors.push_back({index, VK_IMAGE_LAYOUT_GENERAL}); - } - while (!colors.empty() && colors.back().attachment == VK_ATTACHMENT_UNUSED) colors.pop_back(); - if (fbo.IsDefault || fbo.Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone) - depth.attachment = add(fbo.Depth, VK_IMAGE_ASPECT_DEPTH_BIT); - else if (fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone) - depth.attachment = add(fbo.Stencil, VK_IMAGE_ASPECT_STENCIL_BIT); - if (attachments.empty()) { - // ARB_framebuffer_no_attachments (GL 4.6 core 9.2): a framebuffer with no image - // attached rasterizes at its default width/height/layers/samples, which the record - // carries in that case (FillGeometry). Vulkan expresses it directly as a render pass - // with no attachments over a framebuffer of that extent. - const Bool hasAnyAttachment = fbo.Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone || - fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone || - std::any_of(std::begin(fbo.Color), std::end(fbo.Color), [](const MG_Pipe::MGPSurface& surface) { - return surface.Kind != MG_Pipe::kMGPipeSurfaceKindNone; - }); - if (fbo.IsDefault || hasAnyAttachment || fbo.Width == 0 || fbo.Height == 0) { - MGL_WIRE_DECLINE_AT(NoAttachments, "the draw framebuffer resolved to no attachments"); - return false; + while (!colors.empty() && colors.back().attachment == VK_ATTACHMENT_UNUSED) + colors.pop_back(); + if (fbo.IsDefault || fbo.Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone) + depth.attachment = add(fbo.Depth, VK_IMAGE_ASPECT_DEPTH_BIT); + else if (fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone) + depth.attachment = add(fbo.Stencil, VK_IMAGE_ASPECT_STENCIL_BIT); + if (attachments.empty()) { + // ARB_framebuffer_no_attachments (GL 4.6 core 9.2): a framebuffer with no image + // attached rasterizes at its default width/height/layers/samples, which the record + // carries in that case (FillGeometry). Vulkan expresses it directly as a render pass + // with no attachments over a framebuffer of that extent. + const Bool hasAnyAttachment = + fbo.Depth.Kind != MG_Pipe::kMGPipeSurfaceKindNone || + fbo.Stencil.Kind != MG_Pipe::kMGPipeSurfaceKindNone || + std::any_of(std::begin(fbo.Color), std::end(fbo.Color), [](const MG_Pipe::MGPSurface& surface) { + return surface.Kind != MG_Pipe::kMGPipeSurfaceKindNone; + }); + if (fbo.IsDefault || hasAnyAttachment || fbo.Width == 0 || fbo.Height == 0) { + MGL_WIRE_DECLINE_AT(NoAttachments, "the draw framebuffer resolved to no attachments"); + return false; + } + const auto& limits = m_physicalDevice.properties.limits; + extent.width = std::min(fbo.Width, limits.maxFramebufferWidth); + extent.height = std::min(fbo.Height, limits.maxFramebufferHeight); + layers = std::clamp(fbo.Layers, 1u, std::max(limits.maxFramebufferLayers, 1u)); + // The smallest count the device rasterizes attachment-less passes at that covers the + // request (GL lets the effective count exceed the requested one), else its largest. + samples = VK_SAMPLE_COUNT_1_BIT; + const VkSampleCountFlags supported = limits.framebufferNoAttachmentsSampleCounts; + if (fbo.Samples > 1) { + for (Uint32 bit = VK_SAMPLE_COUNT_2_BIT; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) { + if ((supported & bit) == 0) continue; + samples = static_cast(bit); + if (bit >= fbo.Samples) break; + } + } + passKey.noAttachmentSamples = static_cast(samples); + passHash = XXH64(&samples, sizeof(samples), passHash); } - const auto& limits = m_physicalDevice.properties.limits; - extent.width = std::min(fbo.Width, limits.maxFramebufferWidth); - extent.height = std::min(fbo.Height, limits.maxFramebufferHeight); - layers = std::clamp(fbo.Layers, 1u, std::max(limits.maxFramebufferLayers, 1u)); - // The smallest count the device rasterizes attachment-less passes at that covers the - // request (GL lets the effective count exceed the requested one), else its largest. - samples = VK_SAMPLE_COUNT_1_BIT; - const VkSampleCountFlags supported = limits.framebufferNoAttachmentsSampleCounts; - if (fbo.Samples > 1) { - for (Uint32 bit = VK_SAMPLE_COUNT_2_BIT; bit <= VK_SAMPLE_COUNT_64_BIT; bit <<= 1) { - if ((supported & bit) == 0) continue; - samples = static_cast(bit); - if (bit >= fbo.Samples) break; + passKey.width = extent.width; + passKey.height = extent.height; + passKey.layers = layers; + passKey.depthReference = depth.attachment; + for (const auto& color : colors) + passKey.colorReferences.push_back(color.attachment); + // A native handle can be recycled after image replacement or death. + // Sample epochs after every lazy resolve, then compare them with the key. + passKey.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); + passKey.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch(); + VkSubpassDescription subpass{}; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.colorAttachmentCount = static_cast(colors.size()); + subpass.pColorAttachments = colors.data(); + subpass.pDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED ? &depth : nullptr; + VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; + passInfo.attachmentCount = static_cast(attachments.size()); + passInfo.pAttachments = attachments.data(); + passInfo.subpassCount = 1; + passInfo.pSubpasses = &subpass; + continuing = m_wireDrawPass && m_wireDrawPassKey == passKey; + { + const auto* active = VkRenderPassManager::GetActiveRenderPass(); + const Bool stillOpen = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer; + // A new pass orders itself after earlier writes to its attachments; a continued one + // is the same pass, whose attachment writes are ordered by rasterization. + if (!stillOpen) + for (auto& image : sameLayoutAttachments) + TransitionWireImage(image, VK_IMAGE_LAYOUT_GENERAL, /*coalesceWithLastFullBarrier=*/true); + } + if (!continuing) { + RetireWireDrawPass(); + m_wireDrawPassCompatibilityId = 0; + } + if (!continuing && !AcquireCachedWireDrawPass(passKey)) { + // A copy, not a move: passKey is the scratch's, and a new pass is the rare case. + m_wireDrawPassKey = passKey; + Vector views; + views.reserve(viewInfos.size()); + for (const auto& info : viewInfos) { + VkImageView view = VK_NULL_HANDLE; + VK_VERIFY(vkCreateImageView(m_device, &info, nullptr, &view)); + m_wireDrawViews.push_back(view); + views.push_back(view); + } + m_wireDrawPass = MakeUnique(); + auto& created = *m_wireDrawPass; + VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &created.renderPass)); + VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + fbInfo.renderPass = created.renderPass; + fbInfo.attachmentCount = static_cast(views.size()); + fbInfo.pAttachments = views.data(); + fbInfo.width = extent.width; + fbInfo.height = extent.height; + fbInfo.layers = layers; + VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &created.framebuffer)); + created.attachmentCount = static_cast(attachments.size()); + created.colorAttachmentCount = static_cast(colors.size()); + created.hasDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED; + created.sampleCount = samples; + created.extent = {static_cast(extent.width), static_cast(extent.height)}; + created.layers = layers; + created.hash = created.compatibilityHash = + XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash); + created.wireAttachments.assign(attachments.begin(), attachments.end()); + created.wireColors.assign(colors.begin(), colors.end()); + created.wireDepth = depth; + } + // The attachments' GPU-write marks (MarkWireTextureGpuWritten): idempotent while nothing + // clears a GPU-dirty mark, so a draw that continues a pass whose draws already marked the + // same attachments - the pass key compared equal - repeats them only after a clear. + { + auto& store = MG_Record::ServerStagedTexture(); + if (!continuing || !m_wirePassMarksValid || + m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) { + for (const auto& mark : scratch.gpuWriteMarks) + m_textureManager->MarkWireTextureGpuWritten(mark.storage, mark.level, mark.layer, mark.layers); + m_wirePassMarksValid = true; + m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration(); } } - passKey.noAttachmentSamples = static_cast(samples); - passHash = XXH64(&samples, sizeof(samples), passHash); - } - passKey.width = extent.width; - passKey.height = extent.height; - passKey.layers = layers; - passKey.depthReference = depth.attachment; - for (const auto& color : colors) passKey.colorReferences.push_back(color.attachment); - // A native handle can be recycled after image replacement or death. - // Sample epochs after every lazy resolve, then compare them with the key. - passKey.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); - passKey.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch(); - VkSubpassDescription subpass{}; - subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; - subpass.colorAttachmentCount = static_cast(colors.size()); - subpass.pColorAttachments = colors.data(); - subpass.pDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED ? &depth : nullptr; - VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; - passInfo.attachmentCount = static_cast(attachments.size()); - passInfo.pAttachments = attachments.data(); - passInfo.subpassCount = 1; - passInfo.pSubpasses = &subpass; - continuing = m_wireDrawPass && m_wireDrawPassKey == passKey; - { - const auto* active = VkRenderPassManager::GetActiveRenderPass(); - const Bool stillOpen = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer; - // A new pass orders itself after earlier writes to its attachments; a continued one - // is the same pass, whose attachment writes are ordered by rasterization. - if (!stillOpen) - for (auto& image : sameLayoutAttachments) - TransitionWireImage(image, VK_IMAGE_LAYOUT_GENERAL, /*coalesceWithLastFullBarrier=*/true); - } - if (!continuing) { - RetireWireDrawPass(); - m_wireDrawPassCompatibilityId = 0; - } - if (!continuing && !AcquireCachedWireDrawPass(passKey)) { - // A copy, not a move: passKey is the scratch's, and a new pass is the rare case. - m_wireDrawPassKey = passKey; - Vector views; - views.reserve(viewInfos.size()); - for (const auto& info : viewInfos) { - VkImageView view = VK_NULL_HANDLE; - VK_VERIFY(vkCreateImageView(m_device, &info, nullptr, &view)); - m_wireDrawViews.push_back(view); - views.push_back(view); + { + // What this walk proved, for WirePassMemoHolds on the next draw. + const auto& st = MG_Pipe::MGPipeApplier(); + auto& memo = m_wirePassMemo; + memo.valid = !fbo.IsDefault; + memo.framebufferSerial = st.FramebufferSerial; + memo.contextSerial = st.ContextSerial; + memo.drawFramebuffer = st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)]; + memo.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); + memo.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch(); + memo.framebufferSrgb = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb); + memo.passHash = m_wireDrawPass->hash; + memo.marks.assign(scratch.gpuWriteMarks.begin(), scratch.gpuWriteMarks.end()); } - m_wireDrawPass = MakeUnique(); - auto& created = *m_wireDrawPass; - VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &created.renderPass)); - VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - fbInfo.renderPass = created.renderPass; - fbInfo.attachmentCount = static_cast(views.size()); - fbInfo.pAttachments = views.data(); - fbInfo.width = extent.width; fbInfo.height = extent.height; fbInfo.layers = layers; - VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &created.framebuffer)); - created.attachmentCount = static_cast(attachments.size()); - created.colorAttachmentCount = static_cast(colors.size()); - created.hasDepthStencilAttachment = depth.attachment != VK_ATTACHMENT_UNUSED; - created.sampleCount = samples; - created.extent = {static_cast(extent.width), static_cast(extent.height)}; - created.layers = layers; - created.hash = created.compatibilityHash = XXH64(colors.data(), colors.size() * sizeof(colors[0]), passHash); - created.wireAttachments.assign(attachments.begin(), attachments.end()); - created.wireColors.assign(colors.begin(), colors.end()); - created.wireDepth = depth; - } - // The attachments' GPU-write marks (MarkWireTextureGpuWritten): idempotent while nothing - // clears a GPU-dirty mark, so a draw that continues a pass whose draws already marked the - // same attachments - the pass key compared equal - repeats them only after a clear. - { - auto& store = MG_Record::ServerStagedTexture(); - if (!continuing || !m_wirePassMarksValid || - m_wirePassMarksClearGeneration != store.GpuDirtyClearGeneration()) { - for (const auto& mark : scratch.gpuWriteMarks) - m_textureManager->MarkWireTextureGpuWritten(mark.storage, mark.level, mark.layer, mark.layers); - m_wirePassMarksValid = true; - m_wirePassMarksClearGeneration = store.GpuDirtyClearGeneration(); + // The compatibility key is built from this walk's attachments only when the pass is new. + if (m_wireDrawPassCompatibilityId == 0) { + auto& pipelinePassKey = scratch.compatibilityKey; + pipelinePassKey.attachmentFormatsAndSamples.clear(); + pipelinePassKey.colorReferences.clear(); + for (const auto& attachment : attachments) + pipelinePassKey.attachmentFormatsAndSamples.push_back( + (static_cast(attachment.format) << 32) | static_cast(attachment.samples)); + for (const auto& color : colors) + pipelinePassKey.colorReferences.push_back(color.attachment); + pipelinePassKey.depthReference = depth.attachment; + m_wireDrawPassCompatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey); } - } - { - // What this walk proved, for WirePassMemoHolds on the next draw. - const auto& st = MG_Pipe::MGPipeApplier(); - auto& memo = m_wirePassMemo; - memo.valid = !fbo.IsDefault; - memo.framebufferSerial = st.FramebufferSerial; - memo.contextSerial = st.ContextSerial; - memo.drawFramebuffer = st.BoundFramebuffer[SizeT(MG_Pipe::MGPipeFramebufferTarget::Draw)]; - memo.textureImageEpoch = m_textureManager->GetTextureImageEpoch(); - memo.resourceEraseEpoch = m_textureManager->GetResourceEraseEpoch(); - memo.framebufferSrgb = MG_Pipe::gPipeInputs->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb); - memo.passHash = m_wireDrawPass->hash; - memo.marks.assign(scratch.gpuWriteMarks.begin(), scratch.gpuWriteMarks.end()); - } - // The compatibility key is built from this walk's attachments only when the pass is new. - if (m_wireDrawPassCompatibilityId == 0) { - auto& pipelinePassKey = scratch.compatibilityKey; - pipelinePassKey.attachmentFormatsAndSamples.clear(); - pipelinePassKey.colorReferences.clear(); - for (const auto& attachment : attachments) - pipelinePassKey.attachmentFormatsAndSamples.push_back( - (static_cast(attachment.format) << 32) | static_cast(attachment.samples)); - for (const auto& color : colors) pipelinePassKey.colorReferences.push_back(color.attachment); - pipelinePassKey.depthReference = depth.attachment; - m_wireDrawPassCompatibilityId = m_wireRenderPassCompatibility.Intern(pipelinePassKey); - } } // !passContinues return true; } - - Bool VulkanRenderer::SetupWireDraw(FrameContext::FrameData& frame, GLenum mode, - Flags aspects, const DrawCmdParam& drawParams, const IndexBufferView* indices) { + Bool VulkanRenderer::SetupWireDraw(FrameContext::FrameData& frame, GLenum mode, Flags aspects, + const DrawCmdParam& drawParams, const IndexBufferView* indices) { auto& state = MG_Pipe::MGPipeApplier(); // The per-draw containers (VulkanRenderer.h's WireDrawScratch). Nothing re-enters this // function from inside it today; if something ever does, it gets its own set rather @@ -696,8 +715,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // P8-D: the wire arm's own indirect draw (DrawWireIndirectNative) is the one caller that // may pass this aspect; a monolith indirect entry point reached here is still the role // violation. - if ((aspects & DrawSetupAspect::IndirectDrawBuffer) && !m_wireNativeIndirectDraw) - WireBufferLegacyFatal(); + if ((aspects & DrawSetupAspect::IndirectDrawBuffer) && !m_wireNativeIndirectDraw) WireBufferLegacyFatal(); MakeXfbWritesVisible(); const auto* fbo = state.DrawFramebuffer(); if (!fbo) MagmaWireFatal("draw-framebuffer-record"); @@ -710,9 +728,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { WirePreparationScope preparing(m_wirePreparationDepth); const RenderStateParameters& clipControl = MG_Pipe::gPipeInputs->GetRenderStateParameters(); - ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionFlags( - ProgramFactory::ApplyClipControlToTransformFlags(GetBaseTransformFlagsRaw(fbo->IsDefault), - clipControl.ClipOrigin, clipControl.ClipDepthMode)); + ProgramFactory::CompileOptionFlags flags = + ProgramFactory::CompileOptionFlags(ProgramFactory::ApplyClipControlToTransformFlags( + GetBaseTransformFlagsRaw(fbo->IsDefault), clipControl.ClipOrigin, clipControl.ClipDepthMode)); if (m_transformFeedbackFeatureEnabled && MG_Pipe::gPipeInputs->IsTransformFeedbackActive() && program.GetTransformFeedbackVaryingCount() > 0) flags |= ProgramFactory::CompileOptionBit::XfbCapture; @@ -722,17 +740,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { programObject = &m_programFactory->GetOrCreateProgram(program, flags); } if (programObject->invalidSpirvDeclined) { - MGL_WIRE_DECLINE_AT(ProgramInvalidSpirv, "a module of the program fails SPIR-V validation and was never built"); + MGL_WIRE_DECLINE_AT(ProgramInvalidSpirv, + "a module of the program fails SPIR-V validation and was never built"); return false; } if (programObject->pointSizeCapabilityUnsupported) { - MGL_WIRE_DECLINE_AT(PointSizeCapability, "the program writes gl_PointSize and the device cannot express it"); + MGL_WIRE_DECLINE_AT(PointSizeCapability, + "the program writes gl_PointSize and the device cannot express it"); return false; } const auto& programObj = *programObject; m_currentDrawXfbCaptureDeclined = programObj.xfbCaptureDeclined; if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) { - MGL_WIRE_DECLINE_AT(TextureResourcesUnprepared, "a sampled/storage image could not be made ready; see the Tex* tally"); + MGL_WIRE_DECLINE_AT(TextureResourcesUnprepared, + "a sampled/storage image could not be made ready; see the Tex* tally"); return false; } // Shared images another session writes: acquired ahead of the recording that samples them @@ -776,10 +797,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // preparation, which builds into its own scratch rather than under the outer draw's feet. const VertexInputStateFactory::BackendVertexInputState* resolvedInput = nullptr; if (&scratch == &m_wireDrawScratch) { - resolvedInput = m_vertexInputStateFactory->ResolveWireVertexInput(vao, state, - programObj.activeVertexInputLocationMask); - } else if (m_vertexInputStateFactory->BuildWireVertexInput(vao, state, - programObj.activeVertexInputLocationMask, scratch.input)) { + resolvedInput = + m_vertexInputStateFactory->ResolveWireVertexInput(vao, state, programObj.activeVertexInputLocationMask); + } else if (m_vertexInputStateFactory->BuildWireVertexInput(vao, state, programObj.activeVertexInputLocationMask, + scratch.input)) { resolvedInput = &scratch.input; } if (resolvedInput == nullptr) MagmaWireFatal("vertex-layout-buffer-window"); @@ -815,9 +836,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // draw record is built, or refuses the draw itself (MG_Impl/Pipe/OwnedDrawInputs.h), // so an attribute the program reads with no buffer handle is a foreign peer's record. if (MG_Pipe::MGPipeHandleIsNull(resource.Res)) - MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, + MG_Pipe::MGPipeSessionFail( + MG_Pipe::MGPipeFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"Magma:client-vertex-array\"} location=%u: an " - "attribute the program reads carries no buffer handle", location); + "attribute the program reads carries no buffer handle", + location); BufferSlice slice{}; if (!m_bufferManager.AcquireWireSlice(BufferKind::Vertex, resource.Res, slice)) { // A buffer with no data store is legal GL to bind (glGenBuffers + glBindBuffer, no @@ -837,7 +860,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto type = static_cast(attr.Type); const SizeT elementSize = VertexInputStateFactory::GetAttributeByteSize(type, attr.Size, attr.IsBgra); if (offset > slice.size || elementSize > slice.size - offset) { - MGL_WIRE_DECLINE_AT(VertexAttributeWindow, "an enabled attribute's first element lies outside its buffer"); + MGL_WIRE_DECLINE_AT(VertexAttributeWindow, + "an enabled attribute's first element lies outside its buffer"); return false; } const auto conversion = input.bindingConversions[binding]; @@ -858,11 +882,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { // changes (VkBufferManager's converted-stream cache): a multi-draw over a chunk arena // names the same stream for every chunk, frame after frame. Uint64 contentSerial = 0; - const Bool cacheable = MG_Config::Features.MagmaVertexConversionCache != MG_Config::QuirkOverride::ForceOff && - m_bufferManager.GetWireBufferContentSerial(resource.Res, contentSerial); - const VkBufferManager::ConvertedStreamKey cacheKey{contentSerial, offset, static_cast(stride), - static_cast(attr.Type), static_cast(attr.Size), static_cast(conversion), - attr.Normalized != 0, attr.IsInteger != 0, attr.IsBgra != 0}; + const Bool cacheable = + MG_Config::Features.MagmaVertexConversionCache != MG_Config::QuirkOverride::ForceOff && + m_bufferManager.GetWireBufferContentSerial(resource.Res, contentSerial); + const VkBufferManager::ConvertedStreamKey cacheKey{contentSerial, + offset, + static_cast(stride), + static_cast(attr.Type), + static_cast(attr.Size), + static_cast(conversion), + attr.Normalized != 0, + attr.IsInteger != 0, + attr.IsBgra != 0}; if (cacheable && m_bufferManager.LookupConvertedStream(cacheKey, count, vertexSlices[binding])) { ++m_wireStreamsReused; continue; @@ -885,17 +916,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!ConvertWireVertexStream(attr, conversion, source.data(), stride, count, converted)) { MGLOG_E_ONCE("Magma wire draw skipped: no vertex stream converter for " "location=%u type=%u size=%u conversion=%d", - location, attr.Type, static_cast(attr.Size), - static_cast(conversion)); + location, attr.Type, static_cast(attr.Size), static_cast(conversion)); WireDeclineTally::Count(WireDeclineSite::VertexStreamConverter); return false; } ++m_wireStreamsConverted; if (!(cacheable && m_bufferManager.StoreConvertedStream(cacheKey, resource.Res, count, converted.data(), - converted.size(), vertexSlices[binding])) && + converted.size(), vertexSlices[binding])) && !m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), - converted.data(), converted.size(), 16, vertexSlices[binding])) { - MGL_WIRE_DECLINE_AT(VertexTransientUpload, "the converted vertex stream did not fit the transient arena"); + converted.data(), converted.size(), 16, vertexSlices[binding])) { + MGL_WIRE_DECLINE_AT(VertexTransientUpload, + "the converted vertex stream did not fit the transient arena"); return false; } } else { @@ -921,12 +952,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto& restart = MG_Pipe::gPipeInputs->GetRenderStateParameters(); const Uint32 fixed = MG_Util::FixedRestartIndexForGLType(indices->indexType); const Bool replaceRestart = restart.PrimitiveRestartEnabled && !restart.PrimitiveRestartFixedIndexEnabled && - restart.PrimitiveRestartIndex < fixed; + restart.PrimitiveRestartIndex < fixed; const Bool expandByte = width == 1 && !m_indexTypeUint8ExtensionEnabled; const Bool cpuIndices = indices->forceClientMemory || MG_Pipe::MGPipeHandleIsNull(state.IndexBuffer.Res); Vector copied; const void* bytes = nullptr; - if (cpuIndices) bytes = reinterpret_cast(indices->indexByteOffset); + if (cpuIndices) + bytes = reinterpret_cast(indices->indexByteOffset); else { if (!m_bufferManager.AcquireWireSlice(BufferKind::Index, state.IndexBuffer.Res, indexSlice)) { // As for a vertex buffer: a bound element buffer with no data store yet. @@ -965,15 +997,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 value = 0; Memcpy(&value, static_cast(bytes) + i * width, width); if (replaceRestart && value == restart.PrimitiveRestartIndex) value = fixed; - if (expandByte && ResolvePrimitiveRestartEnable(aspects, indices) && value == fixed) value = 0xffffu; + if (expandByte && ResolvePrimitiveRestartEnable(aspects, indices) && value == fixed) + value = 0xffffu; Memcpy(rewritten.data() + i * outWidth, &value, outWidth); } bytes = rewritten.data(); uploadSize = rewritten.size(); if (expandByte) indexType = VK_INDEX_TYPE_UINT16; } - if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), - bytes, uploadSize, 16, indexSlice)) { + if (!m_bufferManager.UploadTransient(BufferKind::Index, m_frameContext.GetCurrentFrameIndex(), bytes, + uploadSize, 16, indexSlice)) { MGL_WIRE_DECLINE_AT(IndexTransientUpload, "the rewritten indices did not fit the transient arena"); return false; } @@ -989,7 +1022,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Resolve it before constructing framebuffer views so both roles of that image // name the same allocation even on its first storage-image use. if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, - m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_GRAPHICS)) { + m_frameContext.GetCurrentFrameIndex(), + VK_PIPELINE_BIND_POINT_GRAPHICS)) { MGL_WIRE_DECLINE_AT(UniformBufferBinding, "the program's uniform/storage bindings could not be bound"); return false; } @@ -1008,7 +1042,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Interned once per pass (above): a continued pass has the same compatibility. const Uint64 compatibilityId = m_wireDrawPassCompatibilityId; const auto pipeline = GetOrCreatePipelineWithInput(mode, program, programObj, flags, input, pass, - ResolvePrimitiveRestartEnable(aspects, indices), WireFramebufferIsLayered(*fbo), compatibilityId); + ResolvePrimitiveRestartEnable(aspects, indices), + WireFramebufferIsLayered(*fbo), compatibilityId); if (pipeline == VK_NULL_HANDLE) { MGL_WIRE_DECLINE_AT(PipelineUnavailable, "no graphics pipeline could be created for this draw"); return false; @@ -1051,19 +1086,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { Uint32 binding = static_cast(vertexSlices.size()); for (Uint32 location = 0; location < kMaxVertexAttribs; ++location) { if (!(programObj.activeVertexInputLocationMask & (1u << location)) || - (input.attributeLocationMask & (1u << location))) continue; + (input.attributeLocationMask & (1u << location))) + continue; VkFormat format = VK_FORMAT_UNDEFINED; const void* data = nullptr; VkDeviceSize size = 0; if (!TryGetCurrentVertexAttributeUploadPayload(MG_Pipe::gPipeInputs->GetCurrentVertexAttribute(location), - programObj.vertexInputTypes[location], format, data, size)) { - MGL_WIRE_DECLINE_AT(CurrentAttributePayload, "a disabled array's current attribute value has no upload payload"); + programObj.vertexInputTypes[location], format, data, size)) { + MGL_WIRE_DECLINE_AT(CurrentAttributePayload, + "a disabled array's current attribute value has no upload payload"); return false; } BufferSlice slice{}; - if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), - data, size, 16, slice)) { - MGL_WIRE_DECLINE_AT(CurrentAttributeUpload, "the current attribute value did not fit the transient arena"); + if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), data, size, + 16, slice)) { + MGL_WIRE_DECLINE_AT(CurrentAttributeUpload, + "the current attribute value did not fit the transient arena"); return false; } vkCmdBindVertexBuffers(frame.commandBuffer, binding++, 1, &slice.buffer, &slice.offset); @@ -1093,7 +1131,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { bindShadow.graphicsPipeline = pipeline; } ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault, - ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin)); + ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin)); if (m_fragmentShadingRate.Pipeline) { ShadowedSetFragmentShadingRate(frame.commandBuffer, s_vkCmdSetFragmentShadingRateKHR); } @@ -1130,7 +1168,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // attachment whose extent or layer count differs from the pass's (the pass covers the common area // only), and a stencil clear the pass's depth attachment does not carry. GLbitfield VulkanRenderer::ClearWireFramebufferInPass(const MG_Pipe::MGPFramebufferState& fbo, - const ClearAttachmentPayload& payload, GLint drawbuffer) { + const ClearAttachmentPayload& payload, GLint drawbuffer) { GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (!remaining || MG_Config::Features.MagmaInPassClear == MG_Config::QuirkOverride::ForceOff) return remaining; // Nested inside a draw's preparation the scratch and the pass belong to that draw. @@ -1141,7 +1179,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // vkCmdClearAttachments - while the same clear split across the two paths, or left // standalone, renders correctly. Default-framebuffer clears keep the standalone path until // that is explained (perf-p15/HANDOFF.md). - if (fbo.IsDefault && MG_Config::Features.MagmaInPassClearDefault != MG_Config::QuirkOverride::ForceOn) return remaining; + if (fbo.IsDefault && MG_Config::Features.MagmaInPassClearDefault != MG_Config::QuirkOverride::ForceOn) + return remaining; auto& frame = m_frameContext.GetCurrent(); WirePreparationScope preparing(m_wirePreparationDepth); // As SetupWireDraw: resolve every attachment (a lazy upload or promotion may submit the @@ -1188,22 +1227,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!fbo.IsDefault && fbo.Color[attachment].Kind == MG_Pipe::kMGPipeSurfaceKindNone) continue; const auto mask = MG_Pipe::gPipeInputs->GetColorMaskIndexed(i); if (!mask.r() && !mask.g() && !mask.b() && !mask.a()) continue; - if (!mask.r() || !mask.g() || !mask.b() || !mask.a()) { all = false; break; } + if (!mask.r() || !mask.g() || !mask.b() || !mask.a()) { + all = false; + break; + } if (i >= key.colorReferences.size() || key.colorReferences[i] == VK_ATTACHMENT_UNUSED || - !fits(key.colorReferences[i])) { all = false; break; } + !fits(key.colorReferences[i])) { + all = false; + break; + } auto& clear = colorClears[colorCount++]; clear.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; clear.colorAttachment = i; clear.clearValue.color = MakeVkClearColorValue(payload, false); } if (all) { - for (Uint32 c = 0; c < colorCount; ++c) clears[count++] = colorClears[c]; + for (Uint32 c = 0; c < colorCount; ++c) + clears[count++] = colorClears[c]; handled |= GL_COLOR_BUFFER_BIT; } } - const WireDrawAttachmentKey* depthKey = - key.depthReference != VK_ATTACHMENT_UNUSED && fits(key.depthReference) ? &key.attachments[key.depthReference] - : nullptr; + const WireDrawAttachmentKey* depthKey = key.depthReference != VK_ATTACHMENT_UNUSED && fits(key.depthReference) + ? &key.attachments[key.depthReference] + : nullptr; VkImageAspectFlags depthStencilAspects = 0; if (remaining & GL_DEPTH_BUFFER_BIT) { if (!MG_Pipe::gPipeInputs->GetDepthMask()) { @@ -1228,9 +1274,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // subresource (a DEPTH_STENCIL attachment point). const auto& depth = fbo.Depth; const auto& stencil = fbo.Stencil; - const Bool same = fbo.IsDefault || depthKey->requestedAspect == VK_IMAGE_ASPECT_STENCIL_BIT || - (depth.Kind == stencil.Kind && depth.Res == stencil.Res && - depth.Level == stencil.Level && depth.Layer == stencil.Layer && depth.Layered == stencil.Layered); + const Bool same = + fbo.IsDefault || depthKey->requestedAspect == VK_IMAGE_ASPECT_STENCIL_BIT || + (depth.Kind == stencil.Kind && depth.Res == stencil.Res && depth.Level == stencil.Level && + depth.Layer == stencil.Layer && depth.Layered == stencil.Layered); if (same) { depthStencilAspects |= VK_IMAGE_ASPECT_STENCIL_BIT; handled |= GL_STENCIL_BUFFER_BIT; @@ -1253,8 +1300,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // An empty box clears nothing, on either path. if (x0 >= x1 || y0 >= y1) return 0; area = {{x0, y0}, {static_cast(x1 - x0), static_cast(y1 - y0)}}; - if (fbo.IsDefault && !MapDefaultFramebufferReadbackRect(x0, y0, x1 - x0, y1 - y0, VkExtent2D{key.width, key.height}, - m_swapchainObject.GetPreTransform(), &area.offset, &area.extent)) + if (fbo.IsDefault && + !MapDefaultFramebufferReadbackRect(x0, y0, x1 - x0, y1 - y0, VkExtent2D{key.width, key.height}, + m_swapchainObject.GetPreTransform(), &area.offset, &area.extent)) MagmaWireFatal("clear-surface-transform"); } const Bool wholeArea = area.offset.x == 0 && area.offset.y == 0 && area.extent.width == key.width && @@ -1333,7 +1381,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { constexpr Uint32 kMaxAttachments = MG_Pipe::kMGPipeMaxColorAttachments + 1; VkClearValue merged[kMaxAttachments]{}; if (values != nullptr) - for (Uint32 a = 0; a < attachmentCount && a < kMaxAttachments; ++a) merged[a] = values[a]; + for (Uint32 a = 0; a < attachmentCount && a < kMaxAttachments; ++a) + merged[a] = values[a]; for (const auto& fold : m_wireFoldClears) { if (fold.attachment >= attachmentCount || fold.attachment >= kMaxAttachments || fold.resource == nullptr) continue; @@ -1359,8 +1408,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { merged[a].depthStencil.stencil = entry.value.depthStencil.stencil; } entry.aspects &= ~taken; - if (entry.aspects == 0) pending.erase(pending.begin() + static_cast(i)); - else ++i; + if (entry.aspects == 0) + pending.erase(pending.begin() + static_cast(i)); + else + ++i; } } m_wireFoldClears.clear(); @@ -1394,7 +1445,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { break; } } - const VkImageAspectFlags depthStencil = taken & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); + const VkImageAspectFlags depthStencil = + taken & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT); if (depthStencil && pass.wireDepth.attachment == fold.attachment && count < MG_Pipe::kMGPipeMaxColorAttachments + 1) { auto& clear = clears[count++]; @@ -1402,14 +1454,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { clear.clearValue.depthStencil = entry.value.depthStencil; } entry.aspects &= ~taken; - if (entry.aspects == 0) pending.erase(pending.begin() + static_cast(i)); - else ++i; + if (entry.aspects == 0) + pending.erase(pending.begin() + static_cast(i)); + else + ++i; } } m_wireFoldClears.clear(); if (count == 0) return; - const VkClearRect rect{{{0, 0}, {static_cast(pass.extent.x()), static_cast(pass.extent.y())}}, - 0, pass.layers}; + const VkClearRect rect{ + {{0, 0}, {static_cast(pass.extent.x()), static_cast(pass.extent.y())}}, 0, pass.layers}; vkCmdClearAttachments(frame.commandBuffer, count, clears, 1, &rect); } @@ -1448,14 +1502,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { // it for drawCount > 1; a peer's record that breaks either is declined, not issued. if (stride < static_cast(commandSize) || (stride & 3) != 0 || (offset & 3u) != 0 || offset > commands.size || span > commands.size - offset) { - MGL_WIRE_DECLINE_AT(IndirectCommandRange, "the indirect commands lie outside their store or are misaligned"); + MGL_WIRE_DECLINE_AT(IndirectCommandRange, + "the indirect commands lie outside their store or are misaligned"); return true; } BufferSlice parameters{}; if (counted) { if (MG_Pipe::MGPipeHandleIsNull(state.VerbIndirectParameterBuffer) || - !m_bufferManager.AcquireWireSlice(BufferKind::Indirect, state.VerbIndirectParameterBuffer, parameters) || - (countOffset & 3u) != 0 || countOffset > parameters.size || parameters.size - countOffset < sizeof(Uint32)) { + !m_bufferManager.AcquireWireSlice(BufferKind::Indirect, state.VerbIndirectParameterBuffer, + parameters) || + (countOffset & 3u) != 0 || countOffset > parameters.size || + parameters.size - countOffset < sizeof(Uint32)) { MGL_WIRE_DECLINE_AT(IndirectCountRange, "the indirect draw count lies outside its parameter store"); return true; } @@ -1489,7 +1546,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // application's own glMemoryBarrier is BuildMemoryBarrierForGlBarriers, as on the monolith // arm; transfer writes carry their own MEMORY_READ after-barrier (VkBufferManager.cpp). const Bool commandsWritten = m_bufferManager.TakeWireIndirectReadBarrier(state.VerbIndirectBuffer); - const Bool countWritten = counted && m_bufferManager.TakeWireIndirectReadBarrier(state.VerbIndirectParameterBuffer); + const Bool countWritten = + counted && m_bufferManager.TakeWireIndirectReadBarrier(state.VerbIndirectParameterBuffer); if (commandsWritten || countWritten) { RetireWireDrawPass(); auto& barrierFrame = m_frameContext.GetCurrent(); @@ -1518,24 +1576,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 byteStride = static_cast(stride); if (counted) { const auto countEntry = indexed ? s_vkCmdDrawIndexedIndirectCount : s_vkCmdWireDrawIndirectCount; - countEntry(commandBuffer, commands.buffer, first, parameters.buffer, parameters.offset + countOffset, - count, byteStride); + countEntry(commandBuffer, commands.buffer, first, parameters.buffer, parameters.offset + countOffset, count, + byteStride); } else if (count == 1 || (!m_multiDrawForceUnrollIndirect && m_multiDrawIndirectFeatureEnabled)) { // maxDrawIndirectCount bounds one call; GL's drawcount does not. const Uint32 limit = std::max(1u, m_physicalDevice.properties.limits.maxDrawIndirectCount); for (Uint32 base = 0; base < count; base += limit) { const Uint32 chunk = std::min(count - base, limit); const VkDeviceSize at = first + static_cast(base) * byteStride; - if (indexed) vkCmdDrawIndexedIndirect(commandBuffer, commands.buffer, at, chunk, byteStride); - else vkCmdDrawIndirect(commandBuffer, commands.buffer, at, chunk, byteStride); + if (indexed) + vkCmdDrawIndexedIndirect(commandBuffer, commands.buffer, at, chunk, byteStride); + else + vkCmdDrawIndirect(commandBuffer, commands.buffer, at, chunk, byteStride); } } else { // No multiDrawIndirect: one indirect draw per command, still read by the GPU // (the monolith arm's own fallback). for (Uint32 i = 0; i < count; ++i) { const VkDeviceSize at = first + static_cast(i) * byteStride; - if (indexed) vkCmdDrawIndexedIndirect(commandBuffer, commands.buffer, at, 1, 0); - else vkCmdDrawIndirect(commandBuffer, commands.buffer, at, 1, 0); + if (indexed) + vkCmdDrawIndexedIndirect(commandBuffer, commands.buffer, at, 1, 0); + else + vkCmdDrawIndirect(commandBuffer, commands.buffer, at, 1, 0); } } if (MG_Util::PipeStats::Enabled()) @@ -1553,11 +1615,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto& programObj = m_programFactory->GetOrCreateProgram(program, {}); if (programObj.invalidSpirvDeclined) { - MGL_WIRE_DECLINE_AT(ComputeProgramInvalidSpirv, "a module of the compute program fails SPIR-V validation and was never built"); + MGL_WIRE_DECLINE_AT(ComputeProgramInvalidSpirv, + "a module of the compute program fails SPIR-V validation and was never built"); return; } if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) { - MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, "a sampled/storage image could not be made ready for the dispatch"); + MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, + "a sampled/storage image could not be made ready for the dispatch"); return; } AcquireNotedSharedImages(); // shared images another session writes (WireSharedImage.cpp) @@ -1568,8 +1632,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording(); if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, - m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) { - MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, "the dispatch's uniform/storage bindings could not be bound"); + m_frameContext.GetCurrentFrameIndex(), + VK_PIPELINE_BIND_POINT_COMPUTE)) { + MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, + "the dispatch's uniform/storage bindings could not be bound"); return; } vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); @@ -1599,7 +1665,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { BufferSlice parameters{}; if (MG_Pipe::MGPipeHandleIsNull(state.VerbDispatchIndirectBuffer) || !m_bufferManager.AcquireWireSlice(BufferKind::Indirect, state.VerbDispatchIndirectBuffer, parameters)) { - MGL_WIRE_DECLINE_AT(DispatchIndirectBufferUnbound, "an indirect dispatch names no live dispatch-indirect store"); + MGL_WIRE_DECLINE_AT(DispatchIndirectBufferUnbound, + "an indirect dispatch names no live dispatch-indirect store"); return; } // GL refuses an offset that is negative or not a multiple of four (INVALID_VALUE) and a @@ -1607,7 +1674,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // either is declined, not issued. if (offset < 0 || (static_cast(offset) & 3u) != 0 || static_cast(offset) > parameters.size || parameters.size - static_cast(offset) < kCommandSize) { - MGL_WIRE_DECLINE_AT(DispatchIndirectRange, "the dispatch group counts lie outside their store or are misaligned"); + MGL_WIRE_DECLINE_AT(DispatchIndirectRange, + "the dispatch group counts lie outside their store or are misaligned"); return; } auto& frame = m_frameContext.GetCurrent(); @@ -1630,11 +1698,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto& programObj = m_programFactory->GetOrCreateProgram(program, {}); if (programObj.invalidSpirvDeclined) { - MGL_WIRE_DECLINE_AT(ComputeProgramInvalidSpirv, "a module of the compute program fails SPIR-V validation and was never built"); + MGL_WIRE_DECLINE_AT(ComputeProgramInvalidSpirv, + "a module of the compute program fails SPIR-V validation and was never built"); return; } if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) { - MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, "a sampled/storage image could not be made ready for the dispatch"); + MGL_WIRE_DECLINE_AT(ComputeTextureResourcesUnprepared, + "a sampled/storage image could not be made ready for the dispatch"); return; } AcquireNotedSharedImages(); // shared images another session writes (WireSharedImage.cpp) @@ -1647,8 +1717,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& dispatchFrame = m_frameContext.GetCurrent(); if (!dispatchFrame.isCommandRecording) m_frameContext.BeginCommandRecording(); if (!m_uniformManager->BindProgramUniformBuffers(dispatchFrame.commandBuffer, program, programObj, - m_frameContext.GetCurrentFrameIndex(), VK_PIPELINE_BIND_POINT_COMPUTE)) { - MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, "the dispatch's uniform/storage bindings could not be bound"); + m_frameContext.GetCurrentFrameIndex(), + VK_PIPELINE_BIND_POINT_COMPUTE)) { + MGL_WIRE_DECLINE_AT(ComputeUniformBufferBinding, + "the dispatch's uniform/storage bindings could not be bound"); return; } vkCmdBindPipeline(dispatchFrame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.cpp index cb2f212a..96c7f46a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.cpp @@ -190,8 +190,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { twinBarrier.srcQueueFamilyIndex = twinBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; twinBarrier.image = twin; twinBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, VK_REMAINING_ARRAY_LAYERS}; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - 0, 1, &memory, 0, nullptr, 1, &twinBarrier); + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, + 1, &memory, 0, nullptr, 1, &twinBarrier); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, region.volume ? kit.volumePipeline : kit.arrayPipeline); @@ -255,9 +255,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { void Destroy() { DestroyWireE5RawCopyObjects(device, copy); - 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); DestroyWireE5RawCopyKit(device, kit); @@ -341,8 +344,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto bindImage = [&](VkImage image) -> Bool { VkMemoryRequirements requirements{}; vkGetImageMemoryRequirements(device, image, &requirements); - const Optional type = PickMemoryType(memoryProperties, requirements.memoryTypeBits, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 0); + const Optional 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; @@ -426,7 +429,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (vkMapMemory(device, upload.memory, 0, VK_WHOLE_SIZE, 0, &mapped) != VK_SUCCESS || mapped == nullptr) return fail("vkMapMemory (upload) failed"); Uint32* words = static_cast(mapped); - for (Uint32 i = 0; i < kTexels; ++i) words[i] = NonCanonicalWord(i); + for (Uint32 i = 0; i < kTexels; ++i) + words[i] = NonCanonicalWord(i); if (!upload.coherent) { VkMappedMemoryRange range{VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE}; range.memory = upload.memory; @@ -551,13 +555,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { return measurement; } - WireE5TransferProbeMeasurement RunWireE5TransferProbeOnThrowawayDevice(PFN_vkGetInstanceProcAddr getInstanceProcAddr, - VkInstance instance, - VkPhysicalDevice physicalDevice, - Uint32 graphicsQueueFamilyIndex) { + WireE5TransferProbeMeasurement RunWireE5TransferProbeOnThrowawayDevice( + PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice, + Uint32 graphicsQueueFamilyIndex) { WireE5TransferProbeMeasurement measurement; - const auto createDevice = - reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); + const auto createDevice = reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDevice")); if (createDevice == nullptr) { measurement.failureReason = "no vkCreateDevice"; return measurement; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.h index 30e295e9..eae3ec29 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.h @@ -128,10 +128,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { inline const char* WireE5TransferProbeVerdictName(WireE5TransferProbeVerdict verdict) { switch (verdict) { - case WireE5TransferProbeVerdict::NotRun: return "not-run"; - case WireE5TransferProbeVerdict::Inconclusive: return "inconclusive"; - case WireE5TransferProbeVerdict::Clean: return "clean"; - case WireE5TransferProbeVerdict::TransferReadNormalizes: return "transfer-read-normalizes"; + case WireE5TransferProbeVerdict::NotRun: + return "not-run"; + case WireE5TransferProbeVerdict::Inconclusive: + return "inconclusive"; + case WireE5TransferProbeVerdict::Clean: + return "clean"; + case WireE5TransferProbeVerdict::TransferReadNormalizes: + return "transfer-read-normalizes"; } return "?"; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h index 35ee32fc..9f09eb1b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h @@ -14,834 +14,100 @@ #pragma once static constexpr Uint32 kWireE5RawCopySpirv[] = { - 0x07230203u, - 0x00010000u, - 0x0008000bu, - 0x00000046u, - 0x00000000u, - 0x00020011u, - 0x00000001u, - 0x0006000bu, - 0x00000001u, - 0x4c534c47u, - 0x6474732eu, - 0x3035342eu, - 0x00000000u, - 0x0003000eu, - 0x00000000u, - 0x00000001u, - 0x0006000fu, - 0x00000005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x0000000du, - 0x00060010u, - 0x00000004u, - 0x00000011u, - 0x00000008u, - 0x00000008u, - 0x00000001u, - 0x00030003u, - 0x00000002u, - 0x000001c2u, - 0x00040005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x00030005u, - 0x00000009u, - 0x00006469u, - 0x00080005u, - 0x0000000du, - 0x475f6c67u, - 0x61626f6cu, - 0x766e496cu, - 0x7461636fu, - 0x496e6f69u, - 0x00000044u, - 0x00040005u, - 0x00000012u, - 0x69676552u, - 0x00006e6fu, - 0x00070006u, - 0x00000012u, - 0x00000000u, - 0x72756f73u, - 0x664f6563u, - 0x74657366u, - 0x00000000u, - 0x00050006u, - 0x00000012u, - 0x00000001u, - 0x65747865u, - 0x0000746eu, - 0x00040005u, - 0x00000014u, - 0x69676572u, - 0x00006e6fu, - 0x00040005u, - 0x00000022u, - 0x64726f77u, - 0x00000000u, - 0x00050005u, - 0x00000026u, - 0x72756f73u, - 0x6d496563u, - 0x00656761u, - 0x00050005u, - 0x0000003eu, - 0x6e697774u, - 0x67616d49u, - 0x00000065u, - 0x00040047u, - 0x0000000du, - 0x0000000bu, - 0x0000001cu, - 0x00030047u, - 0x00000012u, - 0x00000002u, - 0x00050048u, - 0x00000012u, - 0x00000000u, - 0x00000023u, - 0x00000000u, - 0x00050048u, - 0x00000012u, - 0x00000001u, - 0x00000023u, - 0x00000010u, - 0x00040047u, - 0x00000026u, - 0x00000021u, - 0x00000000u, - 0x00040047u, - 0x00000026u, - 0x00000022u, - 0x00000000u, - 0x00030047u, - 0x0000003eu, - 0x00000019u, - 0x00040047u, - 0x0000003eu, - 0x00000021u, - 0x00000001u, - 0x00040047u, - 0x0000003eu, - 0x00000022u, - 0x00000000u, - 0x00040047u, - 0x00000045u, - 0x0000000bu, - 0x00000019u, - 0x00020013u, - 0x00000002u, - 0x00030021u, - 0x00000003u, - 0x00000002u, - 0x00040015u, - 0x00000006u, - 0x00000020u, - 0x00000001u, - 0x00040017u, - 0x00000007u, - 0x00000006u, - 0x00000003u, - 0x00040020u, - 0x00000008u, - 0x00000007u, - 0x00000007u, - 0x00040015u, - 0x0000000au, - 0x00000020u, - 0x00000000u, - 0x00040017u, - 0x0000000bu, - 0x0000000au, - 0x00000003u, - 0x00040020u, - 0x0000000cu, - 0x00000001u, - 0x0000000bu, - 0x0004003bu, - 0x0000000cu, - 0x0000000du, - 0x00000001u, - 0x00040017u, - 0x00000011u, - 0x00000006u, - 0x00000004u, - 0x0004001eu, - 0x00000012u, - 0x00000011u, - 0x00000011u, - 0x00040020u, - 0x00000013u, - 0x00000009u, - 0x00000012u, - 0x0004003bu, - 0x00000013u, - 0x00000014u, - 0x00000009u, - 0x0004002bu, - 0x00000006u, - 0x00000015u, - 0x00000001u, - 0x00040020u, - 0x00000016u, - 0x00000009u, - 0x00000011u, - 0x00020014u, - 0x0000001au, - 0x00040017u, - 0x0000001bu, - 0x0000001au, - 0x00000003u, - 0x00040020u, - 0x00000021u, - 0x00000007u, - 0x0000000au, - 0x00090019u, - 0x00000023u, - 0x0000000au, - 0x00000001u, - 0x00000000u, - 0x00000001u, - 0x00000000u, - 0x00000001u, - 0x00000000u, - 0x0003001bu, - 0x00000024u, - 0x00000023u, - 0x00040020u, - 0x00000025u, - 0x00000000u, - 0x00000024u, - 0x0004003bu, - 0x00000025u, - 0x00000026u, - 0x00000000u, - 0x0004002bu, - 0x00000006u, - 0x00000028u, - 0x00000000u, - 0x00040017u, - 0x00000029u, - 0x00000006u, - 0x00000002u, - 0x0004002bu, - 0x0000000au, - 0x00000030u, - 0x00000002u, - 0x00040020u, - 0x00000031u, - 0x00000007u, - 0x00000006u, - 0x00040017u, - 0x00000038u, - 0x0000000au, - 0x00000004u, - 0x0004002bu, - 0x0000000au, - 0x0000003au, - 0x00000000u, - 0x00090019u, - 0x0000003cu, - 0x0000000au, - 0x00000001u, - 0x00000000u, - 0x00000001u, - 0x00000000u, - 0x00000002u, - 0x00000021u, - 0x00040020u, - 0x0000003du, - 0x00000000u, - 0x0000003cu, - 0x0004003bu, - 0x0000003du, - 0x0000003eu, - 0x00000000u, - 0x0004002bu, - 0x0000000au, - 0x00000043u, - 0x00000008u, - 0x0004002bu, - 0x0000000au, - 0x00000044u, - 0x00000001u, - 0x0006002cu, - 0x0000000bu, - 0x00000045u, - 0x00000043u, - 0x00000043u, - 0x00000044u, - 0x00050036u, - 0x00000002u, - 0x00000004u, - 0x00000000u, - 0x00000003u, - 0x000200f8u, - 0x00000005u, - 0x0004003bu, - 0x00000008u, - 0x00000009u, - 0x00000007u, - 0x0004003bu, - 0x00000021u, - 0x00000022u, - 0x00000007u, - 0x0004003du, - 0x0000000bu, - 0x0000000eu, - 0x0000000du, - 0x0004007cu, - 0x00000007u, - 0x0000000fu, - 0x0000000eu, - 0x0003003eu, - 0x00000009u, - 0x0000000fu, - 0x0004003du, - 0x00000007u, - 0x00000010u, - 0x00000009u, - 0x00050041u, - 0x00000016u, - 0x00000017u, - 0x00000014u, - 0x00000015u, - 0x0004003du, - 0x00000011u, - 0x00000018u, - 0x00000017u, - 0x0008004fu, - 0x00000007u, - 0x00000019u, - 0x00000018u, - 0x00000018u, - 0x00000000u, - 0x00000001u, - 0x00000002u, - 0x000500afu, - 0x0000001bu, - 0x0000001cu, - 0x00000010u, - 0x00000019u, - 0x0004009au, - 0x0000001au, - 0x0000001du, - 0x0000001cu, - 0x000300f7u, - 0x0000001fu, - 0x00000000u, - 0x000400fau, - 0x0000001du, - 0x0000001eu, - 0x0000001fu, - 0x000200f8u, - 0x0000001eu, - 0x000100fdu, - 0x000200f8u, - 0x0000001fu, - 0x0004003du, - 0x00000024u, - 0x00000027u, - 0x00000026u, - 0x00050041u, - 0x00000016u, - 0x0000002au, - 0x00000014u, - 0x00000028u, - 0x0004003du, - 0x00000011u, - 0x0000002bu, - 0x0000002au, - 0x0007004fu, - 0x00000029u, - 0x0000002cu, - 0x0000002bu, - 0x0000002bu, - 0x00000000u, - 0x00000001u, - 0x0004003du, - 0x00000007u, - 0x0000002du, - 0x00000009u, - 0x0007004fu, - 0x00000029u, - 0x0000002eu, - 0x0000002du, - 0x0000002du, - 0x00000000u, - 0x00000001u, - 0x00050080u, - 0x00000029u, - 0x0000002fu, - 0x0000002cu, - 0x0000002eu, - 0x00050041u, - 0x00000031u, - 0x00000032u, - 0x00000009u, - 0x00000030u, - 0x0004003du, - 0x00000006u, - 0x00000033u, - 0x00000032u, - 0x00050051u, - 0x00000006u, - 0x00000034u, - 0x0000002fu, - 0x00000000u, - 0x00050051u, - 0x00000006u, - 0x00000035u, - 0x0000002fu, - 0x00000001u, - 0x00060050u, - 0x00000007u, - 0x00000036u, - 0x00000034u, - 0x00000035u, - 0x00000033u, - 0x00040064u, - 0x00000023u, - 0x00000037u, - 0x00000027u, - 0x0007005fu, - 0x00000038u, - 0x00000039u, - 0x00000037u, - 0x00000036u, - 0x00000002u, - 0x00000028u, - 0x00050051u, - 0x0000000au, - 0x0000003bu, - 0x00000039u, - 0x00000000u, - 0x0003003eu, - 0x00000022u, - 0x0000003bu, - 0x0004003du, - 0x0000003cu, - 0x0000003fu, - 0x0000003eu, - 0x0004003du, - 0x00000007u, - 0x00000040u, - 0x00000009u, - 0x0004003du, - 0x0000000au, - 0x00000041u, - 0x00000022u, - 0x00070050u, - 0x00000038u, - 0x00000042u, - 0x00000041u, - 0x0000003au, - 0x0000003au, - 0x0000003au, - 0x00040063u, - 0x0000003fu, - 0x00000040u, - 0x00000042u, - 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000046u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0006000fu, 0x00000005u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00060010u, 0x00000004u, 0x00000011u, 0x00000008u, 0x00000008u, + 0x00000001u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, + 0x00000009u, 0x00006469u, 0x00080005u, 0x0000000du, 0x475f6c67u, 0x61626f6cu, 0x766e496cu, 0x7461636fu, 0x496e6f69u, + 0x00000044u, 0x00040005u, 0x00000012u, 0x69676552u, 0x00006e6fu, 0x00070006u, 0x00000012u, 0x00000000u, 0x72756f73u, + 0x664f6563u, 0x74657366u, 0x00000000u, 0x00050006u, 0x00000012u, 0x00000001u, 0x65747865u, 0x0000746eu, 0x00040005u, + 0x00000014u, 0x69676572u, 0x00006e6fu, 0x00040005u, 0x00000022u, 0x64726f77u, 0x00000000u, 0x00050005u, 0x00000026u, + 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00050005u, 0x0000003eu, 0x6e697774u, 0x67616d49u, 0x00000065u, 0x00040047u, + 0x0000000du, 0x0000000bu, 0x0000001cu, 0x00030047u, 0x00000012u, 0x00000002u, 0x00050048u, 0x00000012u, 0x00000000u, + 0x00000023u, 0x00000000u, 0x00050048u, 0x00000012u, 0x00000001u, 0x00000023u, 0x00000010u, 0x00040047u, 0x00000026u, + 0x00000021u, 0x00000000u, 0x00040047u, 0x00000026u, 0x00000022u, 0x00000000u, 0x00030047u, 0x0000003eu, 0x00000019u, + 0x00040047u, 0x0000003eu, 0x00000021u, 0x00000001u, 0x00040047u, 0x0000003eu, 0x00000022u, 0x00000000u, 0x00040047u, + 0x00000045u, 0x0000000bu, 0x00000019u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, + 0x00000006u, 0x00000020u, 0x00000001u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000003u, 0x00040020u, 0x00000008u, + 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, 0x00000000u, 0x00040017u, 0x0000000bu, 0x0000000au, + 0x00000003u, 0x00040020u, 0x0000000cu, 0x00000001u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000001u, + 0x00040017u, 0x00000011u, 0x00000006u, 0x00000004u, 0x0004001eu, 0x00000012u, 0x00000011u, 0x00000011u, 0x00040020u, + 0x00000013u, 0x00000009u, 0x00000012u, 0x0004003bu, 0x00000013u, 0x00000014u, 0x00000009u, 0x0004002bu, 0x00000006u, + 0x00000015u, 0x00000001u, 0x00040020u, 0x00000016u, 0x00000009u, 0x00000011u, 0x00020014u, 0x0000001au, 0x00040017u, + 0x0000001bu, 0x0000001au, 0x00000003u, 0x00040020u, 0x00000021u, 0x00000007u, 0x0000000au, 0x00090019u, 0x00000023u, + 0x0000000au, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x00000024u, + 0x00000023u, 0x00040020u, 0x00000025u, 0x00000000u, 0x00000024u, 0x0004003bu, 0x00000025u, 0x00000026u, 0x00000000u, + 0x0004002bu, 0x00000006u, 0x00000028u, 0x00000000u, 0x00040017u, 0x00000029u, 0x00000006u, 0x00000002u, 0x0004002bu, + 0x0000000au, 0x00000030u, 0x00000002u, 0x00040020u, 0x00000031u, 0x00000007u, 0x00000006u, 0x00040017u, 0x00000038u, + 0x0000000au, 0x00000004u, 0x0004002bu, 0x0000000au, 0x0000003au, 0x00000000u, 0x00090019u, 0x0000003cu, 0x0000000au, + 0x00000001u, 0x00000000u, 0x00000001u, 0x00000000u, 0x00000002u, 0x00000021u, 0x00040020u, 0x0000003du, 0x00000000u, + 0x0000003cu, 0x0004003bu, 0x0000003du, 0x0000003eu, 0x00000000u, 0x0004002bu, 0x0000000au, 0x00000043u, 0x00000008u, + 0x0004002bu, 0x0000000au, 0x00000044u, 0x00000001u, 0x0006002cu, 0x0000000bu, 0x00000045u, 0x00000043u, 0x00000043u, + 0x00000044u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, + 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000021u, 0x00000022u, 0x00000007u, 0x0004003du, 0x0000000bu, + 0x0000000eu, 0x0000000du, 0x0004007cu, 0x00000007u, 0x0000000fu, 0x0000000eu, 0x0003003eu, 0x00000009u, 0x0000000fu, + 0x0004003du, 0x00000007u, 0x00000010u, 0x00000009u, 0x00050041u, 0x00000016u, 0x00000017u, 0x00000014u, 0x00000015u, + 0x0004003du, 0x00000011u, 0x00000018u, 0x00000017u, 0x0008004fu, 0x00000007u, 0x00000019u, 0x00000018u, 0x00000018u, + 0x00000000u, 0x00000001u, 0x00000002u, 0x000500afu, 0x0000001bu, 0x0000001cu, 0x00000010u, 0x00000019u, 0x0004009au, + 0x0000001au, 0x0000001du, 0x0000001cu, 0x000300f7u, 0x0000001fu, 0x00000000u, 0x000400fau, 0x0000001du, 0x0000001eu, + 0x0000001fu, 0x000200f8u, 0x0000001eu, 0x000100fdu, 0x000200f8u, 0x0000001fu, 0x0004003du, 0x00000024u, 0x00000027u, + 0x00000026u, 0x00050041u, 0x00000016u, 0x0000002au, 0x00000014u, 0x00000028u, 0x0004003du, 0x00000011u, 0x0000002bu, + 0x0000002au, 0x0007004fu, 0x00000029u, 0x0000002cu, 0x0000002bu, 0x0000002bu, 0x00000000u, 0x00000001u, 0x0004003du, + 0x00000007u, 0x0000002du, 0x00000009u, 0x0007004fu, 0x00000029u, 0x0000002eu, 0x0000002du, 0x0000002du, 0x00000000u, + 0x00000001u, 0x00050080u, 0x00000029u, 0x0000002fu, 0x0000002cu, 0x0000002eu, 0x00050041u, 0x00000031u, 0x00000032u, + 0x00000009u, 0x00000030u, 0x0004003du, 0x00000006u, 0x00000033u, 0x00000032u, 0x00050051u, 0x00000006u, 0x00000034u, + 0x0000002fu, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000035u, 0x0000002fu, 0x00000001u, 0x00060050u, 0x00000007u, + 0x00000036u, 0x00000034u, 0x00000035u, 0x00000033u, 0x00040064u, 0x00000023u, 0x00000037u, 0x00000027u, 0x0007005fu, + 0x00000038u, 0x00000039u, 0x00000037u, 0x00000036u, 0x00000002u, 0x00000028u, 0x00050051u, 0x0000000au, 0x0000003bu, + 0x00000039u, 0x00000000u, 0x0003003eu, 0x00000022u, 0x0000003bu, 0x0004003du, 0x0000003cu, 0x0000003fu, 0x0000003eu, + 0x0004003du, 0x00000007u, 0x00000040u, 0x00000009u, 0x0004003du, 0x0000000au, 0x00000041u, 0x00000022u, 0x00070050u, + 0x00000038u, 0x00000042u, 0x00000041u, 0x0000003au, 0x0000003au, 0x0000003au, 0x00040063u, 0x0000003fu, 0x00000040u, + 0x00000042u, 0x000100fdu, 0x00010038u, }; static constexpr Uint32 kWireE5RawCopy3DSpirv[] = { - 0x07230203u, - 0x00010000u, - 0x0008000bu, - 0x0000003du, - 0x00000000u, - 0x00020011u, - 0x00000001u, - 0x0006000bu, - 0x00000001u, - 0x4c534c47u, - 0x6474732eu, - 0x3035342eu, - 0x00000000u, - 0x0003000eu, - 0x00000000u, - 0x00000001u, - 0x0006000fu, - 0x00000005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x0000000du, - 0x00060010u, - 0x00000004u, - 0x00000011u, - 0x00000008u, - 0x00000008u, - 0x00000001u, - 0x00030003u, - 0x00000002u, - 0x000001c2u, - 0x00040005u, - 0x00000004u, - 0x6e69616du, - 0x00000000u, - 0x00030005u, - 0x00000009u, - 0x00006469u, - 0x00080005u, - 0x0000000du, - 0x475f6c67u, - 0x61626f6cu, - 0x766e496cu, - 0x7461636fu, - 0x496e6f69u, - 0x00000044u, - 0x00040005u, - 0x00000012u, - 0x69676552u, - 0x00006e6fu, - 0x00070006u, - 0x00000012u, - 0x00000000u, - 0x72756f73u, - 0x664f6563u, - 0x74657366u, - 0x00000000u, - 0x00050006u, - 0x00000012u, - 0x00000001u, - 0x65747865u, - 0x0000746eu, - 0x00040005u, - 0x00000014u, - 0x69676572u, - 0x00006e6fu, - 0x00040005u, - 0x00000022u, - 0x64726f77u, - 0x00000000u, - 0x00050005u, - 0x00000026u, - 0x72756f73u, - 0x6d496563u, - 0x00656761u, - 0x00050005u, - 0x00000035u, - 0x6e697774u, - 0x67616d49u, - 0x00000065u, - 0x00040047u, - 0x0000000du, - 0x0000000bu, - 0x0000001cu, - 0x00030047u, - 0x00000012u, - 0x00000002u, - 0x00050048u, - 0x00000012u, - 0x00000000u, - 0x00000023u, - 0x00000000u, - 0x00050048u, - 0x00000012u, - 0x00000001u, - 0x00000023u, - 0x00000010u, - 0x00040047u, - 0x00000026u, - 0x00000021u, - 0x00000000u, - 0x00040047u, - 0x00000026u, - 0x00000022u, - 0x00000000u, - 0x00030047u, - 0x00000035u, - 0x00000019u, - 0x00040047u, - 0x00000035u, - 0x00000021u, - 0x00000001u, - 0x00040047u, - 0x00000035u, - 0x00000022u, - 0x00000000u, - 0x00040047u, - 0x0000003cu, - 0x0000000bu, - 0x00000019u, - 0x00020013u, - 0x00000002u, - 0x00030021u, - 0x00000003u, - 0x00000002u, - 0x00040015u, - 0x00000006u, - 0x00000020u, - 0x00000001u, - 0x00040017u, - 0x00000007u, - 0x00000006u, - 0x00000003u, - 0x00040020u, - 0x00000008u, - 0x00000007u, - 0x00000007u, - 0x00040015u, - 0x0000000au, - 0x00000020u, - 0x00000000u, - 0x00040017u, - 0x0000000bu, - 0x0000000au, - 0x00000003u, - 0x00040020u, - 0x0000000cu, - 0x00000001u, - 0x0000000bu, - 0x0004003bu, - 0x0000000cu, - 0x0000000du, - 0x00000001u, - 0x00040017u, - 0x00000011u, - 0x00000006u, - 0x00000004u, - 0x0004001eu, - 0x00000012u, - 0x00000011u, - 0x00000011u, - 0x00040020u, - 0x00000013u, - 0x00000009u, - 0x00000012u, - 0x0004003bu, - 0x00000013u, - 0x00000014u, - 0x00000009u, - 0x0004002bu, - 0x00000006u, - 0x00000015u, - 0x00000001u, - 0x00040020u, - 0x00000016u, - 0x00000009u, - 0x00000011u, - 0x00020014u, - 0x0000001au, - 0x00040017u, - 0x0000001bu, - 0x0000001au, - 0x00000003u, - 0x00040020u, - 0x00000021u, - 0x00000007u, - 0x0000000au, - 0x00090019u, - 0x00000023u, - 0x0000000au, - 0x00000002u, - 0x00000000u, - 0x00000000u, - 0x00000000u, - 0x00000001u, - 0x00000000u, - 0x0003001bu, - 0x00000024u, - 0x00000023u, - 0x00040020u, - 0x00000025u, - 0x00000000u, - 0x00000024u, - 0x0004003bu, - 0x00000025u, - 0x00000026u, - 0x00000000u, - 0x0004002bu, - 0x00000006u, - 0x00000028u, - 0x00000000u, - 0x00040017u, - 0x0000002fu, - 0x0000000au, - 0x00000004u, - 0x0004002bu, - 0x0000000au, - 0x00000031u, - 0x00000000u, - 0x00090019u, - 0x00000033u, - 0x0000000au, - 0x00000002u, - 0x00000000u, - 0x00000000u, - 0x00000000u, - 0x00000002u, - 0x00000021u, - 0x00040020u, - 0x00000034u, - 0x00000000u, - 0x00000033u, - 0x0004003bu, - 0x00000034u, - 0x00000035u, - 0x00000000u, - 0x0004002bu, - 0x0000000au, - 0x0000003au, - 0x00000008u, - 0x0004002bu, - 0x0000000au, - 0x0000003bu, - 0x00000001u, - 0x0006002cu, - 0x0000000bu, - 0x0000003cu, - 0x0000003au, - 0x0000003au, - 0x0000003bu, - 0x00050036u, - 0x00000002u, - 0x00000004u, - 0x00000000u, - 0x00000003u, - 0x000200f8u, - 0x00000005u, - 0x0004003bu, - 0x00000008u, - 0x00000009u, - 0x00000007u, - 0x0004003bu, - 0x00000021u, - 0x00000022u, - 0x00000007u, - 0x0004003du, - 0x0000000bu, - 0x0000000eu, - 0x0000000du, - 0x0004007cu, - 0x00000007u, - 0x0000000fu, - 0x0000000eu, - 0x0003003eu, - 0x00000009u, - 0x0000000fu, - 0x0004003du, - 0x00000007u, - 0x00000010u, - 0x00000009u, - 0x00050041u, - 0x00000016u, - 0x00000017u, - 0x00000014u, - 0x00000015u, - 0x0004003du, - 0x00000011u, - 0x00000018u, - 0x00000017u, - 0x0008004fu, - 0x00000007u, - 0x00000019u, - 0x00000018u, - 0x00000018u, - 0x00000000u, - 0x00000001u, - 0x00000002u, - 0x000500afu, - 0x0000001bu, - 0x0000001cu, - 0x00000010u, - 0x00000019u, - 0x0004009au, - 0x0000001au, - 0x0000001du, - 0x0000001cu, - 0x000300f7u, - 0x0000001fu, - 0x00000000u, - 0x000400fau, - 0x0000001du, - 0x0000001eu, - 0x0000001fu, - 0x000200f8u, - 0x0000001eu, - 0x000100fdu, - 0x000200f8u, - 0x0000001fu, - 0x0004003du, - 0x00000024u, - 0x00000027u, - 0x00000026u, - 0x00050041u, - 0x00000016u, - 0x00000029u, - 0x00000014u, - 0x00000028u, - 0x0004003du, - 0x00000011u, - 0x0000002au, - 0x00000029u, - 0x0008004fu, - 0x00000007u, - 0x0000002bu, - 0x0000002au, - 0x0000002au, - 0x00000000u, - 0x00000001u, - 0x00000002u, - 0x0004003du, - 0x00000007u, - 0x0000002cu, - 0x00000009u, - 0x00050080u, - 0x00000007u, - 0x0000002du, - 0x0000002bu, - 0x0000002cu, - 0x00040064u, - 0x00000023u, - 0x0000002eu, - 0x00000027u, - 0x0007005fu, - 0x0000002fu, - 0x00000030u, - 0x0000002eu, - 0x0000002du, - 0x00000002u, - 0x00000028u, - 0x00050051u, - 0x0000000au, - 0x00000032u, - 0x00000030u, - 0x00000000u, - 0x0003003eu, - 0x00000022u, - 0x00000032u, - 0x0004003du, - 0x00000033u, - 0x00000036u, - 0x00000035u, - 0x0004003du, - 0x00000007u, - 0x00000037u, - 0x00000009u, - 0x0004003du, - 0x0000000au, - 0x00000038u, - 0x00000022u, - 0x00070050u, - 0x0000002fu, - 0x00000039u, - 0x00000038u, - 0x00000031u, - 0x00000031u, - 0x00000031u, - 0x00040063u, - 0x00000036u, - 0x00000037u, - 0x00000039u, - 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000003du, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0006000fu, 0x00000005u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00060010u, 0x00000004u, 0x00000011u, 0x00000008u, 0x00000008u, + 0x00000001u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, + 0x00000009u, 0x00006469u, 0x00080005u, 0x0000000du, 0x475f6c67u, 0x61626f6cu, 0x766e496cu, 0x7461636fu, 0x496e6f69u, + 0x00000044u, 0x00040005u, 0x00000012u, 0x69676552u, 0x00006e6fu, 0x00070006u, 0x00000012u, 0x00000000u, 0x72756f73u, + 0x664f6563u, 0x74657366u, 0x00000000u, 0x00050006u, 0x00000012u, 0x00000001u, 0x65747865u, 0x0000746eu, 0x00040005u, + 0x00000014u, 0x69676572u, 0x00006e6fu, 0x00040005u, 0x00000022u, 0x64726f77u, 0x00000000u, 0x00050005u, 0x00000026u, + 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00050005u, 0x00000035u, 0x6e697774u, 0x67616d49u, 0x00000065u, 0x00040047u, + 0x0000000du, 0x0000000bu, 0x0000001cu, 0x00030047u, 0x00000012u, 0x00000002u, 0x00050048u, 0x00000012u, 0x00000000u, + 0x00000023u, 0x00000000u, 0x00050048u, 0x00000012u, 0x00000001u, 0x00000023u, 0x00000010u, 0x00040047u, 0x00000026u, + 0x00000021u, 0x00000000u, 0x00040047u, 0x00000026u, 0x00000022u, 0x00000000u, 0x00030047u, 0x00000035u, 0x00000019u, + 0x00040047u, 0x00000035u, 0x00000021u, 0x00000001u, 0x00040047u, 0x00000035u, 0x00000022u, 0x00000000u, 0x00040047u, + 0x0000003cu, 0x0000000bu, 0x00000019u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, + 0x00000006u, 0x00000020u, 0x00000001u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000003u, 0x00040020u, 0x00000008u, + 0x00000007u, 0x00000007u, 0x00040015u, 0x0000000au, 0x00000020u, 0x00000000u, 0x00040017u, 0x0000000bu, 0x0000000au, + 0x00000003u, 0x00040020u, 0x0000000cu, 0x00000001u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000001u, + 0x00040017u, 0x00000011u, 0x00000006u, 0x00000004u, 0x0004001eu, 0x00000012u, 0x00000011u, 0x00000011u, 0x00040020u, + 0x00000013u, 0x00000009u, 0x00000012u, 0x0004003bu, 0x00000013u, 0x00000014u, 0x00000009u, 0x0004002bu, 0x00000006u, + 0x00000015u, 0x00000001u, 0x00040020u, 0x00000016u, 0x00000009u, 0x00000011u, 0x00020014u, 0x0000001au, 0x00040017u, + 0x0000001bu, 0x0000001au, 0x00000003u, 0x00040020u, 0x00000021u, 0x00000007u, 0x0000000au, 0x00090019u, 0x00000023u, + 0x0000000au, 0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x00000024u, + 0x00000023u, 0x00040020u, 0x00000025u, 0x00000000u, 0x00000024u, 0x0004003bu, 0x00000025u, 0x00000026u, 0x00000000u, + 0x0004002bu, 0x00000006u, 0x00000028u, 0x00000000u, 0x00040017u, 0x0000002fu, 0x0000000au, 0x00000004u, 0x0004002bu, + 0x0000000au, 0x00000031u, 0x00000000u, 0x00090019u, 0x00000033u, 0x0000000au, 0x00000002u, 0x00000000u, 0x00000000u, + 0x00000000u, 0x00000002u, 0x00000021u, 0x00040020u, 0x00000034u, 0x00000000u, 0x00000033u, 0x0004003bu, 0x00000034u, + 0x00000035u, 0x00000000u, 0x0004002bu, 0x0000000au, 0x0000003au, 0x00000008u, 0x0004002bu, 0x0000000au, 0x0000003bu, + 0x00000001u, 0x0006002cu, 0x0000000bu, 0x0000003cu, 0x0000003au, 0x0000003au, 0x0000003bu, 0x00050036u, 0x00000002u, + 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000008u, 0x00000009u, 0x00000007u, + 0x0004003bu, 0x00000021u, 0x00000022u, 0x00000007u, 0x0004003du, 0x0000000bu, 0x0000000eu, 0x0000000du, 0x0004007cu, + 0x00000007u, 0x0000000fu, 0x0000000eu, 0x0003003eu, 0x00000009u, 0x0000000fu, 0x0004003du, 0x00000007u, 0x00000010u, + 0x00000009u, 0x00050041u, 0x00000016u, 0x00000017u, 0x00000014u, 0x00000015u, 0x0004003du, 0x00000011u, 0x00000018u, + 0x00000017u, 0x0008004fu, 0x00000007u, 0x00000019u, 0x00000018u, 0x00000018u, 0x00000000u, 0x00000001u, 0x00000002u, + 0x000500afu, 0x0000001bu, 0x0000001cu, 0x00000010u, 0x00000019u, 0x0004009au, 0x0000001au, 0x0000001du, 0x0000001cu, + 0x000300f7u, 0x0000001fu, 0x00000000u, 0x000400fau, 0x0000001du, 0x0000001eu, 0x0000001fu, 0x000200f8u, 0x0000001eu, + 0x000100fdu, 0x000200f8u, 0x0000001fu, 0x0004003du, 0x00000024u, 0x00000027u, 0x00000026u, 0x00050041u, 0x00000016u, + 0x00000029u, 0x00000014u, 0x00000028u, 0x0004003du, 0x00000011u, 0x0000002au, 0x00000029u, 0x0008004fu, 0x00000007u, + 0x0000002bu, 0x0000002au, 0x0000002au, 0x00000000u, 0x00000001u, 0x00000002u, 0x0004003du, 0x00000007u, 0x0000002cu, + 0x00000009u, 0x00050080u, 0x00000007u, 0x0000002du, 0x0000002bu, 0x0000002cu, 0x00040064u, 0x00000023u, 0x0000002eu, + 0x00000027u, 0x0007005fu, 0x0000002fu, 0x00000030u, 0x0000002eu, 0x0000002du, 0x00000002u, 0x00000028u, 0x00050051u, + 0x0000000au, 0x00000032u, 0x00000030u, 0x00000000u, 0x0003003eu, 0x00000022u, 0x00000032u, 0x0004003du, 0x00000033u, + 0x00000036u, 0x00000035u, 0x0004003du, 0x00000007u, 0x00000037u, 0x00000009u, 0x0004003du, 0x0000000au, 0x00000038u, + 0x00000022u, 0x00070050u, 0x0000002fu, 0x00000039u, 0x00000038u, 0x00000031u, 0x00000031u, 0x00000031u, 0x00040063u, + 0x00000036u, 0x00000037u, 0x00000039u, 0x000100fdu, 0x00010038u, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.cpp index 6b98ddc7..92cf2bb4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.cpp @@ -19,12 +19,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Generic VkFormat texel decode into the wide RGBA row layouts the shared readback // store expects: GL_FLOAT rows for normalized/float sources, GL_INT / GL_UNSIGNED_INT // rows for integer sources. Missing channels take GL defaults (0,0,0,1). - enum class ReadbackSourceClass : Uint8 { Unsupported, Float, SignedInt, UnsignedInt }; + enum class ReadbackSourceClass : Uint8 { + Unsupported, + Float, + SignedInt, + UnsignedInt + }; struct ReadbackSourceDesc { ReadbackSourceClass sourceClass = ReadbackSourceClass::Unsupported; - Int channels = 0; // component count stored per texel - Int componentBits = 0; // per-component bits for regular formats; 0 for special packed + Int channels = 0; // component count stored per texel + Int componentBits = 0; // per-component bits for regular formats; 0 for special packed Bool isSnorm = false; Bool isSrgb = false; Bool bgraSwizzle = false; @@ -34,74 +39,159 @@ namespace MobileGL::MG_Backend::DirectVulkan { static Bool GetReadbackSourceDesc(VkFormat format, ReadbackSourceDesc& out) { out = ReadbackSourceDesc{}; switch (format) { - // --- regular UNORM --- - case VK_FORMAT_R8_UNORM: out = {ReadbackSourceClass::Float, 1, 8}; return true; - case VK_FORMAT_R8G8_UNORM: out = {ReadbackSourceClass::Float, 2, 8}; return true; - case VK_FORMAT_R8G8B8A8_UNORM: out = {ReadbackSourceClass::Float, 4, 8}; return true; - case VK_FORMAT_B8G8R8A8_UNORM: out = {ReadbackSourceClass::Float, 4, 8, false, false, true}; return true; - case VK_FORMAT_R16_UNORM: out = {ReadbackSourceClass::Float, 1, 16}; return true; - case VK_FORMAT_R16G16_UNORM: out = {ReadbackSourceClass::Float, 2, 16}; return true; - case VK_FORMAT_R16G16B16A16_UNORM: out = {ReadbackSourceClass::Float, 4, 16}; return true; - // --- SRGB (decode to linear like GL readback of sRGB textures) --- - // GL GetTexImage/ReadPixels of sRGB textures return the raw sRGB-encoded - // bytes (GL 3.3 has no FRAMEBUFFER_SRGB read decode) - do NOT linearize. - case VK_FORMAT_R8G8B8A8_SRGB: out = {ReadbackSourceClass::Float, 4, 8}; return true; - case VK_FORMAT_B8G8R8A8_SRGB: out = {ReadbackSourceClass::Float, 4, 8, false, false, true}; return true; - // --- SNORM --- - case VK_FORMAT_R8_SNORM: out = {ReadbackSourceClass::Float, 1, 8, true}; return true; - case VK_FORMAT_R8G8_SNORM: out = {ReadbackSourceClass::Float, 2, 8, true}; return true; - case VK_FORMAT_R8G8B8A8_SNORM: out = {ReadbackSourceClass::Float, 4, 8, true}; return true; - case VK_FORMAT_R16_SNORM: out = {ReadbackSourceClass::Float, 1, 16, true}; return true; - case VK_FORMAT_R16G16_SNORM: out = {ReadbackSourceClass::Float, 2, 16, true}; return true; - case VK_FORMAT_R16G16B16A16_SNORM: out = {ReadbackSourceClass::Float, 4, 16, true}; return true; - // --- SFLOAT --- - case VK_FORMAT_R16_SFLOAT: out = {ReadbackSourceClass::Float, 1, 16}; out.special = format; return true; - case VK_FORMAT_R16G16_SFLOAT: out = {ReadbackSourceClass::Float, 2, 16}; out.special = format; return true; - case VK_FORMAT_R16G16B16A16_SFLOAT: out = {ReadbackSourceClass::Float, 4, 16}; out.special = format; return true; - case VK_FORMAT_R32_SFLOAT: out = {ReadbackSourceClass::Float, 1, 32}; out.special = format; return true; - case VK_FORMAT_R32G32_SFLOAT: out = {ReadbackSourceClass::Float, 2, 32}; out.special = format; return true; - case VK_FORMAT_R32G32B32A32_SFLOAT: out = {ReadbackSourceClass::Float, 4, 32}; out.special = format; return true; - // --- UINT --- - case VK_FORMAT_R8_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 8}; return true; - case VK_FORMAT_R8G8_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 8}; return true; - case VK_FORMAT_R8G8B8A8_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 8}; return true; - case VK_FORMAT_R16_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 16}; return true; - case VK_FORMAT_R16G16_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 16}; return true; - case VK_FORMAT_R16G16B16A16_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 16}; return true; - case VK_FORMAT_R32_UINT: out = {ReadbackSourceClass::UnsignedInt, 1, 32}; return true; - case VK_FORMAT_R32G32_UINT: out = {ReadbackSourceClass::UnsignedInt, 2, 32}; return true; - case VK_FORMAT_R32G32B32A32_UINT: out = {ReadbackSourceClass::UnsignedInt, 4, 32}; return true; - // --- SINT --- - case VK_FORMAT_R8_SINT: out = {ReadbackSourceClass::SignedInt, 1, 8}; return true; - case VK_FORMAT_R8G8_SINT: out = {ReadbackSourceClass::SignedInt, 2, 8}; return true; - case VK_FORMAT_R8G8B8A8_SINT: out = {ReadbackSourceClass::SignedInt, 4, 8}; return true; - case VK_FORMAT_R16_SINT: out = {ReadbackSourceClass::SignedInt, 1, 16}; return true; - case VK_FORMAT_R16G16_SINT: out = {ReadbackSourceClass::SignedInt, 2, 16}; return true; - case VK_FORMAT_R16G16B16A16_SINT: out = {ReadbackSourceClass::SignedInt, 4, 16}; return true; - case VK_FORMAT_R32_SINT: out = {ReadbackSourceClass::SignedInt, 1, 32}; return true; - case VK_FORMAT_R32G32_SINT: out = {ReadbackSourceClass::SignedInt, 2, 32}; return true; - case VK_FORMAT_R32G32B32A32_SINT: out = {ReadbackSourceClass::SignedInt, 4, 32}; return true; - // --- packed / special --- - case VK_FORMAT_A2B10G10R10_UNORM_PACK32: - case VK_FORMAT_A2B10G10R10_UINT_PACK32: - case VK_FORMAT_A2R10G10B10_UNORM_PACK32: - case VK_FORMAT_A2R10G10B10_UINT_PACK32: - case VK_FORMAT_B10G11R11_UFLOAT_PACK32: - case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: - case VK_FORMAT_R5G6B5_UNORM_PACK16: - case VK_FORMAT_B5G6R5_UNORM_PACK16: - case VK_FORMAT_A1R5G5B5_UNORM_PACK16: - case VK_FORMAT_R5G5B5A1_UNORM_PACK16: - case VK_FORMAT_B5G5R5A1_UNORM_PACK16: - case VK_FORMAT_R4G4B4A4_UNORM_PACK16: - case VK_FORMAT_B4G4R4A4_UNORM_PACK16: - out.sourceClass = (format == VK_FORMAT_A2B10G10R10_UINT_PACK32 || - format == VK_FORMAT_A2R10G10B10_UINT_PACK32) ? - ReadbackSourceClass::UnsignedInt : ReadbackSourceClass::Float; - out.special = format; - return true; - default: - return false; + // --- regular UNORM --- + case VK_FORMAT_R8_UNORM: + out = {ReadbackSourceClass::Float, 1, 8}; + return true; + case VK_FORMAT_R8G8_UNORM: + out = {ReadbackSourceClass::Float, 2, 8}; + return true; + case VK_FORMAT_R8G8B8A8_UNORM: + out = {ReadbackSourceClass::Float, 4, 8}; + return true; + case VK_FORMAT_B8G8R8A8_UNORM: + out = {ReadbackSourceClass::Float, 4, 8, false, false, true}; + return true; + case VK_FORMAT_R16_UNORM: + out = {ReadbackSourceClass::Float, 1, 16}; + return true; + case VK_FORMAT_R16G16_UNORM: + out = {ReadbackSourceClass::Float, 2, 16}; + return true; + case VK_FORMAT_R16G16B16A16_UNORM: + out = {ReadbackSourceClass::Float, 4, 16}; + return true; + // --- SRGB (decode to linear like GL readback of sRGB textures) --- + // GL GetTexImage/ReadPixels of sRGB textures return the raw sRGB-encoded + // bytes (GL 3.3 has no FRAMEBUFFER_SRGB read decode) - do NOT linearize. + case VK_FORMAT_R8G8B8A8_SRGB: + out = {ReadbackSourceClass::Float, 4, 8}; + return true; + case VK_FORMAT_B8G8R8A8_SRGB: + out = {ReadbackSourceClass::Float, 4, 8, false, false, true}; + return true; + // --- SNORM --- + case VK_FORMAT_R8_SNORM: + out = {ReadbackSourceClass::Float, 1, 8, true}; + return true; + case VK_FORMAT_R8G8_SNORM: + out = {ReadbackSourceClass::Float, 2, 8, true}; + return true; + case VK_FORMAT_R8G8B8A8_SNORM: + out = {ReadbackSourceClass::Float, 4, 8, true}; + return true; + case VK_FORMAT_R16_SNORM: + out = {ReadbackSourceClass::Float, 1, 16, true}; + return true; + case VK_FORMAT_R16G16_SNORM: + out = {ReadbackSourceClass::Float, 2, 16, true}; + return true; + case VK_FORMAT_R16G16B16A16_SNORM: + out = {ReadbackSourceClass::Float, 4, 16, true}; + return true; + // --- SFLOAT --- + case VK_FORMAT_R16_SFLOAT: + out = {ReadbackSourceClass::Float, 1, 16}; + out.special = format; + return true; + case VK_FORMAT_R16G16_SFLOAT: + out = {ReadbackSourceClass::Float, 2, 16}; + out.special = format; + return true; + case VK_FORMAT_R16G16B16A16_SFLOAT: + out = {ReadbackSourceClass::Float, 4, 16}; + out.special = format; + return true; + case VK_FORMAT_R32_SFLOAT: + out = {ReadbackSourceClass::Float, 1, 32}; + out.special = format; + return true; + case VK_FORMAT_R32G32_SFLOAT: + out = {ReadbackSourceClass::Float, 2, 32}; + out.special = format; + return true; + case VK_FORMAT_R32G32B32A32_SFLOAT: + out = {ReadbackSourceClass::Float, 4, 32}; + out.special = format; + return true; + // --- UINT --- + case VK_FORMAT_R8_UINT: + out = {ReadbackSourceClass::UnsignedInt, 1, 8}; + return true; + case VK_FORMAT_R8G8_UINT: + out = {ReadbackSourceClass::UnsignedInt, 2, 8}; + return true; + case VK_FORMAT_R8G8B8A8_UINT: + out = {ReadbackSourceClass::UnsignedInt, 4, 8}; + return true; + case VK_FORMAT_R16_UINT: + out = {ReadbackSourceClass::UnsignedInt, 1, 16}; + return true; + case VK_FORMAT_R16G16_UINT: + out = {ReadbackSourceClass::UnsignedInt, 2, 16}; + return true; + case VK_FORMAT_R16G16B16A16_UINT: + out = {ReadbackSourceClass::UnsignedInt, 4, 16}; + return true; + case VK_FORMAT_R32_UINT: + out = {ReadbackSourceClass::UnsignedInt, 1, 32}; + return true; + case VK_FORMAT_R32G32_UINT: + out = {ReadbackSourceClass::UnsignedInt, 2, 32}; + return true; + case VK_FORMAT_R32G32B32A32_UINT: + out = {ReadbackSourceClass::UnsignedInt, 4, 32}; + return true; + // --- SINT --- + case VK_FORMAT_R8_SINT: + out = {ReadbackSourceClass::SignedInt, 1, 8}; + return true; + case VK_FORMAT_R8G8_SINT: + out = {ReadbackSourceClass::SignedInt, 2, 8}; + return true; + case VK_FORMAT_R8G8B8A8_SINT: + out = {ReadbackSourceClass::SignedInt, 4, 8}; + return true; + case VK_FORMAT_R16_SINT: + out = {ReadbackSourceClass::SignedInt, 1, 16}; + return true; + case VK_FORMAT_R16G16_SINT: + out = {ReadbackSourceClass::SignedInt, 2, 16}; + return true; + case VK_FORMAT_R16G16B16A16_SINT: + out = {ReadbackSourceClass::SignedInt, 4, 16}; + return true; + case VK_FORMAT_R32_SINT: + out = {ReadbackSourceClass::SignedInt, 1, 32}; + return true; + case VK_FORMAT_R32G32_SINT: + out = {ReadbackSourceClass::SignedInt, 2, 32}; + return true; + case VK_FORMAT_R32G32B32A32_SINT: + out = {ReadbackSourceClass::SignedInt, 4, 32}; + return true; + // --- packed / special --- + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_A2B10G10R10_UINT_PACK32: + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + case VK_FORMAT_A2R10G10B10_UINT_PACK32: + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: + out.sourceClass = + (format == VK_FORMAT_A2B10G10R10_UINT_PACK32 || format == VK_FORMAT_A2R10G10B10_UINT_PACK32) + ? ReadbackSourceClass::UnsignedInt + : ReadbackSourceClass::Float; + out.special = format; + return true; + default: + return false; } } @@ -119,8 +209,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return static_cast(mantissa) / 64.0f * std::pow(2.0f, -14.0f); } if (exponent == 31) { - return mantissa == 0 ? std::numeric_limits::infinity() - : std::numeric_limits::quiet_NaN(); + return mantissa == 0 ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); } return (1.0f + static_cast(mantissa) / 64.0f) * std::pow(2.0f, static_cast(static_cast(exponent)) - 15.0f); @@ -133,137 +222,137 @@ namespace MobileGL::MG_Backend::DirectVulkan { return static_cast(mantissa) / 32.0f * std::pow(2.0f, -14.0f); } if (exponent == 31) { - return mantissa == 0 ? std::numeric_limits::infinity() - : std::numeric_limits::quiet_NaN(); + return mantissa == 0 ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); } return (1.0f + static_cast(mantissa) / 32.0f) * std::pow(2.0f, static_cast(static_cast(exponent)) - 15.0f); } static void DecodeReadbackTexelSpecialFloat(const Uint8* source, VkFormat format, Float* rgba) { - rgba[0] = 0.0f; rgba[1] = 0.0f; rgba[2] = 0.0f; rgba[3] = 1.0f; + rgba[0] = 0.0f; + rgba[1] = 0.0f; + rgba[2] = 0.0f; + rgba[3] = 1.0f; switch (format) { - case VK_FORMAT_R16_SFLOAT: - case VK_FORMAT_R16G16_SFLOAT: - case VK_FORMAT_R16G16B16A16_SFLOAT: { - const Int channels = format == VK_FORMAT_R16_SFLOAT ? 1 : - (format == VK_FORMAT_R16G16_SFLOAT ? 2 : 4); - for (Int c = 0; c < channels; ++c) { - Uint16 bits = 0; - Memcpy(&bits, source + static_cast(c) * sizeof(bits), sizeof(bits)); - rgba[c] = MG_Util::DecodeHalfBitsToFloat(bits); - } - return; + case VK_FORMAT_R16_SFLOAT: + case VK_FORMAT_R16G16_SFLOAT: + case VK_FORMAT_R16G16B16A16_SFLOAT: { + const Int channels = format == VK_FORMAT_R16_SFLOAT ? 1 : (format == VK_FORMAT_R16G16_SFLOAT ? 2 : 4); + for (Int c = 0; c < channels; ++c) { + Uint16 bits = 0; + Memcpy(&bits, source + static_cast(c) * sizeof(bits), sizeof(bits)); + rgba[c] = MG_Util::DecodeHalfBitsToFloat(bits); } - case VK_FORMAT_R32_SFLOAT: - case VK_FORMAT_R32G32_SFLOAT: - case VK_FORMAT_R32G32B32A32_SFLOAT: { - const Int channels = format == VK_FORMAT_R32_SFLOAT ? 1 : - (format == VK_FORMAT_R32G32_SFLOAT ? 2 : 4); - Memcpy(rgba, source, static_cast(channels) * sizeof(Float)); - return; - } - case VK_FORMAT_A2B10G10R10_UNORM_PACK32: { - Uint32 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[0] = static_cast(word & 0x3FFu) / 1023.0f; - rgba[1] = static_cast((word >> 10) & 0x3FFu) / 1023.0f; - rgba[2] = static_cast((word >> 20) & 0x3FFu) / 1023.0f; - rgba[3] = static_cast((word >> 30) & 0x3u) / 3.0f; - return; - } - case VK_FORMAT_A2R10G10B10_UNORM_PACK32: { - Uint32 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[2] = static_cast(word & 0x3FFu) / 1023.0f; - rgba[1] = static_cast((word >> 10) & 0x3FFu) / 1023.0f; - rgba[0] = static_cast((word >> 20) & 0x3FFu) / 1023.0f; - rgba[3] = static_cast((word >> 30) & 0x3u) / 3.0f; - return; - } - case VK_FORMAT_B10G11R11_UFLOAT_PACK32: { - Uint32 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[0] = DecodeUnsignedF11(word & 0x7FFu); - rgba[1] = DecodeUnsignedF11((word >> 11) & 0x7FFu); - rgba[2] = DecodeUnsignedF10((word >> 22) & 0x3FFu); - return; - } - case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: { - Uint32 word = 0; - Memcpy(&word, source, sizeof(word)); - const Int exponent = static_cast((word >> 27) & 0x1Fu) - 15 - 9; - const Float scale = std::pow(2.0f, static_cast(exponent)); - rgba[0] = static_cast(word & 0x1FFu) * scale; - rgba[1] = static_cast((word >> 9) & 0x1FFu) * scale; - rgba[2] = static_cast((word >> 18) & 0x1FFu) * scale; - return; - } - case VK_FORMAT_R5G6B5_UNORM_PACK16: - case VK_FORMAT_B5G6R5_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - const Float c0 = static_cast((word >> 11) & 0x1Fu) / 31.0f; - const Float c1 = static_cast((word >> 5) & 0x3Fu) / 63.0f; - const Float c2 = static_cast(word & 0x1Fu) / 31.0f; - const Bool bgr = format == VK_FORMAT_B5G6R5_UNORM_PACK16; - rgba[0] = bgr ? c2 : c0; - rgba[1] = c1; - rgba[2] = bgr ? c0 : c2; - return; - } - case VK_FORMAT_A1R5G5B5_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[3] = static_cast((word >> 15) & 0x1u); - rgba[0] = static_cast((word >> 10) & 0x1Fu) / 31.0f; - rgba[1] = static_cast((word >> 5) & 0x1Fu) / 31.0f; - rgba[2] = static_cast(word & 0x1Fu) / 31.0f; - return; - } - case VK_FORMAT_R5G5B5A1_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[0] = static_cast((word >> 11) & 0x1Fu) / 31.0f; - rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; - rgba[2] = static_cast((word >> 1) & 0x1Fu) / 31.0f; - rgba[3] = static_cast(word & 0x1u); - return; - } - case VK_FORMAT_B5G5R5A1_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[2] = static_cast((word >> 11) & 0x1Fu) / 31.0f; - rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; - rgba[0] = static_cast((word >> 1) & 0x1Fu) / 31.0f; - rgba[3] = static_cast(word & 0x1u); - return; - } - case VK_FORMAT_R4G4B4A4_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[0] = static_cast((word >> 12) & 0xFu) / 15.0f; - rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; - rgba[2] = static_cast((word >> 4) & 0xFu) / 15.0f; - rgba[3] = static_cast(word & 0xFu) / 15.0f; - return; - } - case VK_FORMAT_B4G4R4A4_UNORM_PACK16: { - Uint16 word = 0; - Memcpy(&word, source, sizeof(word)); - rgba[2] = static_cast((word >> 12) & 0xFu) / 15.0f; - rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; - rgba[0] = static_cast((word >> 4) & 0xFu) / 15.0f; - rgba[3] = static_cast(word & 0xFu) / 15.0f; - return; - } - default: - return; + return; + } + case VK_FORMAT_R32_SFLOAT: + case VK_FORMAT_R32G32_SFLOAT: + case VK_FORMAT_R32G32B32A32_SFLOAT: { + const Int channels = format == VK_FORMAT_R32_SFLOAT ? 1 : (format == VK_FORMAT_R32G32_SFLOAT ? 2 : 4); + Memcpy(rgba, source, static_cast(channels) * sizeof(Float)); + return; + } + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: { + Uint32 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast(word & 0x3FFu) / 1023.0f; + rgba[1] = static_cast((word >> 10) & 0x3FFu) / 1023.0f; + rgba[2] = static_cast((word >> 20) & 0x3FFu) / 1023.0f; + rgba[3] = static_cast((word >> 30) & 0x3u) / 3.0f; + return; + } + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: { + Uint32 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[2] = static_cast(word & 0x3FFu) / 1023.0f; + rgba[1] = static_cast((word >> 10) & 0x3FFu) / 1023.0f; + rgba[0] = static_cast((word >> 20) & 0x3FFu) / 1023.0f; + rgba[3] = static_cast((word >> 30) & 0x3u) / 3.0f; + return; + } + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: { + Uint32 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = DecodeUnsignedF11(word & 0x7FFu); + rgba[1] = DecodeUnsignedF11((word >> 11) & 0x7FFu); + rgba[2] = DecodeUnsignedF10((word >> 22) & 0x3FFu); + return; + } + case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: { + Uint32 word = 0; + Memcpy(&word, source, sizeof(word)); + const Int exponent = static_cast((word >> 27) & 0x1Fu) - 15 - 9; + const Float scale = std::pow(2.0f, static_cast(exponent)); + rgba[0] = static_cast(word & 0x1FFu) * scale; + rgba[1] = static_cast((word >> 9) & 0x1FFu) * scale; + rgba[2] = static_cast((word >> 18) & 0x1FFu) * scale; + return; + } + case VK_FORMAT_R5G6B5_UNORM_PACK16: + case VK_FORMAT_B5G6R5_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + const Float c0 = static_cast((word >> 11) & 0x1Fu) / 31.0f; + const Float c1 = static_cast((word >> 5) & 0x3Fu) / 63.0f; + const Float c2 = static_cast(word & 0x1Fu) / 31.0f; + const Bool bgr = format == VK_FORMAT_B5G6R5_UNORM_PACK16; + rgba[0] = bgr ? c2 : c0; + rgba[1] = c1; + rgba[2] = bgr ? c0 : c2; + return; + } + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[3] = static_cast((word >> 15) & 0x1u); + rgba[0] = static_cast((word >> 10) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 5) & 0x1Fu) / 31.0f; + rgba[2] = static_cast(word & 0x1Fu) / 31.0f; + return; + } + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast((word >> 11) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; + rgba[2] = static_cast((word >> 1) & 0x1Fu) / 31.0f; + rgba[3] = static_cast(word & 0x1u); + return; + } + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[2] = static_cast((word >> 11) & 0x1Fu) / 31.0f; + rgba[1] = static_cast((word >> 6) & 0x1Fu) / 31.0f; + rgba[0] = static_cast((word >> 1) & 0x1Fu) / 31.0f; + rgba[3] = static_cast(word & 0x1u); + return; + } + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[0] = static_cast((word >> 12) & 0xFu) / 15.0f; + rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; + rgba[2] = static_cast((word >> 4) & 0xFu) / 15.0f; + rgba[3] = static_cast(word & 0xFu) / 15.0f; + return; + } + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: { + Uint16 word = 0; + Memcpy(&word, source, sizeof(word)); + rgba[2] = static_cast((word >> 12) & 0xFu) / 15.0f; + rgba[1] = static_cast((word >> 8) & 0xFu) / 15.0f; + rgba[0] = static_cast((word >> 4) & 0xFu) / 15.0f; + rgba[3] = static_cast(word & 0xFu) / 15.0f; + return; + } + default: + return; } } - static Bool DecodeReadbackRowsToWide(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width, - GLsizei height, Vector& outWide, GLenum& outWideType) { + static Bool DecodeReadbackRowsToWide(const Uint8* srcPixels, VkFormat srcFormat, GLsizei width, GLsizei height, + Vector& outWide, GLenum& outWideType) { ReadbackSourceDesc desc{}; if (!GetReadbackSourceDesc(srcFormat, desc)) { return false; @@ -435,10 +524,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector wide; GLenum wideType = GL_FLOAT; - if (!DecodeReadbackRowsToWide(srcPixels, srcFormat, width, - sliceHeight * sliceCount, wide, wideType)) { + if (!DecodeReadbackRowsToWide(srcPixels, srcFormat, width, sliceHeight * sliceCount, wide, wideType)) { MGLOG_E_ONCE("DirectVulkan readback skipped: unsupported source format=%d", - static_cast(srcFormat)); + static_cast(srcFormat)); return false; } @@ -446,8 +534,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // clamping fixed-point (normalized) buffers to [0,1] - visible for SNORM reads. if (applyReadColorClamp && wideType == GL_FLOAT) { const GLenum clampMode = MG_Pipe::gPipeInputs->GetClampReadColor(); - const Bool clamp = clampMode == GL_TRUE || - (clampMode == GL_FIXED_ONLY && !IsFloatingPointReadbackFormat(srcFormat)); + const Bool clamp = + clampMode == GL_TRUE || (clampMode == GL_FIXED_ONLY && !IsFloatingPointReadbackFormat(srcFormat)); if (clamp) { Float* values = reinterpret_cast(wide.data()); const SizeT count = wide.size() / sizeof(Float); @@ -459,8 +547,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool sourceIsInteger = wideType == GL_INT || wideType == GL_UNSIGNED_INT; if (sourceIsInteger != mapping.isInteger) { - MGLOG_E_ONCE("DirectVulkan readback skipped: integerness mismatch (format=0x%x source=%d)", - format, static_cast(srcFormat)); + MGLOG_E_ONCE("DirectVulkan readback skipped: integerness mismatch (format=0x%x source=%d)", format, + static_cast(srcFormat)); return false; } @@ -480,9 +568,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // the line it logs keeps this word and this detail. static String MagmaWireShapeDetail(const char* name, VkFormat format, VkFormat storageFormat, const VkFormatProperties& props, VkImageAspectFlags aspect, - VkImageViewType viewType, VkImageUsageFlags usage, - const VkExtent2D& extent, Uint32 depth, Uint32 levels, - Uint32 baseLayer, Uint32 layerCount, Uint32 arrayLayers) { + VkImageViewType viewType, VkImageUsageFlags usage, const VkExtent2D& extent, + Uint32 depth, Uint32 levels, Uint32 baseLayer, Uint32 layerCount, + Uint32 arrayLayers) { const auto hex = [](Uint32 value) { static const char* digits = "0123456789abcdef"; String out = "0x"; @@ -514,14 +602,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A view that reinterprets its owner's format is the OTHER reason the native blit // arm is skipped, and it is invisible in the format number alone. if (storageFormat != format) out += " storage=" + std::to_string(static_cast(storageFormat)); - out += " extent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height) + - "x" + std::to_string(depth); + out += " extent=" + std::to_string(extent.width) + "x" + std::to_string(extent.height) + "x" + + std::to_string(depth); out += " levels=" + std::to_string(levels); out += " aspect=" + hex(static_cast(aspect)); out += " view=" + std::to_string(static_cast(viewType)); out += " usage=" + hex(static_cast(usage)); - out += " layers=" + std::to_string(baseLayer) + "+" + std::to_string(layerCount) + - "/" + std::to_string(arrayLayers); + out += " layers=" + std::to_string(baseLayer) + "+" + std::to_string(layerCount) + "/" + + std::to_string(arrayLayers); out += " missing=" + missing; return out; } @@ -564,16 +652,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { format == VK_FORMAT_D32_SFLOAT_S8_UINT; } - VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage( - const MG_Pipe::MGPFramebufferState& fbo, const MG_Pipe::MGPSurface& surface, - VkImageAspectFlags aspect, Bool isWriteTarget) { + VulkanRenderer::WireImage VulkanRenderer::ResolveWireImage(const MG_Pipe::MGPFramebufferState& fbo, + const MG_Pipe::MGPSurface& surface, + VkImageAspectFlags aspect, Bool isWriteTarget) { WireImage out{}; out.aspect = aspect; if (fbo.IsDefault) { out.isDefault = true; out.extent = m_swapchainObject.GetExtent(); - out.swapchainImageIndex = isWriteTarget ? DefaultFramebufferWriteIndex(true) - : DefaultFramebufferReadIndex(true); + out.swapchainImageIndex = + isWriteTarget ? DefaultFramebufferWriteIndex(true) : DefaultFramebufferReadIndex(true); if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) { // Every use of the acquired image is reached through here, ahead of its commands: // the work recorded before it goes out without the acquire wait. @@ -599,8 +687,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { uploadTarget <= TextureUploadTarget::CubeMapNegativeZ) layer = static_cast(uploadTarget) - static_cast(TextureUploadTarget::CubeMapPositiveX); VkFormat viewFormat = VK_FORMAT_UNDEFINED; - out.storage = renderbuffer ? surface.Res : m_textureManager->ResolveWireTextureStorage( - surface.Res, level, layer, &viewFormat, &availableLayers); + out.storage = renderbuffer ? surface.Res + : m_textureManager->ResolveWireTextureStorage(surface.Res, level, layer, &viewFormat, + &availableLayers); if (MG_Pipe::MGPipeHandleIsNull(out.storage)) MagmaWireFatal("framebuffer-view-record"); auto* resource = m_textureManager->SyncTextureResourceByHandle(out.storage, renderbuffer); if (!resource) MagmaWireFatal("framebuffer-resource"); @@ -613,8 +702,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } out.image = resource->image; out.format = viewFormat != VK_FORMAT_UNDEFINED ? viewFormat : resource->format; - out.extent = {std::max(resource->extent.width >> level, 1u), - std::max(resource->extent.height >> level, 1u)}; + out.extent = {std::max(resource->extent.width >> level, 1u), std::max(resource->extent.height >> level, 1u)}; out.layout = resource->layout; out.trackedLayout = &resource->layout; out.resource = resource; @@ -629,12 +717,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { out.layer = layer; out.is3D = resource->depth > 1; out.layers = surface.Layered ? (out.is3D ? std::max(resource->depth >> level, 1u) : availableLayers) : 1; - const Bool oneDimensional = resource->viewType == VK_IMAGE_VIEW_TYPE_1D || - resource->viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY; - out.viewType = oneDimensional ? (out.layers > 1 ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) : - (out.layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D); - if (out.level >= resource->mipLevels || out.layer + out.layers > - (out.is3D ? std::max(resource->depth >> level, 1u) : resource->arrayLayers)) + const Bool oneDimensional = + resource->viewType == VK_IMAGE_VIEW_TYPE_1D || resource->viewType == VK_IMAGE_VIEW_TYPE_1D_ARRAY; + out.viewType = oneDimensional ? (out.layers > 1 ? VK_IMAGE_VIEW_TYPE_1D_ARRAY : VK_IMAGE_VIEW_TYPE_1D) + : (out.layers > 1 ? VK_IMAGE_VIEW_TYPE_2D_ARRAY : VK_IMAGE_VIEW_TYPE_2D); + if (out.level >= resource->mipLevels || + out.layer + out.layers > (out.is3D ? std::max(resource->depth >> level, 1u) : resource->arrayLayers)) MagmaWireFatal("framebuffer-subresource"); return out; } @@ -681,19 +769,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memory, 0, nullptr, 0, nullptr); + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memory, 0, nullptr, 0, nullptr); if (coalesceWithLastFullBarrier && m_uniformManager) m_uniformManager->NoteWireImageBarrier(); } } - if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer, image.image, image.layout, layout, - stage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, access, - VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, barrierAspect, 0, image.levels)) + if (!VkTextureManager::TransitionImageLayout( + frame.commandBuffer, image.image, image.layout, layout, stage, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + access, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, barrierAspect, 0, image.levels)) MagmaWireFatal("image-transition"); if (image.trackedLayout) *image.trackedLayout = image.layout; if (image.isDefault) { if (image.aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageLayout(image.swapchainImageIndex, image.layout); - else m_swapchainObject.SetDepthStencilImageLayout(image.swapchainImageIndex, image.layout); + else + m_swapchainObject.SetDepthStencilImageLayout(image.swapchainImageIndex, image.layout); } } @@ -719,7 +808,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // session with a float colour or a depth/stencil format, clearable by a transfer command. Bool VulkanRenderer::WireImageCanDeferClear(const WireImage& image) { const auto* resource = image.resource; - if (resource == nullptr || image.isDefault || image.is3D || image.samples != VK_SAMPLE_COUNT_1_BIT) return false; + if (resource == nullptr || image.isDefault || image.is3D || image.samples != VK_SAMPLE_COUNT_1_BIT) + return false; if (resource->sharedImageId != 0 || resource->yuvImageId != 0) return false; if ((resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) return false; if (image.aspect == VK_IMAGE_ASPECT_COLOR_BIT && @@ -740,8 +830,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { EndActiveRenderPassOn(frame.commandBuffer); // After every earlier access to the image (the clears were issued after them). if (resource.layout != VK_IMAGE_LAYOUT_GENERAL) { - if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer, resource.image, resource.layout, - VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + if (!VkTextureManager::TransitionImageLayout( + frame.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_WRITE_BIT, VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, resource.aspect, 0, resource.mipLevels)) MagmaWireFatal("pending-clear-transition"); @@ -750,13 +841,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memory, 0, nullptr, 0, nullptr); + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &memory, 0, nullptr, 0, nullptr); } for (const auto& pending : resource.pendingClears) { const VkImageSubresourceRange range{pending.aspects, pending.level, 1, pending.layer, pending.layers}; if (pending.aspects & VK_IMAGE_ASPECT_COLOR_BIT) - vkCmdClearColorImage(frame.commandBuffer, resource.image, VK_IMAGE_LAYOUT_GENERAL, - &pending.value.color, 1, &range); + vkCmdClearColorImage(frame.commandBuffer, resource.image, VK_IMAGE_LAYOUT_GENERAL, &pending.value.color, + 1, &range); else vkCmdClearDepthStencilImage(frame.commandBuffer, resource.image, VK_IMAGE_LAYOUT_GENERAL, &pending.value.depthStencil, 1, &range); @@ -808,7 +899,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // pass's own (the depth-only clear framebuffer) - is recorded inside that pass. Returns the // buffers it did not take. GLbitfield VulkanRenderer::ClearWireAttachmentsOfOpenPass(const MG_Pipe::MGPFramebufferState& fbo, - const ClearAttachmentPayload& payload, GLint drawbuffer) { + const ClearAttachmentPayload& payload, GLint drawbuffer) { GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (!remaining || fbo.IsDefault || m_wirePreparationDepth != 0 || !m_wireDrawPass) return remaining; auto& frame = m_frameContext.GetCurrent(); @@ -847,7 +938,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (fbo.Color[slot].Kind == MG_Pipe::kMGPipeSurfaceKindNone) continue; const auto mask = MG_Pipe::gPipeInputs->GetColorMaskIndexed(i); if (!mask.r() && !mask.g() && !mask.b() && !mask.a()) continue; - if (!mask.r() || !mask.g() || !mask.b() || !mask.a() || payload.colorEncoding != ClearColorEncoding::Float) { + if (!mask.r() || !mask.g() || !mask.b() || !mask.a() || + payload.colorEncoding != ClearColorEncoding::Float) { all = false; break; } @@ -868,7 +960,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { clear.clearValue.color = MakeVkClearColorValue(payload, false); } if (all) { - for (Uint32 c = 0; c < colorCount; ++c) clears[count++] = colorClears[c]; + for (Uint32 c = 0; c < colorCount; ++c) + clears[count++] = colorClears[c]; handled |= GL_COLOR_BUFFER_BIT; } } @@ -917,7 +1010,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A whole-image clear of images the open pass does not hold waits on the images (see the // section comment). Returns the buffers it did not take. GLbitfield VulkanRenderer::DeferWireClear(const MG_Pipe::MGPFramebufferState& fbo, - const ClearAttachmentPayload& payload, GLint drawbuffer) { + const ClearAttachmentPayload& payload, GLint drawbuffer) { GLbitfield remaining = payload.mask & (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); if (!remaining || fbo.IsDefault || drawbuffer >= 0 || m_wirePreparationDepth != 0) return remaining; const Bool scissor = GLScissorTestClips(); @@ -966,12 +1059,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { target.value.color = MakeVkClearColorValue(payload, false); target.texture = surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture; } - if (all) handled |= GL_COLOR_BUFFER_BIT; - else targetCount = first; + if (all) + handled |= GL_COLOR_BUFFER_BIT; + else + targetCount = first; } const auto depthStencil = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Bool { const WireImage image = ResolveWireImage(fbo, surface, aspect, true); - if (!image.image || !WireImageCanDeferClear(image) || !wholeImage(image) || heldByOpenPass(image)) return false; + if (!image.image || !WireImageCanDeferClear(image) || !wholeImage(image) || heldByOpenPass(image)) + return false; for (Uint32 t = 0; t < targetCount; ++t) { auto& target = targets[t]; if (target.image.image == image.image && target.image.level == image.level && @@ -1057,7 +1153,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (aspect & VK_IMAGE_ASPECT_STENCIL_BIT) { const Uint32 mask = MG_Pipe::gPipeInputs->GetStencilState(StencilFace::Front).WriteMask & 255u; - if (!mask) aspect &= ~VK_IMAGE_ASPECT_STENCIL_BIT; + if (!mask) + aspect &= ~VK_IMAGE_ASPECT_STENCIL_BIT; else if (mask != 255u) { MGLOG_W_ONCE("DirectVulkan: glClear with a partial stencil write mask is not supported"); aspect &= ~VK_IMAGE_ASPECT_STENCIL_BIT; @@ -1074,21 +1171,26 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int32 x1 = std::min(box.x() + box.z(), static_cast(image.extent.width)); const Int32 y1 = std::min(box.y() + box.w(), static_cast(image.extent.height)); if (x0 >= x1 || y0 >= y1) return; - area = {{x0, y0}, {static_cast(x1-x0), static_cast(y1-y0)}}; - if (image.isDefault && !MapDefaultFramebufferReadbackRect(x0, y0, x1-x0, y1-y0, - image.extent, m_swapchainObject.GetPreTransform(), &area.offset, &area.extent)) + area = {{x0, y0}, {static_cast(x1 - x0), static_cast(y1 - y0)}}; + if (image.isDefault && + !MapDefaultFramebufferReadbackRect(x0, y0, x1 - x0, y1 - y0, image.extent, + m_swapchainObject.GetPreTransform(), &area.offset, &area.extent)) MagmaWireFatal("clear-surface-transform"); } const Bool color = aspect == VK_IMAGE_ASPECT_COLOR_BIT; - const auto layout = color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; + const auto layout = + color ? VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; TransitionWireImage(image, layout); VkImageViewCreateInfo viewInfo{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; viewInfo.image = image.image; viewInfo.viewType = image.viewType; viewInfo.format = image.format; - viewInfo.subresourceRange = {color ? VK_IMAGE_ASPECT_COLOR_BIT : - (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT ? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT : image.aspect), image.level, 1, image.layer, image.layers}; + viewInfo.subresourceRange = { + color ? VK_IMAGE_ASPECT_COLOR_BIT + : (image.format == VK_FORMAT_D24_UNORM_S8_UINT || image.format == VK_FORMAT_D32_SFLOAT_S8_UINT + ? VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT + : image.aspect), + image.level, 1, image.layer, image.layers}; VkImageView view = VK_NULL_HANDLE; VK_VERIFY(vkCreateImageView(m_device, &viewInfo, nullptr, &view)); VkAttachmentDescription attachment{}; @@ -1107,17 +1209,25 @@ namespace MobileGL::MG_Backend::DirectVulkan { subpass.pColorAttachments = color ? &ref : nullptr; subpass.pDepthStencilAttachment = color ? nullptr : &ref; VkRenderPassCreateInfo passInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO}; - passInfo.attachmentCount = 1; passInfo.pAttachments = &attachment; - passInfo.subpassCount = 1; passInfo.pSubpasses = &subpass; + passInfo.attachmentCount = 1; + passInfo.pAttachments = &attachment; + passInfo.subpassCount = 1; + passInfo.pSubpasses = &subpass; VkRenderPass pass = VK_NULL_HANDLE; VK_VERIFY(vkCreateRenderPass(m_device, &passInfo, nullptr, &pass)); VkFramebufferCreateInfo fbInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - fbInfo.renderPass = pass; fbInfo.attachmentCount = 1; fbInfo.pAttachments = &view; - fbInfo.width = image.extent.width; fbInfo.height = image.extent.height; fbInfo.layers = image.layers; + fbInfo.renderPass = pass; + fbInfo.attachmentCount = 1; + fbInfo.pAttachments = &view; + fbInfo.width = image.extent.width; + fbInfo.height = image.extent.height; + fbInfo.layers = image.layers; VkFramebuffer fb = VK_NULL_HANDLE; VK_VERIFY(vkCreateFramebuffer(m_device, &fbInfo, nullptr, &fb)); VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - begin.renderPass = pass; begin.framebuffer = fb; begin.renderArea = area; + begin.renderPass = pass; + begin.framebuffer = fb; + begin.renderArea = area; auto clearPayload = payload; if (color) PreCompensateSrgbClearColor(clearPayload, image.format); VkClearAttachment clear{}; @@ -1138,8 +1248,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!image.isDefault && surface.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) m_textureManager->MarkWireTextureGpuWritten(image.storage, image.level, image.layer, image.layers); if (image.isDefault) { - if (color) m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true); - else m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true); + if (color) + m_swapchainObject.SetImageContentDefined(image.swapchainImageIndex, true); + else + m_swapchainObject.SetDepthStencilContentDefined(image.swapchainImageIndex, true); } }; if (payload.mask & GL_COLOR_BUFFER_BIT) { @@ -1187,12 +1299,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_MEMORY_READ_BIT; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + 1, &barrier, 0, nullptr, 0, nullptr); } - void VulkanRenderer::ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height, - GLenum format, GLenum type, void* pixels) { + void VulkanRenderer::ReadWirePixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + void* pixels) { if (width <= 0 || height <= 0) return; if (MG_Pipe::MGPipeDebugDeviceLossDue()) NoteDeviceLoss(VK_ERROR_DEVICE_LOST, "the debug knob (read-pixels)"); if (m_deviceLost && LatchWireDeviceLoss("read-pixels")) return; @@ -1203,12 +1315,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto* fbo = MG_Pipe::MGPipeApplier().ReadFramebuffer(); if (!fbo) MagmaWireFatal("read-framebuffer-record"); - VkImageAspectFlags aspect = format == GL_DEPTH_COMPONENT ? VK_IMAGE_ASPECT_DEPTH_BIT : - format == GL_STENCIL_INDEX ? VK_IMAGE_ASPECT_STENCIL_BIT : - VK_IMAGE_ASPECT_COLOR_BIT; + VkImageAspectFlags aspect = format == GL_DEPTH_COMPONENT ? VK_IMAGE_ASPECT_DEPTH_BIT + : format == GL_STENCIL_INDEX ? VK_IMAGE_ASPECT_STENCIL_BIT + : VK_IMAGE_ASPECT_COLOR_BIT; if (format == GL_DEPTH_STENCIL) aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; - const auto& surface = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) ? fbo->Depth : - aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? fbo->Stencil : fbo->ReadSurface; + const auto& surface = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) ? fbo->Depth + : aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? fbo->Stencil + : fbo->ReadSurface; WireImage source = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/false); if (!source.image) return; if (source.isDefault && aspect == VK_IMAGE_ASPECT_COLOR_BIT && @@ -1231,9 +1344,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // window's new size one frame before its surface follows the resize. Nothing is // copied, and nothing is recorded. MGLOG_E_ONCE("Magma wire: default-framebuffer read %dx%d at (%d,%d) does not fit surface %llx's " - "%ux%u (transform %u); the read is left unfilled", width, height, x, y, - static_cast(m_activeTargetKey), source.extent.width, - source.extent.height, static_cast(m_swapchainObject.GetPreTransform())); + "%ux%u (transform %u); the read is left unfilled", + width, height, x, y, static_cast(m_activeTargetKey), + source.extent.width, source.extent.height, + static_cast(m_swapchainObject.GetPreTransform())); return; } } @@ -1241,30 +1355,36 @@ namespace MobileGL::MG_Backend::DirectVulkan { RecordWireReadbackWriteBarrier(); TransitionWireImage(source, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); if (aspect != VK_IMAGE_ASPECT_COLOR_BIT) { - ReadDepthStencilImageToClient(source.image,source.format, - source.trackedLayout ? source.trackedLayout : &source.layout,aspect, - source.level,source.layer,x,y,width,height,format,type,pixels,source.isDefault); - if (source.isDefault) m_swapchainObject.SetDepthStencilImageLayout(source.swapchainImageIndex,source.layout); + ReadDepthStencilImageToClient( + source.image, source.format, source.trackedLayout ? source.trackedLayout : &source.layout, aspect, + source.level, source.layer, x, y, width, height, format, type, pixels, source.isDefault); + if (source.isDefault) + m_swapchainObject.SetDepthStencilImageLayout(source.swapchainImageIndex, source.layout); return; } const SizeT texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1 : GetReadbackTexelSize(source.format); if (!texelSize) MagmaWireFatal("read-format"); const VkDeviceSize size = static_cast(width) * height * texelSize; VkBufferObject buffer; - if (!buffer.Create({.allocator=m_allocator, .size=size, .usage=VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage=VMA_MEMORY_USAGE_AUTO, .allocationFlags=VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT})) + if (!buffer.Create({.allocator = m_allocator, + .size = size, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT})) MagmaWireFatal("read-buffer-allocation"); - VkOffset2D offset{x,y}; + VkOffset2D offset{x, y}; VkExtent2D extent{static_cast(width), static_cast(height)}; - if (source.isDefault && !MapDefaultFramebufferReadbackRect(x,y,width,height,source.extent, - m_swapchainObject.GetPreTransform(),&offset,&extent)) MagmaWireFatal("read-surface-transform"); + if (source.isDefault && + !MapDefaultFramebufferReadbackRect(x, y, width, height, source.extent, m_swapchainObject.GetPreTransform(), + &offset, &extent)) + MagmaWireFatal("read-surface-transform"); VkBufferImageCopy region{}; - region.imageSubresource = {aspect,source.level,source.is3D ? 0u : source.layer,1}; - region.imageOffset = {offset.x,offset.y,source.is3D ? static_cast(source.layer) : 0}; - region.imageExtent = {extent.width,extent.height,1}; + region.imageSubresource = {aspect, source.level, source.is3D ? 0u : source.layer, 1}; + region.imageOffset = {offset.x, offset.y, source.is3D ? static_cast(source.layer) : 0}; + region.imageExtent = {extent.width, extent.height, 1}; auto& frame = m_frameContext.GetCurrent(); - vkCmdCopyImageToBuffer(frame.commandBuffer,source.image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - buffer.GetHandle(),1,®ion); + vkCmdCopyImageToBuffer(frame.commandBuffer, source.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + buffer.GetHandle(), 1, ®ion); if (!SubmitReadbackCommandsAndWait(frame)) { if (LatchWireDeviceLoss("read-submit")) return; MagmaWireFatal("read-submit"); @@ -1274,17 +1394,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector remapped; if (source.isDefault) { remapped.resize(static_cast(size)); - if (!RemapDefaultFramebufferReadback(bytes,width,height,m_swapchainObject.GetPreTransform(), - texelSize,remapped.data())) MagmaWireFatal("read-remap"); + if (!RemapDefaultFramebufferReadback(bytes, width, height, m_swapchainObject.GetPreTransform(), texelSize, + remapped.data())) + MagmaWireFatal("read-remap"); bytes = remapped.data(); } - if (!PackReadbackToClientOrPbo(bytes,source.format,width,height,1,format,type,pixels,false,true)) + if (!PackReadbackToClientOrPbo(bytes, source.format, width, height, 1, format, type, pixels, false, true)) MagmaWireFatal("read-conversion"); } - Bool VulkanRenderer::DeclineWireDepthStencilResolveShape(const WireImage& source, - const WireImage& destination, GLint sx0, GLint sy0, GLint sx1, GLint sy1, - GLint dx0, GLint dy0, GLint dx1, GLint dy1) { + Bool VulkanRenderer::DeclineWireDepthStencilResolveShape(const WireImage& source, const WireImage& destination, + GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, + GLint dy0, GLint dx1, GLint dy1) { const Int64 spanX = static_cast(sx1) - sx0; const Int64 spanY = static_cast(sy1) - sy0; const Int64 dstSpanX = static_cast(dx1) - dx0; @@ -1351,13 +1472,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { // a depth-only region would replace the destination's stencil). The multisample resolve's // copy-out and the single-sample copy arm share this body; the returned buffer is still named // by the recorded commands and must be retired with them. - UniquePtr VulkanRenderer::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) { + UniquePtr VulkanRenderer::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) { auto& frame = m_frameContext.GetCurrent(); - const VkDeviceSize texelSize = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? 1 : - (format == VK_FORMAT_D16_UNORM || format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4); + const VkDeviceSize texelSize = + aspect == VK_IMAGE_ASPECT_STENCIL_BIT + ? 1 + : (format == VK_FORMAT_D16_UNORM || format == VK_FORMAT_D16_UNORM_S8_UINT ? 2 : 4); // THE ROW STRIDE IS PADDED TO 4 BYTES. A depth/stencil buffer<->image copy wants // every bufferOffset on a multiple of 4 (VUID-vkCmdCopyBufferToImage-dstImage-07978, // pRegions-07978 in older layers), and the mirror below starts a region on every @@ -1371,9 +1496,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { (static_cast(width) + texelsPer4Bytes - 1) / texelsPer4Bytes * texelsPer4Bytes; const VkDeviceSize bytes = paddedWidth * height * texelSize; auto aspectBuffer = MakeUnique(); - if (!aspectBuffer->Create({.allocator = m_allocator, .size = bytes, - .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE})) + if (!aspectBuffer->Create({.allocator = m_allocator, + .size = bytes, + .usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE})) MagmaWireFatal(fatalDetail); VkBufferImageCopy copy{}; copy.bufferRowLength = static_cast(paddedWidth); @@ -1388,8 +1514,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { barrier.srcQueueFamilyIndex = barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.buffer = aspectBuffer->GetHandle(); barrier.size = bytes; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - 0, 0, nullptr, 1, &barrier, 0, nullptr); + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, + nullptr, 1, &barrier, 0, nullptr); copy.imageSubresource = {aspect, destination.level, destination.layer, 1}; copy.imageOffset = {dx, dy, 0}; if (!mirrorY) { @@ -1410,8 +1536,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { region.imageExtent = {width, 1, 1}; } vkCmdCopyBufferToImage(frame.commandBuffer, aspectBuffer->GetHandle(), destination.image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - static_cast(rows.size()), rows.data()); + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(rows.size()), rows.data()); } return aspectBuffer; } @@ -1429,9 +1554,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // vkCmdBlitImage, and for one aspect of a packed format that moved the whole native word: // lavapipe replaced the destination's stencil on a depth-only blit. Returns false when the // shape is not a plain copy; the caller then takes the vkCmdBlitImage arm as before. - Bool VulkanRenderer::CopyWireDepthStencilBlit(WireImage& source, WireImage& destination, - GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, - VkImageAspectFlags aspect) { + Bool VulkanRenderer::CopyWireDepthStencilBlit(WireImage& source, WireImage& destination, GLint sx0, GLint sy0, + GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1, + VkImageAspectFlags aspect) { const Int64 spanX = static_cast(sx1) - sx0; const Int64 spanY = static_cast(sy1) - sy0; if (spanX == 0 || spanY == 0 || spanX != static_cast(dx1) - dx0 || @@ -1455,10 +1580,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (WireFormatIsPackedDepthStencil(source.format)) { WireRetiredObjects retired{}; retired.buffers.push_back(CopyWireAspectThroughBuffer( - source.image, source.level, source.layer, static_cast(sxMin), - static_cast(syMin), destination, static_cast(dxMin), - static_cast(dyMin), static_cast(width), static_cast(height), - aspect, source.format, /*mirrorY=*/false, "depth-stencil-copy-buffer")); + source.image, source.level, source.layer, static_cast(sxMin), static_cast(syMin), + destination, static_cast(dxMin), static_cast(dyMin), static_cast(width), + static_cast(height), aspect, source.format, /*mirrorY=*/false, "depth-stencil-copy-buffer")); RetireWireObjects(Move(retired)); return true; } @@ -1468,14 +1592,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.dstSubresource = {aspect, destination.level, destination.layer, 1}; copy.dstOffset = {static_cast(dxMin), static_cast(dyMin), 0}; copy.extent = {static_cast(width), static_cast(height), 1}; - vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, source.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + destination.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); return true; } - void VulkanRenderer::ResolveWireDepthStencil(WireImage source, WireImage destination, - GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1) { + void VulkanRenderer::ResolveWireDepthStencil(WireImage source, WireImage destination, GLint sx0, GLint sy0, + GLint sx1, GLint sy1, GLint dx0, GLint dy0, GLint dx1, GLint dy1) { // P7 wave 2-B2 (review round): A REVERSED RECTANGLE IS NOT A MALFORMED ONE. This guard // used to require `sx1 > sx0` and `sy1 > sy0` and take the session with a named Fatal // otherwise - so `glBlitFramebuffer(0, h, w, 0, 0, 0, w, h, COLOR|DEPTH)` out of a @@ -1493,8 +1616,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // forbids (the whole call errors) - and an X-mirrored one mirrored its colour and left // its depth alone. The call below is the same question for a caller that did not ask; // on the BlitWireFramebuffers path it has already answered false. - if (DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1)) - return; + if (DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1)) return; const Int64 spanX = static_cast(sx1) - sx0; const Int64 spanY = static_cast(sy1) - sy0; if (!spanX || !spanY) return; // both sides empty on an axis (the helper errored otherwise) @@ -1504,9 +1626,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool mirrorY = (spanY < 0) != (dy1 < dy0); const GLint sxMin = std::min(sx0, sx1), syMin = std::min(sy0, sy1); const GLint dxMin = std::min(dx0, dx1), dyMin = std::min(dy0, dy1); - if (source.isDefault || destination.isDefault || source.is3D || destination.is3D || - sxMin < 0 || syMin < 0 || dxMin < 0 || dyMin < 0 || - static_cast(sxMin + width) > source.extent.width || + if (source.isDefault || destination.isDefault || source.is3D || destination.is3D || sxMin < 0 || syMin < 0 || + dxMin < 0 || dyMin < 0 || static_cast(sxMin + width) > source.extent.width || static_cast(syMin + height) > source.extent.height || static_cast(dxMin + width) > destination.extent.width || static_cast(dyMin + height) > destination.extent.height) @@ -1527,10 +1648,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // rest of the scratch undefined - which is safe because the copy below reads exactly // that aspect. const Bool extensionResolve = !MagmaWireForcedShaderDepthResolve() && m_wireCreateRenderPass2 && - (!(formatAspects & VK_IMAGE_ASPECT_DEPTH_BIT) || - (m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)) && - (!(formatAspects & VK_IMAGE_ASPECT_STENCIL_BIT) || - (m_wireStencilResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)); + (!(formatAspects & VK_IMAGE_ASPECT_DEPTH_BIT) || + (m_wireDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)) && + (!(formatAspects & VK_IMAGE_ASPECT_STENCIL_BIT) || + (m_wireStencilResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)); // vkCmdResolveImage is color-only. A depth/stencil resolve subpass must // write at the source coordinates and may require both aspects to use @@ -1549,7 +1670,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, &imageInfo, &allocation, &scratch->image, &scratch->allocation, nullptr) != VK_SUCCESS) + if (vmaCreateImage(m_allocator, &imageInfo, &allocation, &scratch->image, &scratch->allocation, nullptr) != + VK_SUCCESS) MagmaWireFatal("multisample-depth-resolve-allocation"); scratch->format = source.format; scratch->extent = source.extent; @@ -1613,34 +1735,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wirePreferShaderDepthResolve ? 1 : 0); } if (!resolvedByShader && extensionResolve) { - TransitionWireImage(source, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - TransitionWireImage(resolved, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + TransitionWireImage(source, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + TransitionWireImage(resolved, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - // The view shapes and the pass are WireDepthResolveProbe.cpp's: the resolve probe runs this - // exact pass on the device at bring-up, so there is one definition of it (g5-msprobe). - const VkImageViewCreateInfo sourceViewInfo = - WireDepthStencilViewInfo(source.image, source.format, formatAspects, source.level, source.layer); - VK_VERIFY(vkCreateImageView(m_device, &sourceViewInfo, nullptr, &views[0])); - const VkImageViewCreateInfo resolvedViewInfo = - WireDepthStencilViewInfo(resolved.image, source.format, formatAspects, 0, 0); - VK_VERIFY(vkCreateImageView(m_device, &resolvedViewInfo, nullptr, &views[1])); - VK_VERIFY(CreateWireDepthStencilResolvePass(m_wireCreateRenderPass2, m_device, source.format, source.samples, - &pass)); - VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; - framebufferInfo.renderPass = pass; - framebufferInfo.attachmentCount = 2; - framebufferInfo.pAttachments = views; - framebufferInfo.width = source.extent.width; - framebufferInfo.height = source.extent.height; - framebufferInfo.layers = 1; - VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer)); - VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; - begin.renderPass = pass; - begin.framebuffer = framebuffer; - begin.renderArea = {{sxMin, syMin}, {static_cast(width), static_cast(height)}}; - EndActiveRenderPassOn(m_frameContext.GetCurrent().commandBuffer); - vkCmdBeginRenderPass(m_frameContext.GetCurrent().commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); - vkCmdEndRenderPass(m_frameContext.GetCurrent().commandBuffer); + // The view shapes and the pass are WireDepthResolveProbe.cpp's: the resolve probe runs this + // exact pass on the device at bring-up, so there is one definition of it (g5-msprobe). + const VkImageViewCreateInfo sourceViewInfo = + WireDepthStencilViewInfo(source.image, source.format, formatAspects, source.level, source.layer); + VK_VERIFY(vkCreateImageView(m_device, &sourceViewInfo, nullptr, &views[0])); + const VkImageViewCreateInfo resolvedViewInfo = + WireDepthStencilViewInfo(resolved.image, source.format, formatAspects, 0, 0); + VK_VERIFY(vkCreateImageView(m_device, &resolvedViewInfo, nullptr, &views[1])); + VK_VERIFY(CreateWireDepthStencilResolvePass(m_wireCreateRenderPass2, m_device, source.format, + source.samples, &pass)); + VkFramebufferCreateInfo framebufferInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + framebufferInfo.renderPass = pass; + framebufferInfo.attachmentCount = 2; + framebufferInfo.pAttachments = views; + framebufferInfo.width = source.extent.width; + framebufferInfo.height = source.extent.height; + framebufferInfo.layers = 1; + VK_VERIFY(vkCreateFramebuffer(m_device, &framebufferInfo, nullptr, &framebuffer)); + VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + begin.renderPass = pass; + begin.framebuffer = framebuffer; + begin.renderArea = {{sxMin, syMin}, {static_cast(width), static_cast(height)}}; + EndActiveRenderPassOn(m_frameContext.GetCurrent().commandBuffer); + vkCmdBeginRenderPass(m_frameContext.GetCurrent().commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); + vkCmdEndRenderPass(m_frameContext.GetCurrent().commandBuffer); } else if (!resolvedByShader) { // NEITHER ARM, and rule I (a) says what that is: this device has no // VK_KHR_depth_stencil_resolve and the pass cannot stand in for it either - the @@ -1654,8 +1776,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { "(stencil export=%d, source usage 0x%x); declining the blit", static_cast(source.aspect), static_cast(source.samples), static_cast(source.format), m_wireCreateRenderPass2 ? 1 : 0, - static_cast(m_wireDepthResolveModes), - static_cast(m_wireStencilResolveModes), + static_cast(m_wireDepthResolveModes), static_cast(m_wireStencilResolveModes), m_wireShaderStencilExport ? 1 : 0, static_cast(source.usageFlags)); RecordTextureCopyError("ResolveWireDepthStencil", ErrorCode::InvalidOperation, "This device cannot resolve that multisample depth/stencil aspect."); @@ -1669,13 +1790,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { // unlike ordinary depth attachment writes. Include both when exposing // the resolved image to a transfer; layout-based inference only covers // EARLY/LATE_FRAGMENT_TESTS and misses the resolve's store operation. - if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer, resolved.image, scratch->layout, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | - VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - VK_ACCESS_TRANSFER_READ_BIT, formatAspects)) + if (!VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resolved.image, scratch->layout, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, formatAspects)) MagmaWireFatal("multisample-depth-resolve-transition"); resolved.layout = scratch->layout; TransitionWireImage(destination, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); @@ -1685,20 +1806,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { // depth/stencil image-copy paths copy the complete native word even // for a single-aspect region. A stencil byte or depth-only word in // this GPU buffer cannot replace the destination's other aspect. - aspectBuffer = CopyWireAspectThroughBuffer(resolved.image, 0, 0, sxMin, syMin, destination, - dxMin, dyMin, static_cast(width), - static_cast(height), source.aspect, - source.format, mirrorY, - "multisample-depth-resolve-buffer"); + aspectBuffer = CopyWireAspectThroughBuffer( + resolved.image, 0, 0, sxMin, syMin, destination, dxMin, dyMin, static_cast(width), + static_cast(height), source.aspect, source.format, mirrorY, "multisample-depth-resolve-buffer"); } else { // A logical GL depth format may use different native renderbuffer // and texture formats. Re-encode only after producing real resolved // samples. Keep all scratch objects local across this helper's flush. if (!BlitDepthAcrossFormats(frame, resolved.image, resolved.format, &scratch->layout, 0, 0, - destination.image, destination.format, destination.trackedLayout, destination.level, destination.layer, - sxMin, syMin, dxMin, dyMin, static_cast(width), static_cast(height), - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - source.aspect == VK_IMAGE_ASPECT_STENCIL_BIT)) + destination.image, destination.format, destination.trackedLayout, + destination.level, destination.layer, sxMin, syMin, dxMin, dyMin, + static_cast(width), static_cast(height), + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + source.aspect == VK_IMAGE_ASPECT_STENCIL_BIT)) MagmaWireFatal("multisample-depth-resolve-conversion"); } WireRetiredObjects retired{}; @@ -1712,8 +1832,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { RetireWireObjects(Move(retired)); } - void VulkanRenderer::BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1, - GLint dx0, GLint dy0, GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) { + void VulkanRenderer::BlitWireFramebuffers(GLint sx0, GLint sy0, GLint sx1, GLint sy1, GLint dx0, GLint dy0, + GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) { RetireWireDrawPass(); // The scissor test clips blit writes: intersect the destination rectangle with // the scissor box and shrink the source proportionally. @@ -1747,8 +1867,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } auto& state = MG_Pipe::MGPipeApplier(); - const Bool named = !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitReadFbo) || - !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitDrawFbo); + const Bool named = + !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitReadFbo) || !MG_Pipe::MGPipeHandleIsNull(state.VerbBlitDrawFbo); const auto* read = named ? state.FramebufferRecordFor(state.VerbBlitReadFbo) : state.ReadFramebuffer(); const auto* draw = named ? state.FramebufferRecordFor(state.VerbBlitDrawFbo) : state.DrawFramebuffer(); if (!read || !draw) MagmaWireFatal("blit-framebuffer-record"); @@ -1780,8 +1900,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!source.image || source.samples == VK_SAMPLE_COUNT_1_BIT) return false; const WireImage destination = ResolveWireImage(*draw, dst, aspect, /*isWriteTarget=*/false); if (!destination.image || destination.samples != VK_SAMPLE_COUNT_1_BIT) return false; - return DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, - dx0, dy0, dx1, dy1); + return DeclineWireDepthStencilResolveShape(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1); }; if (((mask & GL_DEPTH_BUFFER_BIT) && declines(read->Depth, draw->Depth, VK_IMAGE_ASPECT_DEPTH_BIT)) || ((mask & GL_STENCIL_BUFFER_BIT) && declines(read->Stencil, draw->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT))) @@ -1789,15 +1908,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const auto blit = [&](const MG_Pipe::MGPSurface& src, const MG_Pipe::MGPSurface& dst, VkImageAspectFlags aspect) { - WireImage source = ResolveWireImage(*read,src,aspect, /*isWriteTarget=*/false); - WireImage destination = ResolveWireImage(*draw,dst,aspect, /*isWriteTarget=*/true); + WireImage source = ResolveWireImage(*read, src, aspect, /*isWriteTarget=*/false); + WireImage destination = ResolveWireImage(*draw, dst, aspect, /*isWriteTarget=*/true); if (!source.image || !destination.image) return; m_textureManager->FlushPendingUploads(); if (aspect != VK_IMAGE_ASPECT_COLOR_BIT && source.samples != VK_SAMPLE_COUNT_1_BIT && destination.samples == VK_SAMPLE_COUNT_1_BIT) { ResolveWireDepthStencil(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1); if (dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) - m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer, 1); + m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, + destination.layer, 1); return; } if (aspect == VK_IMAGE_ASPECT_COLOR_BIT && source.samples != VK_SAMPLE_COUNT_1_BIT && @@ -1821,8 +1941,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Int64 width = std::abs(static_cast(sx1) - sx0); const Int64 height = std::abs(static_cast(sy1) - sy0); if (!width || !height || dx0 == dx1 || dy0 == dy1) return; - if (source.isDefault || source.is3D || - std::min(sx0, sx1) < 0 || std::min(sy0, sy1) < 0 || + if (source.isDefault || source.is3D || std::min(sx0, sx1) < 0 || std::min(sy0, sy1) < 0 || static_cast(std::max(sx0, sx1)) > source.extent.width || static_cast(std::max(sy0, sy1)) > source.extent.height) MagmaWireFatal("multisample-resolve-region"); @@ -1834,12 +1953,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { info.mipLevels = info.arrayLayers = 1; info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; - info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + info.usage = + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 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) MagmaWireFatal("multisample-resolve-allocation"); scratch->format = source.format; scratch->extent = {info.extent.width, info.extent.height}; @@ -1859,14 +1980,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { resolve.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; resolve.extent = info.extent; vkCmdResolveImage(m_frameContext.GetCurrent().commandBuffer, source.image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, resolved.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &resolve); + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, resolved.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &resolve); const GLint rx0 = sx1 > sx0 ? 0 : static_cast(width); const GLint rx1 = sx1 > sx0 ? static_cast(width) : 0; const GLint ry0 = sy1 > sy0 ? 0 : static_cast(height); const GLint ry1 = sy1 > sy0 ? static_cast(height) : 0; if (destination.isDefault) { - if (!BlitWireColorToDefault(resolved, destination, rx0, ry0, rx1, ry1, - dx0, dy0, dx1, dy1, filter)) + if (!BlitWireColorToDefault(resolved, destination, rx0, ry0, rx1, ry1, dx0, dy0, dx1, dy1, filter)) MagmaWireFatal("multisample-default-blit"); m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex, true); } else { @@ -1874,11 +1995,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { TransitionWireImage(destination, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); VkImageBlit copy{}; copy.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; - copy.srcOffsets[0] = {rx0, ry0, 0}; copy.srcOffsets[1] = {rx1, ry1, 1}; + copy.srcOffsets[0] = {rx0, ry0, 0}; + copy.srcOffsets[1] = {rx1, ry1, 1}; copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, destination.level, - destination.is3D ? 0u : destination.layer, 1}; + destination.is3D ? 0u : destination.layer, 1}; const Int32 z = destination.is3D ? static_cast(destination.layer) : 0; - copy.dstOffsets[0] = {dx0, dy0, z}; copy.dstOffsets[1] = {dx1, dy1, z + 1}; + copy.dstOffsets[0] = {dx0, dy0, z}; + copy.dstOffsets[1] = {dx1, dy1, z + 1}; // The filter only started MATTERING with the scale above: at 1:1 LINEAR and // NEAREST read the same texel, so nothing here ever asked whether the format // could be filtered. VK_FILTER_LINEAR on a source whose optimalTilingFeatures @@ -1890,12 +2013,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkFilter blitFilter = filter == GL_LINEAR && (scratchProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) - ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer, resolved.image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - 1, ©, blitFilter); + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©, blitFilter); if (dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) - m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer, 1); + m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, + destination.layer, 1); } // The shader blit's views were retired first. Keep their backing // image until the same submit completes; resizing a shared @@ -1982,8 +2107,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } if (!MagmaWireForcedDefaultBlitScratch() && - BlitWireColorToDefault(source, destination, sx0, sy0, sx1, sy1, - dx0, dy0, dx1, dy1, filter)) { + BlitWireColorToDefault(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, dy1, filter)) { m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex, true); return; } @@ -1993,19 +2117,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { // must produce the same pixels the fall-through below produces - which is what // makes it reachable from a host lane at all (every host surface here is // identity, and the Redmi evidence is a pbuffer, so nothing else would run it). - if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR || - MagmaWireForcedDefaultBlitScratch()) { - if (BlitWireColorToDefaultThroughScratch(source, destination, sx0, sy0, sx1, sy1, - dx0, dy0, dx1, dy1, filter)) { + if (transform != VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR || MagmaWireForcedDefaultBlitScratch()) { + if (BlitWireColorToDefaultThroughScratch(source, destination, sx0, sy0, sx1, sy1, dx0, dy0, dx1, + dy1, filter)) { m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex, true); return; } MGLOG_E_ONCE("DirectVulkan::BlitWireFramebuffers: a %ux%u source of format %d " "(view type %d) cannot reach the rotated (0x%x) default framebuffer " "through either the shader pass or a scratch copy; declining the blit", - source.extent.width, source.extent.height, - static_cast(source.format), static_cast(source.viewType), - static_cast(transform)); + source.extent.width, source.extent.height, static_cast(source.format), + static_cast(source.viewType), static_cast(transform)); return; } // Identity, and the shader pass above declined this source (an integer format, a @@ -2020,9 +2142,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool sameImage = source.image == destination.image; const VkImageLayout srcLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; const VkImageLayout dstLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - TransitionWireImage(source,srcLayout); - if (sameImage) destination.layout = source.layout; - else TransitionWireImage(destination,dstLayout); + TransitionWireImage(source, srcLayout); + if (sameImage) + destination.layout = source.layout; + else + TransitionWireImage(destination, dstLayout); VkImageBlit region{}; // A 3D IMAGE CARRIES ITS SLICE ON Z, NOT ON THE LAYER AXIS. Its only subresource is // layer 0, and the selected slice is the z of the offsets - which is how the copy @@ -2035,18 +2159,20 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 dstBaseLayer = destination.is3D ? 0u : destination.layer; const Int32 srcZ = source.is3D ? static_cast(source.layer) : 0; const Int32 dstZ = destination.is3D ? static_cast(destination.layer) : 0; - region.srcSubresource = {aspect,source.level,srcBaseLayer,1}; - region.dstSubresource = {aspect,destination.level,dstBaseLayer,1}; - region.srcOffsets[0] = {sx0,sy0,srcZ}; region.srcOffsets[1] = {sx1,sy1,srcZ+1}; - region.dstOffsets[0] = {dx0,dy0,dstZ}; region.dstOffsets[1] = {dx1,dy1,dstZ+1}; + region.srcSubresource = {aspect, source.level, srcBaseLayer, 1}; + region.dstSubresource = {aspect, destination.level, dstBaseLayer, 1}; + region.srcOffsets[0] = {sx0, sy0, srcZ}; + region.srcOffsets[1] = {sx1, sy1, srcZ + 1}; + region.dstOffsets[0] = {dx0, dy0, dstZ}; + region.dstOffsets[1] = {dx1, dy1, dstZ + 1}; // Headless and identity surfaces store GL's bottom row at Vulkan's last row. if (source.isDefault) { - region.srcOffsets[0].y = static_cast(source.extent.height)-sy0; - region.srcOffsets[1].y = static_cast(source.extent.height)-sy1; + region.srcOffsets[0].y = static_cast(source.extent.height) - sy0; + region.srcOffsets[1].y = static_cast(source.extent.height) - sy1; } if (destination.isDefault) { - region.dstOffsets[0].y = static_cast(destination.extent.height)-dy0; - region.dstOffsets[1].y = static_cast(destination.extent.height)-dy1; + region.dstOffsets[0].y = static_cast(destination.extent.height) - dy0; + region.dstOffsets[1].y = static_cast(destination.extent.height) - dy1; } auto command = m_frameContext.GetCurrent().commandBuffer; if (source.samples != VK_SAMPLE_COUNT_1_BIT || destination.samples != VK_SAMPLE_COUNT_1_BIT) { @@ -2073,11 +2199,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { "rectangle (format %d, %u sample(s)) - a blit onto a multisample " "attachment carries no scale, no flip and no format conversion; " "declining the blit", - width, height, static_cast(source.format), - static_cast(source.samples), + width, height, static_cast(source.format), static_cast(source.samples), region.dstOffsets[1].x - region.dstOffsets[0].x, - region.dstOffsets[1].y - region.dstOffsets[0].y, - static_cast(destination.format), + region.dstOffsets[1].y - region.dstOffsets[0].y, static_cast(destination.format), static_cast(destination.samples)); RecordTextureCopyError("BlitWireFramebuffers", ErrorCode::InvalidOperation, "glBlitFramebuffer onto a multisampled draw framebuffer requires " @@ -2085,8 +2209,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } if (source.samples == destination.samples) { - VkImageCopy copy{region.srcSubresource, region.srcOffsets[0], region.dstSubresource, - region.dstOffsets[0], {static_cast(width), static_cast(height), 1}}; + VkImageCopy copy{region.srcSubresource, + region.srcOffsets[0], + region.dstSubresource, + region.dstOffsets[0], + {static_cast(width), static_cast(height), 1}}; vkCmdCopyImage(command, source.image, srcLayout, destination.image, dstLayout, 1, ©); } else { // `multisample-blit-aspect@P7` RETIRES. The half with an arm is @@ -2118,31 +2245,34 @@ namespace MobileGL::MG_Backend::DirectVulkan { return; } } else { - vkCmdBlitImage(command,source.image,srcLayout,destination.image,dstLayout,1,®ion, - filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); + vkCmdBlitImage(command, source.image, srcLayout, destination.image, dstLayout, 1, ®ion, + filter == GL_LINEAR ? VK_FILTER_LINEAR : VK_FILTER_NEAREST); } if (!destination.isDefault && dst.Kind == MG_Pipe::kMGPipeSurfaceKindTexture) - m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer, destination.layers); + m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer, + destination.layers); if (destination.isDefault) { - if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex,true); - else m_swapchainObject.SetDepthStencilContentDefined(destination.swapchainImageIndex,true); + if (aspect == VK_IMAGE_ASPECT_COLOR_BIT) + m_swapchainObject.SetImageContentDefined(destination.swapchainImageIndex, true); + else + m_swapchainObject.SetDepthStencilContentDefined(destination.swapchainImageIndex, true); } }; if (mask & GL_COLOR_BUFFER_BIT) { - for (Uint32 i=0;iDrawBuffers[i]; - if (attachment>=0 && attachment(MG_Pipe::kMGPipeMaxColorAttachments)) - blit(read->ReadSurface,draw->Color[attachment],VK_IMAGE_ASPECT_COLOR_BIT); + if (attachment >= 0 && attachment < static_cast(MG_Pipe::kMGPipeMaxColorAttachments)) + blit(read->ReadSurface, draw->Color[attachment], VK_IMAGE_ASPECT_COLOR_BIT); } } - if (mask & GL_DEPTH_BUFFER_BIT) blit(read->Depth,draw->Depth,VK_IMAGE_ASPECT_DEPTH_BIT); - if (mask & GL_STENCIL_BUFFER_BIT) blit(read->Stencil,draw->Stencil,VK_IMAGE_ASPECT_STENCIL_BIT); + if (mask & GL_DEPTH_BUFFER_BIT) blit(read->Depth, draw->Depth, VK_IMAGE_ASPECT_DEPTH_BIT); + if (mask & GL_STENCIL_BUFFER_BIT) blit(read->Stencil, draw->Stencil, VK_IMAGE_ASPECT_STENCIL_BIT); } - void VulkanRenderer::CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint x, GLint y, GLsizei width, GLsizei height) { + void VulkanRenderer::CopyWireFramebufferToTexture(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, + GLint y, GLsizei width, GLsizei height) { if (width <= 0 || height <= 0) return; if (target != GL_TEXTURE_2D) { - RecordTextureCopyError(__func__,ErrorCode::InvalidOperation, + RecordTextureCopyError(__func__, ErrorCode::InvalidOperation, "CopyTexSubImage2D currently only supports GL_TEXTURE_2D destinations."); return; } @@ -2158,30 +2288,37 @@ namespace MobileGL::MG_Backend::DirectVulkan { surface.Kind = MG_Pipe::kMGPipeSurfaceKindTexture; surface.Res = state.VerbCopyTexDst; surface.Level = static_cast(level); - WireImage source = ResolveWireImage(*read,aspect & VK_IMAGE_ASPECT_DEPTH_BIT ? read->Depth : - aspect & VK_IMAGE_ASPECT_STENCIL_BIT ? read->Stencil : read->ReadSurface,aspect, - /*isWriteTarget=*/false); - WireImage destination = ResolveWireImage(fake,surface,aspect, /*isWriteTarget=*/false); + WireImage source = ResolveWireImage(*read, + aspect & VK_IMAGE_ASPECT_DEPTH_BIT ? read->Depth + : aspect & VK_IMAGE_ASPECT_STENCIL_BIT ? read->Stencil + : read->ReadSurface, + aspect, + /*isWriteTarget=*/false); + WireImage destination = ResolveWireImage(fake, surface, aspect, /*isWriteTarget=*/false); if (!source.image || !destination.image) return; m_textureManager->FlushPendingUploads(); const Bool sameImage = source.image == destination.image; const VkImageLayout srcLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; const VkImageLayout dstLayout = sameImage ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; - TransitionWireImage(source,srcLayout); - if (sameImage) destination.layout = source.layout; - else TransitionWireImage(destination,dstLayout); + TransitionWireImage(source, srcLayout); + if (sameImage) + destination.layout = source.layout; + else + TransitionWireImage(destination, dstLayout); VkImageBlit region{}; - region.srcSubresource = {aspect,source.level,source.layer,1}; - region.dstSubresource = {aspect,destination.level,destination.layer,1}; - region.srcOffsets[0] = {x,y,0}; region.srcOffsets[1] = {x+width,y+height,1}; - region.dstOffsets[0] = {xoffset,yoffset,0}; region.dstOffsets[1] = {xoffset+width,yoffset+height,1}; + region.srcSubresource = {aspect, source.level, source.layer, 1}; + region.dstSubresource = {aspect, destination.level, destination.layer, 1}; + region.srcOffsets[0] = {x, y, 0}; + region.srcOffsets[1] = {x + width, y + height, 1}; + region.dstOffsets[0] = {xoffset, yoffset, 0}; + region.dstOffsets[1] = {xoffset + width, yoffset + height, 1}; if (source.isDefault) { - region.srcOffsets[0].y = static_cast(source.extent.height)-y; - region.srcOffsets[1].y = static_cast(source.extent.height)-y-height; + region.srcOffsets[0].y = static_cast(source.extent.height) - y; + region.srcOffsets[1].y = static_cast(source.extent.height) - y - height; } - vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer,source.image,srcLayout, - destination.image,dstLayout,1,®ion,VK_FILTER_NEAREST); - m_textureManager->MarkWireTextureGpuWritten(destination.storage,destination.level,destination.layer); + vkCmdBlitImage(m_frameContext.GetCurrent().commandBuffer, source.image, srcLayout, destination.image, dstLayout, + 1, ®ion, VK_FILTER_NEAREST); + m_textureManager->MarkWireTextureGpuWritten(destination.storage, destination.level, destination.layer); } void VulkanRenderer::GenerateWireMipmap() { @@ -2211,8 +2348,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (logicalEnd <= logicalBase + 1) return; Uint32 base = logicalBase, baseLayer = 0, layerCount = 1; VkFormat viewFormat = VK_FORMAT_UNDEFINED; - const auto handle = m_textureManager->ResolveWireTextureStorage(state.VerbMipRes, base, baseLayer, - &viewFormat, &layerCount); + const auto handle = + m_textureManager->ResolveWireTextureStorage(state.VerbMipRes, base, baseLayer, &viewFormat, &layerCount); auto* resource = m_textureManager->SyncTextureResourceByHandle(handle); if (!resource) MagmaWireFatal("mipmap-resource"); // A shared image is one level of a buffer someone else allocated; growing the chain would @@ -2228,8 +2365,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& frame = m_frameContext.GetCurrent(); if (!frame.isCommandRecording) m_frameContext.BeginCommandRecording(); Uint32 fullLevelCount = 1; - for (Uint32 size = std::max({resource->extent.width, resource->extent.height, resource->depth}); - size > 1; size >>= 1) ++fullLevelCount; + for (Uint32 size = std::max({resource->extent.width, resource->extent.height, resource->depth}); size > 1; + size >>= 1) + ++fullLevelCount; if (base >= fullLevelCount) MagmaWireFatal("mipmap-base-extent"); const Uint32 count = std::min(logicalEnd - logicalBase, fullLevelCount - base); if (count <= 1) return; @@ -2251,8 +2389,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { "DirectVulkan::GenerateWireMipmap: aspect 0x%x (a combined depth/stencil or " "stencil-only texture, format %d) has no mip generation on either arm; " "declining and leaving levels %u..%u as they are", - static_cast(resource->aspect), static_cast(resource->format), - base + 1, base + count - 1); + static_cast(resource->aspect), static_cast(resource->format), base + 1, + base + count - 1); return; } // P8-F: WHICH ARM TAKES THIS CHAIN IS DECIDED BEFORE THE CHAIN IS GROWN, for the @@ -2261,15 +2399,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { // same before and after it (GrowWireTextureMipChain carries every one of them over). VkFormatProperties props{}; const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat; - vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle,format,&props); + vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, format, &props); const auto required = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT; const Uint32 forcedShaderTier = MagmaWireForcedShaderMipmapTier(); - const Bool nativeBlit = forcedShaderTier == 0 && format == resource->format && - (props.optimalTilingFeatures & required) == required; - const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT ? - VK_FILTER_LINEAR : VK_FILTER_NEAREST; - const Bool layersInRange = baseLayer < resource->arrayLayers && - layerCount <= resource->arrayLayers - baseLayer; + const Bool nativeBlit = + forcedShaderTier == 0 && format == resource->format && (props.optimalTilingFeatures & required) == required; + const VkFilter filter = props.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; + const Bool layersInRange = baseLayer < resource->arrayLayers && layerCount <= resource->arrayLayers - baseLayer; // Both shader arms render a 2D view of one layer of one level, so a 1D, 1D-array or 3D // image is a shape neither takes. const Bool planar = resource->viewType != VK_IMAGE_VIEW_TYPE_1D && @@ -2283,20 +2421,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { // view of the 1D image. const auto depthPassFeatures = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; - const Bool depthArmTakesShape = planar && - layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures; + const Bool depthArmTakesShape = + planar && layersInRange && (props.optimalTilingFeatures & depthPassFeatures) == depthPassFeatures; // The colour arm's shape (the two shader shapes are explained at the colour arm below). const Bool colour = resource->aspect == VK_IMAGE_ASPECT_COLOR_BIT; // The shader arm samples through a sampler2D and writes a vec4, which is what // BlitWireColorImage's own float check enforces; naming it here rather than letting the // blit return false is what puts the reason in the decline line. const Bool sampledFloat = vkuFormatIsSampledFloat(format); - const Bool renderableFormat = - (props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; - const Bool attachable = forcedShaderTier < 2 && - (resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; - const Bool viaScratch = !attachable && renderableFormat && - (resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; + const Bool renderableFormat = (props.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0; + const Bool attachable = + forcedShaderTier < 2 && (resource->usageFlags & VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT) != 0; + const Bool viaScratch = + !attachable && renderableFormat && (resource->usageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) != 0; const Bool colourArmTakesShape = colour && planar && layersInRange && sampledFloat && (attachable || viaScratch); // P8-F: `mipmap-shader-format-or-shape` IS A NAMED DECLINE, NOT A FATAL (rule I (a)). @@ -2307,15 +2444,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool shaderArmTakesShape = resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT ? depthArmTakesShape : colourArmTakesShape; if (!nativeBlit && !shaderArmTakesShape) { - const String detail = MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, - resource->format, props, resource->aspect, resource->viewType, resource->usageFlags, - resource->extent, resource->depth, resource->mipLevels, baseLayer, layerCount, - resource->arrayLayers); - MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; no mip arm takes this shape, levels %u..%u left as they are", - detail.c_str(), base + 1, base + count - 1); + const String detail = + MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, resource->format, props, resource->aspect, + resource->viewType, resource->usageFlags, resource->extent, resource->depth, + resource->mipLevels, baseLayer, layerCount, resource->arrayLayers); + MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, + "%s; no mip arm takes this shape, levels %u..%u left as they are", detail.c_str(), + base + 1, base + count - 1); return; } - if (!m_textureManager->GrowWireTextureMipChain(handle,base+count,frame.commandBuffer)) + if (!m_textureManager->GrowWireTextureMipChain(handle, base + count, frame.commandBuffer)) MagmaWireFatal("mipmap-growth"); if (!nativeBlit && resource->aspect == VK_IMAGE_ASPECT_DEPTH_BIT) { // P7 wave 2-B, CONTRACT-P7 §5.1 (A): THE DEPTH ARM, and it is first because a @@ -2355,9 +2493,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // - and then it is the same named decline (P8-F), not a session's end. It // can only refuse the first level: the shape does not change down the chain. if (!GenerateWireDepthMipLevel(source, destination)) { - const String detail = MagmaWireShapeDetail("mipmap-shader-format-or-shape", format, - resource->format, props, resource->aspect, resource->viewType, - resource->usageFlags, resource->extent, resource->depth, + const String detail = MagmaWireShapeDetail( + "mipmap-shader-format-or-shape", format, resource->format, props, resource->aspect, + resource->viewType, resource->usageFlags, resource->extent, resource->depth, resource->mipLevels, baseLayer, layerCount, resource->arrayLayers); MGL_WIRE_DECLINE_AT(MipmapShaderFormatOrShape, "%s; the depth pass refused level %u", detail.c_str(), level); @@ -2401,9 +2539,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::max(resource->extent.height >> level, 1u)}; const GLenum blitFilter = filter == VK_FILTER_LINEAR ? GL_LINEAR : GL_NEAREST; if (attachable) { - if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, - source.extent.height, 0, 0, destination.extent.width, - destination.extent.height, blitFilter, true)) + if (!BlitWireColorImage(source, destination, 0, 0, source.extent.width, source.extent.height, 0, + 0, destination.extent.width, destination.extent.height, blitFilter, + true)) MagmaWireFatal("mipmap-shader-blit"); continue; } @@ -2424,8 +2562,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) MagmaWireFatal("mipmap-shader-scratch-allocation"); scratch->format = format; scratch->extent = destination.extent; @@ -2437,9 +2575,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { target.aspect = VK_IMAGE_ASPECT_COLOR_BIT; target.extent = destination.extent; target.trackedLayout = &scratch->layout; - if (!BlitWireColorImage(source, target, 0, 0, source.extent.width, - source.extent.height, 0, 0, target.extent.width, target.extent.height, - blitFilter, true)) + if (!BlitWireColorImage(source, target, 0, 0, source.extent.width, source.extent.height, 0, 0, + target.extent.width, target.extent.height, blitFilter, true)) MagmaWireFatal("mipmap-shader-blit"); target.layout = scratch->layout; TransitionWireImage(target, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); @@ -2451,8 +2588,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { copy.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, level, layer, 1}; copy.extent = {destination.extent.width, destination.extent.height, 1}; vkCmdCopyImage(m_frameContext.GetCurrent().commandBuffer, scratch->image, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, resource->image, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, resource->image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); WireRetiredObjects retired{}; retired.textures.push_back(Move(scratch)); RetireWireObjects(Move(retired)); @@ -2461,10 +2598,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else { VkPipelineStageFlags stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkAccessFlags access = 0; - GetImageTransitionSourceState(resource->layout,stage,access); - if (!VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,resource->layout, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,stage,VK_PIPELINE_STAGE_TRANSFER_BIT,access, - VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,0,resource->mipLevels)) MagmaWireFatal("mipmap-transition"); + GetImageTransitionSourceState(resource->layout, stage, access); + if (!VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, stage, + VK_PIPELINE_STAGE_TRANSFER_BIT, access, VK_ACCESS_TRANSFER_WRITE_BIT, resource->aspect, 0, + resource->mipLevels)) + MagmaWireFatal("mipmap-transition"); // P8-SV: A 3D IMAGE HAS ONE ARRAY LAYER, AND ITS DEPTH IS THE Z EXTENT OF THE BLIT. // ResolveWireTextureStorage answers `layerCount` in slices for Tex3D (Depth >> level, // VkTextureManager.cpp) - the addressing attachments, descriptors and readback use - and @@ -2475,30 +2614,32 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool volume = resource->viewType == VK_IMAGE_VIEW_TYPE_3D; const Uint32 blitBaseLayer = volume ? 0u : baseLayer; const Uint32 blitLayerCount = volume ? 1u : layerCount; - for (Uint32 level=base+1;levelimage,previous, - VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT,VK_ACCESS_TRANSFER_READ_BIT,resource->aspect,level-1,1); + VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, previous, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_ACCESS_TRANSFER_READ_BIT, resource->aspect, level - 1, 1); VkImageBlit blit{}; - blit.srcSubresource = {resource->aspect,level-1,blitBaseLayer,blitLayerCount}; - blit.dstSubresource = {resource->aspect,level,blitBaseLayer,blitLayerCount}; - blit.srcOffsets[1] = {static_cast(std::max(resource->extent.width>>(level-1),1u)), - static_cast(std::max(resource->extent.height>>(level-1),1u)), - static_cast(std::max(resource->depth>>(level-1),1u))}; - blit.dstOffsets[1] = {static_cast(std::max(resource->extent.width>>level,1u)), - static_cast(std::max(resource->extent.height>>level,1u)), - static_cast(std::max(resource->depth>>level,1u))}; - vkCmdBlitImage(frame.commandBuffer,resource->image,VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - resource->image,VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,1,&blit,filter); - VkTextureManager::TransitionImageLayout(frame.commandBuffer,resource->image,previous, - VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,VK_PIPELINE_STAGE_TRANSFER_BIT,VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_TRANSFER_READ_BIT,VK_ACCESS_TRANSFER_WRITE_BIT,resource->aspect,level-1,1); + blit.srcSubresource = {resource->aspect, level - 1, blitBaseLayer, blitLayerCount}; + blit.dstSubresource = {resource->aspect, level, blitBaseLayer, blitLayerCount}; + blit.srcOffsets[1] = {static_cast(std::max(resource->extent.width >> (level - 1), 1u)), + static_cast(std::max(resource->extent.height >> (level - 1), 1u)), + static_cast(std::max(resource->depth >> (level - 1), 1u))}; + blit.dstOffsets[1] = {static_cast(std::max(resource->extent.width >> level, 1u)), + static_cast(std::max(resource->extent.height >> level, 1u)), + static_cast(std::max(resource->depth >> level, 1u))}; + vkCmdBlitImage(frame.commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, filter); + VkTextureManager::TransitionImageLayout( + frame.commandBuffer, resource->image, previous, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, resource->aspect, level - 1, 1); } resource->layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; } auto& record = state.TextureResources[handle.Slot]; - record.Desc.Levels = std::max(record.Desc.Levels,static_cast(base+count)); + record.Desc.Levels = std::max(record.Desc.Levels, static_cast(base + count)); resource->syncedWireSerial = ++record.Serial; auto& store = MG_Record::ServerStagedTexture(); const Uint64 key = MG_Record::StagedTextureStore::KeyForHandle(handle); @@ -2509,24 +2650,37 @@ namespace MobileGL::MG_Backend::DirectVulkan { face < (target == MG_Pipe::MGPipeResourceTarget::TexCube ? baseLayer + layerCount : 1u); ++face) { TextureUploadTarget upload = TextureUploadTarget::Texture2D; switch (target) { - case MG_Pipe::MGPipeResourceTarget::Tex1D: upload=TextureUploadTarget::Texture1D; break; - case MG_Pipe::MGPipeResourceTarget::Tex3D: upload=TextureUploadTarget::Texture3D; break; - case MG_Pipe::MGPipeResourceTarget::Tex1DArray: upload=TextureUploadTarget::Texture1DArray; break; - case MG_Pipe::MGPipeResourceTarget::Tex2DArray: upload=TextureUploadTarget::Texture2DArray; break; - case MG_Pipe::MGPipeResourceTarget::TexCubeArray: upload=TextureUploadTarget::CubeMapArray; break; + case MG_Pipe::MGPipeResourceTarget::Tex1D: + upload = TextureUploadTarget::Texture1D; + break; + case MG_Pipe::MGPipeResourceTarget::Tex3D: + upload = TextureUploadTarget::Texture3D; + break; + case MG_Pipe::MGPipeResourceTarget::Tex1DArray: + upload = TextureUploadTarget::Texture1DArray; + break; + case MG_Pipe::MGPipeResourceTarget::Tex2DArray: + upload = TextureUploadTarget::Texture2DArray; + break; + case MG_Pipe::MGPipeResourceTarget::TexCubeArray: + upload = TextureUploadTarget::CubeMapArray; + break; case MG_Pipe::MGPipeResourceTarget::TexCube: - upload=static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX)+face); break; - default: break; + upload = + static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + face); + break; + default: + break; } - for (Uint32 level=base+1;level 1, which the Tex2D device-limit rule // refuses by name (TextureViewScenario.GenerateMipmapThroughAView on all Magma arms). - store.NoteLevelDefined(key,static_cast(upload),static_cast(level),extent, - record.Desc.Target,record.Desc.InternalFormat); - store.MarkLevelGpuDirty(key,static_cast(upload),static_cast(level),true); + store.NoteLevelDefined(key, static_cast(upload), static_cast(level), extent, + record.Desc.Target, record.Desc.InternalFormat); + store.MarkLevelGpuDirty(key, static_cast(upload), static_cast(level), true); } } } @@ -2543,12 +2697,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT pixelBytes = GetReadbackDstPixelSize(mapping, info.Type); const Uint64 rows64 = Uint64(info.Box.H) * info.Box.D; if (!info.Box.W || !rows64 || rows64 > std::numeric_limits::max() || - rows64 > std::numeric_limits::max() / 16 / info.Box.W) return false; + rows64 > std::numeric_limits::max() / 16 / info.Box.W) + return false; const SizeT pixels = static_cast(info.Box.W) * static_cast(rows64); if (!sourcePixelSize || !pixelBytes || sourceBytes != pixels * sourcePixelSize || - info.DstSize != pixels * pixelBytes) return false; - if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(GetPackedReadbackInternalFormat(format), - MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), + info.DstSize != pixels * pixelBytes) + return false; + if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer( + GetPackedReadbackInternalFormat(format), MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))) { if (pixelBytes != sourcePixelSize) return false; result.assign(source, source + sourceBytes); @@ -2558,7 +2714,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLenum wideType = GL_NONE; const GLsizei rows = static_cast(Uint64(info.Box.H) * info.Box.D); if (!DecodeReadbackRowsToWide(source, format, info.Box.W, rows, wide, wideType) || - mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) return false; + mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) + return false; if (ResolveTextureFormatInfo(logicalFormat).expandRgbToRgba) { const Float floatOne = 1.0f; const Uint32 integerOne = 1; @@ -2569,18 +2726,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { result.resize(info.DstSize); for (GLsizei row = 0; row < rows; ++row) ConvertWideReadbackRow(wide.data() + row * static_cast(info.Box.W) * 16, - result.data() + row * static_cast(info.Box.W) * pixelBytes, - info.Box.W, wideType, mapping, info.Type); + result.data() + row * static_cast(info.Box.W) * pixelBytes, info.Box.W, + wideType, mapping, info.Type); return true; } - Bool VulkanRenderer::ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, - Vector& ownedBytes) { + Bool VulkanRenderer::ReadTextureImageWire(const MG_Pipe::MGPReadbackInfo& info, Vector& ownedBytes) { ownedBytes.clear(); const auto& box = info.Box; if (box.X || box.Y || box.Z || !box.W || !box.H || !box.D || box.W > static_cast(std::numeric_limits::max()) || - Uint64(box.H) * box.D > static_cast(std::numeric_limits::max())) return false; + Uint64(box.H) * box.D > static_cast(std::numeric_limits::max())) + return false; const auto& state = MG_Pipe::MGPipeApplier(); if (MG_Pipe::MGPipeHandleIsNull(info.Res) || info.Res.Slot >= state.TextureResources.size()) return false; const auto& record = state.TextureResources[info.Res.Slot]; @@ -2594,12 +2751,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkExtent3D extent{box.W, box.H, 1}; if (oneDArray) { if (info.Target != GL_TEXTURE_1D_ARRAY || box.D != 1) return false; - layerCount = box.H; extent.height = 1; + layerCount = box.H; + extent.height = 1; } else if (volume) { if (info.Target != GL_TEXTURE_3D) return false; extent.depth = box.D; } else if (target == Target::Tex2DArray || target == Target::TexCubeArray) { - if (info.Target != (target == Target::Tex2DArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_CUBE_MAP_ARRAY)) return false; + if (info.Target != (target == Target::Tex2DArray ? GL_TEXTURE_2D_ARRAY : GL_TEXTURE_CUBE_MAP_ARRAY)) + return false; layerCount = box.D; } else if (target == Target::TexCube) { if (info.Target == GL_TEXTURE_CUBE_MAP) { @@ -2608,16 +2767,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { } else if (info.Target >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && info.Target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z) { if (box.D != 1) return false; layer = info.Target - GL_TEXTURE_CUBE_MAP_POSITIVE_X; - } else return false; + } else + return false; } else { if (box.D != 1) return false; - const GLenum expected = target == Target::Tex1D ? GL_TEXTURE_1D : - target == Target::Tex2D ? GL_TEXTURE_2D : - target == Target::TexRect ? GL_TEXTURE_RECTANGLE : GL_NONE; + const GLenum expected = target == Target::Tex1D ? GL_TEXTURE_1D + : target == Target::Tex2D ? GL_TEXTURE_2D + : target == Target::TexRect ? GL_TEXTURE_RECTANGLE + : GL_NONE; if (!expected || info.Target != expected || (target == Target::Tex1D && box.H != 1)) return false; } VkFormat viewFormat = VK_FORMAT_UNDEFINED; - const auto storage = m_textureManager->ResolveWireTextureStorage(info.Res, level, layer, &viewFormat, &availableLayers); + const auto storage = + m_textureManager->ResolveWireTextureStorage(info.Res, level, layer, &viewFormat, &availableLayers); if (MG_Pipe::MGPipeHandleIsNull(storage)) return false; const Uint64 pixels64 = Uint64(box.W) * box.H * box.D; if (pixels64 > std::numeric_limits::max() / 16) return false; @@ -2627,10 +2789,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (storage != info.Res || info.Target == GL_TEXTURE_CUBE_MAP) return false; Vector raw; VkFormat nativeFormat = VK_FORMAT_UNDEFINED; - if (!m_textureManager->ReadUnbackedWireLevel(storage, - MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target), info.Level, - IntVec3{static_cast(box.W), static_cast(box.H), static_cast(box.D)}, - nativeFormat, raw)) return false; + if (!m_textureManager->ReadUnbackedWireLevel( + storage, MG_Util::ConvertGLEnumToTextureUploadTarget(info.Target), info.Level, + IntVec3{static_cast(box.W), static_cast(box.H), static_cast(box.D)}, nativeFormat, + raw)) + return false; // A depth-only level's shadow holds the format's own depth words, which the // depth packer reads exactly as it reads them off a Vulkan copy. if (nativeFormat == VK_FORMAT_D16_UNORM || nativeFormat == VK_FORMAT_D32_SFLOAT || @@ -2641,12 +2804,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (raw.size() < texels * wordBytes) return false; Vector packed; if (!PackDepthStencilTight(raw.data(), nullptr, nativeFormat, texels, info.Format, info.Type, packed) || - packed.size() != info.DstSize) return false; + packed.size() != info.DstSize) + return false; ownedBytes = Move(packed); return true; } return PackWireColorTight(raw.data(), raw.size(), nativeFormat, - static_cast(record.Desc.InternalFormat), info, ownedBytes); + static_cast(record.Desc.InternalFormat), info, ownedBytes); }; RetireWireDrawPass(); MakeXfbWritesVisible(); @@ -2657,42 +2821,63 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Bool nativeVolume = resource->viewType == VK_IMAGE_VIEW_TYPE_3D; const Uint32 nativeDepth = nativeVolume ? std::max(resource->depth >> level, 1u) : resource->arrayLayers; if (extent.width != std::max(resource->extent.width >> level, 1u) || - extent.height != std::max(resource->extent.height >> level, 1u) || - layer >= nativeDepth || (nativeVolume ? extent.depth : layerCount) > nativeDepth - layer || - (!nativeVolume && layerCount > availableLayers)) return readUnbacked(); + extent.height != std::max(resource->extent.height >> level, 1u) || layer >= nativeDepth || + (nativeVolume ? extent.depth : layerCount) > nativeDepth - layer || + (!nativeVolume && layerCount > availableLayers)) + return readUnbacked(); // A view can reinterpret the same native words. Decode using the outer // view format, while Vulkan copies the storage allocation's bytes. const VkFormat format = viewFormat == VK_FORMAT_UNDEFINED ? resource->format : viewFormat; const Bool depth = info.Format == GL_DEPTH_COMPONENT || info.Format == GL_DEPTH_STENCIL; const Bool stencil = info.Format == GL_STENCIL_INDEX || info.Format == GL_DEPTH_STENCIL; const Bool depthStencil = depth || stencil; - const VkImageAspectFlags aspects = depthStencil ? - ((depth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0)) : VK_IMAGE_ASPECT_COLOR_BIT; + const VkImageAspectFlags aspects = + depthStencil ? ((depth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) | (stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0)) + : VK_IMAGE_ASPECT_COLOR_BIT; if ((resource->aspect & aspects) != aspects) return false; const SizeT pixels = static_cast(pixels64); - const SizeT destinationPixelSize = MG_Util::GetInputBytesPerPixel( - MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type)); + const SizeT destinationPixelSize = + MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), + MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type)); if (!destinationPixelSize || pixels64 * destinationPixelSize != info.DstSize) return false; SizeT sourcePixelSize = GetReadbackTexelSize(resource->format); if (depthStencil) { switch (resource->format) { - case VK_FORMAT_D16_UNORM: sourcePixelSize = 2; break; - case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_X8_D24_UNORM_PACK32: - case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D32_SFLOAT_S8_UINT: sourcePixelSize = 4; break; - case VK_FORMAT_S8_UINT: sourcePixelSize = 0; break; - default: return false; + case VK_FORMAT_D16_UNORM: + sourcePixelSize = 2; + break; + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + sourcePixelSize = 4; + break; + case VK_FORMAT_S8_UINT: + sourcePixelSize = 0; + break; + default: + return false; } - } else if (!sourcePixelSize || sourcePixelSize != GetReadbackTexelSize(format)) return false; + } else if (!sourcePixelSize || sourcePixelSize != GetReadbackTexelSize(format)) + return false; const VkDeviceSize firstBytes = pixels * (depthStencil ? (depth ? sourcePixelSize : 0) : sourcePixelSize); const VkDeviceSize stencilOffset = (firstBytes + 3) & ~VkDeviceSize{3}; const VkDeviceSize copyBytes = depthStencil ? stencilOffset + (stencil ? pixels : 0) : firstBytes; if (!copyBytes) return false; VkBufferObject readback; - if (!readback.Create({.allocator = m_allocator, .size = copyBytes, .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT})) return false; + if (!readback.Create({.allocator = m_allocator, + .size = copyBytes, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + .allocationFlags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT})) + return false; WireImage image{}; - image.image = resource->image; image.format = resource->format; image.aspect = resource->aspect; - image.layout = resource->layout; image.trackedLayout = &resource->layout; image.levels = resource->mipLevels; + image.image = resource->image; + image.format = resource->format; + image.aspect = resource->aspect; + image.layout = resource->layout; + image.trackedLayout = &resource->layout; + image.levels = resource->mipLevels; const auto restore = image.layout == VK_IMAGE_LAYOUT_UNDEFINED ? VK_IMAGE_LAYOUT_GENERAL : image.layout; auto& frame = m_frameContext.GetCurrent(); VkBufferImageCopy regions[2]{}; @@ -2736,26 +2921,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (copySource == image.image) TransitionWireImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); vkCmdCopyImageToBuffer(frame.commandBuffer, copySource, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, - readback.GetHandle(), regionCount, regions); + readback.GetHandle(), regionCount, regions); TransitionWireImage(image, restore); VkMemoryBarrier host{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; - host.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; host.dstAccessMask = VK_ACCESS_HOST_READ_BIT; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, - 0, 1, &host, 0, nullptr, 0, nullptr); + host.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + host.dstAccessMask = VK_ACCESS_HOST_READ_BIT; + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, + &host, 0, nullptr, 0, nullptr); if (!SubmitReadbackCommandsAndWait(frame)) return false; const auto* mapped = static_cast(readback.Map()); if (!mapped || !readback.Invalidate(copyBytes)) return false; Vector result; if (depthStencil) { if (!PackDepthStencilTight(depth ? mapped : nullptr, stencil ? mapped + stencilOffset : nullptr, - resource->format, pixels, info.Format, info.Type, result)) return false; + resource->format, pixels, info.Format, info.Type, result)) + return false; } else { using namespace ClientReadback; ReadbackChannelMapping mapping{}; if (!GetReadbackChannelMapping(info.Format, mapping) || - GetReadbackDstPixelSize(mapping, info.Type) != destinationPixelSize) return false; - if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer(GetPackedReadbackInternalFormat(format), - MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), + GetReadbackDstPixelSize(mapping, info.Type) != destinationPixelSize) + return false; + if (MG_Util::PixelStoreProcessor::IsRawPackedPixelTransfer( + GetPackedReadbackInternalFormat(format), MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))) { if (destinationPixelSize != sourcePixelSize) return false; result.assign(mapped, mapped + firstBytes); @@ -2764,8 +2952,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLenum wideType = GL_NONE; const GLsizei rows = static_cast(Uint64(box.H) * box.D); if (!DecodeReadbackRowsToWide(mapped, format, box.W, rows, wide, wideType) || - mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) return false; - if (ResolveTextureFormatInfo(static_cast(record.Desc.InternalFormat)).expandRgbToRgba) { + mapping.isInteger != (wideType == GL_INT || wideType == GL_UNSIGNED_INT)) + return false; + if (ResolveTextureFormatInfo(static_cast(record.Desc.InternalFormat)) + .expandRgbToRgba) { const Float floatOne = 1.0f; const Uint32 integerOne = 1; const void* alpha = wideType == GL_FLOAT ? static_cast(&floatOne) : &integerOne; @@ -2776,8 +2966,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const SizeT srcRow = static_cast(box.W) * 4 * sizeof(Uint32); const SizeT dstRow = static_cast(box.W) * destinationPixelSize; for (GLsizei row = 0; row < rows; ++row) - ConvertWideReadbackRow(wide.data() + row * srcRow, result.data() + row * dstRow, - box.W, wideType, mapping, info.Type); + ConvertWideReadbackRow(wide.data() + row * srcRow, result.data() + row * dstRow, box.W, wideType, + mapping, info.Type); } } if (result.size() != info.DstSize) return false; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.cpp index 7db73b7e..09a93fb7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.cpp @@ -58,7 +58,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // multisampled and the destination not. Returns false for every shape it cannot express, // which the caller turns into a decline; it never ends the session. Bool VulkanRenderer::ResolveWireDepthStencilWithShader(WireImage source, WireImage resolved, - VkImageAspectFlags aspect, GLint sx0, GLint sy0, Uint32 width, Uint32 height) { + VkImageAspectFlags aspect, GLint sx0, GLint sy0, + Uint32 width, Uint32 height) { if (!source.image || !resolved.image || !width || !height) return false; if (aspect != VK_IMAGE_ASPECT_DEPTH_BIT && aspect != VK_IMAGE_ASPECT_STENCIL_BIT) return false; // A fragment shader cannot write the stencil aspect at all without @@ -72,8 +73,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!(source.usageFlags & VK_IMAGE_USAGE_SAMPLED_BIT)) 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; const VkImageAspectFlags formatAspects = WireDepthStencilFormatAspects(source.format); @@ -85,21 +86,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_wireMultisampleResolveResources->kit)); } auto& cache = *m_wireMultisampleResolveResources; - const VkShaderModule fragment = aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? cache.kit.stencilFragment - : cache.kit.depthFragment; + const VkShaderModule fragment = + aspect == VK_IMAGE_ASPECT_STENCIL_BIT ? cache.kit.stencilFragment : cache.kit.depthFragment; if (fragment == VK_NULL_HANDLE) return false; WireMultisampleResolveResources::Target* target = nullptr; for (auto& entry : cache.targets) - if (entry.format == resolved.format && entry.aspect == aspect) { target = &entry; break; } + if (entry.format == resolved.format && entry.aspect == aspect) { + target = &entry; + break; + } if (!target) { cache.targets.emplace_back(); target = &cache.targets.back(); target->format = resolved.format; target->aspect = aspect; VK_VERIFY(CreateWireShaderResolveRenderPass(m_device, resolved.format, &target->renderPass)); - if (CreateWireShaderResolvePipeline(m_device, cache.kit, target->renderPass, aspect, - &target->pipeline) != VK_SUCCESS) { + if (CreateWireShaderResolvePipeline(m_device, cache.kit, target->renderPass, aspect, &target->pipeline) != + VK_SUCCESS) { vkDestroyRenderPass(m_device, target->renderPass, nullptr); cache.targets.pop_back(); return false; @@ -149,8 +153,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { alloc.descriptorSetCount = 1; alloc.pSetLayouts = &cache.kit.descriptorLayout; VK_VERIFY(vkAllocateDescriptorSets(m_device, &alloc, &descriptorSet)); - const VkDescriptorImageInfo sampled{cache.kit.nearest, sourceView, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; + const VkDescriptorImageInfo sampled{cache.kit.nearest, sourceView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL}; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; write.dstSet = descriptorSet; write.dstBinding = 0; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h index 8a5c90ea..f37f9823 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h @@ -15,97 +15,87 @@ #pragma once static constexpr Uint32 kWireMultisampleResolveVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000028u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, - 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, - 0x0007000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x0000001au, 0x00030003u, - 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x0000000bu, - 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, - 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, - 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, - 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, - 0x0000000du, 0x00000000u, 0x00060005u, 0x0000001au, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, - 0x00050005u, 0x0000001du, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00030047u, 0x0000000bu, 0x00000002u, - 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, - 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, - 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x0000001au, 0x0000000bu, 0x0000002au, - 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, - 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, - 0x0004002bu, 0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, - 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, - 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, - 0x00000020u, 0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040017u, 0x00000010u, - 0x00000006u, 0x00000002u, 0x0004002bu, 0x00000008u, 0x00000011u, 0x00000003u, 0x0004001cu, 0x00000012u, - 0x00000010u, 0x00000011u, 0x0004002bu, 0x00000006u, 0x00000013u, 0xbf800000u, 0x0005002cu, 0x00000010u, - 0x00000014u, 0x00000013u, 0x00000013u, 0x0004002bu, 0x00000006u, 0x00000015u, 0x40400000u, 0x0005002cu, - 0x00000010u, 0x00000016u, 0x00000015u, 0x00000013u, 0x0005002cu, 0x00000010u, 0x00000017u, 0x00000013u, - 0x00000015u, 0x0006002cu, 0x00000012u, 0x00000018u, 0x00000014u, 0x00000016u, 0x00000017u, 0x00040020u, - 0x00000019u, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x00000019u, 0x0000001au, 0x00000001u, 0x00040020u, - 0x0000001cu, 0x00000007u, 0x00000012u, 0x00040020u, 0x0000001eu, 0x00000007u, 0x00000010u, 0x0004002bu, - 0x00000006u, 0x00000021u, 0x00000000u, 0x0004002bu, 0x00000006u, 0x00000022u, 0x3f800000u, 0x00040020u, - 0x00000026u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, - 0x000200f8u, 0x00000005u, 0x0004003bu, 0x0000001cu, 0x0000001du, 0x00000007u, 0x0004003du, 0x0000000eu, - 0x0000001bu, 0x0000001au, 0x0003003eu, 0x0000001du, 0x00000018u, 0x00050041u, 0x0000001eu, 0x0000001fu, - 0x0000001du, 0x0000001bu, 0x0004003du, 0x00000010u, 0x00000020u, 0x0000001fu, 0x00050051u, 0x00000006u, - 0x00000023u, 0x00000020u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000024u, 0x00000020u, 0x00000001u, - 0x00070050u, 0x00000007u, 0x00000025u, 0x00000023u, 0x00000024u, 0x00000021u, 0x00000022u, 0x00050041u, - 0x00000026u, 0x00000027u, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x00000027u, 0x00000025u, 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000028u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x0000001au, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, + 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, + 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, + 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, + 0x00030005u, 0x0000000du, 0x00000000u, 0x00060005u, 0x0000001au, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, + 0x00050005u, 0x0000001du, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00030047u, 0x0000000bu, 0x00000002u, 0x00050048u, + 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, + 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, + 0x00000004u, 0x00040047u, 0x0000001au, 0x0000000bu, 0x0000002au, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, + 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040015u, + 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, + 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, + 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, + 0x00000020u, 0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040017u, 0x00000010u, 0x00000006u, + 0x00000002u, 0x0004002bu, 0x00000008u, 0x00000011u, 0x00000003u, 0x0004001cu, 0x00000012u, 0x00000010u, 0x00000011u, + 0x0004002bu, 0x00000006u, 0x00000013u, 0xbf800000u, 0x0005002cu, 0x00000010u, 0x00000014u, 0x00000013u, 0x00000013u, + 0x0004002bu, 0x00000006u, 0x00000015u, 0x40400000u, 0x0005002cu, 0x00000010u, 0x00000016u, 0x00000015u, 0x00000013u, + 0x0005002cu, 0x00000010u, 0x00000017u, 0x00000013u, 0x00000015u, 0x0006002cu, 0x00000012u, 0x00000018u, 0x00000014u, + 0x00000016u, 0x00000017u, 0x00040020u, 0x00000019u, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x00000019u, 0x0000001au, + 0x00000001u, 0x00040020u, 0x0000001cu, 0x00000007u, 0x00000012u, 0x00040020u, 0x0000001eu, 0x00000007u, 0x00000010u, + 0x0004002bu, 0x00000006u, 0x00000021u, 0x00000000u, 0x0004002bu, 0x00000006u, 0x00000022u, 0x3f800000u, 0x00040020u, + 0x00000026u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, + 0x00000005u, 0x0004003bu, 0x0000001cu, 0x0000001du, 0x00000007u, 0x0004003du, 0x0000000eu, 0x0000001bu, 0x0000001au, + 0x0003003eu, 0x0000001du, 0x00000018u, 0x00050041u, 0x0000001eu, 0x0000001fu, 0x0000001du, 0x0000001bu, 0x0004003du, + 0x00000010u, 0x00000020u, 0x0000001fu, 0x00050051u, 0x00000006u, 0x00000023u, 0x00000020u, 0x00000000u, 0x00050051u, + 0x00000006u, 0x00000024u, 0x00000020u, 0x00000001u, 0x00070050u, 0x00000007u, 0x00000025u, 0x00000023u, 0x00000024u, + 0x00000021u, 0x00000022u, 0x00050041u, 0x00000026u, 0x00000027u, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x00000027u, + 0x00000025u, 0x000100fdu, 0x00010038u, }; static constexpr Uint32 kWireMultisampleDepthResolveFragmentSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001du, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, - 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, - 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000008u, 0x00000010u, 0x00030010u, - 0x00000004u, 0x00000007u, 0x00030010u, 0x00000004u, 0x0000000cu, 0x00030003u, 0x00000002u, 0x000001c2u, - 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x00000008u, 0x465f6c67u, 0x44676172u, - 0x68747065u, 0x00000000u, 0x00050005u, 0x0000000cu, 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00060005u, - 0x00000010u, 0x465f6c67u, 0x43676172u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000008u, 0x0000000bu, - 0x00000016u, 0x00040047u, 0x0000000cu, 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000cu, 0x00000022u, - 0x00000000u, 0x00040047u, 0x00000010u, 0x0000000bu, 0x0000000fu, 0x00020013u, 0x00000002u, 0x00030021u, - 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040020u, 0x00000007u, 0x00000003u, - 0x00000006u, 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000003u, 0x00090019u, 0x00000009u, 0x00000006u, - 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x0000000au, - 0x00000009u, 0x00040020u, 0x0000000bu, 0x00000000u, 0x0000000au, 0x0004003bu, 0x0000000bu, 0x0000000cu, - 0x00000000u, 0x00040017u, 0x0000000eu, 0x00000006u, 0x00000004u, 0x00040020u, 0x0000000fu, 0x00000001u, - 0x0000000eu, 0x0004003bu, 0x0000000fu, 0x00000010u, 0x00000001u, 0x00040017u, 0x00000011u, 0x00000006u, - 0x00000002u, 0x00040015u, 0x00000014u, 0x00000020u, 0x00000001u, 0x00040017u, 0x00000015u, 0x00000014u, - 0x00000002u, 0x0004002bu, 0x00000014u, 0x00000017u, 0x00000000u, 0x00040015u, 0x0000001au, 0x00000020u, - 0x00000000u, 0x0004002bu, 0x0000001au, 0x0000001bu, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, - 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x0000000au, 0x0000000du, 0x0000000cu, - 0x0004003du, 0x0000000eu, 0x00000012u, 0x00000010u, 0x0007004fu, 0x00000011u, 0x00000013u, 0x00000012u, - 0x00000012u, 0x00000000u, 0x00000001u, 0x0004006eu, 0x00000015u, 0x00000016u, 0x00000013u, 0x00040064u, - 0x00000009u, 0x00000018u, 0x0000000du, 0x0007005fu, 0x0000000eu, 0x00000019u, 0x00000018u, 0x00000016u, - 0x00000040u, 0x00000017u, 0x00050051u, 0x00000006u, 0x0000001cu, 0x00000019u, 0x00000000u, 0x0003003eu, - 0x00000008u, 0x0000001cu, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001du, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000008u, 0x00000010u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030010u, + 0x00000004u, 0x0000000cu, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, + 0x00060005u, 0x00000008u, 0x465f6c67u, 0x44676172u, 0x68747065u, 0x00000000u, 0x00050005u, 0x0000000cu, 0x72756f73u, + 0x6d496563u, 0x00656761u, 0x00060005u, 0x00000010u, 0x465f6c67u, 0x43676172u, 0x64726f6fu, 0x00000000u, 0x00040047u, + 0x00000008u, 0x0000000bu, 0x00000016u, 0x00040047u, 0x0000000cu, 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000cu, + 0x00000022u, 0x00000000u, 0x00040047u, 0x00000010u, 0x0000000bu, 0x0000000fu, 0x00020013u, 0x00000002u, 0x00030021u, + 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040020u, 0x00000007u, 0x00000003u, 0x00000006u, + 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000003u, 0x00090019u, 0x00000009u, 0x00000006u, 0x00000001u, 0x00000000u, + 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x0000000au, 0x00000009u, 0x00040020u, 0x0000000bu, + 0x00000000u, 0x0000000au, 0x0004003bu, 0x0000000bu, 0x0000000cu, 0x00000000u, 0x00040017u, 0x0000000eu, 0x00000006u, + 0x00000004u, 0x00040020u, 0x0000000fu, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x0000000fu, 0x00000010u, 0x00000001u, + 0x00040017u, 0x00000011u, 0x00000006u, 0x00000002u, 0x00040015u, 0x00000014u, 0x00000020u, 0x00000001u, 0x00040017u, + 0x00000015u, 0x00000014u, 0x00000002u, 0x0004002bu, 0x00000014u, 0x00000017u, 0x00000000u, 0x00040015u, 0x0000001au, + 0x00000020u, 0x00000000u, 0x0004002bu, 0x0000001au, 0x0000001bu, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, + 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x0000000au, 0x0000000du, 0x0000000cu, 0x0004003du, + 0x0000000eu, 0x00000012u, 0x00000010u, 0x0007004fu, 0x00000011u, 0x00000013u, 0x00000012u, 0x00000012u, 0x00000000u, + 0x00000001u, 0x0004006eu, 0x00000015u, 0x00000016u, 0x00000013u, 0x00040064u, 0x00000009u, 0x00000018u, 0x0000000du, + 0x0007005fu, 0x0000000eu, 0x00000019u, 0x00000018u, 0x00000016u, 0x00000040u, 0x00000017u, 0x00050051u, 0x00000006u, + 0x0000001cu, 0x00000019u, 0x00000000u, 0x0003003eu, 0x00000008u, 0x0000001cu, 0x000100fdu, 0x00010038u, }; static constexpr Uint32 kWireMultisampleStencilResolveFragmentSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001fu, 0x00000000u, 0x00020011u, 0x00000001u, 0x00020011u, - 0x00001395u, 0x0009000au, 0x5f565053u, 0x5f545845u, 0x64616873u, 0x735f7265u, 0x636e6574u, 0x655f6c69u, - 0x726f7078u, 0x00000074u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, - 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00000008u, 0x00000012u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030010u, 0x00000004u, 0x000013a3u, - 0x00030003u, 0x00000002u, 0x000001c2u, 0x00090004u, 0x415f4c47u, 0x735f4252u, 0x65646168u, 0x74735f72u, - 0x69636e65u, 0x78655f6cu, 0x74726f70u, 0x00000000u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00080005u, 0x00000008u, 0x465f6c67u, 0x53676172u, 0x636e6574u, 0x65526c69u, 0x42524166u, 0x00000000u, - 0x00050005u, 0x0000000du, 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00060005u, 0x00000012u, 0x465f6c67u, - 0x43676172u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000008u, 0x0000000bu, 0x00001396u, 0x00040047u, - 0x0000000du, 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000du, 0x00000022u, 0x00000000u, 0x00040047u, - 0x00000012u, 0x0000000bu, 0x0000000fu, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, - 0x00040015u, 0x00000006u, 0x00000020u, 0x00000001u, 0x00040020u, 0x00000007u, 0x00000003u, 0x00000006u, - 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000003u, 0x00040015u, 0x00000009u, 0x00000020u, 0x00000000u, - 0x00090019u, 0x0000000au, 0x00000009u, 0x00000001u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000001u, - 0x00000000u, 0x0003001bu, 0x0000000bu, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000000u, 0x0000000bu, - 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000000u, 0x00030016u, 0x0000000fu, 0x00000020u, 0x00040017u, - 0x00000010u, 0x0000000fu, 0x00000004u, 0x00040020u, 0x00000011u, 0x00000001u, 0x00000010u, 0x0004003bu, - 0x00000011u, 0x00000012u, 0x00000001u, 0x00040017u, 0x00000013u, 0x0000000fu, 0x00000002u, 0x00040017u, - 0x00000016u, 0x00000006u, 0x00000002u, 0x0004002bu, 0x00000006u, 0x00000018u, 0x00000000u, 0x00040017u, - 0x0000001au, 0x00000009u, 0x00000004u, 0x0004002bu, 0x00000009u, 0x0000001cu, 0x00000000u, 0x00050036u, - 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x0000000bu, - 0x0000000eu, 0x0000000du, 0x0004003du, 0x00000010u, 0x00000014u, 0x00000012u, 0x0007004fu, 0x00000013u, - 0x00000015u, 0x00000014u, 0x00000014u, 0x00000000u, 0x00000001u, 0x0004006eu, 0x00000016u, 0x00000017u, - 0x00000015u, 0x00040064u, 0x0000000au, 0x00000019u, 0x0000000eu, 0x0007005fu, 0x0000001au, 0x0000001bu, - 0x00000019u, 0x00000017u, 0x00000040u, 0x00000018u, 0x00050051u, 0x00000009u, 0x0000001du, 0x0000001bu, - 0x00000000u, 0x0004007cu, 0x00000006u, 0x0000001eu, 0x0000001du, 0x0003003eu, 0x00000008u, 0x0000001eu, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001fu, 0x00000000u, 0x00020011u, 0x00000001u, 0x00020011u, 0x00001395u, + 0x0009000au, 0x5f565053u, 0x5f545845u, 0x64616873u, 0x735f7265u, 0x636e6574u, 0x655f6c69u, 0x726f7078u, 0x00000074u, + 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000008u, 0x00000012u, 0x00030010u, 0x00000004u, + 0x00000007u, 0x00030010u, 0x00000004u, 0x000013a3u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00090004u, 0x415f4c47u, + 0x735f4252u, 0x65646168u, 0x74735f72u, 0x69636e65u, 0x78655f6cu, 0x74726f70u, 0x00000000u, 0x00040005u, 0x00000004u, + 0x6e69616du, 0x00000000u, 0x00080005u, 0x00000008u, 0x465f6c67u, 0x53676172u, 0x636e6574u, 0x65526c69u, 0x42524166u, + 0x00000000u, 0x00050005u, 0x0000000du, 0x72756f73u, 0x6d496563u, 0x00656761u, 0x00060005u, 0x00000012u, 0x465f6c67u, + 0x43676172u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000008u, 0x0000000bu, 0x00001396u, 0x00040047u, 0x0000000du, + 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000du, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000012u, 0x0000000bu, + 0x0000000fu, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00040015u, 0x00000006u, 0x00000020u, + 0x00000001u, 0x00040020u, 0x00000007u, 0x00000003u, 0x00000006u, 0x0004003bu, 0x00000007u, 0x00000008u, 0x00000003u, + 0x00040015u, 0x00000009u, 0x00000020u, 0x00000000u, 0x00090019u, 0x0000000au, 0x00000009u, 0x00000001u, 0x00000000u, + 0x00000000u, 0x00000001u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x0000000bu, 0x0000000au, 0x00040020u, 0x0000000cu, + 0x00000000u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000000u, 0x00030016u, 0x0000000fu, 0x00000020u, + 0x00040017u, 0x00000010u, 0x0000000fu, 0x00000004u, 0x00040020u, 0x00000011u, 0x00000001u, 0x00000010u, 0x0004003bu, + 0x00000011u, 0x00000012u, 0x00000001u, 0x00040017u, 0x00000013u, 0x0000000fu, 0x00000002u, 0x00040017u, 0x00000016u, + 0x00000006u, 0x00000002u, 0x0004002bu, 0x00000006u, 0x00000018u, 0x00000000u, 0x00040017u, 0x0000001au, 0x00000009u, + 0x00000004u, 0x0004002bu, 0x00000009u, 0x0000001cu, 0x00000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, + 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x0000000bu, 0x0000000eu, 0x0000000du, 0x0004003du, 0x00000010u, + 0x00000014u, 0x00000012u, 0x0007004fu, 0x00000013u, 0x00000015u, 0x00000014u, 0x00000014u, 0x00000000u, 0x00000001u, + 0x0004006eu, 0x00000016u, 0x00000017u, 0x00000015u, 0x00040064u, 0x0000000au, 0x00000019u, 0x0000000eu, 0x0007005fu, + 0x0000001au, 0x0000001bu, 0x00000019u, 0x00000017u, 0x00000040u, 0x00000018u, 0x00050051u, 0x00000009u, 0x0000001du, + 0x0000001bu, 0x00000000u, 0x0004007cu, 0x00000006u, 0x0000001eu, 0x0000001du, 0x0003003eu, 0x00000008u, 0x0000001eu, 0x000100fdu, 0x00010038u, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h index bbdd5154..41503374 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireRenderPassCompatibility.h @@ -29,9 +29,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { hash ^= static_cast(value) + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2); }; mix(key.attachmentFormatsAndSamples.size()); - for (const auto value : key.attachmentFormatsAndSamples) mix(value); + for (const auto value : key.attachmentFormatsAndSamples) + mix(value); mix(key.colorReferences.size()); - for (const auto value : key.colorReferences) mix(value); + for (const auto value : key.colorReferences) + mix(value); mix(key.depthReference); return hash; } @@ -48,8 +50,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_ids.emplace(key, id); return id; } + private: UnorderedMap m_ids; Uint64 m_nextId = 1; // zero selects the native-handle pipeline domain }; -} +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireSharedImage.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireSharedImage.cpp index 51e8ba4c..b2fa4115 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireSharedImage.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireSharedImage.cpp @@ -77,7 +77,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { const MG_Util::Damage::Region& region) { if (m_device == VK_NULL_HANDLE || view.Width == 0 || view.Height == 0) return false; // The debug knob's device check (MGPipeDebugDeviceLossDue) rides the frame boundary here. - if (MG_Pipe::MGPipeDebugDeviceLossDue()) NoteDeviceLoss(VK_ERROR_DEVICE_LOST, "the debug knob (shared-image present)"); + if (MG_Pipe::MGPipeDebugDeviceLossDue()) + NoteDeviceLoss(VK_ERROR_DEVICE_LOST, "the debug knob (shared-image present)"); if (m_deviceLost && LatchWireDeviceLoss("shared-image-present")) return false; DestroySharedImagePresentTargets(true); @@ -210,8 +211,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { acquire.dstQueueFamilyIndex = destination.queueFamily; acquire.image = destination.image; acquire.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, - 0, nullptr, 0, nullptr, 1, &acquire); + vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, + nullptr, 0, nullptr, 1, &acquire); const VkImageSubresourceLayers layers{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; if (partial) { @@ -267,11 +268,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { region.dstSubresource = layers; region.dstOffsets[0] = halfTurn ? VkOffset3D{width, height, 0} : VkOffset3D{0, 0, 0}; region.dstOffsets[1] = halfTurn ? VkOffset3D{0, 0, 1} : VkOffset3D{width, height, 1}; - const VkFilter filter = - !sameExtent && (sourceProperties.optimalTilingFeatures & - VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 - ? VK_FILTER_LINEAR - : VK_FILTER_NEAREST; + const VkFilter filter = !sameExtent && (sourceProperties.optimalTilingFeatures & + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0 + ? VK_FILTER_LINEAR + : VK_FILTER_NEAREST; vkCmdBlitImage(frame.commandBuffer, source.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, destination.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion, filter); } @@ -455,9 +455,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } if (done != 0) { - m_sharedImageSemaphoresInFlight.erase( - m_sharedImageSemaphoresInFlight.begin(), - m_sharedImageSemaphoresInFlight.begin() + static_cast(done)); + m_sharedImageSemaphoresInFlight.erase(m_sharedImageSemaphoresInFlight.begin(), + m_sharedImageSemaphoresInFlight.begin() + + static_cast(done)); } } @@ -467,15 +467,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (semaphore != VK_NULL_HANDLE && m_device != VK_NULL_HANDLE) vkDestroySemaphore(m_device, semaphore, nullptr); }; - for (const auto& entry : m_sharedImageSemaphoresInFlight) destroy(entry.semaphore); + for (const auto& entry : m_sharedImageSemaphoresInFlight) + destroy(entry.semaphore); m_sharedImageSemaphoresInFlight.clear(); - for (const auto& wait : m_sharedImageWaits) destroy(wait.semaphore); + for (const auto& wait : m_sharedImageWaits) + destroy(wait.semaphore); m_sharedImageWaits.clear(); destroy(std::exchange(m_sharedImageSignal, VK_NULL_HANDLE)); m_sharedImageSignalSubmitted = VK_NULL_HANDLE; - for (const VkSemaphore semaphore : m_freeSharedImageWaitSemaphores) destroy(semaphore); + for (const VkSemaphore semaphore : m_freeSharedImageWaitSemaphores) + destroy(semaphore); m_freeSharedImageWaitSemaphores.clear(); - for (const VkSemaphore semaphore : m_freeSharedImageSignalSemaphores) destroy(semaphore); + for (const VkSemaphore semaphore : m_freeSharedImageSignalSemaphores) + destroy(semaphore); m_freeSharedImageSignalSemaphores.clear(); m_sharedImagesHeld.clear(); m_sharedImageReads.PublishFrame(-1); // drops the frame's references; publishes nothing @@ -528,7 +532,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // A session that writes images (implicit sync by flush) may be about to overwrite what // another session still samples: those reads go first. if (m_sharedImageImplicitSync && !resource.sharedImageHeld) { - for (const int read : SI::DupPendingReadFences(*image)) StageSharedImageWait(read, kSharedImageUseStages); + for (const int read : SI::DupPendingReadFences(*image)) + StageSharedImageWait(read, kSharedImageUseStages); } const Uint32 family = static_cast(m_physicalDevice.queueFamilies.graphicsFamily); @@ -655,8 +660,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (m_sharedImageSyncFd && m_sharedImageSignal == VK_NULL_HANDLE) m_sharedImageSignal = TakeSharedImageSemaphore(/*exportable=*/true); m_sharedImageSignalSubmitted = VK_NULL_HANDLE; - if (!HasPendingRecordedWork() && m_sharedImageSignal != VK_NULL_HANDLE) - m_frameContext.BeginCommandRecording(); + if (!HasPendingRecordedWork() && m_sharedImageSignal != VK_NULL_HANDLE) m_frameContext.BeginCommandRecording(); const Bool submitted = HasPendingRecordedWork() && FlushPendingCommands(); if (HasPendingRecordedWork()) MGLOG_E_ONCE("Magma: a native fence's commands could not be submitted"); const VkSemaphore signal = @@ -707,13 +711,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_sharedImagesHeld[kept++] = hold; continue; } - if (auto* resource = m_textureManager->FindWireTextureResource(hold.key)) - resource->sharedImageHeld = false; + if (auto* resource = m_textureManager->FindWireTextureResource(hold.key)) resource->sharedImageHeld = false; } m_sharedImagesHeld.resize(kept); // Its waits and signal were never submitted. A wait semaphore's unconsumed temporary // payload is replaced by its next import; the signal semaphore was never signaled. - for (const auto& wait : m_sharedImageWaits) m_freeSharedImageWaitSemaphores.push_back(wait.semaphore); + for (const auto& wait : m_sharedImageWaits) + m_freeSharedImageWaitSemaphores.push_back(wait.semaphore); m_sharedImageWaits.clear(); if (m_sharedImageSignal != VK_NULL_HANDLE) m_freeSharedImageSignalSemaphores.push_back(std::exchange(m_sharedImageSignal, VK_NULL_HANDLE)); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h index ccff2eee..a6e85bdd 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h @@ -14,73 +14,65 @@ #pragma once static constexpr Uint32 kWireYuvConvertVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000031u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, - 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, - 0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000015u, 0x00000020u, 0x0000002bu, - 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, - 0x00000009u, 0x00000070u, 0x00060005u, 0x00000015u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, - 0x00050005u, 0x00000018u, 0x65646e69u, 0x6c626178u, 0x00000065u, 0x00060005u, 0x0000001eu, 0x505f6c67u, - 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000001eu, 0x00000000u, 0x505f6c67u, 0x7469736fu, - 0x006e6f69u, 0x00070006u, 0x0000001eu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, 0x00000000u, - 0x00070006u, 0x0000001eu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, - 0x0000001eu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x00000020u, - 0x00000000u, 0x00050005u, 0x0000002bu, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000015u, - 0x0000000bu, 0x0000002au, 0x00030047u, 0x0000001eu, 0x00000002u, 0x00050048u, 0x0000001eu, 0x00000000u, - 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000001eu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, - 0x0000001eu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000001eu, 0x00000003u, 0x0000000bu, - 0x00000004u, 0x00040047u, 0x0000002bu, 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, 0x00000003u, 0x0004001cu, 0x0000000cu, 0x00000007u, - 0x0000000bu, 0x0004002bu, 0x00000006u, 0x0000000du, 0xbf800000u, 0x0005002cu, 0x00000007u, 0x0000000eu, - 0x0000000du, 0x0000000du, 0x0004002bu, 0x00000006u, 0x0000000fu, 0x40400000u, 0x0005002cu, 0x00000007u, - 0x00000010u, 0x0000000fu, 0x0000000du, 0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu, - 0x0006002cu, 0x0000000cu, 0x00000012u, 0x0000000eu, 0x00000010u, 0x00000011u, 0x00040015u, 0x00000013u, - 0x00000020u, 0x00000001u, 0x00040020u, 0x00000014u, 0x00000001u, 0x00000013u, 0x0004003bu, 0x00000014u, - 0x00000015u, 0x00000001u, 0x00040020u, 0x00000017u, 0x00000007u, 0x0000000cu, 0x00040017u, 0x0000001bu, - 0x00000006u, 0x00000004u, 0x0004002bu, 0x0000000au, 0x0000001cu, 0x00000001u, 0x0004001cu, 0x0000001du, - 0x00000006u, 0x0000001cu, 0x0006001eu, 0x0000001eu, 0x0000001bu, 0x00000006u, 0x0000001du, 0x0000001du, - 0x00040020u, 0x0000001fu, 0x00000003u, 0x0000001eu, 0x0004003bu, 0x0000001fu, 0x00000020u, 0x00000003u, - 0x0004002bu, 0x00000013u, 0x00000021u, 0x00000000u, 0x0004002bu, 0x00000006u, 0x00000023u, 0x00000000u, - 0x0004002bu, 0x00000006u, 0x00000024u, 0x3f800000u, 0x00040020u, 0x00000028u, 0x00000003u, 0x0000001bu, - 0x00040020u, 0x0000002au, 0x00000003u, 0x00000007u, 0x0004003bu, 0x0000002au, 0x0000002bu, 0x00000003u, - 0x0004002bu, 0x00000006u, 0x0000002fu, 0x3f000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, - 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, - 0x00000017u, 0x00000018u, 0x00000007u, 0x0004003du, 0x00000013u, 0x00000016u, 0x00000015u, 0x0003003eu, - 0x00000018u, 0x00000012u, 0x00050041u, 0x00000008u, 0x00000019u, 0x00000018u, 0x00000016u, 0x0004003du, - 0x00000007u, 0x0000001au, 0x00000019u, 0x0003003eu, 0x00000009u, 0x0000001au, 0x0004003du, 0x00000007u, - 0x00000022u, 0x00000009u, 0x00050051u, 0x00000006u, 0x00000025u, 0x00000022u, 0x00000000u, 0x00050051u, - 0x00000006u, 0x00000026u, 0x00000022u, 0x00000001u, 0x00070050u, 0x0000001bu, 0x00000027u, 0x00000025u, - 0x00000026u, 0x00000023u, 0x00000024u, 0x00050041u, 0x00000028u, 0x00000029u, 0x00000020u, 0x00000021u, - 0x0003003eu, 0x00000029u, 0x00000027u, 0x0004003du, 0x00000007u, 0x0000002cu, 0x00000009u, 0x00050050u, - 0x00000007u, 0x0000002du, 0x00000024u, 0x00000024u, 0x00050081u, 0x00000007u, 0x0000002eu, 0x0000002cu, - 0x0000002du, 0x0005008eu, 0x00000007u, 0x00000030u, 0x0000002eu, 0x0000002fu, 0x0003003eu, 0x0000002bu, - 0x00000030u, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000031u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000015u, 0x00000020u, 0x0000002bu, 0x00030003u, 0x00000002u, 0x000001c2u, + 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00030005u, 0x00000009u, 0x00000070u, 0x00060005u, 0x00000015u, + 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00050005u, 0x00000018u, 0x65646e69u, 0x6c626178u, 0x00000065u, + 0x00060005u, 0x0000001eu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000001eu, 0x00000000u, + 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000001eu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, + 0x00000000u, 0x00070006u, 0x0000001eu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, + 0x0000001eu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x00000020u, 0x00000000u, + 0x00050005u, 0x0000002bu, 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000015u, 0x0000000bu, 0x0000002au, + 0x00030047u, 0x0000001eu, 0x00000002u, 0x00050048u, 0x0000001eu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, + 0x0000001eu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000001eu, 0x00000002u, 0x0000000bu, 0x00000003u, + 0x00050048u, 0x0000001eu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x0000002bu, 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, 0x00000003u, 0x0004001cu, 0x0000000cu, 0x00000007u, + 0x0000000bu, 0x0004002bu, 0x00000006u, 0x0000000du, 0xbf800000u, 0x0005002cu, 0x00000007u, 0x0000000eu, 0x0000000du, + 0x0000000du, 0x0004002bu, 0x00000006u, 0x0000000fu, 0x40400000u, 0x0005002cu, 0x00000007u, 0x00000010u, 0x0000000fu, + 0x0000000du, 0x0005002cu, 0x00000007u, 0x00000011u, 0x0000000du, 0x0000000fu, 0x0006002cu, 0x0000000cu, 0x00000012u, + 0x0000000eu, 0x00000010u, 0x00000011u, 0x00040015u, 0x00000013u, 0x00000020u, 0x00000001u, 0x00040020u, 0x00000014u, + 0x00000001u, 0x00000013u, 0x0004003bu, 0x00000014u, 0x00000015u, 0x00000001u, 0x00040020u, 0x00000017u, 0x00000007u, + 0x0000000cu, 0x00040017u, 0x0000001bu, 0x00000006u, 0x00000004u, 0x0004002bu, 0x0000000au, 0x0000001cu, 0x00000001u, + 0x0004001cu, 0x0000001du, 0x00000006u, 0x0000001cu, 0x0006001eu, 0x0000001eu, 0x0000001bu, 0x00000006u, 0x0000001du, + 0x0000001du, 0x00040020u, 0x0000001fu, 0x00000003u, 0x0000001eu, 0x0004003bu, 0x0000001fu, 0x00000020u, 0x00000003u, + 0x0004002bu, 0x00000013u, 0x00000021u, 0x00000000u, 0x0004002bu, 0x00000006u, 0x00000023u, 0x00000000u, 0x0004002bu, + 0x00000006u, 0x00000024u, 0x3f800000u, 0x00040020u, 0x00000028u, 0x00000003u, 0x0000001bu, 0x00040020u, 0x0000002au, + 0x00000003u, 0x00000007u, 0x0004003bu, 0x0000002au, 0x0000002bu, 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000002fu, + 0x3f000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003bu, + 0x00000008u, 0x00000009u, 0x00000007u, 0x0004003bu, 0x00000017u, 0x00000018u, 0x00000007u, 0x0004003du, 0x00000013u, + 0x00000016u, 0x00000015u, 0x0003003eu, 0x00000018u, 0x00000012u, 0x00050041u, 0x00000008u, 0x00000019u, 0x00000018u, + 0x00000016u, 0x0004003du, 0x00000007u, 0x0000001au, 0x00000019u, 0x0003003eu, 0x00000009u, 0x0000001au, 0x0004003du, + 0x00000007u, 0x00000022u, 0x00000009u, 0x00050051u, 0x00000006u, 0x00000025u, 0x00000022u, 0x00000000u, 0x00050051u, + 0x00000006u, 0x00000026u, 0x00000022u, 0x00000001u, 0x00070050u, 0x0000001bu, 0x00000027u, 0x00000025u, 0x00000026u, + 0x00000023u, 0x00000024u, 0x00050041u, 0x00000028u, 0x00000029u, 0x00000020u, 0x00000021u, 0x0003003eu, 0x00000029u, + 0x00000027u, 0x0004003du, 0x00000007u, 0x0000002cu, 0x00000009u, 0x00050050u, 0x00000007u, 0x0000002du, 0x00000024u, + 0x00000024u, 0x00050081u, 0x00000007u, 0x0000002eu, 0x0000002cu, 0x0000002du, 0x0005008eu, 0x00000007u, 0x00000030u, + 0x0000002eu, 0x0000002fu, 0x0003003eu, 0x0000002bu, 0x00000030u, 0x000100fdu, 0x00010038u, }; static constexpr Uint32 kWireYuvConvertFragmentSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001cu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, - 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, - 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x00000011u, 0x00030010u, - 0x00000004u, 0x00000007u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, - 0x00000000u, 0x00050005u, 0x00000009u, 0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00050005u, 0x0000000du, - 0x49767579u, 0x6567616du, 0x00000000u, 0x00050005u, 0x00000011u, 0x43786574u, 0x64726f6fu, 0x00000000u, - 0x00040047u, 0x00000009u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000000du, 0x00000021u, 0x00000000u, - 0x00040047u, 0x0000000du, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000011u, 0x0000001eu, 0x00000000u, - 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, - 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, 0x00000008u, 0x00000003u, 0x00000007u, - 0x0004003bu, 0x00000008u, 0x00000009u, 0x00000003u, 0x00090019u, 0x0000000au, 0x00000006u, 0x00000001u, - 0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x0000000bu, 0x0000000au, - 0x00040020u, 0x0000000cu, 0x00000000u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000000u, - 0x00040017u, 0x0000000fu, 0x00000006u, 0x00000002u, 0x00040020u, 0x00000010u, 0x00000001u, 0x0000000fu, - 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, 0x0004002bu, 0x00000006u, 0x00000013u, 0x00000000u, - 0x00040017u, 0x00000015u, 0x00000006u, 0x00000003u, 0x0004002bu, 0x00000006u, 0x00000017u, 0x3f800000u, - 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, - 0x0000000bu, 0x0000000eu, 0x0000000du, 0x0004003du, 0x0000000fu, 0x00000012u, 0x00000011u, 0x00070058u, - 0x00000007u, 0x00000014u, 0x0000000eu, 0x00000012u, 0x00000002u, 0x00000013u, 0x0008004fu, 0x00000015u, - 0x00000016u, 0x00000014u, 0x00000014u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00050051u, 0x00000006u, - 0x00000018u, 0x00000016u, 0x00000000u, 0x00050051u, 0x00000006u, 0x00000019u, 0x00000016u, 0x00000001u, - 0x00050051u, 0x00000006u, 0x0000001au, 0x00000016u, 0x00000002u, 0x00070050u, 0x00000007u, 0x0000001bu, - 0x00000018u, 0x00000019u, 0x0000001au, 0x00000017u, 0x0003003eu, 0x00000009u, 0x0000001bu, 0x000100fdu, - 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001cu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, 0x00000001u, + 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u, + 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x00000011u, 0x00030010u, 0x00000004u, 0x00000007u, 0x00030003u, + 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00050005u, 0x00000009u, 0x4374756fu, + 0x726f6c6fu, 0x00000000u, 0x00050005u, 0x0000000du, 0x49767579u, 0x6567616du, 0x00000000u, 0x00050005u, 0x00000011u, + 0x43786574u, 0x64726f6fu, 0x00000000u, 0x00040047u, 0x00000009u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000000du, + 0x00000021u, 0x00000000u, 0x00040047u, 0x0000000du, 0x00000022u, 0x00000000u, 0x00040047u, 0x00000011u, 0x0000001eu, + 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, + 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, 0x00000008u, 0x00000003u, 0x00000007u, 0x0004003bu, + 0x00000008u, 0x00000009u, 0x00000003u, 0x00090019u, 0x0000000au, 0x00000006u, 0x00000001u, 0x00000000u, 0x00000000u, + 0x00000000u, 0x00000001u, 0x00000000u, 0x0003001bu, 0x0000000bu, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000000u, + 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000000u, 0x00040017u, 0x0000000fu, 0x00000006u, 0x00000002u, + 0x00040020u, 0x00000010u, 0x00000001u, 0x0000000fu, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, 0x0004002bu, + 0x00000006u, 0x00000013u, 0x00000000u, 0x00040017u, 0x00000015u, 0x00000006u, 0x00000003u, 0x0004002bu, 0x00000006u, + 0x00000017u, 0x3f800000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, + 0x0004003du, 0x0000000bu, 0x0000000eu, 0x0000000du, 0x0004003du, 0x0000000fu, 0x00000012u, 0x00000011u, 0x00070058u, + 0x00000007u, 0x00000014u, 0x0000000eu, 0x00000012u, 0x00000002u, 0x00000013u, 0x0008004fu, 0x00000015u, 0x00000016u, + 0x00000014u, 0x00000014u, 0x00000000u, 0x00000001u, 0x00000002u, 0x00050051u, 0x00000006u, 0x00000018u, 0x00000016u, + 0x00000000u, 0x00050051u, 0x00000006u, 0x00000019u, 0x00000016u, 0x00000001u, 0x00050051u, 0x00000006u, 0x0000001au, + 0x00000016u, 0x00000002u, 0x00070050u, 0x00000007u, 0x0000001bu, 0x00000018u, 0x00000019u, 0x0000001au, 0x00000017u, + 0x0003003eu, 0x00000009u, 0x0000001bu, 0x000100fdu, 0x00010038u, }; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.cpp index ddd8baed..d25e9303 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.cpp @@ -183,9 +183,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { key.externalFormat = format.format == VK_FORMAT_UNDEFINED ? format.externalFormat : 0; key.format = format.format; key.model = hints.ColorSpace != 0 ? YcbcrModelFor(hints) : format.suggestedYcbcrModel; - key.range = hints.Range != 0 ? (hints.FullRange() ? VK_SAMPLER_YCBCR_RANGE_ITU_FULL - : VK_SAMPLER_YCBCR_RANGE_ITU_NARROW) - : format.suggestedYcbcrRange; + key.range = hints.Range != 0 + ? (hints.FullRange() ? VK_SAMPLER_YCBCR_RANGE_ITU_FULL : VK_SAMPLER_YCBCR_RANGE_ITU_NARROW) + : format.suggestedYcbcrRange; key.components = format.samplerYcbcrConversionComponents; const auto chroma = [&format](Uint32 siting, VkChromaLocation suggested) { const Bool midpoint = siting == SI::Yuv::kEglYuvChromaSiting0_5; @@ -207,10 +207,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { for (SizeT i = 0; i < cache.pipelines.size(); ++i) { const auto& p = cache.pipelines[i]; if (p.externalFormat == key.externalFormat && p.format == key.format && p.model == key.model && - p.range == key.range && p.components.r == key.components.r && - p.components.g == key.components.g && p.components.b == key.components.b && - p.components.a == key.components.a && p.xOffset == key.xOffset && p.yOffset == key.yOffset && - p.chromaFilter == key.chromaFilter && p.target == key.target) { + p.range == key.range && p.components.r == key.components.r && p.components.g == key.components.g && + p.components.b == key.components.b && p.components.a == key.components.a && + p.xOffset == key.xOffset && p.yOffset == key.yOffset && p.chromaFilter == key.chromaFilter && + p.target == key.target) { index = i; break; } @@ -296,8 +296,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { raster.cullMode = VK_CULL_MODE_NONE; raster.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; raster.lineWidth = 1.0f; - VkPipelineMultisampleStateCreateInfo samples{ - VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO}; + 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 | @@ -483,8 +482,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { begin.renderArea = {{0, 0}, resource.extent}; vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, chosen.pipeline); - const VkViewport viewport{0.0f, 0.0f, static_cast(resource.extent.width), - static_cast(resource.extent.height), 0.0f, 1.0f}; + const VkViewport viewport{ + 0.0f, 0.0f, static_cast(resource.extent.width), static_cast(resource.extent.height), + 0.0f, 1.0f}; const VkRect2D scissor{{0, 0}, resource.extent}; vkCmdSetViewport(frame.commandBuffer, 0, 1, &viewport); vkCmdSetScissor(frame.commandBuffer, 0, 1, &scissor); diff --git a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h index 38a8df4b..0c81a9c7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h +++ b/MobileGL/MG_Backend/DirectVulkan/VkIncludes.h @@ -10,42 +10,44 @@ #include "VulkanRendererConfig.h" -#define ENUM_STR_CASE(c) case c: return #c; +#define ENUM_STR_CASE(c) \ + case c: \ + return #c; namespace MobileGL::MG_Backend::DirectVulkan { inline const char* VkResultToString(VkResult result) { switch (result) { - ENUM_STR_CASE(VK_SUCCESS) - ENUM_STR_CASE(VK_NOT_READY) - ENUM_STR_CASE(VK_TIMEOUT) - ENUM_STR_CASE(VK_EVENT_SET) - ENUM_STR_CASE(VK_EVENT_RESET) - ENUM_STR_CASE(VK_INCOMPLETE) - ENUM_STR_CASE(VK_ERROR_OUT_OF_HOST_MEMORY) - ENUM_STR_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY) - ENUM_STR_CASE(VK_ERROR_INITIALIZATION_FAILED) - ENUM_STR_CASE(VK_ERROR_DEVICE_LOST) - ENUM_STR_CASE(VK_ERROR_MEMORY_MAP_FAILED) - ENUM_STR_CASE(VK_ERROR_LAYER_NOT_PRESENT) - ENUM_STR_CASE(VK_ERROR_EXTENSION_NOT_PRESENT) - ENUM_STR_CASE(VK_ERROR_FEATURE_NOT_PRESENT) - ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DRIVER) - ENUM_STR_CASE(VK_ERROR_TOO_MANY_OBJECTS) - ENUM_STR_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED) - ENUM_STR_CASE(VK_ERROR_FRAGMENTED_POOL) - ENUM_STR_CASE(VK_ERROR_UNKNOWN) - ENUM_STR_CASE(VK_ERROR_OUT_OF_POOL_MEMORY) - ENUM_STR_CASE(VK_ERROR_INVALID_EXTERNAL_HANDLE) - ENUM_STR_CASE(VK_ERROR_FRAGMENTATION) - ENUM_STR_CASE(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS) - ENUM_STR_CASE(VK_PIPELINE_COMPILE_REQUIRED) - ENUM_STR_CASE(VK_ERROR_SURFACE_LOST_KHR) - ENUM_STR_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) - ENUM_STR_CASE(VK_SUBOPTIMAL_KHR) - ENUM_STR_CASE(VK_ERROR_OUT_OF_DATE_KHR) - ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR) - ENUM_STR_CASE(VK_ERROR_VALIDATION_FAILED_EXT) - ENUM_STR_CASE(VK_ERROR_INVALID_SHADER_NV) + ENUM_STR_CASE(VK_SUCCESS) + ENUM_STR_CASE(VK_NOT_READY) + ENUM_STR_CASE(VK_TIMEOUT) + ENUM_STR_CASE(VK_EVENT_SET) + ENUM_STR_CASE(VK_EVENT_RESET) + ENUM_STR_CASE(VK_INCOMPLETE) + ENUM_STR_CASE(VK_ERROR_OUT_OF_HOST_MEMORY) + ENUM_STR_CASE(VK_ERROR_OUT_OF_DEVICE_MEMORY) + ENUM_STR_CASE(VK_ERROR_INITIALIZATION_FAILED) + ENUM_STR_CASE(VK_ERROR_DEVICE_LOST) + ENUM_STR_CASE(VK_ERROR_MEMORY_MAP_FAILED) + ENUM_STR_CASE(VK_ERROR_LAYER_NOT_PRESENT) + ENUM_STR_CASE(VK_ERROR_EXTENSION_NOT_PRESENT) + ENUM_STR_CASE(VK_ERROR_FEATURE_NOT_PRESENT) + ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DRIVER) + ENUM_STR_CASE(VK_ERROR_TOO_MANY_OBJECTS) + ENUM_STR_CASE(VK_ERROR_FORMAT_NOT_SUPPORTED) + ENUM_STR_CASE(VK_ERROR_FRAGMENTED_POOL) + ENUM_STR_CASE(VK_ERROR_UNKNOWN) + ENUM_STR_CASE(VK_ERROR_OUT_OF_POOL_MEMORY) + ENUM_STR_CASE(VK_ERROR_INVALID_EXTERNAL_HANDLE) + ENUM_STR_CASE(VK_ERROR_FRAGMENTATION) + ENUM_STR_CASE(VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS) + ENUM_STR_CASE(VK_PIPELINE_COMPILE_REQUIRED) + ENUM_STR_CASE(VK_ERROR_SURFACE_LOST_KHR) + ENUM_STR_CASE(VK_ERROR_NATIVE_WINDOW_IN_USE_KHR) + ENUM_STR_CASE(VK_SUBOPTIMAL_KHR) + ENUM_STR_CASE(VK_ERROR_OUT_OF_DATE_KHR) + ENUM_STR_CASE(VK_ERROR_INCOMPATIBLE_DISPLAY_KHR) + ENUM_STR_CASE(VK_ERROR_VALIDATION_FAILED_EXT) + ENUM_STR_CASE(VK_ERROR_INVALID_SHADER_NV) default: return "VK_RESULT_UNKNOWN"; } @@ -92,12 +94,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (_vk_verify_result != VK_SUCCESS) { \ __VA_OPT__(MGLOG_F(__VA_ARGS__);) \ MGLOG_F("Vulkan error %s (%d) at %s:%d", \ - MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \ - _vk_verify_result, __FILE__, __LINE__); \ + MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), _vk_verify_result, \ + __FILE__, __LINE__); \ } \ MOBILEGL_ASSERT(_vk_verify_result == VK_SUCCESS, "Vulkan error %s (%d) at %s:%d", \ - MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), \ - _vk_verify_result, __FILE__, __LINE__); \ + MobileGL::MG_Backend::DirectVulkan::VkResultToString(_vk_verify_result), _vk_verify_result, \ + __FILE__, __LINE__); \ } while (0) #define XXHASH_VERIFY(expr, ...) \ diff --git a/MobileGL/MG_Backend/GpuHangWatch.cpp b/MobileGL/MG_Backend/GpuHangWatch.cpp index 701dd991..4dde9d3a 100644 --- a/MobileGL/MG_Backend/GpuHangWatch.cpp +++ b/MobileGL/MG_Backend/GpuHangWatch.cpp @@ -107,7 +107,8 @@ namespace MobileGL::MG_Backend::GpuHangWatch { } // namespace Uint32 BudgetMs() { - if (g_budgetOverridden.load(std::memory_order_acquire)) return g_budgetOverrideMs.load(std::memory_order_acquire); + if (g_budgetOverridden.load(std::memory_order_acquire)) + return g_budgetOverrideMs.load(std::memory_order_acquire); static const Uint32 budget = ReadBudgetMs(); return budget; } @@ -117,7 +118,9 @@ namespace MobileGL::MG_Backend::GpuHangWatch { return overrideMs != 0 ? overrideMs : kDefaultPollMs; } - void SetPollIntervalForTesting(Uint32 ms) { g_pollOverrideMs.store(ms, std::memory_order_release); } + void SetPollIntervalForTesting(Uint32 ms) { + g_pollOverrideMs.store(ms, std::memory_order_release); + } void SetBudgetForTesting(Uint32 ms) { g_budgetOverrideMs.store(ms, std::memory_order_release); diff --git a/MobileGL/MG_Backend/GpuHangWatch.h b/MobileGL/MG_Backend/GpuHangWatch.h index 9f1f1663..57a50497 100644 --- a/MobileGL/MG_Backend/GpuHangWatch.h +++ b/MobileGL/MG_Backend/GpuHangWatch.h @@ -47,8 +47,8 @@ namespace MobileGL::MG_Backend::GpuHangWatch { // One in-flight submission as the GPU reports progress on it. struct SubmissionProgress { - Uint64 Id = 0; // the backend's own monotonic id for it; never reused - Bool Started = false; // the GPU has begun executing it + Uint64 Id = 0; // the backend's own monotonic id for it; never reused + Bool Started = false; // the GPU has begun executing it Bool Finished = false; // ... and completed it }; diff --git a/MobileGL/MG_Backend/Init.cpp b/MobileGL/MG_Backend/Init.cpp index 861ae529..14e27053 100644 --- a/MobileGL/MG_Backend/Init.cpp +++ b/MobileGL/MG_Backend/Init.cpp @@ -108,10 +108,12 @@ namespace MobileGL::MG_Backend { // itself establish run-ahead readiness: that has its separate gate below. Uint64 ConsumedSubsystemsFor(BackendType type) { switch (type) { - case BackendType::DirectGLES: return MG_Pipe::kMGPipeSubsystemsMigratedAtP5e; + case BackendType::DirectGLES: + return MG_Pipe::kMGPipeSubsystemsMigratedAtP5e; case BackendType::DirectVulkan: return MG_Pipe::kMGPipeSubsystemsMigratedAtP4a | MG_Pipe::kMGPipeSubsystemBufferBindings; - default: return 0; + default: + return 0; } } @@ -201,8 +203,7 @@ namespace MobileGL::MG_Backend { // MGL_BACKEND_SLOT_CAP at GL_Drawing.cpp's FixupGsStripCaptureOrder. Uint64 capBits = 0; if (const MG_Backend::BackendObject* serverBackend = loop.Backend(); - serverBackend != nullptr && - serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) { + serverBackend != nullptr && serverBackend->GetBackendFunctions().GL.EndTransformFeedback != nullptr) { capBits |= MG_Pipe::kCapBackendOwnsXfbCapture; } if (const auto* backend = loop.Backend()) { @@ -210,14 +211,13 @@ namespace MobileGL::MG_Backend { // The query owner publishes only complete native query/reply paths. // Timer hardware support is refreshed when caps are published after // make-current; no CPU primitive-accounting preference is advertised. - if (gl.IsQueryResultAvailable && gl.GetQueryResult64 && gl.DeleteBackendQuery && - gl.FenceSync && gl.ClientWaitSync && gl.DeleteSync) { + if (gl.IsQueryResultAvailable && gl.GetQueryResult64 && gl.DeleteBackendQuery && gl.FenceSync && + gl.ClientWaitSync && gl.DeleteSync) { if (gl.BeginXfbPrimitivesQuery && gl.EndXfbPrimitivesQuery) capBits |= MG_Pipe::kCapXfbPrimitivesQuery; - if (gl.BeginOcclusionQuery && gl.EndOcclusionQuery) - capBits |= MG_Pipe::kCapOcclusionQuery; - if (gl.IsTimerQuerySupported && gl.BeginTimeElapsedQuery && - gl.EndTimeElapsedQuery && gl.QueryCounterTimestamp) + if (gl.BeginOcclusionQuery && gl.EndOcclusionQuery) capBits |= MG_Pipe::kCapOcclusionQuery; + if (gl.IsTimerQuerySupported && gl.BeginTimeElapsedQuery && gl.EndTimeElapsedQuery && + gl.QueryCounterTimestamp) capBits |= MG_Pipe::kCapTimerQuery; } } @@ -250,7 +250,8 @@ namespace MobileGL::MG_Backend { // Each backend has an independent implementation-readiness gate. // The runtime RunAhead knob can decline the feature, never create it. const Bool runAheadReady = MG_Config::ActiveBackendType == BackendType::DirectVulkan - ? kMGPipeMagmaRunAheadReady : kMGPipeP5eRunAheadReady; + ? kMGPipeMagmaRunAheadReady + : kMGPipeP5eRunAheadReady; capBits |= MG_Pipe::MGPipeRunAheadCapBitsFor(MG_Config::ActiveBackendType, runAheadReady); session.SetCapabilityBits(capBits); session.SetBackend(loop.Backend()); @@ -338,27 +339,27 @@ namespace MobileGL::MG_Backend { // step-5 re-run's ability to catch a table REPLACED between accept and here, and // that is a question about one address space; it cannot be asked across two. if (MG_Config::Transport != MG_Config::TransportMode::Spawn) { - // m-6, re-worded per review v2 N-8. The honesty cross-check runs a SECOND time, now - // that step 4's pActiveBackendObject (the client's BackendObject_Remote) exists and - // its Initialize() has run inside InitSpecificBackendLibs. What the re-run CAN catch - // is a table that was REMOVED between step 2 and here (the claim would then be a lie - // again). What it cannot catch - and its first comment claimed it could - is a client - // object that REGISTERED a table of its own: that leaves "is a table registered" - // true. Only the pointer tells those apart, so the table is compared against the one - // step 2 saw and a swap is refused by name: the applier would otherwise dispatch the - // server's resource records into the CLIENT object's table under its feet. - AssertConsumerMaskIsHonest(ConsumedSubsystemsFor(MG_Config::ActiveBackendType)); - if (MG_Pipe::MGPipeGetResourceOps() != g_resourceOpsAtStep2) { - MGLOG_F("MGPipe: Fatal{ConsumerMaskLie, \"resource ops table replaced\"} - the " - "resource op table MGPipeGetResourceOps() answers with is not the one the " - "server's backend registered at step 1 (%p now, %p then). Something between " - "ServerSession::Accept and the client object's Initialize() registered its " - "own table, and the applier would dispatch every resource record into it. A " - "mask is a statement about the server's backend, and so is the table", - static_cast(MG_Pipe::MGPipeGetResourceOps()), - static_cast(g_resourceOpsAtStep2)); - std::abort(); - } + // m-6, re-worded per review v2 N-8. The honesty cross-check runs a SECOND time, now + // that step 4's pActiveBackendObject (the client's BackendObject_Remote) exists and + // its Initialize() has run inside InitSpecificBackendLibs. What the re-run CAN catch + // is a table that was REMOVED between step 2 and here (the claim would then be a lie + // again). What it cannot catch - and its first comment claimed it could - is a client + // object that REGISTERED a table of its own: that leaves "is a table registered" + // true. Only the pointer tells those apart, so the table is compared against the one + // step 2 saw and a swap is refused by name: the applier would otherwise dispatch the + // server's resource records into the CLIENT object's table under its feet. + AssertConsumerMaskIsHonest(ConsumedSubsystemsFor(MG_Config::ActiveBackendType)); + if (MG_Pipe::MGPipeGetResourceOps() != g_resourceOpsAtStep2) { + MGLOG_F("MGPipe: Fatal{ConsumerMaskLie, \"resource ops table replaced\"} - the " + "resource op table MGPipeGetResourceOps() answers with is not the one the " + "server's backend registered at step 1 (%p now, %p then). Something between " + "ServerSession::Accept and the client object's Initialize() registered its " + "own table, and the applier would dispatch every resource record into it. A " + "mask is a statement about the server's backend, and so is the table", + static_cast(MG_Pipe::MGPipeGetResourceOps()), + static_cast(g_resourceOpsAtStep2)); + std::abort(); + } } LogBackendInfo(); } @@ -411,7 +412,9 @@ namespace MobileGL::MG_Backend { // it: 2 symbols added, 1 removed, .text +16 bytes. The pull build's identity // is byte-for-byte, and "it returns false there" is not the same as "it is // not there". - Bool InitServerRoleForSpawn() { return InitServerRoleCommon(); } + Bool InitServerRoleForSpawn() { + return InitServerRoleCommon(); + } #endif void Init() { diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp index ab14ef77..649b48bd 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp @@ -108,13 +108,11 @@ namespace MobileGL::MG_Pipe { // "UNBARRIERED" is the value not existing (§3.3). The `@` prefix and the // Fatal{UnmigratedPipeInput tag are unchanged, so every filter written since P5c still // matches. - [[noreturn]] void StrictBarrierPullFatal(MGPipeInputField field, MGPipeVerb verb, - const char* why) { + [[noreturn]] void StrictBarrierPullFatal(MGPipeInputField field, MGPipeVerb verb, const char* why) { const SizeT index = static_cast(field); MGLOG_F("MGPipe: Fatal{UnmigratedPipeInput, \"%s@%s\"} [BARRIER-PULLED, %s, " "retires in %s]", - kMGPipeInputFieldNames[index], MGPipeVerbName(verb), why, - kMGPipeFieldRetiringPhase[index]); + kMGPipeInputFieldNames[index], MGPipeVerbName(verb), why, kMGPipeFieldRetiringPhase[index]); std::abort(); } @@ -144,8 +142,7 @@ namespace MobileGL::MG_Pipe { MGLOG_W("MGPipe: Admitted{UnmigratedPipeInput, \"%s@%s\"} [BARRIER-PULLED, %s, " "retires in %s]", kMGPipeInputFieldNames[fieldIndex], MGPipeVerbName(verb), - escalated ? "ADMITTED-ESCALATED" : "ADMITTED", - kMGPipeFieldRetiringPhase[fieldIndex]); + escalated ? "ADMITTED-ESCALATED" : "ADMITTED", kMGPipeFieldRetiringPhase[fieldIndex]); } // Decides what a BARRIER-PULLED read does, for the field accessors and the sticky forwards: @@ -237,8 +234,7 @@ namespace MobileGL::MG_Pipe { const MGPipeFieldOwnership ownership = kMGPipeFieldOwnership[i]; const Bool answerable = (ownership == MGPipeFieldOwnership::kRecordSupplied || ownership == MGPipeFieldOwnership::kApplierDerived) && - MGPipeFieldMaskHas( - kMGPipeClassFieldMask[static_cast(verbClass)], field); + MGPipeFieldMaskHas(kMGPipeClassFieldMask[static_cast(verbClass)], field); if (answerable) mask.Words[i / 64] |= (Uint64{1} << (i % 64)); } return mask; @@ -276,9 +272,7 @@ namespace MobileGL::MG_Pipe { struct MGPipeStampAccess { static MGPipeFilledState& Filled(PipeInputs& inputs) { return inputs.m_filled; } static void SetVerb(PipeInputs& inputs, MGPipeVerb verb) { inputs.m_currentVerb = verb; } - static void SetServerStamped(PipeInputs& inputs, Bool stamped) { - inputs.m_serverStampedVerb = stamped; - } + static void SetServerStamped(PipeInputs& inputs, Bool stamped) { inputs.m_serverStampedVerb = stamped; } // P5f (f1): the server block's identity half of the stamp door (CONTRACT-P5E §3.2). // The client's SetIdentity lives in MG_Impl's MGPipeFillAccess and writes the CLIENT // block now; this one is how the server block learns which served context it describes @@ -301,8 +295,7 @@ namespace MobileGL::MG_Pipe { // stamp and the read verdicts around them are the record arm's (PipeInputs.h's dual block // gives a build without a transport the one-block answers inline). Bool MGPipeRoleSplitRehearsalActive() { - return MG_Config::Ipc.RoleSplitState && - MG_Config::Transport != MG_Config::TransportMode::Monolith; + return MG_Config::Ipc.RoleSplitState && MG_Config::Transport != MG_Config::TransportMode::Monolith; } // ---- D1c's predicates (CONTRACT-P6 3.2) ------------------------------------------------- @@ -316,9 +309,15 @@ namespace MobileGL::MG_Pipe { Bool g_sessionLive = false; } // namespace - void MGPipeSetServerProcessRole(Bool isServerProcess) { g_serverProcessRole = isServerProcess; } - void MGPipeSetApplyThreadProbe(Bool (*probe)()) { g_applyThreadProbe = probe; } - void MGPipeSetSessionLive(Bool live) { g_sessionLive = live; } + void MGPipeSetServerProcessRole(Bool isServerProcess) { + g_serverProcessRole = isServerProcess; + } + void MGPipeSetApplyThreadProbe(Bool (*probe)()) { + g_applyThreadProbe = probe; + } + void MGPipeSetSessionLive(Bool live) { + g_sessionLive = live; + } Bool MGPipeServerArm() { // THE PROCESS FACT FIRST, and it short-circuits: in a spawn server every thread is a @@ -328,7 +327,9 @@ namespace MobileGL::MG_Pipe { return g_applyThreadProbe != nullptr && g_applyThreadProbe(); } - Bool MGPipeSessionLive() { return g_sessionLive; } + Bool MGPipeSessionLive() { + return g_sessionLive; + } Bool MGPipeBlocksAreDistinct() { // TWO DIFFERENT REASONS, ONE ANSWER. The rehearsal makes two blocks in one process; @@ -337,8 +338,7 @@ namespace MobileGL::MG_Pipe { // question were therefore disarmed in the one shape where the answer matters most - a // spawn server's block is never the client's, whatever MOBILEGL_IPC_ROLE_SPLIT_STATE // says. - return MGPipeRoleSplitRehearsalActive() || - MG_Config::Transport == MG_Config::TransportMode::Spawn; + return MGPipeRoleSplitRehearsalActive() || MG_Config::Transport == MG_Config::TransportMode::Spawn; } PipeInputs& MGPipeClientInputs() { @@ -355,13 +355,21 @@ namespace MobileGL::MG_Pipe { MGPipeServerBlockNoteIdentity(); } - Bool MGPipeServerContextIsLive() { return MGPipeStampAccess::ServerContextLive(gPipeInputs); } + Bool MGPipeServerContextIsLive() { + return MGPipeStampAccess::ServerContextLive(gPipeInputs); + } - namespace { const void* g_serverOwnedWindow = nullptr; } + namespace { + const void* g_serverOwnedWindow = nullptr; + } // namespace - void MGPipeServerSetOwnedWindow(const void* window) { g_serverOwnedWindow = window; } + void MGPipeServerSetOwnedWindow(const void* window) { + g_serverOwnedWindow = window; + } - const void* MGPipeServerOwnedWindow() { return g_serverOwnedWindow; } + const void* MGPipeServerOwnedWindow() { + return g_serverOwnedWindow; + } namespace { Uint32 g_serverOwnedWindowWidth = 0; @@ -399,9 +407,9 @@ namespace MobileGL::MG_Pipe { // hands out a null slot - an unnamed crash where the rehearsal exists to produce a // named one. const Uint64 serial = MGPipeApplierContextSerial(); - MGPipeStampAccess::SetIdentity(gPipeInputs, MGPipeServerContextIsLive(), - reinterpret_cast(static_cast( - (serial << 1) | Uint64{1}))); + MGPipeStampAccess::SetIdentity( + gPipeInputs, MGPipeServerContextIsLive(), + reinterpret_cast(static_cast((serial << 1) | Uint64{1}))); } #endif // MOBILEGL_BUILD_DISAGGREGATED @@ -463,10 +471,16 @@ namespace MobileGL::MG_Pipe { #endif } - void MGPipeServerClearVerbBoundary() { MGPipeStampAccess::SetServerStamped(gPipeInputs, false); } + void MGPipeServerClearVerbBoundary() { + MGPipeStampAccess::SetServerStamped(gPipeInputs, false); + } - Uint64 MGPipeResidualPullCount() { return g_residualPulls; } - void MGPipeResetResidualPullCountForTesting() { g_residualPulls = 0; } + Uint64 MGPipeResidualPullCount() { + return g_residualPulls; + } + void MGPipeResetResidualPullCountForTesting() { + g_residualPulls = 0; + } void MGPipeInputUnfreshRead(MGPipeInputField field, MGPipeVerb verb, Bool serverStamped) { // OUTSIDE A SERVER-STAMPED VERB THIS IS THE MONOLITH ANSWER, UNCHANGED. A split BUILD @@ -523,8 +537,7 @@ namespace MobileGL::MG_Pipe { MGLOG_F("MGPipe: Fatal{UnmigratedPipeInput, \"%s@%s\"} [argument 0 = %u is %s while the " "field is %s]", kMGPipeInputFieldNames[static_cast(field)], MGPipeVerbName(verb), arg0, - MGPipeFieldOwnershipName(narrowed), - MGPipeFieldOwnershipName(MGPipeFieldOwnershipOf(field))); + MGPipeFieldOwnershipName(narrowed), MGPipeFieldOwnershipName(MGPipeFieldOwnershipOf(field))); std::abort(); } if (narrowed == MGPipeFieldOwnership::kBarrierPulled) { @@ -540,10 +553,8 @@ namespace MobileGL::MG_Pipe { // stamp's withdrawal. This is the only thing that puts them in `rsp`. if (!gPipeInputs->ServerStampedVerb()) return; const auto ownership = MGPipeFieldOwnershipOf(field); - if (ownership == MGPipeFieldOwnership::kFatal) - MGPipeInputPoisonFatalForVerb(field, gPipeInputs->CurrentVerb()); - if (ownership == MGPipeFieldOwnership::kBarrierPulled) - CountBarrierPull(field, gPipeInputs->CurrentVerb()); + if (ownership == MGPipeFieldOwnership::kFatal) MGPipeInputPoisonFatalForVerb(field, gPipeInputs->CurrentVerb()); + if (ownership == MGPipeFieldOwnership::kBarrierPulled) CountBarrierPull(field, gPipeInputs->CurrentVerb()); } #endif // MOBILEGL_BUILD_RECORD_ARM && MOBILEGL_PIPE_POISON diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.h b/MobileGL/MG_Backend/MGPipe/PipeInputs.h index 794a0ccd..b556b857 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.h +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.h @@ -77,15 +77,15 @@ namespace MobileGL::MG_Pipe { #if MOBILEGL_PIPE_POISON #define MGP_INPUT_CHECK(Field) \ do { \ - if ((m_serverStampedVerb && ::MobileGL::MG_Pipe::MGPipeFieldOwnershipOf(Field) == \ - ::MobileGL::MG_Pipe::MGPipeFieldOwnership::kFatal) || \ + if ((m_serverStampedVerb && ::MobileGL::MG_Pipe::MGPipeFieldOwnershipOf(Field) == \ + ::MobileGL::MG_Pipe::MGPipeFieldOwnership::kFatal) || \ !::MobileGL::MG_Pipe::MGPipeInputFieldIsFresh(m_filled, (Field))) { \ ::MobileGL::MG_Pipe::MGPipeInputUnfreshRead((Field), m_currentVerb, m_serverStampedVerb); \ } \ } while (0) #define MGP_INPUT_CHECK_ARG(Field, Arg0) \ do { \ - if (!::MobileGL::MG_Pipe::MGPipeInputArgumentRead((Field), static_cast(Arg0), m_currentVerb, \ + if (!::MobileGL::MG_Pipe::MGPipeInputArgumentRead((Field), static_cast(Arg0), m_currentVerb, \ m_serverStampedVerb)) { \ MGP_INPUT_CHECK(Field); \ } \ @@ -800,7 +800,8 @@ namespace MobileGL::MG_Pipe { PipeInputs& Get() { PipeInputs* bound = m_bound; - if (bound == nullptr) [[unlikely]] bound = m_bound = &MGPipeServerInputsForCallingThread(); + if (bound == nullptr) [[unlikely]] + bound = m_bound = &MGPipeServerInputsForCallingThread(); return *bound; } PipeInputs* operator->() { return &Get(); } @@ -869,9 +870,15 @@ namespace MobileGL::MG_Pipe { void MGPipeSetApplyThreadProbe(Bool (*probe)()); void MGPipeSetSessionLive(Bool live); #else - inline constexpr Bool MGPipeServerArm() { return false; } - inline constexpr Bool MGPipeSessionLive() { return false; } - inline constexpr Bool MGPipeBlocksAreDistinct() { return false; } + inline constexpr Bool MGPipeServerArm() { + return false; + } + inline constexpr Bool MGPipeSessionLive() { + return false; + } + inline constexpr Bool MGPipeBlocksAreDistinct() { + return false; + } // Setters too: a caller guarded only at its own site would still need these to LINK. inline void MGPipeSetServerProcessRole(Bool) {} inline void MGPipeSetApplyThreadProbe(Bool (*)()) {} @@ -918,10 +925,14 @@ namespace MobileGL::MG_Pipe { // folds onto gPipeInputs and PipeFill.cpp reads identically in both build flavours. An // inline that no caller in such a build ever has a reason to call twice - the disaggregated // arm above is the real one. - inline PipeInputs& MGPipeClientInputs() { return gPipeInputs; } + inline PipeInputs& MGPipeClientInputs() { + return gPipeInputs; + } // P13 W5: one block, no client-role stamp to withdraw - and no server role to be on. inline void MGPipeClientClearVerbBoundary() {} - inline constexpr Bool MGPipeServerArm() { return false; } + inline constexpr Bool MGPipeServerArm() { + return false; + } #endif // Every field has storage or is forwarded, and nothing else. diff --git a/MobileGL/MG_Backend/Record/RecordVerbSink.cpp b/MobileGL/MG_Backend/Record/RecordVerbSink.cpp index af612c97..e9d1e28c 100644 --- a/MobileGL/MG_Backend/Record/RecordVerbSink.cpp +++ b/MobileGL/MG_Backend/Record/RecordVerbSink.cpp @@ -35,9 +35,9 @@ namespace MobileGL::MG_Record { m_backend = backend; if (backend != nullptr) { const auto& limits = backend->GetDynamicParameters(); - MG_Record::ServerStagedTexture().SetDeviceLimits( - limits.MaxTextureSize, limits.Max3DTextureSize, limits.MaxCubeMapTextureSize, - limits.MaxArrayTextureLayers, limits.MaxTextureBufferSize); + MG_Record::ServerStagedTexture().SetDeviceLimits(limits.MaxTextureSize, limits.Max3DTextureSize, + limits.MaxCubeMapTextureSize, limits.MaxArrayTextureLayers, + limits.MaxTextureBufferSize); } } @@ -86,18 +86,15 @@ namespace MobileGL::MG_Record { switch (clear.ValueClass) { case MG_Pipe::kMGPipeClearValueClassFloat: if (gl.ClearBufferfv == nullptr) return false; - gl.ClearBufferfv(GL_COLOR, clear.DrawBufferIndex, - reinterpret_cast(clear.ColorValue)); + gl.ClearBufferfv(GL_COLOR, clear.DrawBufferIndex, reinterpret_cast(clear.ColorValue)); break; case MG_Pipe::kMGPipeClearValueClassInt: if (gl.ClearBufferiv == nullptr) return false; - gl.ClearBufferiv(GL_COLOR, clear.DrawBufferIndex, - reinterpret_cast(clear.ColorValue)); + gl.ClearBufferiv(GL_COLOR, clear.DrawBufferIndex, reinterpret_cast(clear.ColorValue)); break; case MG_Pipe::kMGPipeClearValueClassUint: if (gl.ClearBufferuiv == nullptr) return false; - gl.ClearBufferuiv(GL_COLOR, clear.DrawBufferIndex, - reinterpret_cast(clear.ColorValue)); + gl.ClearBufferuiv(GL_COLOR, clear.DrawBufferIndex, reinterpret_cast(clear.ColorValue)); break; default: // A value class outside the three is a wire fault, not a fallback: all three @@ -140,12 +137,9 @@ namespace MobileGL::MG_Record { applierState.ClearVerbHandles(); applierState.VerbBlitReadFbo = blit.ReadFbo; applierState.VerbBlitDrawFbo = blit.DrawFbo; - const Bool named = !MG_Pipe::MGPipeHandleIsNull(blit.ReadFbo) || - !MG_Pipe::MGPipeHandleIsNull(blit.DrawFbo); - table->GL.BlitFramebuffer(blit.SrcX0, blit.SrcY0, blit.SrcX1, blit.SrcY1, blit.DstX0, - blit.DstY0, blit.DstX1, blit.DstY1, - static_cast(blit.Mask), - static_cast(blit.Filter)); + const Bool named = !MG_Pipe::MGPipeHandleIsNull(blit.ReadFbo) || !MG_Pipe::MGPipeHandleIsNull(blit.DrawFbo); + table->GL.BlitFramebuffer(blit.SrcX0, blit.SrcY0, blit.SrcX1, blit.SrcY1, blit.DstX0, blit.DstY0, blit.DstX1, + blit.DstY1, static_cast(blit.Mask), static_cast(blit.Filter)); if (named && !applierState.VerbBlitNamedConsumed) { MGLOG_E_ONCE("MGPipe: a named blit (read {%u, %u}, draw {%u, %u}) reached a backend " "with no named-blit arm; the verb is DECLINED rather than applied to " @@ -162,15 +156,14 @@ namespace MobileGL::MG_Record { Bool RecordVerbSink::OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, MG_Pipe::MGPipeReplySink* replies) { if (!replies) MG_Pipe::MGPipeProtocolFatal("GetTextureImage.reply", "missing reply sink"); - const SizeT bpp = MG_Util::GetInputBytesPerPixel( - MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), - MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type)); + const SizeT bpp = MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), + MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type)); if (!bpp || !info.Box.W || !info.Box.H || !info.Box.D || info.Box.X || info.Box.Y || info.Box.Z) { MG_Pipe::MGPipeProtocolFatal("GetTextureImage.extent", "invalid tight image extent"); } const Uint64 tight = static_cast(info.Box.W) * info.Box.H * info.Box.D * bpp; - if (tight / bpp / info.Box.W / info.Box.H != info.Box.D || - info.DstOffset > tight || !info.DstSize || info.DstSize > tight - info.DstOffset) + if (tight / bpp / info.Box.W / info.Box.H != info.Box.D || info.DstOffset > tight || !info.DstSize || + info.DstSize > tight - info.DstOffset) MG_Pipe::MGPipeProtocolFatal("GetTextureImage.range", "invalid reply window"); auto image = info; image.DstOffset = 0; @@ -193,10 +186,11 @@ namespace MobileGL::MG_Record { // The decoder always passes its ReplySink; a null one means the applier was built // without a reply pool, and answering nothing would leave the client's barrier // waiting for a slot that never gets stamped - a hang, not a wrong picture. - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"read_pixels without a reply sink\"} - " - "the pixels' only destination in P5 is SEG_REPLY (contract table 1 row 23) " - "and a client blocked on seq %llu would never be answered", - static_cast(seq)); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"read_pixels without a reply sink\"} - " + "the pixels' only destination in P5 is SEG_REPLY (contract table 1 row 23) " + "and a client blocked on seq %llu would never be answered", + static_cast(seq)); } const SizeT bytesPerPixel = MG_Util::GetInputBytesPerPixel( MG_Util::ConvertGLEnumToTextureInputFormat(static_cast(info.Format)), @@ -210,8 +204,8 @@ namespace MobileGL::MG_Record { }; if (bytesPerPixel == 0 || info.Box.W == 0 || info.Box.H == 0 || info.Box.W > static_cast(std::numeric_limits::max()) || - info.Box.H > static_cast(std::numeric_limits::max()) || - info.DstSize == 0 || m_maxReplyBytes == 0) { + info.Box.H > static_cast(std::numeric_limits::max()) || info.DstSize == 0 || + m_maxReplyBytes == 0) { return rejectReadback(); } @@ -225,8 +219,7 @@ namespace MobileGL::MG_Record { const Uint64 tight = pixels * bpp; // PH-3's bound is the SERVER's answer size: w*h*bpp against its own maxReplyBytes, // checked before the scratch grows. It is not a rule about DstSize. - if (tight > m_maxReplyBytes || - tight > static_cast(std::numeric_limits::max())) { + if (tight > m_maxReplyBytes || tight > static_cast(std::numeric_limits::max())) { return rejectReadback(); } // ID-49: THE REPLY CROSSES TIGHT, WHATEVER DstSize THE CLIENT SENT. The server reads with @@ -241,9 +234,8 @@ namespace MobileGL::MG_Record { if (tight != info.DstSize) { MGLOG_E_ONCE("MG_Remote server: read_pixels DstSize %llu != tight w*h*bpp %llu " "(%ux%u, bpp %llu); reading the tight extent (ID-49)", - static_cast(info.DstSize), - static_cast(tight), info.Box.W, info.Box.H, - static_cast(bpp)); + static_cast(info.DstSize), static_cast(tight), + info.Box.W, info.Box.H, static_cast(bpp)); } if (!ReadBoundFramebufferTight(info, tight)) { @@ -286,9 +278,9 @@ namespace MobileGL::MG_Record { const MG_Pipe::MGPPixelPackState neutralPack = MG_Pipe::MGPipeNeutralReadPixelsPack(); MG_Pipe::MGPipeApplySetPixelPackState(neutralPack); - table->GL.ReadPixels(info.Box.X, info.Box.Y, static_cast(info.Box.W), - static_cast(info.Box.H), static_cast(info.Format), - static_cast(info.Type), m_readbackScratch.data()); + table->GL.ReadPixels(info.Box.X, info.Box.Y, static_cast(info.Box.W), static_cast(info.Box.H), + static_cast(info.Format), static_cast(info.Type), + m_readbackScratch.data()); MG_Pipe::MGPPixelPackState restorePack{}; restorePack.Pack = savedPack; @@ -342,8 +334,7 @@ namespace MobileGL::MG_Record { // no wider than the tight extent, and both are bounded here by what one MGPSubData can name // (MGPipeSetSubDataBufferRange: an offset up to 2^31-1, a width up to 2^32-1). Whether the // range fits the buffer is the applier's own gate, the one every buffer write meets. - static const char* ReadbackToBufferLayoutFault(const MG_Pipe::MGPReadbackToBuffer& record, - Uint64 bytesPerPixel) { + static const char* ReadbackToBufferLayoutFault(const MG_Pipe::MGPReadbackToBuffer& record, Uint64 bytesPerPixel) { const MG_Pipe::MGPBox& box = record.Src.Box; const Uint64 rowBytes = static_cast(box.W) * bytesPerPixel; const Uint64 tightBytes = rowBytes * box.H * box.D; // the caller proved this fits @@ -374,7 +365,8 @@ namespace MobileGL::MG_Record { if (record.SwapGroup != 0) { const Uint64 group = record.SwapGroup; for (Uint64 at = 0; at + group <= tightBytes; at += group) { - for (Uint64 i = 0; i < group / 2; ++i) std::swap(tight[at + i], tight[at + group - 1 - i]); + for (Uint64 i = 0; i < group / 2; ++i) + std::swap(tight[at + i], tight[at + group - 1 - i]); } } @@ -443,21 +435,25 @@ namespace MobileGL::MG_Record { } } if (fault != nullptr) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ReadPixelsToBuffer.shape\"} %s", fault); + return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ReadPixelsToBuffer.shape\"} %s", + fault); } if (!ReadBoundFramebufferTight(info, tight)) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ReadbackDeclined, "MGPipe: Fatal{ReadbackDeclined, \"ReadPixelsToBuffer\"} - the server " - "has no GL.ReadPixels; the pack buffer would keep bytes the application is " - "about to read as pixels, and there is no reply to carry the decline"); + return MG_Pipe::MGPipeRecordLatch( + MG_Pipe::MGFatalFamily::ReadbackDeclined, + "MGPipe: Fatal{ReadbackDeclined, \"ReadPixelsToBuffer\"} - the server " + "has no GL.ReadPixels; the pack buffer would keep bytes the application is " + "about to read as pixels, and there is no reply to carry the decline"); } return LandReadbackInBuffer(record, m_readbackScratch.data(), bpp); } Bool RecordVerbSink::OnGetTextureImageToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) { const MG_Pipe::MGPReadbackInfo& info = record.Src; - const Uint64 bpp = static_cast(MG_Util::GetInputBytesPerPixel( - MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), - MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))); + const Uint64 bpp = static_cast( + MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(info.Format), + MG_Util::ConvertGLEnumToTexturePixelDataType(info.Type))); const char* fault = nullptr; Uint64 tight = 0; if (!bpp || !info.Box.W || !info.Box.H || !info.Box.D || info.Box.X || info.Box.Y || info.Box.Z) { @@ -473,15 +469,18 @@ namespace MobileGL::MG_Record { } } if (fault != nullptr) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"GetTextureImageToBuffer.shape\"} %s", fault); + return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"GetTextureImageToBuffer.shape\"} %s", + fault); } Vector bytes; if (!ReadTextureImageTight(info, bytes) || bytes.size() != tight) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"GetTextureImageToBuffer\"} - the level " - "could not be read (%llu of %llu bytes); the reply form answers ERROR here, and " - "this form has no reply to carry it", - static_cast(bytes.size()), - static_cast(tight)); + return MG_Pipe::MGPipeRecordLatch( + MG_Pipe::MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"GetTextureImageToBuffer\"} - the level " + "could not be read (%llu of %llu bytes); the reply form answers ERROR here, and " + "this form has no reply to carry it", + static_cast(bytes.size()), static_cast(tight)); } return LandReadbackInBuffer(record, bytes.data(), bpp); } @@ -497,10 +496,12 @@ namespace MobileGL::MG_Record { // PH-1 (3): the shape is the PEER's (a multi-draw record it chose to send), so in an armed // session child the refusal latches and the verb returns false; unarmed it still dies. static Bool MultiDrawShapeLatch(const char* slot) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} a multi-draw " - "record with a shape this protocol does not carry: client indices travel as an owned " - "element buffer and no GL entry point produces an instanced multi-draw", - slot); + return MG_Pipe::MGPipeRecordLatch( + MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} a multi-draw " + "record with a shape this protocol does not carry: client indices travel as an owned " + "element buffer and no GL entry point produces an instanced multi-draw", + slot); } // P5b d1 (docs/archive/contracts/CONTRACT-P5B.md §2 d1): draw_vbo's whole cross product. The record @@ -570,29 +571,29 @@ namespace MobileGL::MG_Record { if (maxTouched < 0) return true; const Uint32 required = static_cast(maxTouched) + 1u; if (st.SamplerViewStart != 0 || st.SamplerViewCount < required) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - %s applies with " - "units 0..%d touched, so set_sampler_views must carry Start=0 and Count >= %u " - "(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which drops " - "the sync of at least one texture this draw samples", - verb, static_cast(maxTouched), required, st.SamplerViewStart, - st.SamplerViewCount); + return MG_Pipe::MGPipeRecordLatch( + MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - %s applies with " + "units 0..%d touched, so set_sampler_views must carry Start=0 and Count >= %u " + "(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which drops " + "the sync of at least one texture this draw samples", + verb, static_cast(maxTouched), required, st.SamplerViewStart, st.SamplerViewCount); } if (st.SamplerStateStart != 0 || st.SamplerStateCount < required) { - return MG_Pipe::MGPipeRecordLatch(MG_Pipe::MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - %s applies with " - "units 0..%d touched, so bind_sampler_states must carry Start=0 and Count >= %u " - "(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which leaves " - "an earlier draw's sampler object on at least one unit", - verb, static_cast(maxTouched), required, st.SamplerStateStart, - st.SamplerStateCount); + return MG_Pipe::MGPipeRecordLatch( + MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"BindSamplerStates.Count\"} - %s applies with " + "units 0..%d touched, so bind_sampler_states must carry Start=0 and Count >= %u " + "(CONTRACT-P5E.md §5.3); the applied window is Start=%u Count=%u, which leaves " + "an earlier draw's sampler object on at least one unit", + verb, static_cast(maxTouched), required, st.SamplerStateStart, st.SamplerStateCount); } return true; } } // namespace - Bool RecordVerbSink::OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, - const MG_Pipe::MGPDrawRange* ranges, - const MG_Pipe::MGHostSpan* userIndices, - const MG_Pipe::MGPDrawIndirect* indirect) { + Bool RecordVerbSink::OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, + const MG_Pipe::MGHostSpan* userIndices, const MG_Pipe::MGPDrawIndirect* indirect) { if (userIndices != nullptr && !CheckUserIndices(info, ranges, *userIndices)) { return false; // PH-1 (3): latched (armed session child); unarmed it died inside } @@ -621,10 +622,17 @@ namespace MobileGL::MG_Record { GLenum indexType = 0; switch (info.IndexSize) { - case 0: break; // arrays - case 1: indexType = GL_UNSIGNED_BYTE; break; - case 2: indexType = GL_UNSIGNED_SHORT; break; - case 4: indexType = GL_UNSIGNED_INT; break; + case 0: + break; // arrays + case 1: + indexType = GL_UNSIGNED_BYTE; + break; + case 2: + indexType = GL_UNSIGNED_SHORT; + break; + case 4: + indexType = GL_UNSIGNED_INT; + break; default: // IndexSize is "0 = arrays, else 1 / 2 / 4" (MGPipeTypes.h) and nothing else is a // legal width; defaulting to 4 would read past the element buffer. @@ -665,8 +673,7 @@ namespace MobileGL::MG_Record { } else { if (counted) { if (gl.MultiDrawElementsIndirectCount == nullptr) return false; - gl.MultiDrawElementsIndirectCount(mode, indexType, offset, parameterOffset, - drawCount, stride); + gl.MultiDrawElementsIndirectCount(mode, indexType, offset, parameterOffset, drawCount, stride); } else if (single) { if (gl.DrawElementsIndirect == nullptr) return false; gl.DrawElementsIndirect(mode, indexType, offset); @@ -703,23 +710,21 @@ namespace MobileGL::MG_Record { m_multiFirsts[i] = static_cast(ranges[i].Start); m_multiCounts[i] = static_cast(ranges[i].Count); } - gl.MultiDrawArrays(mode, m_multiFirsts.data(), m_multiCounts.data(), - static_cast(n)); + gl.MultiDrawArrays(mode, m_multiFirsts.data(), m_multiCounts.data(), static_cast(n)); } else { if (gl.MultiDrawElementsBaseVertex == nullptr) return false; m_multiOffsets.resize(n); m_multiBaseVertices.resize(n); for (SizeT i = 0; i < n; ++i) { - m_multiOffsets[i] = reinterpret_cast( - static_cast(ranges[i].Start) * info.IndexSize); + m_multiOffsets[i] = + reinterpret_cast(static_cast(ranges[i].Start) * info.IndexSize); m_multiCounts[i] = static_cast(ranges[i].Count); m_multiBaseVertices[i] = ranges[i].IndexBias; } // MultiDrawElements is the same call with every base vertex 0, which is what // its ranges carry (CONTRACT-P5B.md d1's MultiDrawElements row). - gl.MultiDrawElementsBaseVertex(mode, m_multiCounts.data(), indexType, - m_multiOffsets.data(), static_cast(n), - m_multiBaseVertices.data()); + gl.MultiDrawElementsBaseVertex(mode, m_multiCounts.data(), indexType, m_multiOffsets.data(), + static_cast(n), m_multiBaseVertices.data()); } ++m_draws; return true; @@ -734,8 +739,8 @@ namespace MobileGL::MG_Record { gl.DrawArrays(mode, static_cast(range.Start), count); } else if (baseInstance != 0) { if (gl.DrawArraysInstancedBaseInstance == nullptr) return false; - gl.DrawArraysInstancedBaseInstance(mode, static_cast(range.Start), count, - instanceCount, baseInstance); + gl.DrawArraysInstancedBaseInstance(mode, static_cast(range.Start), count, instanceCount, + baseInstance); } else { if (gl.DrawArraysInstanced == nullptr) return false; gl.DrawArraysInstanced(mode, static_cast(range.Start), count, instanceCount); @@ -754,13 +759,12 @@ namespace MobileGL::MG_Record { indices = MG_Pipe::MGPipeHostBytes(*userIndices); if (indices == nullptr) { MG_Pipe::MGPipeProtocolFatal("DrawVbo.userIndices", - "the user-index span passed the codec's four honesty arms " - "but MGPipeHostBytes resolved it to null; the server's " - "segment resolver is not installed"); + "the user-index span passed the codec's four honesty arms " + "but MGPipeHostBytes resolved it to null; the server's " + "segment resolver is not installed"); } } else { - indices = reinterpret_cast(static_cast(range.Start) * - info.IndexSize); + indices = reinterpret_cast(static_cast(range.Start) * info.IndexSize); } const GLint baseVertex = range.IndexBias; const Bool ranged = (info.Flags & MG_Pipe::kDrawHasIndexRange) != 0; @@ -777,8 +781,8 @@ namespace MobileGL::MG_Record { if (ranged) { if (baseVertex != 0) { if (gl.DrawRangeElementsBaseVertex == nullptr) return false; - gl.DrawRangeElementsBaseVertex(mode, info.MinIndex, info.MaxIndex, count, indexType, - indices, baseVertex); + gl.DrawRangeElementsBaseVertex(mode, info.MinIndex, info.MaxIndex, count, indexType, indices, + baseVertex); } else { if (gl.DrawRangeElements == nullptr) return false; gl.DrawRangeElements(mode, info.MinIndex, info.MaxIndex, count, indexType, indices); @@ -793,12 +797,11 @@ namespace MobileGL::MG_Record { } else if (baseInstance != 0) { if (baseVertex != 0) { if (gl.DrawElementsInstancedBaseVertexBaseInstance == nullptr) return false; - gl.DrawElementsInstancedBaseVertexBaseInstance(mode, count, indexType, indices, - instanceCount, baseVertex, baseInstance); + gl.DrawElementsInstancedBaseVertexBaseInstance(mode, count, indexType, indices, instanceCount, + baseVertex, baseInstance); } else { if (gl.DrawElementsInstancedBaseInstance == nullptr) return false; - gl.DrawElementsInstancedBaseInstance(mode, count, indexType, indices, instanceCount, - baseInstance); + gl.DrawElementsInstancedBaseInstance(mode, count, indexType, indices, instanceCount, baseInstance); } } else if (baseVertex != 0) { if (gl.DrawElementsInstancedBaseVertex == nullptr) return false; @@ -898,10 +901,14 @@ namespace MobileGL::MG_Record { // Typed handles resolve both texture and renderbuffer storage on the server. MG_Backend::CopyImageEndpoint src{}; MG_Backend::CopyImageEndpoint dst{}; - if (copy.SrcTarget == GL_RENDERBUFFER) src.RenderbufferHandle = copy.Src; - else src.TextureHandle = copy.Src; - if (copy.DstTarget == GL_RENDERBUFFER) dst.RenderbufferHandle = copy.Dst; - else dst.TextureHandle = copy.Dst; + if (copy.SrcTarget == GL_RENDERBUFFER) + src.RenderbufferHandle = copy.Src; + else + src.TextureHandle = copy.Src; + if (copy.DstTarget == GL_RENDERBUFFER) + dst.RenderbufferHandle = copy.Dst; + else + dst.TextureHandle = copy.Dst; if (MG_Pipe::MGPipeHandleIsNull(copy.Src) || MG_Pipe::MGPipeHandleIsNull(copy.Dst)) { // The monolith's own answer to this, in its own words (DirectGLESCopy.cpp's CopyImageSubData, // "source or destination image failed to sync; declining the copy"): the frontend @@ -909,20 +916,17 @@ namespace MobileGL::MG_Record { // application is owed. A decline here is a real answer, not a silent success. MGLOG_E_ONCE("MG_Remote server: resource_copy_region named texture(s) %u -> %u that " "the frontend no longer holds; declining the copy", - static_cast(copy.SrcGlName), - static_cast(copy.DstGlName)); + static_cast(copy.SrcGlName), static_cast(copy.DstGlName)); return false; } // SrcBox is {origin, extent} and the extent is the copy's, spelled once by GL for both // endpoints; the destination contributes only its origin. - table->GL.CopyImageSubData(src, static_cast(copy.SrcTarget), - static_cast(copy.SrcLevel), copy.SrcBox.X, copy.SrcBox.Y, - copy.SrcBox.Z, dst, static_cast(copy.DstTarget), - static_cast(copy.DstLevel), copy.DstX, copy.DstY, - copy.DstZ, static_cast(copy.SrcBox.W), - static_cast(copy.SrcBox.H), - static_cast(copy.SrcBox.D)); + table->GL.CopyImageSubData(src, static_cast(copy.SrcTarget), static_cast(copy.SrcLevel), + copy.SrcBox.X, copy.SrcBox.Y, copy.SrcBox.Z, dst, + static_cast(copy.DstTarget), static_cast(copy.DstLevel), copy.DstX, + copy.DstY, copy.DstZ, static_cast(copy.SrcBox.W), + static_cast(copy.SrcBox.H), static_cast(copy.SrcBox.D)); ++m_imageCopies; return true; } @@ -938,10 +942,8 @@ namespace MobileGL::MG_Record { // arguments travel because rule D says a verb crosses as the CALL, and because P7 is // what makes the backend read them instead of pulling. table->GL.BindImageTexture(static_cast(bind.Unit), static_cast(bind.GlName), - static_cast(bind.Level), - bind.Layered != 0 ? GL_TRUE : GL_FALSE, - static_cast(bind.Layer), - static_cast(bind.Access), + static_cast(bind.Level), bind.Layered != 0 ? GL_TRUE : GL_FALSE, + static_cast(bind.Layer), static_cast(bind.Access), static_cast(bind.Format)); ++m_imageBinds; // P5c (rv): an image bind moves the frontend's texture bind generation, and this verb @@ -952,8 +954,7 @@ namespace MobileGL::MG_Record { return true; } - Bool RecordVerbSink::OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding, - const char* name) { + Bool RecordVerbSink::OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding, const char* name) { const MG_Backend::GlobalBackendFunctionsTable* table = Table("set_storage_block_binding"); if (table == nullptr) return false; if (table->GL.ShaderStorageBlockBinding == nullptr) return false; @@ -1124,12 +1125,12 @@ namespace MobileGL::MG_Record { applierState.VerbCopyTexDst = copy.Dst; if (copy.SubImage) { if (table->GL.CopyTexSubImage2D == nullptr) return false; - table->GL.CopyTexSubImage2D(copy.Target, copy.Level, copy.XOffset, copy.YOffset, - copy.X, copy.Y, copy.Width, copy.Height); + table->GL.CopyTexSubImage2D(copy.Target, copy.Level, copy.XOffset, copy.YOffset, copy.X, copy.Y, copy.Width, + copy.Height); } else { if (table->GL.CopyTexImage2D == nullptr) return false; - table->GL.CopyTexImage2D(copy.Target, copy.Level, copy.InternalFormat, - copy.X, copy.Y, copy.Width, copy.Height, 0); + table->GL.CopyTexImage2D(copy.Target, copy.Level, copy.InternalFormat, copy.X, copy.Y, copy.Width, + copy.Height, 0); } return true; } diff --git a/MobileGL/MG_Backend/Record/RecordVerbSink.h b/MobileGL/MG_Backend/Record/RecordVerbSink.h index 93b9c119..5df83e43 100644 --- a/MobileGL/MG_Backend/Record/RecordVerbSink.h +++ b/MobileGL/MG_Backend/Record/RecordVerbSink.h @@ -33,8 +33,7 @@ namespace MobileGL::MG_Record { Bool OnClear(const MG_Pipe::MGPClear& clear) override; Bool OnBlit(const MG_Pipe::MGPBlit& blit) override; - Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, - MG_Pipe::MGPipeReplySink* replies) override; + Bool OnReadPixels(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, MG_Pipe::MGPipeReplySink* replies) override; Bool OnGetTextureImage(const MG_Pipe::MGPReadbackInfo& info, Uint64 seq, MG_Pipe::MGPipeReplySink* replies) override; // P9 (CONTRACT-P9.md §1): the pack-buffer halves. Nothing is answered; the tight pixels @@ -42,8 +41,7 @@ namespace MobileGL::MG_Record { Bool OnReadPixelsToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override; Bool OnGetTextureImageToBuffer(const MG_Pipe::MGPReadbackToBuffer& record) override; Bool OnDrawVbo(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, - const MG_Pipe::MGHostSpan* userIndices, - const MG_Pipe::MGPDrawIndirect* indirect) override; + const MG_Pipe::MGHostSpan* userIndices, const MG_Pipe::MGPDrawIndirect* indirect) override; // ---- P5b (docs/archive/contracts/CONTRACT-P5B.md): one override per row a migration package owns. // At the contract commit EVERY BODY BELOW IS A STUB that dies @@ -67,8 +65,7 @@ namespace MobileGL::MG_Record { Bool OnMemoryBarrier(const MG_Pipe::MGPMemoryBarrier& barrier) override; Bool OnResourceCopyRegion(const MG_Pipe::MGPCopyRegion& copy) override; Bool OnBindShaderImage(const MG_Pipe::MGPImageBind& bind) override; - Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding, - const char* name) override; + Bool OnSetStorageBlockBinding(const MG_Pipe::MGPStorageBlockBinding& binding, const char* name) override; Bool OnBeginStreamOutput(const MG_Pipe::MGPStreamOutputBegin& begin) override; Bool OnEndStreamOutput(const MG_Pipe::MGPXfbAccounting& accounting) override; Bool OnPauseStreamOutput(const MG_Pipe::MGPStreamOutputControl& control) override; @@ -157,8 +154,7 @@ namespace MobileGL::MG_Record { // P9: `tight` holds Src's pixels, tightly packed; land them in record.Dst at the // record's layout through MGPipeApplyResourceSubData, one write per contiguous run. The // layout was checked before the read (PipeApplier.cpp ReadbackToBufferLayoutFault). - Bool LandReadbackInBuffer(const MG_Pipe::MGPReadbackToBuffer& record, Uint8* tight, - Uint64 bytesPerPixel); + Bool LandReadbackInBuffer(const MG_Pipe::MGPReadbackToBuffer& record, Uint8* tight, Uint64 bytesPerPixel); MG_Backend::BackendObject* m_backend = nullptr; Uint64 m_clears = 0; diff --git a/MobileGL/MG_Backend/Record/StagedShadow.h b/MobileGL/MG_Backend/Record/StagedShadow.h index 42dedf39..e9c00f6c 100644 --- a/MobileGL/MG_Backend/Record/StagedShadow.h +++ b/MobileGL/MG_Backend/Record/StagedShadow.h @@ -164,8 +164,7 @@ namespace MobileGL::MG_Record { // Fatal when a drain reaches bytes no record staged. It fires ONLY for a base that is // this key's server shadow: the legacy arm passes the frontend object's own // MappedData(), which is valid for the whole store and is not this rule's subject. - void RequireCoverage(const void* key, const Uint8* hostBase, SizeT start, SizeT end, - const char* site) const { + void RequireCoverage(const void* key, const Uint8* hostBase, SizeT start, SizeT end, const char* site) const { if (!m_any.load(std::memory_order_acquire) || hostBase == nullptr) return; const std::lock_guard lock(m_mutex); const auto it = m_shadows.find(key); diff --git a/MobileGL/MG_Backend/Record/StagedTextureStore.h b/MobileGL/MG_Backend/Record/StagedTextureStore.h index 91899b5b..92578ff7 100644 --- a/MobileGL/MG_Backend/Record/StagedTextureStore.h +++ b/MobileGL/MG_Backend/Record/StagedTextureStore.h @@ -153,35 +153,33 @@ namespace MobileGL::MG_Record { } inline Bool StagedTextureExtentWithinDeviceLimits(Uint8 resourceTarget, const IntVec3& extent, - const StagedTextureDeviceLimits& limits) { + const StagedTextureDeviceLimits& limits) { if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return false; const auto target = static_cast(resourceTarget); switch (target) { case MG_Pipe::MGPipeResourceTarget::Tex1D: return extent.x() <= limits.MaxTextureSize && extent.y() == 1 && extent.z() == 1; case MG_Pipe::MGPipeResourceTarget::Tex2D: - return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxTextureSize && - extent.z() == 1; + return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxTextureSize && extent.z() == 1; case MG_Pipe::MGPipeResourceTarget::TexRect: - return extent.x() <= limits.MaxRectangleTextureSize && - extent.y() <= limits.MaxRectangleTextureSize && extent.z() == 1; + return extent.x() <= limits.MaxRectangleTextureSize && extent.y() <= limits.MaxRectangleTextureSize && + extent.z() == 1; case MG_Pipe::MGPipeResourceTarget::Tex3D: return extent.x() <= limits.Max3DTextureSize && extent.y() <= limits.Max3DTextureSize && extent.z() <= limits.Max3DTextureSize; case MG_Pipe::MGPipeResourceTarget::Tex1DArray: - return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxArrayTextureLayers && - extent.z() == 1; + return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxArrayTextureLayers && extent.z() == 1; case MG_Pipe::MGPipeResourceTarget::Tex2DArray: return extent.x() <= limits.MaxTextureSize && extent.y() <= limits.MaxTextureSize && extent.z() <= limits.MaxArrayTextureLayers; case MG_Pipe::MGPipeResourceTarget::Tex2DMSArray: return false; // defined levels only; multisample subimage records are never legal case MG_Pipe::MGPipeResourceTarget::TexCube: - return extent.x() <= limits.MaxCubeMapTextureSize && - extent.y() <= limits.MaxCubeMapTextureSize && extent.z() == 1; + return extent.x() <= limits.MaxCubeMapTextureSize && extent.y() <= limits.MaxCubeMapTextureSize && + extent.z() == 1; case MG_Pipe::MGPipeResourceTarget::TexCubeArray: - return extent.x() <= limits.MaxCubeMapTextureSize && - extent.y() <= limits.MaxCubeMapTextureSize && extent.z() <= limits.MaxArrayTextureLayers; + return extent.x() <= limits.MaxCubeMapTextureSize && extent.y() <= limits.MaxCubeMapTextureSize && + extent.z() <= limits.MaxArrayTextureLayers; case MG_Pipe::MGPipeResourceTarget::TexBuffer: return false; // texture buffers have no glTexSubImage entry point default: @@ -191,14 +189,13 @@ namespace MobileGL::MG_Record { // The descriptor's InternalFormat is already the server's uncompressed storage fallback. // Compute the maximum byte coordinate of one declared level without trusting upload bytes. - inline Bool StagedTextureDeclaredLevelByteBound(Uint8 resourceTarget, Uint32 internalFormat, - const IntVec3& extent, - const StagedTextureDeviceLimits& limits, - Uint64* outBound) { + inline Bool StagedTextureDeclaredLevelByteBound(Uint8 resourceTarget, Uint32 internalFormat, const IntVec3& extent, + const StagedTextureDeviceLimits& limits, Uint64* outBound) { if (outBound == nullptr || !StagedTextureTargetSupportsSubData(resourceTarget) || - !StagedTextureExtentWithinDeviceLimits(resourceTarget, extent, limits)) return false; - const SizeT bytesPerTexel = MG_Util::GetSizedInternalFormatSizeInBytes( - static_cast(internalFormat)); + !StagedTextureExtentWithinDeviceLimits(resourceTarget, extent, limits)) + return false; + const SizeT bytesPerTexel = + MG_Util::GetSizedInternalFormatSizeInBytes(static_cast(internalFormat)); if (bytesPerTexel == 0) return false; const Uint64 x = static_cast(extent.x()); const Uint64 y = static_cast(extent.y()); @@ -239,8 +236,7 @@ namespace MobileGL::MG_Record { return extent; } - inline IntVec3 StagedTextureUploadExtent(const MG_Pipe::MGPResourceDesc& desc, - const MG_Pipe::MGPSubData& upload) { + inline IntVec3 StagedTextureUploadExtent(const MG_Pipe::MGPResourceDesc& desc, const MG_Pipe::MGPSubData& upload) { if (upload.LevelWidth && upload.LevelHeight && upload.LevelDepth) return {static_cast(upload.LevelWidth), static_cast(upload.LevelHeight), static_cast(upload.LevelDepth)}; @@ -269,8 +265,7 @@ namespace MobileGL::MG_Record { // A run SHORTER than its box's byte extent is neither shape and answers 0 - not silently // placed: no emitter produces one, and the whole-level reading is the only one that cannot // lose texels the record did carry. - inline Uint64 StagedTextureRunImageOffset(const MG_Pipe::MGPSubData& upload, - const MG_Pipe::MGPSubRegion* regions) { + inline Uint64 StagedTextureRunImageOffset(const MG_Pipe::MGPSubData& upload, const MG_Pipe::MGPSubRegion* regions) { const MG_Pipe::MGPBox& box = upload.UnionBox; if (regions == nullptr || upload.RegionCount == 0) return 0; const Uint64 rowStride = regions[0].SrcRowStride; @@ -279,8 +274,7 @@ namespace MobileGL::MG_Record { const Uint64 texelBytes = rowStride / upload.LevelWidth; if (texelBytes == 0 || box.W == 0 || box.H == 0 || box.D == 0) return 0; const Uint64 boxBytes = static_cast(box.D - 1) * sliceStride + - static_cast(box.H - 1) * rowStride + - static_cast(box.W) * texelBytes; + static_cast(box.H - 1) * rowStride + static_cast(box.W) * texelBytes; if (upload.Blob.Size != boxBytes) return 0; return static_cast(box.Z) * sliceStride + static_cast(box.Y) * rowStride + static_cast(box.X) * texelBytes; @@ -313,16 +307,16 @@ namespace MobileGL::MG_Record { void SetDeviceLimits(Int maxTextureSize, Int max3DTextureSize, Int maxCubeMapTextureSize, Int maxArrayTextureLayers, Int maxTextureBufferSize) { const std::lock_guard lock(m_mutex); - m_deviceLimits = {maxTextureSize, std::min(16384, maxTextureSize), max3DTextureSize, - maxCubeMapTextureSize, maxArrayTextureLayers, maxTextureBufferSize}; + m_deviceLimits = {maxTextureSize, std::min(16384, maxTextureSize), + max3DTextureSize, maxCubeMapTextureSize, + maxArrayTextureLayers, maxTextureBufferSize}; } // The wire handle the record carried, tagged so it can never alias a twin address. // Slot and Gen are the client allocator's identity for one live object, so a recycled // slot's new owner keys a different entry than its predecessor's stale one. static Uint64 KeyForHandle(MG_Pipe::MGPipeHandle handle) { - return (Uint64{1} << 63) | (static_cast(handle.Slot) << 32) | - static_cast(handle.Gen); + return (Uint64{1} << 63) | (static_cast(handle.Slot) << 32) | static_cast(handle.Gen); } // For the caller whose server-side twin has no wire handle (Magma's TextureResource, // T5). Node-stable by that table's own ruling (std::unordered_map nodes). @@ -346,8 +340,8 @@ namespace MobileGL::MG_Record { // one source an aliasing store may keep by address. Every other adoption (the server's own // readbacks, regenerations and promotions) is server memory that dies with the call, and is // copied even by an aliasing store. - const Uint8* AdoptRun(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, - Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow = false) { + const Uint8* AdoptRun(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, Uint64 imageOffset, + const void* bytes, SizeT byteSize, Bool frontendShadow = false) { if (!Holds()) return nullptr; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); @@ -383,8 +377,8 @@ namespace MobileGL::MG_Record { // monolith-shaped caller. AdoptRun(offset = 0) with the entry replaced rather than grown // into; under monolith both do nothing and return nullptr, and the caller // (Ops_H_TextureSubData) has already returned before reaching either. - const Uint8* Adopt(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, - const void* bytes, SizeT byteSize, Bool frontendShadow = false) { + const Uint8* Adopt(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, const void* bytes, + SizeT byteSize, Bool frontendShadow = false) { if (!Holds()) return nullptr; const ShadowKey bucketed = Bucketed(key); const std::lock_guard lock(m_mutex); @@ -433,17 +427,18 @@ namespace MobileGL::MG_Record { const Bool supportsSubData = StagedTextureTargetSupportsSubData(resourceTarget); const Bool validExtent = extent.x() >= 0 && extent.y() >= 0 && extent.z() >= 0; const Bool emptyExtent = validExtent && (extent.x() == 0 || extent.y() == 0 || extent.z() == 0); - const Bool sizedFormat = MG_Util::GetSizedInternalFormatSizeInBytes( - static_cast(internalFormat)) != 0; - if (!validExtent || - (supportsSubData && sizedFormat && !emptyExtent && !StagedTextureDeclaredLevelByteBound( - resourceTarget, internalFormat, extent, m_deviceLimits, &byteBound))) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.NoteLevelDefined\"} - " - "the accepted respecify has no valid server-bounded texture level " - "(key=%llu, target=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)", - static_cast(key), uploadTarget, level, - extent.x(), extent.y(), extent.z(), internalFormat); + const Bool sizedFormat = + MG_Util::GetSizedInternalFormatSizeInBytes(static_cast(internalFormat)) != 0; + if (!validExtent || (supportsSubData && sizedFormat && !emptyExtent && + !StagedTextureDeclaredLevelByteBound(resourceTarget, internalFormat, extent, + m_deviceLimits, &byteBound))) { + MG_Pipe::MGPipeRecordFail( + MG_Pipe::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.NoteLevelDefined\"} - " + "the accepted respecify has no valid server-bounded texture level " + "(key=%llu, target=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)", + static_cast(key), uploadTarget, level, extent.x(), extent.y(), extent.z(), + internalFormat); } LevelShadow& shadow = m_shadows[bucketed].Levels[PackLevel(uploadTarget, level)]; if (!shadow.Defined || shadow.Extent != extent || shadow.InternalFormat != internalFormat || @@ -564,9 +559,8 @@ namespace MobileGL::MG_Record { // source level is complete and its texture was never written by the driver, the destination // level is complete (or the copy covers all of it), both levels' stored texels are one size, // and the box lies inside both. The caller marks the destination driver-written on false. - Bool FollowCopy(Uint64 srcKey, Uint16 srcTarget, Uint16 srcLevel, const IntVec3& srcOrigin, - Uint64 dstKey, Uint16 dstTarget, Uint16 dstLevel, const IntVec3& dstOrigin, - const IntVec3& size) { + Bool FollowCopy(Uint64 srcKey, Uint16 srcTarget, Uint16 srcLevel, const IntVec3& srcOrigin, Uint64 dstKey, + Uint16 dstTarget, Uint16 dstLevel, const IntVec3& dstOrigin, const IntVec3& size) { if (!Holds()) return false; const std::lock_guard lock(m_mutex); if (m_driverWritten.find(Bucketed(srcKey)) != m_driverWritten.end()) return false; @@ -574,7 +568,8 @@ namespace MobileGL::MG_Record { LevelShadow* dst = FindLevelMutable(dstKey, dstTarget, dstLevel); if (src == nullptr || dst == nullptr || !LevelComplete(*src)) return false; const SizeT texel = LevelTexelBytes(*src); - if (texel == 0 || !dst->Defined || dst->DeclaredByteBound == 0 || LevelTexelBytes(*dst) != texel) return false; + if (texel == 0 || !dst->Defined || dst->DeclaredByteBound == 0 || LevelTexelBytes(*dst) != texel) + return false; if (!BoxInside(srcOrigin, size, src->Extent) || !BoxInside(dstOrigin, size, dst->Extent)) return false; const Bool wholeDestination = dstOrigin == IntVec3{0, 0, 0} && size == dst->Extent; if (!LevelComplete(*dst) && !wholeDestination) return false; @@ -584,10 +579,10 @@ namespace MobileGL::MG_Record { const Uint8* srcBytes = LevelData(*src); for (Int z = 0; z < size.z(); ++z) { for (Int y = 0; y < size.y(); ++y) { - std::memcpy(rows.data() + (static_cast(z) * size.y() + y) * rowBytes, - srcBytes + TexelOffset(src->Extent, srcOrigin.x(), srcOrigin.y() + y, - srcOrigin.z() + z, texel), - rowBytes); + std::memcpy( + rows.data() + (static_cast(z) * size.y() + y) * rowBytes, + srcBytes + TexelOffset(src->Extent, srcOrigin.x(), srcOrigin.y() + y, srcOrigin.z() + z, texel), + rowBytes); } } if (dst->Alias != nullptr) { @@ -626,23 +621,22 @@ namespace MobileGL::MG_Record { // level image) - the rest of the level keeps the copy. False, touching nothing, when a box // is outside the level or a row of it is outside the run: the caller then adopts the run as // usual and marks the texture driver-written. - Bool AdoptClientBoxes(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, - Uint64 imageOffset, const void* bytes, SizeT byteSize, - const ClientTexelBox* boxes, SizeT boxCount) { + Bool AdoptClientBoxes(Uint64 key, Uint16 uploadTarget, Uint16 level, const IntVec3& extent, Uint64 imageOffset, + const void* bytes, SizeT byteSize, const ClientTexelBox* boxes, SizeT boxCount) { if (!Holds() || bytes == nullptr || (boxCount != 0 && boxes == nullptr)) return false; const std::lock_guard lock(m_mutex); LevelShadow* shadow = FindLevelMutable(key, uploadTarget, level); // Alias is null on a level holding a followed copy (FollowCopy took it server-owned); // checked anyway, because the boxes below are written into Bytes. - if (shadow == nullptr || !shadow->HoldsFollowedCopy || shadow->Alias != nullptr || !LevelComplete(*shadow) || - shadow->Extent != extent) { + if (shadow == nullptr || !shadow->HoldsFollowedCopy || shadow->Alias != nullptr || + !LevelComplete(*shadow) || shadow->Extent != extent) { return false; } const SizeT texel = LevelTexelBytes(*shadow); if (texel == 0) return false; const auto rowInRun = [&](const ClientTexelBox& box, Uint32 row, Uint32 slice, SizeT& offset) { - offset = TexelOffset(shadow->Extent, box.X, box.Y + static_cast(row), box.Z + static_cast(slice), - texel); + offset = TexelOffset(shadow->Extent, box.X, box.Y + static_cast(row), + box.Z + static_cast(slice), texel); const Uint64 begin = static_cast(offset); const Uint64 end = begin + static_cast(box.W) * texel; return begin >= imageOffset && end <= imageOffset + byteSize; @@ -694,16 +688,16 @@ namespace MobileGL::MG_Record { if (shadow != nullptr && LevelComplete(*shadow)) return LevelData(*shadow); // Verbatim what the MGLOG_F said, through the funnel that publishes it (P7 wave 0). MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::StageSnapshotTooNarrow, - "MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the " - "bytes of (uploadTarget=%u, level=%u) and the server's staged shadow has no " - "COMPLETE covered run for it. Under split the authoritative shadow is " - "SERVER-OWNED (rule C) and resource_subdata is the only way bytes reach it, " - "so these texels have never existed on this side: the record that should " - "have carried them is missing, a piece of a level cut into stage-chunk runs " - "has not arrived, or the level's texels were generated on the GPU and no " - "byte answer exists at all. Re-reading the client's shadow would be the " - "cross-role access this store exists to end", - site, static_cast(uploadTarget), static_cast(level)); + "MGPipe: Fatal{StageSnapshotTooNarrow, \"%s\"} - the texture sync wants the " + "bytes of (uploadTarget=%u, level=%u) and the server's staged shadow has no " + "COMPLETE covered run for it. Under split the authoritative shadow is " + "SERVER-OWNED (rule C) and resource_subdata is the only way bytes reach it, " + "so these texels have never existed on this side: the record that should " + "have carried them is missing, a piece of a level cut into stage-chunk runs " + "has not arrived, or the level's texels were generated on the GPU and no " + "byte answer exists at all. Re-reading the client's shadow would be the " + "cross-role access this store exists to end", + site, static_cast(uploadTarget), static_cast(level)); } // A box of a level for a replay into storage that is not this store's - a shared image, @@ -735,11 +729,11 @@ namespace MobileGL::MG_Record { const std::lock_guard lock(m_mutex); const LevelShadow* shadow = FindLevel(key, uploadTarget, level); if (shadow == nullptr || !shadow->Defined || shadow->DeclaredByteBound == 0 || - LevelBytesHeld(*shadow) != shadow->DeclaredByteBound) return false; + LevelBytesHeld(*shadow) != shadow->DeclaredByteBound) + return false; // THE WHOLE LEVEL, not merely the currently assembled prefix: all runs must cover // the server-derived bound before a reader can treat the level as complete. - return StagedShadowStore::CoverageHas( - shadow->Covered, 0, static_cast(shadow->DeclaredByteBound)); + return StagedShadowStore::CoverageHas(shadow->Covered, 0, static_cast(shadow->DeclaredByteBound)); } // How many runs this level's coverage is made of: 1 for a whole-level adoption, one per // piece for a level cut into stage-chunk slabs. Diagnostics for the unit cases, so a @@ -807,8 +801,7 @@ namespace MobileGL::MG_Record { Uint64 ShareGroupKey = 0; Uint64 Key = 0; Bool operator==(const ShadowKey& other) const { - return SessionKey == other.SessionKey && ShareGroupKey == other.ShareGroupKey && - Key == other.Key; + return SessionKey == other.SessionKey && ShareGroupKey == other.ShareGroupKey && Key == other.Key; } }; struct ShadowKeyHash { @@ -880,74 +873,71 @@ namespace MobileGL::MG_Record { // hold the run, place it, and widen the covered set by exactly its range. An empty run // (a level defined with no bytes, or a null pointer) covers nothing - emptiness is the // coverage answer for such a level, not an error. - void RequireDeclaredLevel(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, - Uint16 level, const IntVec3& extent) { + void RequireDeclaredLevel(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level, + const IntVec3& extent) { // `Defined` alone, which only NoteLevelDefined sets. The first draft also refused // `InternalFormat == 0`, but 0 is TextureInternalFormat::R8, so every R8 upload read // as "arrived before the level was declared". if (!shadow.Defined) { MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - " - "resource_subdata arrived before the server declared texture level " - "(key=%llu, uploadTarget=%u, level=%u)", - static_cast(key), uploadTarget, level); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - " + "resource_subdata arrived before the server declared texture level " + "(key=%llu, uploadTarget=%u, level=%u)", + static_cast(key), uploadTarget, level); } if (!StagedTextureTargetSupportsSubData(shadow.ResourceTarget)) { MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"} - " - "resource_subdata is not legal for the server-declared texture target " - "(key=%llu, uploadTarget=%u, level=%u, target=%u)", - static_cast(key), uploadTarget, level, - static_cast(shadow.ResourceTarget)); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"} - " + "resource_subdata is not legal for the server-declared texture target " + "(key=%llu, uploadTarget=%u, level=%u, target=%u)", + static_cast(key), uploadTarget, level, + static_cast(shadow.ResourceTarget)); } if (shadow.DeclaredByteBound == 0) { MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelBound\"} - " - "resource_subdata into a byteless declared level: its extent is empty or " - "its format has no uncompressed size on this server " - "(key=%llu, uploadTarget=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)", - static_cast(key), uploadTarget, level, - shadow.Extent.x(), shadow.Extent.y(), shadow.Extent.z(), - shadow.InternalFormat); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelBound\"} - " + "resource_subdata into a byteless declared level: its extent is empty or " + "its format has no uncompressed size on this server " + "(key=%llu, uploadTarget=%u, level=%u, extent=%d,%d,%d, internalFormat=%u)", + static_cast(key), uploadTarget, level, shadow.Extent.x(), + shadow.Extent.y(), shadow.Extent.z(), shadow.InternalFormat); } if (shadow.Extent == extent) return; MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - " - "resource_subdata extent disagrees with the server-declared texture level " - "(key=%llu, uploadTarget=%u, level=%u, declared=%d,%d,%d, record=%d,%d,%d)", - static_cast(key), uploadTarget, level, - shadow.Extent.x(), shadow.Extent.y(), shadow.Extent.z(), - extent.x(), extent.y(), extent.z()); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"} - " + "resource_subdata extent disagrees with the server-declared texture level " + "(key=%llu, uploadTarget=%u, level=%u, declared=%d,%d,%d, record=%d,%d,%d)", + static_cast(key), uploadTarget, level, shadow.Extent.x(), + shadow.Extent.y(), shadow.Extent.z(), extent.x(), extent.y(), extent.z()); } // Check the run's exclusive end against the extent recorded by the server's prior // resource_respecify before any cast, coverage edit, or vector growth. - const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level, - Uint64 imageOffset, const void* bytes, SizeT byteSize, Bool frontendShadow) { + const Uint8* CopyRunInto(LevelShadow& shadow, Uint64 key, Uint16 uploadTarget, Uint16 level, Uint64 imageOffset, + const void* bytes, SizeT byteSize, Bool frontendShadow) { if (bytes != nullptr && byteSize != 0) { constexpr Uint64 max = std::numeric_limits::max(); const Uint64 runBytes = static_cast(byteSize); if (imageOffset > max - runBytes) { MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - " - "texture run end overflows (handle=%llu, uploadTarget=%u, level=%u, " - "offset=%llu, size=%llu)", - static_cast(key), uploadTarget, level, - static_cast(imageOffset), - static_cast(runBytes)); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - " + "texture run end overflows (handle=%llu, uploadTarget=%u, level=%u, " + "offset=%llu, size=%llu)", + static_cast(key), uploadTarget, level, + static_cast(imageOffset), + static_cast(runBytes)); } const Uint64 end = imageOffset + runBytes; - if (end > shadow.DeclaredByteBound || - end > static_cast(std::numeric_limits::max())) { + if (end > shadow.DeclaredByteBound || end > static_cast(std::numeric_limits::max())) { MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - " - "texture run exceeds the server-declared level byte bound " - "(handle=%llu, uploadTarget=%u, level=%u, offset=%llu, size=%llu, " - "bound=%llu)", - static_cast(key), uploadTarget, level, - static_cast(imageOffset), - static_cast(runBytes), - static_cast(shadow.DeclaredByteBound)); + "MGPipe: Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"} - " + "texture run exceeds the server-declared level byte bound " + "(handle=%llu, uploadTarget=%u, level=%u, offset=%llu, size=%llu, " + "bound=%llu)", + static_cast(key), uploadTarget, level, + static_cast(imageOffset), + static_cast(runBytes), + static_cast(shadow.DeclaredByteBound)); } const auto* raw = static_cast(bytes); const SizeT begin = static_cast(imageOffset); diff --git a/MobileGL/MG_Backend/SubgroupImplementation.h b/MobileGL/MG_Backend/SubgroupImplementation.h index ec6abbaf..d12bce70 100644 --- a/MobileGL/MG_Backend/SubgroupImplementation.h +++ b/MobileGL/MG_Backend/SubgroupImplementation.h @@ -24,15 +24,19 @@ namespace MobileGL::MG_Backend { // MOBILEGL_SUBGROUP_EMULATION: Auto emulates exactly where nothing native exists; ForceOn // emulates even over native support (to exercise the emulation on any device); ForceOff // never emulates. - enum class SubgroupImplementation : Uint8 { None, Native, Emulated }; + enum class SubgroupImplementation : Uint8 { + None, + Native, + Emulated + }; inline constexpr Uint32 kEmulatedSubgroupSize = 32u; inline constexpr Uint32 kEmulatedSubgroupStages = GL_COMPUTE_SHADER_BIT; inline constexpr Uint32 kEmulatedSubgroupFeatures = - GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_VOTE_BIT_KHR | - GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR | - GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR | GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR | - GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR | GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; + GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_VOTE_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | + GL_SUBGROUP_FEATURE_BALLOT_BIT_KHR | GL_SUBGROUP_FEATURE_SHUFFLE_BIT_KHR | + GL_SUBGROUP_FEATURE_SHUFFLE_RELATIVE_BIT_KHR | GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR | + GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; inline SubgroupImplementation ResolveSubgroupImplementation(const Bool nativeSupported) { switch (MG_Config::Features.SubgroupEmulation) { @@ -54,9 +58,12 @@ namespace MobileGL::MG_Backend { inline const char* SubgroupImplementationName(const SubgroupImplementation implementation) { switch (implementation) { - case SubgroupImplementation::Native: return "native"; - case SubgroupImplementation::Emulated: return "emulated"; - case SubgroupImplementation::None: break; + case SubgroupImplementation::Native: + return "native"; + case SubgroupImplementation::Emulated: + return "emulated"; + case SubgroupImplementation::None: + break; } return "none"; } diff --git a/MobileGL/MG_Benchmark/Buffer/BufferBench.cpp b/MobileGL/MG_Benchmark/Buffer/BufferBench.cpp index 02e7d3e1..ff2b3294 100644 --- a/MobileGL/MG_Benchmark/Buffer/BufferBench.cpp +++ b/MobileGL/MG_Benchmark/Buffer/BufferBench.cpp @@ -180,16 +180,16 @@ BENCHMARK(BM_UpdateDataPartially)->Unit(benchmark::kMillisecond)->UseRealTime(); int main(int argc, char** argv) { Initialize(); - benchmark ::MaybeReenterWithoutASLR(argc, argv); + benchmark::MaybeReenterWithoutASLR(argc, argv); char arg0_default[] = "benchmark"; char* args_default = reinterpret_cast(arg0_default); if (!argv) { argc = 1; argv = &args_default; } - ::benchmark ::Initialize(&argc, argv); - if (::benchmark ::ReportUnrecognizedArguments(argc, argv)) return 1; - ::benchmark ::RunSpecifiedBenchmarks(); - ::benchmark ::Shutdown(); + ::benchmark::Initialize(&argc, argv); + if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; + ::benchmark::RunSpecifiedBenchmarks(); + ::benchmark::Shutdown(); return 0; } diff --git a/MobileGL/MG_Benchmark/Container/UnorderedMapBench.cpp b/MobileGL/MG_Benchmark/Container/UnorderedMapBench.cpp index 13cbdb80..e6e8b856 100644 --- a/MobileGL/MG_Benchmark/Container/UnorderedMapBench.cpp +++ b/MobileGL/MG_Benchmark/Container/UnorderedMapBench.cpp @@ -62,7 +62,8 @@ namespace { Vector SequentialKeys(SizeT n) { Vector keys; keys.reserve(n); - for (SizeT i = 0; i < n; ++i) keys.push_back(static_cast(i) + 1); + for (SizeT i = 0; i < n; ++i) + keys.push_back(static_cast(i) + 1); return keys; } @@ -89,7 +90,8 @@ namespace { Vector keys; keys.reserve(n); std::mt19937_64 rng(0xC0FFEE); - for (SizeT i = 0; i < n; ++i) keys.push_back(rng()); + for (SizeT i = 0; i < n; ++i) + keys.push_back(rng()); return keys; } @@ -117,7 +119,8 @@ namespace { template UnorderedMap Populated(const Vector& keys) { UnorderedMap map; - for (SizeT i = 0; i < keys.size(); ++i) map[keys[i]] = i; + for (SizeT i = 0; i < keys.size(); ++i) + map[keys[i]] = i; return map; } @@ -160,7 +163,8 @@ namespace { const auto& keys = CachedKeys(static_cast(state.range(0))); for (auto _ : state) { UnorderedMap map; - for (SizeT i = 0; i < keys.size(); ++i) map[keys[i]] = i; + for (SizeT i = 0; i < keys.size(); ++i) + map[keys[i]] = i; benchmark::DoNotOptimize(map.size()); } state.SetItemsProcessed(state.iterations() * static_cast(keys.size())); @@ -175,8 +179,10 @@ namespace { state.PauseTiming(); auto map = Populated(keys); state.ResumeTiming(); - for (SizeT i = 0; i < keys.size(); i += 2) benchmark::DoNotOptimize(map.erase(keys[i])); - for (SizeT i = 0; i < keys.size(); i += 2) map[keys[i]] = i; + for (SizeT i = 0; i < keys.size(); i += 2) + benchmark::DoNotOptimize(map.erase(keys[i])); + for (SizeT i = 0; i < keys.size(); i += 2) + map[keys[i]] = i; benchmark::DoNotOptimize(map.size()); } state.SetItemsProcessed(state.iterations() * static_cast(keys.size())); @@ -192,7 +198,8 @@ namespace { state.PauseTiming(); auto map = Populated(keys); state.ResumeTiming(); - for (auto it = map.begin(); it != map.end();) it = map.erase(it); + for (auto it = map.begin(); it != map.end();) + it = map.erase(it); benchmark::DoNotOptimize(map.size()); } state.SetItemsProcessed(state.iterations() * static_cast(keys.size())); @@ -206,7 +213,8 @@ namespace { auto map = Populated(keys); for (auto _ : state) { Uint64 acc = 0; - for (const auto& entry : map) acc += entry.second; + for (const auto& entry : map) + acc += entry.second; benchmark::DoNotOptimize(acc); } state.SetItemsProcessed(state.iterations() * static_cast(keys.size())); @@ -214,11 +222,8 @@ namespace { } // namespace -#define MGL_MAP_BENCH(WORKLOAD, SHAPE, VEC, MAKER) \ - BENCHMARK_TEMPLATE(WORKLOAD, VEC, MAKER) \ - ->Name(#WORKLOAD "/" #SHAPE) \ - ->RangeMultiplier(8) \ - ->Range(kMinSize, kMaxSize) +#define MGL_MAP_BENCH(WORKLOAD, SHAPE, VEC, MAKER) \ + BENCHMARK_TEMPLATE(WORKLOAD, VEC, MAKER)->Name(#WORKLOAD "/" #SHAPE)->RangeMultiplier(8)->Range(kMinSize, kMaxSize) MGL_MAP_BENCH(LookupHit, sequential, Vector, SequentialKeys); MGL_MAP_BENCH(LookupHit, pointer, Vector, PointerKeys); diff --git a/MobileGL/MG_Benchmark/Driver/DriverBench.c b/MobileGL/MG_Benchmark/Driver/DriverBench.c index 496a9107..40df6b8d 100644 --- a/MobileGL/MG_Benchmark/Driver/DriverBench.c +++ b/MobileGL/MG_Benchmark/Driver/DriverBench.c @@ -185,8 +185,7 @@ GLF(void, glBindAttribLocation, (GLuint, GLuint, const GLchar*)) GLF(void, glUniform3fv, (GLint, GLsizei, const float*)) GLF(void, glDrawArrays, (GLenum, GLint, GLsizei)) GLF(void, glDrawElementsBaseVertex, (GLenum, GLsizei, GLenum, const void*, GLint)) -GLF(void, glMultiDrawElementsBaseVertex, - (GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*)) +GLF(void, glMultiDrawElementsBaseVertex, (GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, const GLint*)) GLF(void, glBindBufferRange, (GLenum, GLuint, GLuint, GLintptr, GLsizeiptr)) GLF(void, glBindBufferBase, (GLenum, GLuint, GLuint)) GLF(GLuint, glGetUniformBlockIndex, (GLuint, const GLchar*)) @@ -216,7 +215,6 @@ static int cmp_u64(const void* a, const void* b) { return x < y ? -1 : x > y; } - /* Scene, cases and the case table live next door so the Android plugin's * in-process benchmark runs byte-identical bodies. */ static void bench_gl_failed(const char* what, const char* detail) { @@ -285,8 +283,7 @@ static void run_case(const char* name, case_fn body, long a, long b, long opsPer uint64_t med = samples[g_frames / 2]; double frameMs = med / 1e6; double nsPerOp = opsPerFrame > 0 ? (double)med / (double)opsPerFrame : 0.0; - printf("%s,%d,%ld,%.3f,%.1f,%.1f\n", name, g_frames, opsPerFrame, frameMs, nsPerOp, - 1e9 / (double)med); + printf("%s,%d,%ld,%.3f,%.1f,%.1f\n", name, g_frames, opsPerFrame, frameMs, nsPerOp, 1e9 / (double)med); fflush(stdout); if (glGetError() != GL_NO_ERROR) fprintf(stderr, "WARN: GL error after %s\n", name); } @@ -302,8 +299,7 @@ static EGLDisplay surfaceless_display(void) { if (!fn) fn = dlsym(g_provider, "eglGetPlatformDisplayEXT"); if (!fn) fn = g_eglGetProcAddress("eglGetPlatformDisplayEXT"); if (!fn) return NULL; - return ((EGLDisplay(*)(EGLenum, void*, const void*))fn)(EGL_PLATFORM_SURFACELESS_MESA, - EGL_DEFAULT_DISPLAY, NULL); + return ((EGLDisplay (*)(EGLenum, void*, const void*))fn)(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL); } /* ---- EGL bootstrap: one provider library, pbuffer, desktop-GL context ---- */ @@ -315,9 +311,12 @@ static int boot_egl(void) { fprintf(stderr, "FAIL: dlopen %s: %s\n", libpath, dlerror()); return 1; } -#define ESYM(name) \ - void* p_##name = dlsym(g_provider, #name); \ - if (!p_##name) { fprintf(stderr, "FAIL: dlsym %s\n", #name); return 1; } +#define ESYM(name) \ + void* p_##name = dlsym(g_provider, #name); \ + if (!p_##name) { \ + fprintf(stderr, "FAIL: dlsym %s\n", #name); \ + return 1; \ + } ESYM(eglGetDisplay) ESYM(eglInitialize) ESYM(eglChooseConfig) @@ -329,9 +328,9 @@ static int boot_egl(void) { ESYM(eglGetError) g_eglGetProcAddress = (void* (*)(const char*))p_eglGetProcAddress; - EGLint (*getError)(void) = (EGLint(*)(void))p_eglGetError; + EGLint (*getError)(void) = (EGLint (*)(void))p_eglGetError; EGLBoolean (*initialize)(EGLDisplay, EGLint*, EGLint*) = - (EGLBoolean(*)(EGLDisplay, EGLint*, EGLint*))p_eglInitialize; + (EGLBoolean (*)(EGLDisplay, EGLint*, EGLint*))p_eglInitialize; /* The default display first: it is the one a windowed app would get, and * on a desktop it is the one that reaches the real GPU - which is the @@ -343,7 +342,7 @@ static int boot_egl(void) { * the work being timed. */ EGLint maj = 0, min = 0; const char* how = "default display"; - EGLDisplay dpy = ((EGLDisplay(*)(void*))p_eglGetDisplay)(EGL_DEFAULT_DISPLAY); + EGLDisplay dpy = ((EGLDisplay (*)(void*))p_eglGetDisplay)(EGL_DEFAULT_DISPLAY); if (!dpy || !initialize(dpy, &maj, &min)) { dpy = surfaceless_display(); how = "surfaceless display"; @@ -359,22 +358,26 @@ static int boot_egl(void) { // native driver as the baseline. The .inc picks ESSL shader sources when the // context turns out to be ES. EGLBoolean (*chooseConfig)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*) = - (EGLBoolean(*)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*))p_eglChooseConfig; + (EGLBoolean (*)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*))p_eglChooseConfig; EGLContext (*createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint*) = - (EGLContext(*)(EGLDisplay, EGLConfig, EGLContext, const EGLint*))p_eglCreateContext; - EGLBoolean (*bindApi)(EGLenum) = (EGLBoolean(*)(EGLenum))p_eglBindAPI; + (EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint*))p_eglCreateContext; + EGLBoolean (*bindApi)(EGLenum) = (EGLBoolean (*)(EGLenum))p_eglBindAPI; EGLConfig cfg = NULL; EGLint ncfg = 0; EGLContext ctx = EGL_NO_CONTEXT; if (bindApi(EGL_OPENGL_API)) { - const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, - EGL_DEPTH_SIZE, 24, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE}; + const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, EGL_DEPTH_SIZE, 24, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, EGL_NONE}; if (chooseConfig(dpy, cfgAttribs, &cfg, 1, &ncfg) && ncfg >= 1) { - const EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 2, + const EGLint ctxAttribs[] = {EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 2, EGL_CONTEXT_OPENGL_PROFILE_MASK, - EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE}; + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_NONE}; ctx = createContext(dpy, cfg, EGL_NO_CONTEXT, ctxAttribs); if (ctx == EGL_NO_CONTEXT) ctx = createContext(dpy, cfg, EGL_NO_CONTEXT, NULL); } @@ -384,9 +387,19 @@ static int boot_egl(void) { fprintf(stderr, "FAIL: neither OpenGL nor OpenGL ES is bindable on this provider\n"); return 1; } - const EGLint esCfgAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE}; + const EGLint esCfgAttribs[] = {EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_DEPTH_SIZE, + 24, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES3_BIT, + EGL_NONE}; ncfg = 0; if (!chooseConfig(dpy, esCfgAttribs, &cfg, 1, &ncfg) || ncfg < 1) { // EGL_SURFACE_TYPE 0 matches any config: a stack that offers no @@ -411,12 +424,10 @@ static int boot_egl(void) { * exactly what EGL_KHR_surfaceless_context takes, so the same call covers * both. */ const EGLint pbAttribs[] = {EGL_WIDTH, 64, EGL_HEIGHT, 64, EGL_NONE}; - EGLSurface surf = ((EGLSurface(*)(EGLDisplay, EGLConfig, const EGLint*))p_eglCreatePbufferSurface)( - dpy, cfg, pbAttribs); - if (surf == EGL_NO_SURFACE) - fprintf(stderr, "no pbuffer (0x%x), using a surfaceless context\n", getError()); - if (!((EGLBoolean(*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))p_eglMakeCurrent)(dpy, surf, - surf, ctx)) { + EGLSurface surf = + ((EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint*))p_eglCreatePbufferSurface)(dpy, cfg, pbAttribs); + if (surf == EGL_NO_SURFACE) fprintf(stderr, "no pbuffer (0x%x), using a surfaceless context\n", getError()); + if (!((EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext))p_eglMakeCurrent)(dpy, surf, surf, ctx)) { fprintf(stderr, "FAIL: eglMakeCurrent (0x%x)\n", getError()); return 1; } @@ -424,32 +435,75 @@ static int boot_egl(void) { /* Core GL entry points: eglGetProcAddress first (EGL 1.5 serves core * functions), provider dlsym as fallback (both glvnd and MobileGL export * the gl* symbols directly). */ -#define RESOLVE(name) \ - do { \ - *(void**)&name = g_eglGetProcAddress(#name); \ - if (!name) *(void**)&name = dlsym(g_provider, #name); \ - if (!name) { fprintf(stderr, "FAIL: resolve %s\n", #name); return 1; } \ +#define RESOLVE(name) \ + do { \ + *(void**)&name = g_eglGetProcAddress(#name); \ + if (!name) *(void**)&name = dlsym(g_provider, #name); \ + if (!name) { \ + fprintf(stderr, "FAIL: resolve %s\n", #name); \ + return 1; \ + } \ } while (0) - RESOLVE(glClear); RESOLVE(glClearColor); RESOLVE(glEnable); RESOLVE(glViewport); - RESOLVE(glDisable); RESOLVE(glBlendFuncSeparate); RESOLVE(glDrawBuffers); - RESOLVE(glGetString); RESOLVE(glGetError); RESOLVE(glFinish); RESOLVE(glFlush); - RESOLVE(glGenBuffers); RESOLVE(glBindBuffer); RESOLVE(glBufferData); RESOLVE(glBufferSubData); - RESOLVE(glGenVertexArrays); RESOLVE(glBindVertexArray); RESOLVE(glEnableVertexAttribArray); - RESOLVE(glVertexAttribPointer); RESOLVE(glGenTextures); RESOLVE(glBindTexture); - RESOLVE(glActiveTexture); RESOLVE(glTexImage2D); RESOLVE(glTexSubImage2D); - RESOLVE(glTexParameteri); RESOLVE(glGenerateMipmap); RESOLVE(glCreateShader); - RESOLVE(glPixelStorei); RESOLVE(glGetIntegerv); - RESOLVE(glShaderSource); RESOLVE(glCompileShader); RESOLVE(glGetShaderiv); - RESOLVE(glGetShaderInfoLog); RESOLVE(glCreateProgram); RESOLVE(glAttachShader); - RESOLVE(glLinkProgram); RESOLVE(glGetProgramiv); RESOLVE(glUseProgram); - RESOLVE(glGetUniformLocation); RESOLVE(glUniform1i); RESOLVE(glUniform3f); - RESOLVE(glUniformMatrix4fv); RESOLVE(glDrawElements); RESOLVE(glBindAttribLocation); - RESOLVE(glUniform3fv); RESOLVE(glDrawArrays); RESOLVE(glDrawElementsBaseVertex); - RESOLVE(glBindBufferRange); RESOLVE(glBindBufferBase); - RESOLVE(glGetUniformBlockIndex); RESOLVE(glUniformBlockBinding); - RESOLVE(glGenSamplers); RESOLVE(glBindSampler); RESOLVE(glSamplerParameteri); - RESOLVE(glGenFramebuffers); RESOLVE(glBindFramebuffer); RESOLVE(glGenRenderbuffers); - RESOLVE(glBindRenderbuffer); RESOLVE(glRenderbufferStorage); RESOLVE(glFramebufferRenderbuffer); + RESOLVE(glClear); + RESOLVE(glClearColor); + RESOLVE(glEnable); + RESOLVE(glViewport); + RESOLVE(glDisable); + RESOLVE(glBlendFuncSeparate); + RESOLVE(glDrawBuffers); + RESOLVE(glGetString); + RESOLVE(glGetError); + RESOLVE(glFinish); + RESOLVE(glFlush); + RESOLVE(glGenBuffers); + RESOLVE(glBindBuffer); + RESOLVE(glBufferData); + RESOLVE(glBufferSubData); + RESOLVE(glGenVertexArrays); + RESOLVE(glBindVertexArray); + RESOLVE(glEnableVertexAttribArray); + RESOLVE(glVertexAttribPointer); + RESOLVE(glGenTextures); + RESOLVE(glBindTexture); + RESOLVE(glActiveTexture); + RESOLVE(glTexImage2D); + RESOLVE(glTexSubImage2D); + RESOLVE(glTexParameteri); + RESOLVE(glGenerateMipmap); + RESOLVE(glCreateShader); + RESOLVE(glPixelStorei); + RESOLVE(glGetIntegerv); + RESOLVE(glShaderSource); + RESOLVE(glCompileShader); + RESOLVE(glGetShaderiv); + RESOLVE(glGetShaderInfoLog); + RESOLVE(glCreateProgram); + RESOLVE(glAttachShader); + RESOLVE(glLinkProgram); + RESOLVE(glGetProgramiv); + RESOLVE(glUseProgram); + RESOLVE(glGetUniformLocation); + RESOLVE(glUniform1i); + RESOLVE(glUniform3f); + RESOLVE(glUniformMatrix4fv); + RESOLVE(glDrawElements); + RESOLVE(glBindAttribLocation); + RESOLVE(glUniform3fv); + RESOLVE(glDrawArrays); + RESOLVE(glDrawElementsBaseVertex); + RESOLVE(glBindBufferRange); + RESOLVE(glBindBufferBase); + RESOLVE(glGetUniformBlockIndex); + RESOLVE(glUniformBlockBinding); + RESOLVE(glGenSamplers); + RESOLVE(glBindSampler); + RESOLVE(glSamplerParameteri); + RESOLVE(glGenFramebuffers); + RESOLVE(glBindFramebuffer); + RESOLVE(glGenRenderbuffers); + RESOLVE(glBindRenderbuffer); + RESOLVE(glRenderbufferStorage); + RESOLVE(glFramebufferRenderbuffer); RESOLVE(glCheckFramebufferStatus); // Optional: end_frame_wait() falls back to glFinish when a stack has no // sync objects, so resolve without failing the run. diff --git a/MobileGL/MG_Benchmark/Program/ProgramBench.cpp b/MobileGL/MG_Benchmark/Program/ProgramBench.cpp index 58ab4c01..314bce81 100644 --- a/MobileGL/MG_Benchmark/Program/ProgramBench.cpp +++ b/MobileGL/MG_Benchmark/Program/ProgramBench.cpp @@ -222,16 +222,16 @@ BENCHMARK(BM_CompileAndLink)->Unit(benchmark::kMillisecond); int main(int argc, char** argv) { Initialize(); - benchmark ::MaybeReenterWithoutASLR(argc, argv); + benchmark::MaybeReenterWithoutASLR(argc, argv); char arg0_default[] = "benchmark"; char* args_default = reinterpret_cast(arg0_default); if (!argv) { argc = 1; argv = &args_default; } - ::benchmark ::Initialize(&argc, argv); - if (::benchmark ::ReportUnrecognizedArguments(argc, argv)) return 1; - ::benchmark ::RunSpecifiedBenchmarks(); - ::benchmark ::Shutdown(); + ::benchmark::Initialize(&argc, argv); + if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1; + ::benchmark::RunSpecifiedBenchmarks(); + ::benchmark::Shutdown(); return 0; } diff --git a/MobileGL/MG_Benchmark/ShaderCache/TranslationCacheBench.cpp b/MobileGL/MG_Benchmark/ShaderCache/TranslationCacheBench.cpp index 64ab2bf3..18a53e5d 100644 --- a/MobileGL/MG_Benchmark/ShaderCache/TranslationCacheBench.cpp +++ b/MobileGL/MG_Benchmark/ShaderCache/TranslationCacheBench.cpp @@ -108,8 +108,7 @@ void main() { class CacheModeScope { public: - explicit CacheModeScope(const Bool enabled) - : m_saved(MG_Config::Features.ShaderTranslationCache) { + explicit CacheModeScope(const Bool enabled) : m_saved(MG_Config::Features.ShaderTranslationCache) { MG_Config::Features.ShaderTranslationCache = enabled ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff; } @@ -339,8 +338,7 @@ BENCHMARK(BM_SharedShaderLink_CacheOn)->Arg(0)->Arg(120)->Unit(benchmark::kMicro // --------------------------------------------------------------------------------------- static void BM_EsslTranspile_CacheOff(benchmark::State& state) { MobileGL::Initialize(); - const Vector spirv = - BuildSanitizedFragmentSpirv(SwizzleLikeFragment("", static_cast(state.range(0)))); + const Vector spirv = BuildSanitizedFragmentSpirv(SwizzleLikeFragment("", static_cast(state.range(0)))); if (spirv.empty()) { state.SkipWithError("could not build the fragment module"); return; @@ -359,8 +357,7 @@ BENCHMARK(BM_EsslTranspile_CacheOff)->Arg(0)->Arg(120)->Unit(benchmark::kMicrose static void BM_EsslTranspile_CacheOn(benchmark::State& state) { MobileGL::Initialize(); - const Vector spirv = - BuildSanitizedFragmentSpirv(SwizzleLikeFragment("", static_cast(state.range(0)))); + const Vector spirv = BuildSanitizedFragmentSpirv(SwizzleLikeFragment("", static_cast(state.range(0)))); if (spirv.empty()) { state.SkipWithError("could not build the fragment module"); return; @@ -413,8 +410,7 @@ BENCHMARK(BM_EsslTranspile_CacheOn)->Arg(0)->Arg(120)->Unit(benchmark::kMicrosec // hit rate reported below is high but not exactly 1.0 per iteration. namespace { String UniqueFragmentSource(const Uint64 serial, const int padLines) { - return SwizzleLikeFragment("", padLines) + - "\n// unique-" + std::to_string(serial) + "\n"; + return SwizzleLikeFragment("", padLines) + "\n// unique-" + std::to_string(serial) + "\n"; } } // namespace diff --git a/MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp b/MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp index 61930ff6..678d1fa5 100644 --- a/MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp +++ b/MobileGL/MG_Benchmark/Transpile/TranspileProfile.cpp @@ -104,9 +104,9 @@ namespace { // kMaxReps. Three untimed warm-up calls first, so allocator growth and first-touch page // faults land outside the sample set. template - Stat Measure(F&& body, const double budgetMs = 250.0, const int minReps = 15, - const int maxReps = 2000) { - for (int i = 0; i < 3; ++i) body(); + Stat Measure(F&& body, const double budgetMs = 250.0, const int minReps = 15, const int maxReps = 2000) { + for (int i = 0; i < 3; ++i) + body(); std::vector samples; samples.reserve(64); @@ -127,9 +127,9 @@ namespace { // stages whose set-up (a fresh glslang parse, a fresh link) has to happen per iteration // and must NOT be counted. template - Stat MeasureInner(F&& body, const double budgetMs = 250.0, const int minReps = 15, - const int maxReps = 2000) { - for (int i = 0; i < 3; ++i) (void)body(); + Stat MeasureInner(F&& body, const double budgetMs = 250.0, const int minReps = 15, const int maxReps = 2000) { + for (int i = 0; i < 3; ++i) + (void)body(); std::vector samples; samples.reserve(64); @@ -137,8 +137,7 @@ namespace { while (true) { samples.push_back(body()); if (static_cast(samples.size()) >= maxReps) break; - const double elapsedMs = - std::chrono::duration(Clock::now() - start).count(); + const double elapsedMs = std::chrono::duration(Clock::now() - start).count(); if (static_cast(samples.size()) >= minReps && elapsedMs >= budgetMs) break; } return Summarize(samples); @@ -146,21 +145,20 @@ namespace { struct Row { std::string corpus; - std::string stage; // "VS" / "FS" / "program" + std::string stage; // "VS" / "FS" / "program" std::string what; - std::string kind; // roundtrip / probe / frontend / crosscompile / baseline / merged + std::string kind; // roundtrip / probe / frontend / crosscompile / baseline / merged size_t moduleWords = 0; Stat stat; }; std::vector g_rows; - void Emit(const std::string& corpus, const std::string& stage, const std::string& what, - const std::string& kind, size_t words, const Stat& s) { + void Emit(const std::string& corpus, const std::string& stage, const std::string& what, const std::string& kind, + size_t words, const Stat& s) { g_rows.push_back(Row{corpus, stage, what, kind, words, s}); - std::printf("%-14s %-8s %-40s %-13s %7zu %10.1f %10.1f %10.1f %6d\n", corpus.c_str(), - stage.c_str(), what.c_str(), kind.c_str(), words, s.medianUs, s.p10Us, - s.p90Us, s.reps); + std::printf("%-14s %-8s %-40s %-13s %7zu %10.1f %10.1f %10.1f %6d\n", corpus.c_str(), stage.c_str(), + what.c_str(), kind.c_str(), words, s.medianUs, s.p10Us, s.p90Us, s.reps); std::fflush(stdout); } @@ -648,24 +646,22 @@ void main() { struct BuiltProgram { std::vector types; - std::vector> raw; // straight out of GlslangToSpv - std::vector> sanitized; // after SanitizeAndOptimizeBinary + std::vector> raw; // straight out of GlslangToSpv + std::vector> sanitized; // after SanitizeAndOptimizeBinary }; // Fresh parse of both stages. glslang mutates a TShader during link/mapIO, so anything // that needs to be timed repeatedly has to rebuild these each iteration. - std::vector> ParseBoth(const std::string& vsPre, - const std::string& fsPre, + std::vector> ParseBoth(const std::string& vsPre, const std::string& fsPre, const CompileEnv& env) { std::vector> out; - for (const auto& [type, src] : - {std::pair{GL_VERTEX_SHADER, &vsPre}, - std::pair{GL_FRAGMENT_SHADER, &fsPre}}) { + for (const auto& [type, src] : {std::pair{GL_VERTEX_SHADER, &vsPre}, + std::pair{GL_FRAGMENT_SHADER, &fsPre}}) { ShaderAttrib attrib{.shaderType = type, .sourceStr = *src, .flags = 0, .env = &env}; auto r = ShaderCompiler::CompileShader(attrib); if (!r) { - std::fprintf(stderr, "PARSE FAILED (%s):\n%s\n", - type == GL_VERTEX_SHADER ? "VS" : "FS", r.error().log.c_str()); + std::fprintf(stderr, "PARSE FAILED (%s):\n%s\n", type == GL_VERTEX_SHADER ? "VS" : "FS", + r.error().log.c_str()); std::abort(); } out.push_back(r.value()); @@ -843,8 +839,7 @@ void main() { } { Stat s = Measure([&] { - auto ctx = spvtools::BuildModule(SPV_ENV_VULKAN_1_1, SilentConsumer(), - mod.data(), mod.size()); + auto ctx = spvtools::BuildModule(SPV_ENV_VULKAN_1_1, SilentConsumer(), mod.data(), mod.size()); benchmarkSink(ctx != nullptr); }); Emit(name, stage, "BASELINE BuildModule only (parse, no emit)", "baseline", words, s); @@ -859,27 +854,19 @@ void main() { Emit(name, stage, "ProbeSpirvGateFeatures (merged 2 gates)", "probe", words, s); } { - Stat s = Measure([&] { - benchmarkSink(ShaderCompiler::DeclaresViewportIndexBuiltin(mod)); - }); + Stat s = Measure([&] { benchmarkSink(ShaderCompiler::DeclaresViewportIndexBuiltin(mod)); }); Emit(name, stage, "DeclaresViewportIndexBuiltin", "probe", words, s); } { - Stat s = Measure([&] { - benchmarkSink(ShaderCompiler::DeclaresMultisampledImage(mod)); - }); + Stat s = Measure([&] { benchmarkSink(ShaderCompiler::DeclaresMultisampledImage(mod)); }); Emit(name, stage, "DeclaresMultisampledImage", "probe", words, s); } { - Stat s = Measure([&] { - benchmarkSink(ShaderCompiler::DeclaresFormatlessStorageImage(mod)); - }); + Stat s = Measure([&] { benchmarkSink(ShaderCompiler::DeclaresFormatlessStorageImage(mod)); }); Emit(name, stage, "DeclaresFormatlessStorageImage", "probe", words, s); } { - Stat s = Measure([&] { - benchmarkSink(ShaderCompiler::ModuleDeclaresBufferTextureSampler(mod)); - }); + Stat s = Measure([&] { benchmarkSink(ShaderCompiler::ModuleDeclaresBufferTextureSampler(mod)); }); Emit(name, stage, "ModuleDeclaresBufferTextureSampler", "probe", words, s); } { @@ -931,8 +918,7 @@ void main() { ShaderCompiler::LowerDrawParametersForEssl(mod, out, false); benchmarkSink(out.size()); }); - Emit(name, stage, "LowerDrawParametersForEssl (no-op stage: def-use cost)", - "roundtrip", words, s); + Emit(name, stage, "LowerDrawParametersForEssl (no-op stage: def-use cost)", "roundtrip", words, s); } { Stat s = Measure([&] { @@ -993,8 +979,7 @@ void main() { session.CreateOptions(&options); spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); 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); + spvc_compiler_options_set_bool(options, SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, SPVC_FALSE); session.SetOptions(options); const char* result = nullptr; session.Compile(&result); @@ -1010,23 +995,19 @@ void main() { Stat s = Measure([&] { const Vector* eff = &mod; Vector a, b, c, d, e, f; - if (isVs && ShaderCompiler::LowerDrawParametersForEssl(*eff, a, false) && !a.empty()) - eff = &a; + if (isVs && ShaderCompiler::LowerDrawParametersForEssl(*eff, a, false) && !a.empty()) eff = &a; auto gates = ShaderCompiler::ProbeSpirvGateFeatures(*eff); if (gates.DeclaresMultisampledImage && - ShaderCompiler::ClampMultisampleFetchesForEssl(*eff, b, 4, 1, 1, 4, false) && - !b.empty()) + ShaderCompiler::ClampMultisampleFetchesForEssl(*eff, b, 4, 1, 1, 4, false) && !b.empty()) eff = &b; - if (isVs && ShaderCompiler::SplitArrayVertexInputsForEssl(*eff, c, false) && - !c.empty() && c != *eff) + if (isVs && ShaderCompiler::SplitArrayVertexInputsForEssl(*eff, c, false) && !c.empty() && + c != *eff) eff = &c; - if (ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(*eff, d, false) && !d.empty()) - eff = &d; + if (ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(*eff, d, false) && !d.empty()) eff = &d; if (ShaderCompiler::LowerRectImages(*eff, e, false) && !e.empty()) eff = &e; if (ShaderCompiler::Lower1DArrayImagesForEssl(*eff, f, false) && !f.empty()) eff = &f; Vector g; - if (!isVs && ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(*eff, g, false) && - !g.empty()) + if (!isVs && ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(*eff, g, false) && !g.empty()) eff = &g; benchmarkSink(eff->size()); }); @@ -1043,8 +1024,7 @@ void main() { opt.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass()); } opt.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(4, 1, 1, 4)); - opt.RegisterPass( - StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); + opt.RegisterPass(StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); opt.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass()); spvtools::OptimizerOptions o; o.set_run_validator(false); @@ -1065,8 +1045,7 @@ void main() { opt.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass()); } opt.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass(4, 1, 1, 4)); - opt.RegisterPass( - StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); + opt.RegisterPass(StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); opt.RegisterPass(NormalizeRectCoordinatesPass::CreateNormalizeRectCoordinatesPass()); opt.RegisterPass(Lower1DArrayImagesPass::CreateLower1DArrayImagesPass()); spvtools::OptimizerOptions o; @@ -1112,15 +1091,14 @@ int main(int argc, char** argv) { std::printf("# floor loop; sample me\n"); std::fflush(stdout); for (;;) { - ShaderAttrib a{ - .shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env}; + ShaderAttrib a{.shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env}; auto r = ShaderCompiler::CompileShader(a); benchmarkSink(r.has_value()); } } - std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what", - "kind", "words", "median_us", "p10_us", "p90_us", "reps"); + std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what", "kind", "words", + "median_us", "p10_us", "p90_us", "reps"); std::printf("%s\n", std::string(120, '-').c_str()); // The per-parse FLOOR. glslang caches its version/profile built-in symbol table across @@ -1136,13 +1114,11 @@ int main(int argc, char** argv) { String pre = src; PreprocessShaderSource(ShaderStage::Vertex, pre, env); Stat s = Measure([&] { - ShaderAttrib a{ - .shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env}; + ShaderAttrib a{.shaderType = GL_VERTEX_SHADER, .sourceStr = pre, .flags = 0, .env = &env}; auto r = ShaderCompiler::CompileShader(a); benchmarkSink(r.has_value()); }); - Emit("floor", "VS", std::string("glslang.parse of empty main(), #version ") + label, - "frontend", 0, s); + Emit("floor", "VS", std::string("glslang.parse of empty main(), #version ") + label, "frontend", 0, s); } } @@ -1177,8 +1153,8 @@ int main(int argc, char** argv) { 150.0, 15, 400); glslang::SetThreadPoolAllocator(nullptr); Emit("floor", "VS", " of which: TBuiltIns::initialize (generate only)", "frontend", 0, s); - std::printf("# generated built-in declaration text: common %zu bytes, vertex stage %zu bytes\n", - commonBytes, stageBytes); + std::printf("# generated built-in declaration text: common %zu bytes, vertex stage %zu bytes\n", commonBytes, + stageBytes); } // Splitting the floor further: how much of it is object set-up that never touches the @@ -1216,8 +1192,8 @@ int main(int argc, char** argv) { Emit("floor", "VS", " parse() with NO MobileGL env configuration", "frontend", 0, bare); } - std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what", - "kind", "words", "median_us", "p10_us", "p90_us", "reps"); + std::printf("\n%-14s %-8s %-40s %-13s %7s %10s %10s %10s %6s\n", "corpus", "stage", "what", "kind", "words", + "median_us", "p10_us", "p90_us", "reps"); std::printf("%s\n", std::string(120, '-').c_str()); ProfileCorpus("tiny", kTinyVs, kTinyFs); diff --git a/MobileGL/MG_Gbm/MobileGLGbm.cpp b/MobileGL/MG_Gbm/MobileGLGbm.cpp index d8539ac6..4185175a 100644 --- a/MobileGL/MG_Gbm/MobileGLGbm.cpp +++ b/MobileGL/MG_Gbm/MobileGLGbm.cpp @@ -111,18 +111,21 @@ namespace { } const auto version = reinterpret_cast( dlsym(lib, "mobilegl_shared_image_abi_version")); - api.allocate = reinterpret_cast(dlsym(lib, "mobilegl_shared_image_allocate")); + api.allocate = + reinterpret_cast(dlsym(lib, "mobilegl_shared_image_allocate")); api.import = reinterpret_cast(dlsym(lib, "mobilegl_shared_image_import")); - api.release = reinterpret_cast(dlsym(lib, "mobilegl_shared_image_release")); - api.serverAvailable = reinterpret_cast(dlsym(lib, "mobilegl_server_available")); + api.release = + reinterpret_cast(dlsym(lib, "mobilegl_shared_image_release")); + api.serverAvailable = + reinterpret_cast(dlsym(lib, "mobilegl_server_available")); if (version == nullptr || version() < 1 || api.allocate == nullptr || api.import == nullptr || api.release == nullptr) { std::fprintf(stderr, "mobilegl_gbm: this libMobileGL.so has no shared-image ABI\n"); return; } if (version() >= 2) { - api.formats = reinterpret_cast( - dlsym(lib, "mobilegl_shared_image_formats")); + api.formats = + reinterpret_cast(dlsym(lib, "mobilegl_shared_image_formats")); api.importPlanes = reinterpret_cast( dlsym(lib, "mobilegl_shared_image_import_planes")); } @@ -134,7 +137,8 @@ namespace { // ---- formats ---------------------------------------------------------------------------- uint32_t Canonical(uint32_t format) { - if (g_core != nullptr && g_core->v0.format_canonicalize != nullptr) return g_core->v0.format_canonicalize(format); + if (g_core != nullptr && g_core->v0.format_canonicalize != nullptr) + return g_core->v0.format_canonicalize(format); // The two legacy GBM_BO_FORMAT_* enumerants are the only non-fourcc formats. if (format == GBM_BO_FORMAT_XRGB8888) return GBM_FORMAT_XRGB8888; if (format == GBM_BO_FORMAT_ARGB8888) return GBM_FORMAT_ARGB8888; @@ -172,14 +176,18 @@ namespace { format == MOBILEGL_SHARED_IMAGE_FOURCC_ARGB8888 || format == MOBILEGL_SHARED_IMAGE_FOURCC_XRGB8888; } - uint32_t PlaneCount(uint32_t format) { return FormatIsYuv(format) ? 2 : 1; } + uint32_t PlaneCount(uint32_t format) { + return FormatIsYuv(format) ? 2 : 1; + } // A YUV buffer is sampled only. uint32_t RefusedUsage(uint32_t format) { return kRefusedUsage | (FormatIsYuv(format) ? static_cast(GBM_BO_USE_RENDERING) : 0u); } - bool ModifierAcceptable(uint64_t modifier) { return modifier == kModifierInvalid || modifier == kModifierLinear; } + bool ModifierAcceptable(uint64_t modifier) { + return modifier == kModifierInvalid || modifier == kModifierLinear; + } // There is no GEM handle behind a shared image; callers that only use the handle as a key // (a buffer cache) still need it to be unique. @@ -214,11 +222,15 @@ namespace { return &bo->base; } - Bo* AsBo(gbm_bo* bo) { return reinterpret_cast(bo); } + Bo* AsBo(gbm_bo* bo) { + return reinterpret_cast(bo); + } // ---- device ----------------------------------------------------------------------------- - void DeviceDestroy(gbm_device* gbm) { delete reinterpret_cast(gbm); } + void DeviceDestroy(gbm_device* gbm) { + delete reinterpret_cast(gbm); + } int IsFormatSupported(gbm_device*, uint32_t format, uint32_t usage) { format = Canonical(format); @@ -241,7 +253,8 @@ namespace { if (count > 0) { // The layout is the allocator's, which only a list that admits INVALID accepts. bool admitsImplicit = false; - for (unsigned int i = 0; i < count; ++i) admitsImplicit |= modifiers[i] == kModifierInvalid; + for (unsigned int i = 0; i < count; ++i) + admitsImplicit |= modifiers[i] == kModifierInvalid; if (!admitsImplicit) { errno = EINVAL; return nullptr; @@ -261,8 +274,8 @@ namespace { errno = rc < 0 ? -rc : EIO; return nullptr; } - gbm_bo* bo = NewBo(gbm, image.id, image.fd, image.width, image.height, image.fourcc, image.stride, - image.offset, image.modifier, image.plane1_stride, image.plane1_offset); + gbm_bo* bo = NewBo(gbm, image.id, image.fd, image.width, image.height, image.fourcc, image.stride, image.offset, + image.modifier, image.plane1_stride, image.plane1_offset); if (bo == nullptr) { api->release(image.id); ::close(image.fd); @@ -273,7 +286,7 @@ namespace { // Whether two descriptors name one dma-buf: a YUV buffer's planes must be in one. bool SameBuffer(int a, int b) { if (a == b) return true; - struct stat sa {}, sb {}; + struct stat sa{}, sb{}; return ::fstat(a, &sa) == 0 && ::fstat(b, &sb) == 0 && sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino; } @@ -385,11 +398,17 @@ namespace { return -1; } - int BoGetFd(gbm_bo* bo) { return ::fcntl(AsBo(bo)->fd, F_DUPFD_CLOEXEC, 0); } + int BoGetFd(gbm_bo* bo) { + return ::fcntl(AsBo(bo)->fd, F_DUPFD_CLOEXEC, 0); + } - int BoGetPlanes(gbm_bo* bo) { return static_cast(AsBo(bo)->planes); } + int BoGetPlanes(gbm_bo* bo) { + return static_cast(AsBo(bo)->planes); + } - bool ValidPlane(gbm_bo* bo, int plane) { return plane >= 0 && static_cast(plane) < AsBo(bo)->planes; } + bool ValidPlane(gbm_bo* bo, int plane) { + return plane >= 0 && static_cast(plane) < AsBo(bo)->planes; + } // Every plane is in the one buffer, so every plane has its handle. gbm_bo_handle BoGetHandle(gbm_bo* bo, int plane) { @@ -420,7 +439,9 @@ namespace { return plane == 1 && AsBo(bo)->planes == 2 ? AsBo(bo)->plane1Offset : 0; } - uint64_t BoGetModifier(gbm_bo* bo) { return AsBo(bo)->modifier; } + uint64_t BoGetModifier(gbm_bo* bo) { + return AsBo(bo)->modifier; + } void BoDestroy(gbm_bo* gbm) { Bo* bo = AsBo(gbm); @@ -435,11 +456,15 @@ namespace { return nullptr; } - gbm_bo* SurfaceLockFrontBuffer(gbm_surface*) { return nullptr; } + gbm_bo* SurfaceLockFrontBuffer(gbm_surface*) { + return nullptr; + } void SurfaceReleaseBuffer(gbm_surface*, gbm_bo*) {} - int SurfaceHasFreeBuffers(gbm_surface*) { return 0; } + int SurfaceHasFreeBuffers(gbm_surface*) { + return 0; + } void SurfaceDestroy(gbm_surface*) {} diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp index f8410b98..492c142b 100644 --- a/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.cpp @@ -104,9 +104,7 @@ namespace MobileGL::MG_Impl::CGLImpl { } } - void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat, - CGLPixelFormatAttribute attrib, - GLint value) { + void ApplyPixelFormatAttribute(PixelFormatObject& pixelFormat, CGLPixelFormatAttribute attrib, GLint value) { switch (attrib) { case kCGLPFADoubleBuffer: pixelFormat.DoubleBuffer = true; @@ -163,14 +161,22 @@ namespace MobileGL::MG_Impl::CGLImpl { EGLImpl::BindAPI(EGL_OPENGL_API); const EGLint attribs[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, std::max(pixelFormat->AlphaSize, 0), - EGL_DEPTH_SIZE, std::max(pixelFormat->DepthSize, 0), - EGL_STENCIL_SIZE, std::max(pixelFormat->StencilSize, 0), - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + std::max(pixelFormat->AlphaSize, 0), + EGL_DEPTH_SIZE, + std::max(pixelFormat->DepthSize, 0), + EGL_STENCIL_SIZE, + std::max(pixelFormat->StencilSize, 0), + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, EGL_NONE, }; @@ -191,9 +197,7 @@ namespace MobileGL::MG_Impl::CGLImpl { } const EGLint contextAttribs[] = { - EGL_CONTEXT_MAJOR_VERSION, 3, - EGL_CONTEXT_MINOR_VERSION, 3, - EGL_NONE, + EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE, }; EGLContext eglContext = EGLImpl::CreateContext(display, config, shareContext, contextAttribs); if (eglContext == EGL_NO_CONTEXT) { @@ -246,12 +250,12 @@ namespace MobileGL::MG_Impl::CGLImpl { object.Surface = EGL_NO_SURFACE; } const EGLAttrib attribs[] = { - EGL_WIDTH, std::max(object.SurfaceBackingSize[0], 1), + EGL_WIDTH, std::max(object.SurfaceBackingSize[0], 1), EGL_HEIGHT, std::max(object.SurfaceBackingSize[1], 1), EGL_NONE, }; - EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config, - object.MetalLayer, attribs); + EGLSurface surface = + EGLImpl::CreatePlatformWindowSurface(object.Display, object.Config, object.MetalLayer, attribs); if (surface == EGL_NO_SURFACE) { object.HasDrawable = false; return kCGLBadDrawable; @@ -265,10 +269,9 @@ namespace MobileGL::MG_Impl::CGLImpl { if (object.Surface == EGL_NO_SURFACE) { return kCGLBadDrawable; } - return EGLImpl::ResizePlatformWindowSurface( - object.Display, object.Surface, - std::max(object.SurfaceBackingSize[0], 1), - std::max(object.SurfaceBackingSize[1], 1)) + return EGLImpl::ResizePlatformWindowSurface(object.Display, object.Surface, + std::max(object.SurfaceBackingSize[0], 1), + std::max(object.SurfaceBackingSize[1], 1)) ? kCGLNoError : kCGLBadDrawable; } @@ -528,8 +531,7 @@ namespace MobileGL::MG_Impl::CGLImpl { object->SwapInterval = params[0]; EGLImpl::SwapInterval(object->Display, object->SwapInterval); return kCGLNoError; - case kCGLCPSurfaceBackingSize: - { + case kCGLCPSurfaceBackingSize: { const GLint width = std::max(params[0], 1); const GLint height = std::max(params[1], 1); if (object->SurfaceBackingSize[0] == width && object->SurfaceBackingSize[1] == height) { diff --git a/MobileGL/MG_Impl/CGLImpl/CGLImpl.h b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h index cc482ba4..512a43c6 100644 --- a/MobileGL/MG_Impl/CGLImpl/CGLImpl.h +++ b/MobileGL/MG_Impl/CGLImpl/CGLImpl.h @@ -47,5 +47,5 @@ namespace MobileGL::MG_Impl::CGLImpl { CGLError AttachDrawable(CGLContextObj ctx, void* nsView, void* metalLayer, GLint width, GLint height); void* GetContextNSObject(CGLContextObj ctx); void SetContextNSObject(CGLContextObj ctx, void* nsObject); -} +} // namespace MobileGL::MG_Impl::CGLImpl #endif diff --git a/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp index 32d4b65d..c7c94c1a 100644 --- a/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/CGLImpl/Exporting/Definitions.cpp @@ -10,8 +10,7 @@ #if defined(__APPLE__) -MOBILEGL_CGL_API CGLError CGLChoosePixelFormat(const CGLPixelFormatAttribute* attribs, - CGLPixelFormatObj* pix, +MOBILEGL_CGL_API CGLError CGLChoosePixelFormat(const CGLPixelFormatAttribute* attribs, CGLPixelFormatObj* pix, GLint* npix) { return MobileGL::MG_Impl::CGLImpl::ChoosePixelFormat(attribs, pix, npix); } @@ -20,9 +19,7 @@ MOBILEGL_CGL_API CGLError CGLDestroyPixelFormat(CGLPixelFormatObj pix) { return MobileGL::MG_Impl::CGLImpl::DestroyPixelFormat(pix); } -MOBILEGL_CGL_API CGLError CGLDescribePixelFormat(CGLPixelFormatObj pix, - GLint pix_num, - CGLPixelFormatAttribute attrib, +MOBILEGL_CGL_API CGLError CGLDescribePixelFormat(CGLPixelFormatObj pix, GLint pix_num, CGLPixelFormatAttribute attrib, GLint* value) { return MobileGL::MG_Impl::CGLImpl::DescribePixelFormat(pix, pix_num, attrib, value); } diff --git a/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp b/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp index e73ce516..82909562 100644 --- a/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp +++ b/MobileGL/MG_Impl/DyldInterpose/DyldInterpose.cpp @@ -55,8 +55,7 @@ namespace { constexpr std::uint32_t MaxDisplays = sizeof(CGOpenGLDisplayMask) * 8; CGDirectDisplayID displays[MaxDisplays] = {}; std::uint32_t displayCount = 0; - if (displayMask != 0 && - CGGetActiveDisplayList(MaxDisplays, displays, &displayCount) == kCGErrorSuccess) { + if (displayMask != 0 && CGGetActiveDisplayList(MaxDisplays, displays, &displayCount) == kCGErrorSuccess) { const auto mask = static_cast(displayMask); for (std::uint32_t i = 0; i < displayCount; ++i) { if ((CGDisplayIDToOpenGLDisplayMask(displays[i]) & mask) != 0) { @@ -69,24 +68,22 @@ namespace { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - CVReturn MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext( - CVDisplayLinkRef displayLink, - CGLContextObj context, - CGLPixelFormatObj pixelFormat) { + CVReturn MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(CVDisplayLinkRef displayLink, + CGLContextObj context, + CGLPixelFormatObj pixelFormat) { GLint virtualScreen = 0; if (MobileGL::MG_Impl::CGLImpl::GetVirtualScreen(context, &virtualScreen) == kCGLNoError) { GLint displayMask = 0; - if (!displayLink || - MobileGL::MG_Impl::CGLImpl::DescribePixelFormat( - pixelFormat, virtualScreen, kCGLPFADisplayMask, &displayMask) != kCGLNoError) { + if (!displayLink || MobileGL::MG_Impl::CGLImpl::DescribePixelFormat( + pixelFormat, virtualScreen, kCGLPFADisplayMask, &displayMask) != kCGLNoError) { return kCVReturnInvalidArgument; } return CVDisplayLinkSetCurrentCGDisplay(displayLink, DisplayForMask(displayMask)); } using OriginalFunction = CVReturn (*)(CVDisplayLinkRef, CGLContextObj, CGLPixelFormatObj); - static const auto original = reinterpret_cast( - dlsym(RTLD_NEXT, "CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext")); + static const auto original = + reinterpret_cast(dlsym(RTLD_NEXT, "CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext")); return original ? original(displayLink, context, pixelFormat) : kCVReturnError; } @@ -95,7 +92,7 @@ namespace { {reinterpret_cast(MobileGLDlsym), reinterpret_cast(dlsym)}, {reinterpret_cast(MobileGLCVDisplayLinkSetCurrentCGDisplayFromOpenGLContext), reinterpret_cast(CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext)}, - }; + }; #pragma clang diagnostic pop } // namespace diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index cc14fcab..38af821d 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -84,7 +84,7 @@ namespace MobileGL::MG_Impl::EGLImpl { static Double cpuStartMs = -1.0; const auto threadCpuMs = []() -> Double { #if defined(__unix__) || defined(__linux__) || defined(__ANDROID__) - struct timespec now {}; + struct timespec now{}; if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now) == 0) return static_cast(now.tv_sec) * 1000.0 + static_cast(now.tv_nsec) / 1e6; #endif @@ -195,11 +195,12 @@ namespace MobileGL::MG_Impl::EGLImpl { } // namespace Bool StreamGateActive() { - return MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole(); + return MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole(); } - std::recursive_mutex& StreamMutex() { return EGLOperationMutex(); } + std::recursive_mutex& StreamMutex() { + return EGLOperationMutex(); + } void StreamBindCallingThreadLocked() { const Uint64 token = t_threadContextToken; @@ -253,7 +254,8 @@ namespace MobileGL::MG_Impl::EGLImpl { // server is another process; in the one-process shape the apply thread reads that block // too, and only a barriered apply's return may touch it (ClientSession::ReleaseFillPins). if (ClientSession* session = ClientSession::Active(); - session != nullptr && session->Started() && MG_Config::Transport != MG_Config::TransportMode::InProcess) { + session != nullptr && session->Started() && + MG_Config::Transport != MG_Config::TransportMode::InProcess) { session->WaitForApplyToCatchUp("eglDestroyContext"); session->ReleaseFillPins(); } @@ -454,9 +456,8 @@ namespace MobileGL::MG_Impl::EGLImpl { } EGLint alpha = 0; (void)state->GetConfigAttrib(dpy, config, EGL_ALPHA_SIZE, &alpha); - auto presentation = - Wayland::WindowSurface::Create(reinterpret_cast(static_cast(nativeDisplay)), window, - width, height, alpha > 0); + auto presentation = Wayland::WindowSurface::Create( + reinterpret_cast(static_cast(nativeDisplay)), window, width, height, alpha > 0); if (!presentation) { state->SetError(EGL_BAD_NATIVE_WINDOW); return true; @@ -469,8 +470,8 @@ namespace MobileGL::MG_Impl::EGLImpl { (void)state->ResizeSurface(dpy, surface, width, height); auto* backendObject = GetBackendObject(state); if (!backendObject || !backendObject->CreateEGLPbufferSurface(surface, width, height)) { - MGLOG_E("eglCreateWindowSurface: the backend refused the %dx%d drawable behind a Wayland window", - width, height); + MGLOG_E("eglCreateWindowSurface: the backend refused the %dx%d drawable behind a Wayland window", width, + height); state->DestroySurface(dpy, surface); state->SetError(EGL_BAD_ALLOC); return true; @@ -493,8 +494,8 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!it->second->TakeResize(&width, &height)) return; (void)state->ResizeSurface(dpy, surface, width, height); auto* backendObject = MG_Backend::pActiveBackendObject.get(); - if (backendObject == nullptr || - !backendObject->ResizeEGLWindowSurface(surface, static_cast(width), static_cast(height))) { + if (backendObject == nullptr || !backendObject->ResizeEGLWindowSurface(surface, static_cast(width), + static_cast(height))) { MGLOG_E("Wayland: the drawable behind a %dx%d window could not be resized", width, height); } } @@ -556,10 +557,12 @@ namespace MobileGL::MG_Impl::EGLImpl { lostFramebuffer.reset(); lostDefault.reset(); } - if (auto* remote = dynamic_cast(MG_Backend::pActiveBackendObject.get())) + if (auto* remote = + dynamic_cast(MG_Backend::pActiveBackendObject.get())) remote->OnSessionReplaced(); #if MOBILEGL_WAYLAND_WINDOWS - for (auto& [surface, presentation] : WaylandSurfaces()) presentation->OnSessionReplaced(); + for (auto& [surface, presentation] : WaylandSurfaces()) + presentation->OnSessionReplaced(); #endif return true; } @@ -686,7 +689,8 @@ namespace MobileGL::MG_Impl::EGLImpl { requestedWidth = static_cast(nativeWidth); requestedHeight = static_cast(nativeHeight); MGLOG_I("eglCreateWindowSurface: no EGL_WIDTH/EGL_HEIGHT; the server-owned window is asked for the " - "native window's %dx%d", nativeWidth, nativeHeight); + "native window's %dx%d", + nativeWidth, nativeHeight); } } #endif @@ -725,7 +729,9 @@ namespace MobileGL::MG_Impl::EGLImpl { return surface; } - EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) { return SwapBuffersWithDamage(dpy, draw, nullptr, 0); } + EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) { + return SwapBuffersWithDamage(dpy, draw, nullptr, 0); + } // EGL_KHR/EXT_swap_buffers_with_damage. The rectangles (GL window coordinates, origin // bottom-left; none = the whole surface) are clipped to the surface and fitted into a record. @@ -774,7 +780,8 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!damage.IsFull()) { EGLint width = 0; EGLint height = 0; - if (state->QuerySurface(dpy, draw, EGL_WIDTH, &width) && state->QuerySurface(dpy, draw, EGL_HEIGHT, &height)) { + if (state->QuerySurface(dpy, draw, EGL_WIDTH, &width) && + state->QuerySurface(dpy, draw, EGL_HEIGHT, &height)) { damage.Normalize(width, height); } else { damage.SetFull(); @@ -796,7 +803,8 @@ namespace MobileGL::MG_Impl::EGLImpl { Bool (*const sessionLost)() = nullptr; #endif switch (it->second->Present(damage, state->GetSwapInterval(dpy), sessionLost)) { - case Wayland::WindowSurface::PresentResult::kPresented: break; + case Wayland::WindowSurface::PresentResult::kPresented: + break; case Wayland::WindowSurface::PresentResult::kLost: it->second->CommitPendingState(); state->SetError(EGL_CONTEXT_LOST); @@ -810,7 +818,8 @@ namespace MobileGL::MG_Impl::EGLImpl { const Bool swapped = damage.IsFull() ? backendObject->SwapEGLBuffers(dpy, draw) : backendObject->SwapEGLBuffersWithDamage(dpy, draw, damage); if (!swapped) { - MGLOG_E_ONCE("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, draw); + MGLOG_E_ONCE("eglSwapBuffers failed on thread=%s dpy=%p draw=%p", CurrentThreadIdString().c_str(), dpy, + draw); state->SetError(EGL_BAD_SURFACE); return EGL_FALSE; } @@ -874,8 +883,8 @@ namespace MobileGL::MG_Impl::EGLImpl { // fresh session's all the same. const ClientSession::ScopedCurrentSessionWork currentSession; // A split client builds the context's GL state only once the server has its token (below). - const Bool splitClient = MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole(); + const Bool splitClient = + MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole(); #else constexpr Bool splitClient = false; #endif @@ -899,10 +908,9 @@ namespace MobileGL::MG_Impl::EGLImpl { // handle whose first verb the server refuses - a late failure where an early one is // available, and EGL_NO_CONTEXT with EGL_BAD_ACCESS is the early one. NoWire (the // pre-Start bring-up, the server role) keeps it: there is no server to refuse. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole()) { - auto emitted = MG_Remote::Client::SendCreateContextFrame( - state->GetContextClientToken(context), state->GetContextShareGroupToken(context), 0); + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole()) { + auto emitted = MG_Remote::Client::SendCreateContextFrame(state->GetContextClientToken(context), + state->GetContextShareGroupToken(context), 0); // THE LOSS CAN BE FOUND OUT HERE: a session whose server is gone, which nothing had // noticed yet, refuses the create by hanging up on it. That is the loss, not a refusal: // the half-built context goes (its objects silently - they were never on any server), @@ -941,7 +949,8 @@ namespace MobileGL::MG_Impl::EGLImpl { // built before the token crossed: a context in a share group of its own then drew with // default objects its group never had. if (emitted == MG_Remote::Client::ContextFrameEmit::Sent) { - StreamBindContextLocked(state->GetContextClientToken(context), state->GetContextShareGroupToken(context)); + StreamBindContextLocked(state->GetContextClientToken(context), + state->GetContextShareGroupToken(context)); } (void)state->BuildContextGLState(context); } @@ -1026,8 +1035,9 @@ namespace MobileGL::MG_Impl::EGLImpl { #endif const String threadId = CurrentThreadIdString(); - MGLOG_D("eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p", - threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext); + MGLOG_D( + "eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p", + threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext); #if MOBILEGL_BUILD_DISAGGREGATED // EGL 1.5 3.7.3 / 3.2: after a power-management event - a lost device, here - making a // context created before it current fails with EGL_CONTEXT_LOST. The application destroys @@ -1053,8 +1063,7 @@ namespace MobileGL::MG_Impl::EGLImpl { return EGL_FALSE; } - const Bool releaseCurrentRequest = - draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; + const Bool releaseCurrentRequest = draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT; if (releaseCurrentRequest) { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { if (!backendObject->MakeEGLCurrent(dpy, draw, read, ctx)) { @@ -1097,8 +1106,8 @@ namespace MobileGL::MG_Impl::EGLImpl { } } if (!backendObject->MakeEGLCurrent(dpy, backendDraw, backendRead, ctx)) { - MGLOG_E_ONCE("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", threadId.c_str(), - dpy, draw, read, ctx); + MGLOG_E_ONCE("eglMakeCurrent backend attach failed thread=%s dpy=%p draw=%p read=%p ctx=%p", + threadId.c_str(), dpy, draw, read, ctx); state->SetError(EGL_BAD_ACCESS); state->MakeCurrent(oldDisplay, oldDraw, oldRead, oldContext); return EGL_FALSE; @@ -1306,7 +1315,8 @@ namespace MobileGL::MG_Impl::EGLImpl { // backend: let it bring the surface's record up to date first, so a caller that polls the size // while it draws nothing still sees the change. if (attribute == EGL_WIDTH || attribute == EGL_HEIGHT) { - if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) backendObject->RefreshSurfaceExtent(surface); + if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) + backendObject->RefreshSurfaceExtent(surface); } return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE; } @@ -1338,7 +1348,8 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!region.IsFull()) { EGLint width = 0; EGLint height = 0; - if (state->QuerySurface(dpy, surface, EGL_WIDTH, &width) && state->QuerySurface(dpy, surface, EGL_HEIGHT, &height)) + if (state->QuerySurface(dpy, surface, EGL_WIDTH, &width) && + state->QuerySurface(dpy, surface, EGL_HEIGHT, &height)) region.Normalize(width, height); else region.SetFull(); @@ -1653,7 +1664,9 @@ namespace MobileGL::MG_Impl::EGLImpl { return state->GetSyncAttrib(dpy, sync, attribute, value) ? EGL_TRUE : EGL_FALSE; } - void SetDispatcherCurrentApiHook(EGLenum (*hook)()) { g_dispatcherCurrentApi.store(hook); } + void SetDispatcherCurrentApiHook(EGLenum (*hook)()) { + g_dispatcherCurrentApi.store(hook); + } Bool SharedImagesAvailable() { #if MOBILEGL_BUILD_DISAGGREGATED @@ -1678,8 +1691,8 @@ namespace MobileGL::MG_Impl::EGLImpl { auto* backendObject = MG_Backend::pActiveBackendObject.get(); if (backendObject == nullptr) return false; MG_Backend::SharedImageExport probe{}; - const Bool allocated = backendObject->AllocateSharedImage( - 16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe); + const Bool allocated = + backendObject->AllocateSharedImage(16, 16, MG_State::EGLState::EGLContext::kDrmFourccNv12, &probe); #if !defined(_WIN32) if (probe.Fd >= 0) ::close(probe.Fd); #endif @@ -1695,7 +1708,7 @@ namespace MobileGL::MG_Impl::EGLImpl { #if defined(_WIN32) return false; #else - struct stat sa {}, sb {}; + struct stat sa{}, sb{}; return ::fstat(a, &sa) == 0 && ::fstat(b, &sb) == 0 && sa.st_dev == sb.st_dev && sa.st_ino == sb.st_ino; #endif } @@ -1717,7 +1730,9 @@ namespace MobileGL::MG_Impl::EGLImpl { } } // namespace - void NoteSharedImageBoundToTexture() { SharedImageBoundToTexture().store(true, std::memory_order_relaxed); } + void NoteSharedImageBoundToTexture() { + SharedImageBoundToTexture().store(true, std::memory_order_relaxed); + } void FlushSharedImageAccesses() { if (!SharedImageBoundToTexture().load(std::memory_order_relaxed) && @@ -1794,10 +1809,10 @@ namespace MobileGL::MG_Impl::EGLImpl { static_cast(request.Height), request.Fourcc, layout, &sharedImageId); } else { - imported = backendObject != nullptr && - backendObject->ImportSharedImage(request.Fd, static_cast(request.Width), - static_cast(request.Height), request.Fourcc, - &sharedImageId); + imported = + backendObject != nullptr && + backendObject->ImportSharedImage(request.Fd, static_cast(request.Width), + static_cast(request.Height), request.Fourcc, &sharedImageId); } if (!imported || sharedImageId == 0) { MGLOG_I_ONCE("eglCreateImage: a %dx%d dma-buf is not one of the server's shared images; refused " @@ -1854,7 +1869,8 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!state) { return EGL_FALSE; } - return state->QueryDmaBufFormats(dpy, SharedImagesAvailable(), SharedImageYuvAvailable(), max_formats, formats, num_formats) + return state->QueryDmaBufFormats(dpy, SharedImagesAvailable(), SharedImageYuvAvailable(), max_formats, formats, + num_formats) ? EGL_TRUE : EGL_FALSE; } @@ -1866,8 +1882,9 @@ namespace MobileGL::MG_Impl::EGLImpl { if (!state) { return EGL_FALSE; } - return state->QueryDmaBufModifiers(dpy, SharedImagesAvailable(), SharedImageYuvAvailable(), format, max_modifiers, - reinterpret_cast(modifiers), external_only, num_modifiers) + return state->QueryDmaBufModifiers(dpy, SharedImagesAvailable(), SharedImageYuvAvailable(), format, + max_modifiers, reinterpret_cast(modifiers), external_only, + num_modifiers) ? EGL_TRUE : EGL_FALSE; } diff --git a/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h b/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h index 6b6929ed..96cd293c 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h +++ b/MobileGL/MG_Impl/EGLImpl/EGLPlatformExtensions.h @@ -17,7 +17,7 @@ namespace MobileGL::MG_Impl::EGLImpl { // like a surfaceless one (the frames come from the backend, not from the device), and window // surfaces on it are refused. Chromium's Wayland GPU process hands its GL implementation nothing // but a GBM display, so without the claim it never reaches this library at all. -#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS \ +#define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS \ " EGL_KHR_platform_wayland EGL_EXT_platform_wayland EGL_KHR_platform_gbm EGL_MESA_platform_gbm" #else #define MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS "" @@ -52,4 +52,4 @@ namespace MobileGL::MG_Impl::EGLImpl { inline constexpr const char* kClientExtensionString = "EGL_EXT_client_extensions EGL_KHR_create_context EGL_EXT_platform_base " "EGL_KHR_platform_base EGL_MESA_platform_surfaceless" MOBILEGL_PLATFORM_WAYLAND_EXTENSIONS; -} +} // namespace MobileGL::MG_Impl::EGLImpl diff --git a/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp index e658486c..420e4a2c 100644 --- a/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Exporting/Definitions.cpp @@ -379,7 +379,7 @@ MOBILEGL_EGL_API EGLBoolean eglDestroyImage(EGLDisplay dpy, EGLImage image) { // for every cached client buffer when it dies, wl_shm ones included; a null answer from // eglGetProcAddress made that a jump to address 0. MOBILEGL_EGL_API EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, - const EGLint* attrib_list) { + const EGLint* attrib_list) { const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock; MGLOG_D("eglCreateImageKHR(dpy=%p, ctx=%p, target=%u, buffer=%p, attrib_list=%p)", dpy, ctx, target, buffer, attrib_list); diff --git a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp index edcc8a0a..f72b0885 100644 --- a/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp +++ b/MobileGL/MG_Impl/EGLImpl/Exporting/GlvndVendor.cpp @@ -37,12 +37,10 @@ // Defined in Definitions.cpp; eglext.h declares them only under EGL_EGLEXT_PROTOTYPES. extern "C" EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, - const EGLint* attrib_list); + const EGLint* attrib_list); extern "C" EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImage image); -extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, - EGLint n_rects); -extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, - EGLint n_rects); +extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); +extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); extern "C" EGLBoolean eglSetDamageRegionKHR(EGLDisplay dpy, EGLSurface surface, EGLint* rects, EGLint n_rects); extern "C" EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint* attrib_list); extern "C" EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync); @@ -147,7 +145,9 @@ namespace { // One device, one identity. A pointer to a private object rather than a number, so that a // device from somewhere else cannot compare equal to this one by accident. - struct DeviceToken { int unused; }; + struct DeviceToken { + int unused; + }; DeviceToken g_deviceToken; EGLBoolean EGLAPIENTRY VendorQueryDevicesEXT(EGLint max_devices, EGLDeviceEXT* devices, EGLint* num_devices) { @@ -227,7 +227,8 @@ namespace { return EGL_FALSE; } - EGLint EGLAPIENTRY VendorLabelObjectKHR(EGLDisplay dpy, EGLenum objectType, EGLObjectKHR object, EGLLabelKHR label) { + EGLint EGLAPIENTRY VendorLabelObjectKHR(EGLDisplay dpy, EGLenum objectType, EGLObjectKHR object, + EGLLabelKHR label) { (void)dpy; (void)objectType; (void)object; @@ -333,7 +334,9 @@ namespace { return reinterpret_cast(MG_Impl::EGLImpl::GetProcAddress(procName)); } - void* EGLAPIENTRY VendorGetProcAddress(const char* procName) { return LookupEntryPoint(procName); } + void* EGLAPIENTRY VendorGetProcAddress(const char* procName) { + return LookupEntryPoint(procName); + } // glvnd hands a vendor the index each EGL entry point occupies in its dispatch table, so that a // vendor whose entry points are thin wrappers around glvnd can call back into the right slot. @@ -345,8 +348,10 @@ namespace { (void)index; } - void* EGLAPIENTRY VendorGetDispatchAddress(const char* procName) { return LookupEntryPoint(procName); } -} // namespace + void* EGLAPIENTRY VendorGetDispatchAddress(const char* procName) { + return LookupEntryPoint(procName); + } +} // namespace // libeglabi.h has already declared this prototype without an export attribute, and MSVC refuses a // redeclaration that adds dllexport. Windows has no glvnd to look the symbol up, so it is a plain @@ -356,8 +361,8 @@ namespace { #else #define MOBILEGL_GLVND_EGL_MAIN MOBILEGL_EGL_API #endif -MOBILEGL_GLVND_EGL_MAIN EGLBoolean __egl_Main(uint32_t version, const __EGLapiExports* exports, - __EGLvendorInfo* vendor, __EGLapiImports* imports) { +MOBILEGL_GLVND_EGL_MAIN EGLBoolean __egl_Main(uint32_t version, const __EGLapiExports* exports, __EGLvendorInfo* vendor, + __EGLapiImports* imports) { (void)vendor; if (EGL_VENDOR_ABI_GET_MAJOR_VERSION(version) != EGL_VENDOR_ABI_MAJOR_VERSION) { // A vendor that does not know the ABI it is being handed must say so, not guess: the diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp index 2a258762..f1efd23c 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.cpp @@ -206,8 +206,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { api.readEvents = reinterpret_cast(sym("wl_display_read_events")); api.cancelRead = reinterpret_cast(sym("wl_display_cancel_read")); api.loaded = api.displayInterface && api.registryInterface && api.shmInterface && - api.shmPoolInterface && api.bufferInterface && api.marshalFlags && api.addListener && api.destroy && - api.getVersion && api.createWrapper && api.wrapperDestroy && api.setQueue && + api.shmPoolInterface && api.bufferInterface && api.marshalFlags && api.addListener && + api.destroy && api.getVersion && api.createWrapper && api.wrapperDestroy && api.setQueue && api.createQueue && api.queueDestroy && api.roundtripQueue && api.dispatchQueue && api.dispatchQueuePending && api.flush; if (!api.loaded) MGLOG_E("Wayland: libwayland-client.so.0 lacks an entry point this library uses"); @@ -226,7 +226,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { }; // The compositor is done with a buffer: it may be drawn into again. - void OnBufferRelease(void* data, wl_proxy*) { static_cast(data)->busy = false; } + void OnBufferRelease(void* data, wl_proxy*) { + static_cast(data)->busy = false; + } void (*const kBufferListener[])(void) = {reinterpret_cast(&OnBufferRelease)}; // zwp_linux_dmabuf_v1 and zwp_linux_buffer_params_v1 as their generated protocol code @@ -270,8 +272,12 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { p.paramsEvents[1] = {"failed", "", p.noObjectTypes}; p.dmabuf = {"zwp_linux_dmabuf_v1", static_cast(kDmabufMaxVersion), 2, p.dmabufRequests, 2, p.dmabufEvents}; - p.params = {"zwp_linux_buffer_params_v1", static_cast(kDmabufMaxVersion), 4, p.paramsRequests, - 2, p.paramsEvents}; + p.params = {"zwp_linux_buffer_params_v1", + static_cast(kDmabufMaxVersion), + 4, + p.paramsRequests, + 2, + p.paramsEvents}; ready = true; }); return ready ? &protocol : nullptr; @@ -290,8 +296,12 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { wl_proxy* buffer = nullptr; Bool failed = false; }; - void OnParamsCreated(void* data, wl_proxy*, wl_proxy* buffer) { static_cast(data)->buffer = buffer; } - void OnParamsFailed(void* data, wl_proxy*) { static_cast(data)->failed = true; } + void OnParamsCreated(void* data, wl_proxy*, wl_proxy* buffer) { + static_cast(data)->buffer = buffer; + } + void OnParamsFailed(void* data, wl_proxy*) { + static_cast(data)->failed = true; + } void (*const kParamsListener[])(void) = {reinterpret_cast(&OnParamsCreated), reinterpret_cast(&OnParamsFailed)}; @@ -304,7 +314,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { Bool busy = false; }; - void OnDmabufBufferRelease(void* data, wl_proxy*) { static_cast(data)->busy = false; } + void OnDmabufBufferRelease(void* data, wl_proxy*) { + static_cast(data)->busy = false; + } void (*const kDmabufBufferListener[])(void) = {reinterpret_cast(&OnDmabufBufferRelease)}; // MOBILEGL_WAYLAND_DMABUF=0 keeps every window on wl_shm. @@ -357,8 +369,10 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { const Api& api = WaylandApi(); if (frameCallback) api.destroy(frameCallback); if (surfaceWrapper) api.wrapperDestroy(surfaceWrapper); - for (auto& buffer : buffers) Release(buffer); - for (auto& buffer : dmabufBuffers) Release(buffer); + for (auto& buffer : buffers) + Release(buffer); + for (auto& buffer : dmabufBuffers) + Release(buffer); if (dmabuf) api.marshalFlags(dmabuf, kDmabufDestroy, nullptr, api.getVersion(dmabuf), kMarshalFlagDestroy); // Neither wl_shm (version 1) nor wl_registry has a destroy request: the proxies go // on this side only. @@ -374,8 +388,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { void Release(ShmBuffer& buffer) { const Api& api = WaylandApi(); - if (buffer.buffer) api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer), - kMarshalFlagDestroy); + if (buffer.buffer) + api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer), + kMarshalFlagDestroy); if (buffer.map) munmap(buffer.map, buffer.size); buffer = ShmBuffer{}; } @@ -402,8 +417,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { return false; } // One pool per buffer, destroyed at once: the buffer keeps the memory alive. - wl_proxy* pool = api.marshalFlags(shm, kShmCreatePool, api.shmPoolInterface, api.getVersion(shm), 0, nullptr, - fd, static_cast(size)); + wl_proxy* pool = api.marshalFlags(shm, kShmCreatePool, api.shmPoolInterface, api.getVersion(shm), 0, + nullptr, fd, static_cast(size)); close(fd); if (pool == nullptr) { munmap(map, size); @@ -451,8 +466,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { // as long as it still shows it. void Release(DmabufBuffer& buffer) { const Api& api = WaylandApi(); - if (buffer.buffer) api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer), - kMarshalFlagDestroy); + if (buffer.buffer) + api.marshalFlags(buffer.buffer, kBufferDestroy, nullptr, api.getVersion(buffer.buffer), + kMarshalFlagDestroy); if (buffer.imageId != 0) { if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) { (void)backendObject->ReleaseSharedImage(buffer.imageId); @@ -545,7 +561,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { // The rest of this window's life is wl_shm's. void FallBackToShm() { - for (auto& buffer : dmabufBuffers) Release(buffer); + for (auto& buffer : dmabufBuffers) + Release(buffer); dmabufDamage.InvalidateAll(); useDmabuf = false; } @@ -649,8 +666,7 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { }; namespace { - void OnRegistryGlobal(void* data, wl_proxy* registry, uint32_t name, const char* interface, - uint32_t version) { + void OnRegistryGlobal(void* data, wl_proxy* registry, uint32_t name, const char* interface, uint32_t version) { auto* impl = static_cast(data); const Api& api = WaylandApi(); if (impl->shm == nullptr && std::strcmp(interface, "wl_shm") == 0) { @@ -658,8 +674,7 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { api.marshalFlags(registry, kRegistryBind, api.shmInterface, 1, 0, name, "wl_shm", 1u, nullptr); return; } - if (impl->dmabufWanted && impl->dmabuf == nullptr && - std::strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { + if (impl->dmabufWanted && impl->dmabuf == nullptr && std::strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { const LinuxDmabufProtocol* protocol = LinuxDmabuf(); if (protocol == nullptr) return; const uint32_t bindVersion = std::min(version, kDmabufMaxVersion); @@ -680,7 +695,9 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { void (*const kFrameListener[])(void) = {reinterpret_cast(&OnFrameDone)}; // The application destroyed its wl_egl_window first: nothing may be read from it after this. - void OnWindowDestroyed(void* data) { static_cast(data)->window = nullptr; } + void OnWindowDestroyed(void* data) { + static_cast(data)->window = nullptr; + } } // namespace namespace { @@ -734,8 +751,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { api.setQueue(static_cast(surfaceWrapper), queue); } auto* wrapper = static_cast(surfaceWrapper); - frameCallback = api.marshalFlags(wrapper, kSurfaceFrame, api.callbackInterface, api.getVersion(wrapper), 0, - nullptr); + frameCallback = + api.marshalFlags(wrapper, kSurfaceFrame, api.callbackInterface, api.getVersion(wrapper), 0, nullptr); if (frameCallback == nullptr) return; api.addListener(frameCallback, const_cast(kFrameListener), this); throttle.Requested(NowMs()); @@ -767,8 +784,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { if (impl->queue == nullptr || impl->displayWrapper == nullptr) return nullptr; api.setQueue(static_cast(impl->displayWrapper), impl->queue); auto* wrapper = static_cast(impl->displayWrapper); - impl->registry = api.marshalFlags(wrapper, kDisplayGetRegistry, api.registryInterface, api.getVersion(wrapper), - 0, nullptr); + impl->registry = + api.marshalFlags(wrapper, kDisplayGetRegistry, api.registryInterface, api.getVersion(wrapper), 0, nullptr); if (impl->registry == nullptr) return nullptr; api.addListener(impl->registry, const_cast(kRegistryListener), impl.get()); if (api.roundtripQueue(wlDisplay, impl->queue) < 0 || impl->shm == nullptr) { @@ -852,7 +869,8 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { auto* backendObject = MG_Backend::pActiveBackendObject.get(); if (buffer != nullptr && backendObject != nullptr) { const SizeT index = static_cast(buffer - impl.dmabufBuffers); - const MG_Util::Damage::Region copy = impl.dmabufDamage.TakeForWrite(index, damage, impl.width, impl.height); + const MG_Util::Damage::Region copy = + impl.dmabufDamage.TakeForWrite(index, damage, impl.width, impl.height); if (backendObject->PresentToSharedImage(buffer->imageId, copy)) { impl.Commit(buffer->buffer, damage); buffer->busy = true; diff --git a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h index c3a3c2bf..66dd364a 100644 --- a/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h +++ b/MobileGL/MG_Impl/EGLImpl/WaylandWindow.h @@ -90,7 +90,11 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland { // that failed - or was read back - on a lost device is nothing but zeros: kLost, with no // buffer attached and the window left where it was, so the caller reports the loss at // this swap instead of showing a transparent window that the toolkit never redraws. - enum class PresentResult { kPresented, kNotShown, kLost }; + enum class PresentResult { + kPresented, + kNotShown, + kLost + }; PresentResult Present(const MG_Util::Damage::Region& damage, Int swapInterval, Bool (*sessionLost)()); // wl_egl_window_resize's effect, taken: true (with the new size) when the application gave diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp index 4da81c87..436196bc 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/GL_Buffer.cpp @@ -138,15 +138,14 @@ namespace MobileGL::MG_Impl::GLImpl { return 0; } - const TextureInternalFormat internal = - MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); + const TextureInternalFormat internal = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); const SizeT elementSize = MG_Util::GetSizedInternalFormatSizeInBytes(internal); if (elementSize == 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - std::format("internalformat 0x{:X} has no known element size.", - internalformat))); + MakeUnique( + "MG_Impl/GLImpl", GetBufferOpName(op), + std::format("internalformat 0x{:X} has no known element size.", internalformat))); return 0; } @@ -157,9 +156,8 @@ namespace MobileGL::MG_Impl::GLImpl { GLsizeiptr size, SizeT patternSize, BufferOp op) { if (offset < 0 || size < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "Offset and size must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "Offset and size must be non-negative.")); return false; } @@ -173,10 +171,9 @@ namespace MobileGL::MG_Impl::GLImpl { } if (static_cast(offset) + static_cast(size) > bufferObject->GetSize()) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "Offset and size exceed buffer size.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "Offset and size exceed buffer size.")); return false; } @@ -191,8 +188,8 @@ namespace MobileGL::MG_Impl::GLImpl { return true; } - Bool BuildClearPattern(GLenum internalformat, GLenum format, GLenum type, const void* data, - SizeT patternSize, BufferOp op, Vector& pattern) { + Bool BuildClearPattern(GLenum internalformat, GLenum format, GLenum type, const void* data, SizeT patternSize, + BufferOp op, Vector& pattern) { const TextureInternalFormat internal = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); @@ -212,8 +209,8 @@ namespace MobileGL::MG_Impl::GLImpl { inputPixel = zeroInput.data(); } - if (!MG_Util::PixelStoreProcessor::ConvertOnePixelToInternal( - internal, inputFormat, inputType, inputPixel, pattern)) { + if (!MG_Util::PixelStoreProcessor::ConvertOnePixelToInternal(internal, inputFormat, inputType, inputPixel, + pattern)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique( @@ -233,8 +230,8 @@ namespace MobileGL::MG_Impl::GLImpl { } void ClearBufferRange_State(const SharedPtr& bufferObject, - GLenum internalformat, GLintptr offset, GLsizeiptr size, - GLenum format, GLenum type, const void* data, BufferOp op) { + GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, GLenum type, + const void* data, BufferOp op) { const SizeT patternSize = GetClearPatternSize(internalformat, format, type, op); if (patternSize == 0) return; if (!ValidateBufferClearRange(bufferObject, offset, size, patternSize, op)) return; @@ -298,8 +295,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!MG_Config::Features.CoherentAsFlush) return accessBits; if (!(accessBits & BufferMappingAccessBit::FlushExplicit)) return accessBits; if (!(accessBits & BufferMappingAccessBit::Persistent)) return accessBits; - accessBits = Flags( - accessBits.GetRaw() & ~static_cast(BufferMappingAccessBit::FlushExplicit)); + accessBits = Flags(accessBits.GetRaw() & + ~static_cast(BufferMappingAccessBit::FlushExplicit)); accessBits |= BufferMappingAccessBit::Coherent; return accessBits; } @@ -318,8 +315,9 @@ namespace MobileGL::MG_Impl::GLImpl { if ((flags & GL_MAP_PERSISTENT_BIT) && !(flags & (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT))) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "GL_MAP_PERSISTENT_BIT requires GL_MAP_READ_BIT or GL_MAP_WRITE_BIT.")); + MakeUnique( + "MG_Impl/GLImpl", GetBufferOpName(op), + "GL_MAP_PERSISTENT_BIT requires GL_MAP_READ_BIT or GL_MAP_WRITE_BIT.")); return false; } @@ -382,9 +380,9 @@ namespace MobileGL::MG_Impl::GLImpl { void GetBufferParameteriv_Object(const SharedPtr& bufferObject, GLenum pname, GLint* params, BufferOp op) { if (!params) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "Params pointer cannot be null.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "Params pointer cannot be null.")); return; } @@ -415,10 +413,9 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_BUFFER_ACCESS_FLAGS: // The MapBufferRange flags verbatim; glMapBuffer's access enum has already been // normalised into the same bits. Zero while the buffer is not mapped. - *params = bufferObject->IsMapped() - ? static_cast( - MG_Util::ConvertBufferMappingAccessToGLEnum(bufferObject->GetMappingAccess())) - : 0; + *params = bufferObject->IsMapped() ? static_cast(MG_Util::ConvertBufferMappingAccessToGLEnum( + bufferObject->GetMappingAccess())) + : 0; break; case GL_BUFFER_MAPPED: *params = bufferObject->IsMapped() ? GL_TRUE : GL_FALSE; @@ -437,8 +434,9 @@ namespace MobileGL::MG_Impl::GLImpl { break; default: MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - std::format("Invalid pname enum: 0x{:X}", pname))); + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + std::format("Invalid pname enum: 0x{:X}", pname))); break; } } @@ -446,9 +444,9 @@ namespace MobileGL::MG_Impl::GLImpl { void GetBufferParameteri64v_Object(const SharedPtr& bufferObject, GLenum pname, GLint64* params, BufferOp op) { if (!params) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "Params pointer cannot be null.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "Params pointer cannot be null.")); return; } @@ -460,7 +458,8 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(bufferObject->GetMappedRange().start); break; case GL_BUFFER_MAP_LENGTH: - *params = static_cast(bufferObject->GetMappedRange().end - bufferObject->GetMappedRange().start); + *params = + static_cast(bufferObject->GetMappedRange().end - bufferObject->GetMappedRange().start); break; default: { GLint value = 0; @@ -474,15 +473,16 @@ namespace MobileGL::MG_Impl::GLImpl { void GetBufferPointerv_Object(const SharedPtr& bufferObject, GLenum pname, void** params, BufferOp op) { if (!params) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - "Params pointer cannot be null.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + "Params pointer cannot be null.")); return; } if (pname != GL_BUFFER_MAP_POINTER) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), - std::format("Invalid pname enum: 0x{:X}", pname))); + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", GetBufferOpName(op), + std::format("Invalid pname enum: 0x{:X}", pname))); return; } *params = bufferObject->GetMappedPointer(); @@ -577,8 +577,7 @@ namespace MobileGL::MG_Impl::GLImpl { // time (leaving Persistent|Coherent), so honor the app's flush on such a map as // a no-op: its writes reach the backend without explicit flushes. Other maps // keep the spec error. - if (MG_Config::Features.CoherentAsFlush && - (mappingAccess & BufferMappingAccessBit::Persistent) && + if (MG_Config::Features.CoherentAsFlush && (mappingAccess & BufferMappingAccessBit::Persistent) && (mappingAccess & BufferMappingAccessBit::Coherent)) { return; } @@ -690,7 +689,8 @@ namespace MobileGL::MG_Impl::GLImpl { } } - if ((accessBits & BufferMappingAccessBit::Persistent) && !(accessBits & (BufferMappingAccessBit::Read | BufferMappingAccessBit::Write))) { + if ((accessBits & BufferMappingAccessBit::Persistent) && + !(accessBits & (BufferMappingAccessBit::Read | BufferMappingAccessBit::Write))) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "MapBufferRange_State", @@ -882,10 +882,9 @@ namespace MobileGL::MG_Impl::GLImpl { SizeT bufferSize = bufferObject->GetSize(); if (static_cast(offset) + static_cast(size) > bufferSize) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "BufferSubData_State", - "Offset and size exceed buffer size.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "BufferSubData_State", + "Offset and size exceed buffer size.")); return; } @@ -899,9 +898,8 @@ namespace MobileGL::MG_Impl::GLImpl { (offset < mappedRange.end) && (offset + size > mappedRange.start)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", "BufferSubData_State", - "Cannot modify a non-persistently mapped buffer object.")); + MakeUnique("MG_Impl/GLImpl", "BufferSubData_State", + "Cannot modify a non-persistently mapped buffer object.")); return; } @@ -938,15 +936,13 @@ namespace MobileGL::MG_Impl::GLImpl { SizeT bufferSize = bufferObject->GetSize(); if (static_cast(offset) + static_cast(size) > bufferSize) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", - "Offset and size exceed buffer size.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", + "Offset and size exceed buffer size.")); return; } - if (bufferObject->IsMapped() && - !(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) { + if (bufferObject->IsMapped() && !(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetBufferSubData_State", @@ -966,9 +962,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (size < 0 || offset < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "GetNamedBufferSubData_State", - "Offset and size must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "GetNamedBufferSubData_State", + "Offset and size must be non-negative.")); return; } @@ -977,14 +972,12 @@ namespace MobileGL::MG_Impl::GLImpl { if (static_cast(offset) + static_cast(size) > bufferObject->GetSize()) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "GetNamedBufferSubData_State", - "Offset and size exceed buffer size.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "GetNamedBufferSubData_State", + "Offset and size exceed buffer size.")); return; } - if (bufferObject->IsMapped() && - !(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) { + if (bufferObject->IsMapped() && !(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetNamedBufferSubData_State", @@ -1049,10 +1042,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (!ValidateStorageFlags(flags, BufferOp::BufferStorage)) return; if (bufferObject->IsImmutableStorage()) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "BufferStorage_State", - "Buffer already has immutable storage.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", "BufferStorage_State", + "Buffer already has immutable storage.")); return; } bufferObject->AllocateImmutableStorage(static_cast(size), data, flags); @@ -1066,10 +1058,9 @@ namespace MobileGL::MG_Impl::GLImpl { return; } if (n > 0 && !buffers) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "CreateBuffers_State", - "Buffer output pointer cannot be null.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "CreateBuffers_State", + "Buffer output pointer cannot be null.")); return; } @@ -1095,10 +1086,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (!ValidateStorageFlags(flags, BufferOp::NamedBufferStorage)) return; if (bufferObject->IsImmutableStorage()) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "NamedBufferStorage_State", - "Buffer already has immutable storage.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", "NamedBufferStorage_State", + "Buffer already has immutable storage.")); return; } bufferObject->AllocateImmutableStorage(static_cast(size), data, flags); @@ -1132,17 +1122,15 @@ namespace MobileGL::MG_Impl::GLImpl { void NamedBufferSubData_State(GLuint buffer, GLintptr offset, GLsizeiptr size, const void* data) { if (!data) { - MG_State::pGLContext->RecordError( - ErrorCode::NoError, - MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", - "Data pointer cannot be null.")); + MG_State::pGLContext->RecordError(ErrorCode::NoError, + MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", + "Data pointer cannot be null.")); return; } if (size < 0 || offset < 0) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", - "Offset and size must be non-negative.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", + "Offset and size must be non-negative.")); return; } @@ -1157,10 +1145,9 @@ namespace MobileGL::MG_Impl::GLImpl { return; } if (static_cast(offset) + static_cast(size) > bufferObject->GetSize()) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", - "Offset and size exceed buffer size.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "NamedBufferSubData_State", + "Offset and size exceed buffer size.")); return; } const auto mappingAccess = bufferObject->GetMappingAccess(); @@ -1181,9 +1168,8 @@ namespace MobileGL::MG_Impl::GLImpl { GLsizeiptr size) { if (size < 0 || readOffset < 0 || writeOffset < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "CopyNamedBufferSubData_State", - "Offset and size must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "CopyNamedBufferSubData_State", + "Offset and size must be non-negative.")); return; } @@ -1229,23 +1215,24 @@ namespace MobileGL::MG_Impl::GLImpl { void ClearBufferData_State(GLenum target, GLenum internalformat, GLenum format, GLenum type, const void* data) { auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferData); if (!bufferObject) return; - ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), format, - type, data, BufferOp::ClearBufferData); + ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), + format, type, data, BufferOp::ClearBufferData); } - void ClearBufferSubData_State(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, - GLenum format, GLenum type, const void* data) { + void ClearBufferSubData_State(GLenum target, GLenum internalformat, GLintptr offset, GLsizeiptr size, GLenum format, + GLenum type, const void* data) { auto bufferObject = GetBoundBufferObject(target, BufferOp::ClearBufferSubData); if (!bufferObject) return; ClearBufferRange_State(bufferObject, internalformat, offset, size, format, type, data, BufferOp::ClearBufferSubData); } - void ClearNamedBufferData_State(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, const void* data) { + void ClearNamedBufferData_State(GLuint buffer, GLenum internalformat, GLenum format, GLenum type, + const void* data) { auto bufferObject = GetNamedBufferObject(buffer, BufferOp::ClearNamedBufferData); if (!bufferObject) return; - ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), format, - type, data, BufferOp::ClearNamedBufferData); + ClearBufferRange_State(bufferObject, internalformat, 0, static_cast(bufferObject->GetSize()), + format, type, data, BufferOp::ClearNamedBufferData); } void ClearNamedBufferSubData_State(GLuint buffer, GLenum internalformat, GLintptr offset, GLsizeiptr size, @@ -1291,23 +1278,20 @@ namespace MobileGL::MG_Impl::GLImpl { if (length < 0 || offset < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", - "Offset and length must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", + "Offset and length must be non-negative.")); return nullptr; } if (length == 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", - "Length must be greater than zero.")); + ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", + "Length must be greater than zero.")); return nullptr; } if (static_cast(offset) + static_cast(length) > bufferObject->GetSize()) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", - "Offset and length exceed buffer size.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", + "Offset and length exceed buffer size.")); return nullptr; } @@ -1326,8 +1310,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (accessBits & invalidFlags) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", - "GL_MAP_READ_BIT cannot be combined with invalidation or unsynchronized flags.")); + MakeUnique( + "MG_Impl/GLImpl", "MapNamedBufferRange_State", + "GL_MAP_READ_BIT cannot be combined with invalidation or unsynchronized flags.")); return nullptr; } } @@ -1338,7 +1323,8 @@ namespace MobileGL::MG_Impl::GLImpl { "GL_MAP_FLUSH_EXPLICIT_BIT requires GL_MAP_WRITE_BIT.")); return nullptr; } - if ((accessBits & BufferMappingAccessBit::Persistent) && !(accessBits & (BufferMappingAccessBit::Read | BufferMappingAccessBit::Write))) { + if ((accessBits & BufferMappingAccessBit::Persistent) && + !(accessBits & (BufferMappingAccessBit::Read | BufferMappingAccessBit::Write))) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "MapNamedBufferRange_State", @@ -1388,10 +1374,10 @@ namespace MobileGL::MG_Impl::GLImpl { auto bufferObject = GetNamedBufferObject(buffer, BufferOp::FlushMappedNamedBufferRange); if (!bufferObject) return; if (length < 0 || offset < 0) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State", - "Offset and length must be non-negative.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", + "FlushMappedNamedBufferRange_State", + "Offset and length must be non-negative.")); return; } if (!bufferObject->IsMapped()) { @@ -1403,25 +1389,25 @@ namespace MobileGL::MG_Impl::GLImpl { } const auto mappedRange = bufferObject->GetMappedRange(); if (static_cast(offset) + static_cast(length) > mappedRange.end - mappedRange.start) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State", - "Offset and length exceed mapped range.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", + "FlushMappedNamedBufferRange_State", + "Offset and length exceed mapped range.")); return; } const auto namedMappingAccess = bufferObject->GetMappingAccess(); if (!(namedMappingAccess & BufferMappingAccessBit::FlushExplicit)) { // See FlushMappedBufferRange_State: rewritten coherent-as-flush persistent maps // tolerate app flushes as no-ops. - if (MG_Config::Features.CoherentAsFlush && - (namedMappingAccess & BufferMappingAccessBit::Persistent) && + if (MG_Config::Features.CoherentAsFlush && (namedMappingAccess & BufferMappingAccessBit::Persistent) && (namedMappingAccess & BufferMappingAccessBit::Coherent)) { return; } MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State", - "Cannot flush a buffer object that is not mapped with GL_MAP_FLUSH_EXPLICIT_BIT.")); + MakeUnique( + "MG_Impl/GLImpl", "FlushMappedNamedBufferRange_State", + "Cannot flush a buffer object that is not mapped with GL_MAP_FLUSH_EXPLICIT_BIT.")); return; } bufferObject->FlushMemoryRange(static_cast(offset), static_cast(length)); @@ -1548,8 +1534,8 @@ namespace MobileGL::MG_Impl::GLImpl { // They apply only to a non-zero buffer: buffer 0 detaches the binding point and ignores // offset and size, which is also how glBindBuffersRange spells "reset this element" // (a NULL buffers array, or a zero entry inside one). - static Bool ValidateBufferRangeOffsetAndSize(GLenum target, GLintptr offset, GLsizeiptr size, - const char* funcName, Bool hasBuffer = true) { + static Bool ValidateBufferRangeOffsetAndSize(GLenum target, GLintptr offset, GLsizeiptr size, const char* funcName, + Bool hasBuffer = true) { if (hasBuffer && size <= 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -1578,10 +1564,10 @@ namespace MobileGL::MG_Impl::GLImpl { if (alignment > 0 && (offset % static_cast(alignment)) != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique( - "MG_Impl/GLImpl", funcName, - std::format("offset ({}) must be a multiple of {} ({}).", offset, - MG_Util::ConvertGLEnumToString(alignmentQuery), alignment))); + MakeUnique("MG_Impl/GLImpl", funcName, + std::format("offset ({}) must be a multiple of {} ({}).", offset, + MG_Util::ConvertGLEnumToString(alignmentQuery), + alignment))); return false; } } diff --git a/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp b/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp index a93da564..91be6663 100644 --- a/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/Buffer/Validators.cpp @@ -82,8 +82,8 @@ namespace MobileGL::MG_Impl::GLImpl::BufferImpl { Bool ValidateBufferBindingPointRange(BufferTarget target, Uint first, GLsizei count, const char* funcName) { if (count < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl/BufferImpl", funcName, - "count must be non-negative.")); + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl/BufferImpl", funcName, "count must be non-negative.")); return false; } const SizeT pointCount = GetBufferBindingPointLimit(target); diff --git a/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.cpp b/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.cpp index b65bee11..5f7a6d7e 100644 --- a/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.cpp +++ b/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.cpp @@ -78,9 +78,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (effective < limit) { return true; } - RecordDebugError(ErrorCode::InvalidValue, caller, - std::format("{} length {} is not less than the {} limit of {}.", what, effective, what, - limit)); + RecordDebugError( + ErrorCode::InvalidValue, caller, + std::format("{} length {} is not less than the {} limit of {}.", what, effective, what, limit)); return false; } @@ -163,8 +163,7 @@ namespace MobileGL::MG_Impl::GLImpl { state.groupStack.pop_back(); } - void DebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* buf) { + void DebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* buf) { static_cast(id); if (!ValidateInjectedSource(source, __func__)) return; switch (type) { @@ -180,8 +179,7 @@ namespace MobileGL::MG_Impl::GLImpl { break; default: RecordDebugError(ErrorCode::InvalidEnum, __func__, - std::format("type {} is not a debug message type.", - MG_Util::ConvertGLEnumToString(type))); + std::format("type {} is not a debug message type.", MG_Util::ConvertGLEnumToString(type))); return; } switch (severity) { @@ -191,9 +189,9 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_DEBUG_SEVERITY_NOTIFICATION: break; default: - RecordDebugError(ErrorCode::InvalidEnum, __func__, - std::format("severity {} is not a debug message severity.", - MG_Util::ConvertGLEnumToString(severity))); + RecordDebugError( + ErrorCode::InvalidEnum, __func__, + std::format("severity {} is not a debug message severity.", MG_Util::ConvertGLEnumToString(severity))); return; } if (!ValidateDebugStringLength(length, buf, kMaxDebugMessageLength, __func__, "message")) return; diff --git a/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.h b/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.h index 052a197c..5b521319 100644 --- a/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.h +++ b/MobileGL/MG_Impl/GLImpl/Debug/GL_Debug.h @@ -31,8 +31,7 @@ namespace MobileGL::MG_Impl::GLImpl { // specifies), and a label written with glObjectLabel comes back from glGetObjectLabel. void PushDebugGroup(GLenum source, GLuint id, GLsizei length, const GLchar* message); void PopDebugGroup(); - void DebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar* buf); + void DebugMessageInsert(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* buf); void ObjectLabel(GLenum identifier, GLuint name, GLsizei length, const GLchar* label); void GetObjectLabel(GLenum identifier, GLuint name, GLsizei bufSize, GLsizei* length, GLchar* label); diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index f0f03420..6e581431 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -34,9 +34,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!currentProgram->GetLinkStatus()) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", functionName, - "The current program object is not linked.")); + ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", functionName, + "The current program object is not linked.")); return false; } @@ -165,13 +164,19 @@ namespace MobileGL::MG_Impl::GLImpl { static Uint64 CountPrimitivesForDraw(GLenum mode, GLsizei count) { if (count <= 0) return 0; switch (mode) { - case GL_POINTS: return static_cast(count); - case GL_LINES: return static_cast(count / 2); - case GL_LINE_STRIP: return count >= 2 ? static_cast(count - 1) : 0; - case GL_LINE_LOOP: return count >= 2 ? static_cast(count) : 0; - case GL_TRIANGLES: return static_cast(count / 3); + case GL_POINTS: + return static_cast(count); + case GL_LINES: + return static_cast(count / 2); + case GL_LINE_STRIP: + return count >= 2 ? static_cast(count - 1) : 0; + case GL_LINE_LOOP: + return count >= 2 ? static_cast(count) : 0; + case GL_TRIANGLES: + return static_cast(count / 3); case GL_TRIANGLE_STRIP: - case GL_TRIANGLE_FAN: return count >= 3 ? static_cast(count - 2) : 0; + case GL_TRIANGLE_FAN: + return count >= 3 ? static_cast(count - 2) : 0; // Adjacency primitives (GL 4.6 core table 10.1). Only a geometry stage can consume // them, and it is the ADJACENT-free primitive count that reaches it: 4 vertices per // line, 6 per triangle, one per step for the strips. Answering 0 here - which is what @@ -179,14 +184,19 @@ namespace MobileGL::MG_Impl::GLImpl { // recorded anything, so an adjacency capture advanced neither the captured-vertex // counter the scattered-capture path is bounded by nor the geometry-capture-draw flag // that routes the transform feedback queries to the driver's own counter. - case GL_LINES_ADJACENCY: return static_cast(count / 4); - case GL_LINE_STRIP_ADJACENCY: return count >= 4 ? static_cast(count - 3) : 0; - case GL_TRIANGLES_ADJACENCY: return static_cast(count / 6); - case GL_TRIANGLE_STRIP_ADJACENCY: return count >= 6 ? static_cast((count - 4) / 2) : 0; + case GL_LINES_ADJACENCY: + return static_cast(count / 4); + case GL_LINE_STRIP_ADJACENCY: + return count >= 4 ? static_cast(count - 3) : 0; + case GL_TRIANGLES_ADJACENCY: + return static_cast(count / 6); + case GL_TRIANGLE_STRIP_ADJACENCY: + return count >= 6 ? static_cast((count - 4) / 2) : 0; // GL_PATCHES is deliberately absent: the tessellator's amplification is not knowable // on the CPU, and answering 0 is what defers GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN // to the driver's own counter, which is the only correct source for a patch capture. - default: return 0; + default: + return 0; } } @@ -242,8 +252,8 @@ namespace MobileGL::MG_Impl::GLImpl { for (SizeT i = 0; i < program->GetTransformFeedbackBufferCount(); ++i) { const Uint32 stride = program->GetTransformFeedbackStride(static_cast(i)); if (stride == 0) continue; - const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, - static_cast(i)); + const auto& point = + MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); const Range1D range = point.GetRange(); const Uint64 bytes = range.end > range.start ? static_cast(range.end - range.start) : 0; capacityVertices = std::min(capacityVertices, bytes / stride); @@ -317,8 +327,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (vao && vao->GetExternalIndex() == 0 && !MG_State::IsRelaxedSemanticsActive()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", functionName, - "Default vertex array object cannot be used for drawing in core profile.")); + MakeUnique( + "MG_Impl/GLImpl", functionName, + "Default vertex array object cannot be used for drawing in core profile.")); return false; } @@ -419,8 +430,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool feedbackModeIsProgramDriven = feedbackProgram && (feedbackProgram->HasLinkedShaderStage(ShaderStage::Geometry) || feedbackProgram->HasLinkedShaderStage(ShaderStage::TessEval)); - if (MG_State::pGLContext->IsTransformFeedbackActive() && - !MG_State::pGLContext->IsTransformFeedbackPaused() && !feedbackModeIsProgramDriven) { + if (MG_State::pGLContext->IsTransformFeedbackActive() && !MG_State::pGLContext->IsTransformFeedbackPaused() && + !feedbackModeIsProgramDriven) { const GLenum feedbackMode = MG_State::pGLContext->GetTransformFeedbackPrimitiveMode(); Bool compatible = false; switch (feedbackMode) { @@ -469,10 +480,9 @@ namespace MobileGL::MG_Impl::GLImpl { static Bool ValidateIndirectDrawSource(const char* functionName, const void* indirect, SizeT commandBytes) { const auto offset = reinterpret_cast(indirect); if (offset % 4 != 0) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", functionName, - "indirect offset must be a multiple of 4.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + "indirect offset must be a multiple of 4.")); return false; } @@ -518,9 +528,8 @@ namespace MobileGL::MG_Impl::GLImpl { static Bool ValidateNonNegativeDrawArgument(const char* functionName, const char* argumentName, GLsizei value) { if (value >= 0) return true; MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", functionName, - String(argumentName) + " must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, + String(argumentName) + " must be non-negative.")); return false; } @@ -857,9 +866,8 @@ namespace MobileGL::MG_Impl::GLImpl { GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices); if (value <= 0 || value > maxPatchVertices) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "value must be in [1, GL_MAX_PATCH_VERTICES].")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, + "value must be in [1, GL_MAX_PATCH_VERTICES].")); return; } MG_State::pGLContext->SetPatchVertices(static_cast(value)); @@ -893,8 +901,7 @@ namespace MobileGL::MG_Impl::GLImpl { return; } if (pname == GL_PATCH_DEFAULT_OUTER_LEVEL) { - MG_State::pGLContext->SetPatchDefaultOuterLevel( - FloatVec4(values[0], values[1], values[2], values[3])); + MG_State::pGLContext->SetPatchDefaultOuterLevel(FloatVec4(values[0], values[1], values[2], values[3])); } else { MG_State::pGLContext->SetPatchDefaultInnerLevel(FloatVec2(values[0], values[1])); } @@ -1007,8 +1014,8 @@ namespace MobileGL::MG_Impl::GLImpl { // lies past its end INVALID_OPERATION // * maxdrawcount commands from `indirect` run past the end of the // buffer bound to DRAW_INDIRECT_BUFFER INVALID_OPERATION - static Bool ValidateIndirectCountDraw(GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, - GLsizei stride, SizeT commandSize, const char* funcName) { + static Bool ValidateIndirectCountDraw(GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride, + SizeT commandSize, const char* funcName) { if (drawcount < 0 || (drawcount % 4) != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -1018,8 +1025,7 @@ namespace MobileGL::MG_Impl::GLImpl { } const auto& parameterBuffer = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Parameter).GetBoundObject(); - if (!parameterBuffer || - static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { + if (!parameterBuffer || static_cast(drawcount) + sizeof(Uint32) > parameterBuffer->GetSize()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", funcName, @@ -1039,9 +1045,9 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject(); // A zero maxdrawcount sources nothing, so it cannot run past anything. const SizeT requiredBytes = - maxdrawcount == 0 ? 0 - : static_cast(indirect) + - static_cast(maxdrawcount - 1) * effectiveStride + commandSize; + maxdrawcount == 0 + ? 0 + : static_cast(indirect) + static_cast(maxdrawcount - 1) * effectiveStride + commandSize; if (!indirectBuffer || requiredBytes > indirectBuffer->GetSize()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -1083,8 +1089,8 @@ namespace MobileGL::MG_Impl::GLImpl { MultiDrawElementsIndirectCount_Backend(mode, type, indirect, drawcount, maxdrawcount, 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) { // Argument validation before the backend-availability check: see DispatchComputeIndirect. // DrawArraysIndirectCommand: count, instanceCount, first, baseInstance. if (!ValidateIndirectCountDraw(reinterpret_cast(indirect), drawcount, maxdrawcount, stride, @@ -1279,9 +1285,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!program || !program->GetLinkStatus() || program->GetTransformFeedbackVaryingCount() == 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", __func__, - "No program with transform feedback varyings is active.")); + MakeUnique("MG_Impl/GLImpl", __func__, + "No program with transform feedback varyings is active.")); return; } // Every capture buffer slot the program's mode uses must have a buffer bound. A slot @@ -1290,14 +1295,14 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT usedBufferCount = program->GetTransformFeedbackBufferCount(); for (SizeT i = 0; i < usedBufferCount; ++i) { if (program->GetTransformFeedbackStride(static_cast(i)) == 0) continue; - const auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, - static_cast(i)); + const auto& point = + MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); if (point.GetBoundObject() == nullptr) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", __func__, - "Transform feedback buffer binding point " + std::to_string(i) + " has no buffer bound.")); + MakeUnique("MG_Impl/GLImpl", __func__, + "Transform feedback buffer binding point " + std::to_string(i) + + " has no buffer bound.")); return; } } @@ -1352,9 +1357,8 @@ namespace MobileGL::MG_Impl::GLImpl { for (SizeT bufferIndex = 0; bufferIndex < program->GetTransformFeedbackBufferCount(); ++bufferIndex) { const Uint32 stride = program->GetTransformFeedbackStride(static_cast(bufferIndex)); if (stride == 0) continue; - const auto& bindingPoint = - MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, - static_cast(bufferIndex)); + const auto& bindingPoint = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, + static_cast(bufferIndex)); const auto& buffer = bindingPoint.GetBoundObject(); if (buffer == nullptr) continue; // This fixup READS the captured bytes back through the shadow, so it is the one @@ -1426,8 +1430,7 @@ namespace MobileGL::MG_Impl::GLImpl { // shape a verb barrier cannot make cheap, because it is the driver's wait and not the // barrier's. auto& backendGL = MG_Backend::gBackendFunctionsTable.GL; - const Bool waitForTheCapture = - MG_Config::Transport == MG_Config::TransportMode::Monolith; + const Bool waitForTheCapture = MG_Config::Transport == MG_Config::TransportMode::Monolith; if (waitForTheCapture && backendGL.FenceSync && backendGL.ClientWaitSync) { MGP_FILL(FenceSync); if (auto sync = backendGL.FenceSync()) { @@ -1443,8 +1446,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void PauseTransformFeedback(void) { - if (!MG_State::pGLContext->IsTransformFeedbackActive() || - MG_State::pGLContext->IsTransformFeedbackPaused()) { + if (!MG_State::pGLContext->IsTransformFeedbackActive() || MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, @@ -1459,8 +1461,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ResumeTransformFeedback(void) { - if (!MG_State::pGLContext->IsTransformFeedbackActive() || - !MG_State::pGLContext->IsTransformFeedbackPaused()) { + if (!MG_State::pGLContext->IsTransformFeedbackActive() || !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "Transform feedback is not paused.")); @@ -1542,7 +1543,7 @@ namespace MobileGL::MG_Impl::GLImpl { } SharedPtr ResolveTransformFeedbackBuffer(GLuint buffer, - const char* functionName) { + const char* functionName) { if (buffer == 0) return nullptr; if (!MG_State::pGLContext->ValidateBufferName(buffer)) { MG_State::pGLContext->RecordError( @@ -1566,9 +1567,8 @@ namespace MobileGL::MG_Impl::GLImpl { std::to_string(buffer) + " is not a buffer object.")); return; } - MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, - ResolveTransformFeedbackBuffer(buffer, __func__), {}, - false); + MG_State::pGLContext->SetNamedTransformFeedbackBinding( + xfb, index, ResolveTransformFeedbackBuffer(buffer, __func__), {}, false); } void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) { @@ -1689,8 +1689,7 @@ namespace MobileGL::MG_Impl::GLImpl { return; } // A running capture pins its object; only a paused one may be swapped out. - if (MG_State::pGLContext->IsTransformFeedbackActive() && - !MG_State::pGLContext->IsTransformFeedbackPaused()) { + if (MG_State::pGLContext->IsTransformFeedbackActive() && !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 91ddf633..69cc9bc3 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -40,8 +40,8 @@ namespace MobileGL::MG_Impl::GLImpl { 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); diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp index b9690fb4..444d64e3 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.cpp @@ -59,8 +59,8 @@ namespace MobileGL::MG_Impl::GLImpl { const auto& depthAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Depth); const auto& stencilAttachment = framebufferObject.GetAttachment(FramebufferAttachmentType::Stencil); - if (!depthAttachment.IsComplete() || !stencilAttachment.IsComplete() || - !depthAttachment.IsTexture() || !stencilAttachment.IsTexture()) { + if (!depthAttachment.IsComplete() || !stencilAttachment.IsComplete() || !depthAttachment.IsTexture() || + !stencilAttachment.IsTexture()) { return false; } @@ -214,9 +214,8 @@ namespace MobileGL::MG_Impl::GLImpl { } void RecordUnsupportedFramebufferTextureAttachmentError(const char* functionName, const char* detail) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", functionName, detail)); + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", functionName, detail)); } GLint ClassifyAttachmentComponentType(TextureInternalFormat internalFormat, @@ -290,10 +289,9 @@ namespace MobileGL::MG_Impl::GLImpl { const auto reject = [&]() { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("Attachment {} is not queryable on this framebuffer.", - MG_Util::ConvertGLEnumToString(attachment)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("Attachment {} is not queryable on this framebuffer.", + MG_Util::ConvertGLEnumToString(attachment)))); return false; }; @@ -354,10 +352,9 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("pname {} is only defined for a texture attachment.", - MG_Util::ConvertGLEnumToString(pname)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("pname {} is only defined for a texture attachment.", + MG_Util::ConvertGLEnumToString(pname)))); return false; } @@ -385,10 +382,9 @@ namespace MobileGL::MG_Impl::GLImpl { // attachment name has no single answer. MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", caller, - "GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE cannot be queried on " - "GL_DEPTH_STENCIL_ATTACHMENT.")); + MakeUnique("MG_Impl/GLImpl", caller, + "GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE cannot be queried on " + "GL_DEPTH_STENCIL_ATTACHMENT.")); return true; } @@ -431,8 +427,8 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: *params = (internalFormat == TextureInternalFormat::SRGB8 || internalFormat == TextureInternalFormat::SRGB8Alpha8) - ? GL_SRGB - : GL_LINEAR; + ? GL_SRGB + : GL_LINEAR; break; case GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: *params = ClassifyAttachmentComponentType(internalFormat, attachmentType); @@ -498,10 +494,9 @@ namespace MobileGL::MG_Impl::GLImpl { // one another in that section and a CTS case that walks the family must not get five // different answers - which is exactly what happened when these lived in one helper that // only two of the five went through. - Bool ValidateFramebufferTextureAttachmentPoint(const char* functionName, - const SharedPtr& - framebufferObject, - FramebufferAttachmentType attachmentType) { + Bool ValidateFramebufferTextureAttachmentPoint( + const char* functionName, const SharedPtr& framebufferObject, + FramebufferAttachmentType attachmentType) { // "An INVALID_OPERATION error is generated if COLOR_ATTACHMENTm is used with m greater // than or equal to MAX_COLOR_ATTACHMENTS." if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, functionName)) return false; @@ -533,10 +528,9 @@ namespace MobileGL::MG_Impl::GLImpl { const SharedPtr& textureObject, GLint level) { if (level < 0) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", functionName, - "Texture level must be non-negative.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + "Texture level must be non-negative.")); return false; } if (!textureObject || !textureObject->IsImmutable()) { @@ -585,13 +579,14 @@ namespace MobileGL::MG_Impl::GLImpl { // texture_cube_map_array.stencil_attachments_*_layered and // geometry_shader.layered_framebuffer.stencil_support are built on. AttachFramebufferTextureWithUploadTarget(functionName, target, GL_DEPTH_ATTACHMENT, texture, level, - textureUploadTarget, layered); + textureUploadTarget, layered); AttachFramebufferTextureWithUploadTarget(functionName, target, GL_STENCIL_ATTACHMENT, texture, level, - textureUploadTarget, layered); + textureUploadTarget, layered); return; } - const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); + const FramebufferAttachmentType attachmentType = + MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); const FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target); if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return; @@ -679,8 +674,8 @@ namespace MobileGL::MG_Impl::GLImpl { PipePublishFramebufferByName(readFramebuffer); PipePublishFramebufferByName(drawFramebuffer); MGP_FILL(BlitNamedFramebuffer); - blitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, - dstY1, mask, filter); + blitNamedFramebuffer(readFramebuffer, drawFramebuffer, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, + mask, filter); } void ClearNamedFramebufferfv_Backend(const SharedPtr& framebuffer, @@ -791,10 +786,9 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(format); GLenum normalizedFormat = GL_RGBA; GLenum normalizedType = GL_UNSIGNED_BYTE; - MG_Util::TextureFormatProcessor::NormalizePixelFormat(normalizedInternalFormat, - PixelFormatNormalizeOptionBit::None, - &normalizedInternalFormat, &normalizedFormat, - &normalizedType); + MG_Util::TextureFormatProcessor::NormalizePixelFormat( + normalizedInternalFormat, PixelFormatNormalizeOptionBit::None, &normalizedInternalFormat, &normalizedFormat, + &normalizedType); const Bool isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER || normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER; // The per-format probe first, for the same reason the texture path takes it first: GL 4.6 @@ -913,7 +907,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return; auto& bindingSlot = MG_State::pGLContext->GetRenderbufferBindingSlot(rbTarget); auto& renderbufferObject = bindingSlot.GetBoundObject(); - AllocateRenderbufferStorage_State(renderbufferObject, internalformat, width, height, "RenderbufferStorage_State"); + AllocateRenderbufferStorage_State(renderbufferObject, internalformat, width, height, + "RenderbufferStorage_State"); } GLboolean IsRenderbuffer_State(GLuint renderbuffer) { @@ -950,8 +945,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool depthStencilAlias = attachment == GL_DEPTH_STENCIL_ATTACHMENT; FramebufferAttachmentType attachmentType = depthStencilAlias - ? FramebufferAttachmentType::Depth - : MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); + ? FramebufferAttachmentType::Depth + : MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; Bool depthStencilMismatch = false; @@ -965,7 +960,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool depthLive = depthAttachment.IsValid() && !depthAttachment.IsEmpty(); const Bool stencilLive = stencilAttachment.IsValid() && !stencilAttachment.IsEmpty(); if (depthLive && stencilLive) { - const Bool sameObject = depthAttachment.IsTexture() == stencilAttachment.IsTexture() && + const Bool sameObject = + depthAttachment.IsTexture() == stencilAttachment.IsTexture() && (!depthAttachment.IsTexture() || depthAttachment.GetTexture() == stencilAttachment.GetTexture()) && (!depthAttachment.IsRenderbuffer() || depthAttachment.GetRenderbuffer() == stencilAttachment.GetRenderbuffer()); @@ -1017,8 +1013,8 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid()) - ? static_cast(attachmentObject->GetTextureLevel()) - : 0; + ? static_cast(attachmentObject->GetTextureLevel()) + : 0; break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: if (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid()) { @@ -1043,14 +1039,14 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid()) - ? static_cast(attachmentObject->GetTextureLayer()) - : 0; + ? static_cast(attachmentObject->GetTextureLayer()) + : 0; break; case GL_FRAMEBUFFER_ATTACHMENT_LAYERED: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid() && attachmentObject->IsLayered()) - ? GL_TRUE - : GL_FALSE; + ? GL_TRUE + : GL_FALSE; break; default: if (TryAnswerAttachmentFormatQuery(attachmentObject, attachmentType, depthStencilAlias, pname, params, @@ -1087,9 +1083,8 @@ namespace MobileGL::MG_Impl::GLImpl { } if (n > 0 && !renderbuffers) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "CreateRenderbuffers_State", - "Renderbuffer output pointer cannot be null.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "CreateRenderbuffers_State", + "Renderbuffer output pointer cannot be null.")); return; } @@ -1102,7 +1097,7 @@ namespace MobileGL::MG_Impl::GLImpl { } SharedPtr GetNamedRenderbufferObject_State(GLuint renderbuffer, - const char* caller) { + const char* caller) { if (!FramebufferImpl::ValidateRenderbufferName(renderbuffer, false)) return nullptr; auto& renderbufferObject = MG_State::pGLContext->GetRenderbufferObject(renderbuffer); @@ -1161,9 +1156,8 @@ namespace MobileGL::MG_Impl::GLImpl { } if (n > 0 && !framebuffers) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "CreateFramebuffers_State", - "Framebuffer output pointer cannot be null.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "CreateFramebuffers_State", + "Framebuffer output pointer cannot be null.")); return; } @@ -1176,7 +1170,7 @@ namespace MobileGL::MG_Impl::GLImpl { } SharedPtr GetNamedFramebufferObject_State(GLuint framebuffer, - const char* caller) { + const char* caller) { if (!FramebufferImpl::ValidateFramebufferName(framebuffer, false)) return nullptr; auto& framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer); @@ -1311,9 +1305,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (textarget != GL_TEXTURE_3D) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "FramebufferTexture3D requires GL_TEXTURE_3D.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "FramebufferTexture3D requires GL_TEXTURE_3D.")); return; } AttachFramebufferTextureLayer(__func__, target, attachment, texture, level, zoffset, @@ -1376,15 +1369,13 @@ namespace MobileGL::MG_Impl::GLImpl { if (!ValidateFramebufferTextureLevel("FramebufferTexture2D_State", textureObject, level)) return; const auto expectedTextureTarget = MG_Util::ConvertTextureUploadTargetToTextureTarget(textureUploadTarget); - if (expectedTextureTarget == TextureTarget::Unknown || - textureObject->GetTarget() != expectedTextureTarget) { + if (expectedTextureTarget == TextureTarget::Unknown || textureObject->GetTarget() != expectedTextureTarget) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", "FramebufferTexture2D_State", std::format("Attachment target {} does not match texture {} target {}.", - MG_Util::ConvertGLEnumToString(textarget), - texture, + MG_Util::ConvertGLEnumToString(textarget), texture, MG_Util::ConvertTextureTargetToString(textureObject->GetTarget())))); return; } @@ -1429,8 +1420,8 @@ namespace MobileGL::MG_Impl::GLImpl { Bool layered = false; if (!ResolveRepresentableFramebufferTextureUploadTarget(*textureObject, textureUploadTarget, layered)) { RecordUnsupportedFramebufferTextureAttachmentError( - __func__, - "Layered or multi-image framebuffer texture targets are not fully represented by the current framebuffer attachment model."); + __func__, "Layered or multi-image framebuffer texture targets are not fully represented by the current " + "framebuffer attachment model."); return; } @@ -1455,7 +1446,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (texture == 0) { framebufferObject->Detach(attachmentType); - PipePublishFramebufferByName(framebufferObject); + PipePublishFramebufferByName(framebufferObject); return; } @@ -1476,8 +1467,8 @@ namespace MobileGL::MG_Impl::GLImpl { Bool layered = false; if (!ResolveRepresentableFramebufferTextureUploadTarget(*textureObject, textureUploadTarget, layered)) { RecordUnsupportedFramebufferTextureAttachmentError( - "NamedFramebufferTexture_State", - "Layered or multi-image framebuffer texture targets are not fully represented by the current framebuffer attachment model."); + "NamedFramebufferTexture_State", "Layered or multi-image framebuffer texture targets are not fully " + "represented by the current framebuffer attachment model."); return; } @@ -1490,9 +1481,9 @@ namespace MobileGL::MG_Impl::GLImpl { TextureUploadTarget textureUploadTarget) { if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { NamedFramebufferTextureWithUploadTarget_State(functionName, framebuffer, GL_DEPTH_ATTACHMENT, texture, - level, textureUploadTarget); + level, textureUploadTarget); NamedFramebufferTextureWithUploadTarget_State(functionName, framebuffer, GL_STENCIL_ATTACHMENT, texture, - level, textureUploadTarget); + level, textureUploadTarget); return; } @@ -1506,7 +1497,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (texture == 0) { framebufferObject->Detach(attachmentType); - PipePublishFramebufferByName(framebufferObject); + PipePublishFramebufferByName(framebufferObject); return; } @@ -1520,15 +1511,13 @@ namespace MobileGL::MG_Impl::GLImpl { } const auto expectedTextureTarget = MG_Util::ConvertTextureUploadTargetToTextureTarget(textureUploadTarget); - if (expectedTextureTarget == TextureTarget::Unknown || - textureObject->GetTarget() != expectedTextureTarget) { + if (expectedTextureTarget == TextureTarget::Unknown || textureObject->GetTarget() != expectedTextureTarget) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", functionName, std::format("Attachment target {} does not match texture {} target {}.", - MG_Util::ConvertTextureUploadTargetToString(textureUploadTarget), - texture, + MG_Util::ConvertTextureUploadTargetToString(textureUploadTarget), texture, MG_Util::ConvertTextureTargetToString(textureObject->GetTarget())))); return; } @@ -1567,9 +1556,9 @@ namespace MobileGL::MG_Impl::GLImpl { static_cast(texture); static_cast(level); static_cast(zoffset); - RecordUnsupportedFramebufferTextureAttachmentError( - __func__, - "3D framebuffer texture slice attachments are not represented by the current framebuffer attachment model."); + RecordUnsupportedFramebufferTextureAttachmentError(__func__, + "3D framebuffer texture slice attachments are not " + "represented by the current framebuffer attachment model."); } void NamedFramebufferTextureLayer_State(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, @@ -1589,7 +1578,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (texture == 0) { framebufferObject->Detach(attachmentType); - PipePublishFramebufferByName(framebufferObject); + PipePublishFramebufferByName(framebufferObject); return; } @@ -1619,9 +1608,8 @@ namespace MobileGL::MG_Impl::GLImpl { // GL 4.6 core 9.2.8: only the layered targets have layers to select, and the largest layer // each one admits comes from a different implementation limit. - const auto& limits = MG_Backend::pActiveBackendObject - ? MG_Backend::pActiveBackendObject->GetDynamicParameters() - : MG_Backend::DynamicBackendParameters{}; + const auto& limits = MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters() + : MG_Backend::DynamicBackendParameters{}; TextureUploadTarget textureUploadTarget = TextureUploadTarget::Unknown; Int layerLimit = 0; switch (textureObject->GetTarget()) { @@ -1659,9 +1647,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (layer >= layerLimit) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - std::format("Layer {} is beyond the limit ({}) for this texture target.", - layer, layerLimit))); + MakeUnique( + "MG_Impl/GLImpl", __func__, + std::format("Layer {} is beyond the limit ({}) for this texture target.", layer, layerLimit))); return; } @@ -1682,8 +1670,7 @@ namespace MobileGL::MG_Impl::GLImpl { // already express, so it stays legal even where per-layer selection is not backed. A cube map // array has no such fallback: layer zero is still one face of one cube inside a // cube-compatible image, so it needs the same support layer 5 does. - const Bool needsPerLayerSupport = - layer != 0 || layeredTextureTarget == TextureTarget::TextureCubeMapArray; + const Bool needsPerLayerSupport = layer != 0 || layeredTextureTarget == TextureTarget::TextureCubeMapArray; if (needsPerLayerSupport && !backsThisTargetsLayers) { RecordUnsupportedFramebufferTextureAttachmentError( __func__, "This backend does not resolve a framebuffer attachment's layer onto its image."); @@ -1762,13 +1749,12 @@ namespace MobileGL::MG_Impl::GLImpl { if (renderbuffer == 0) { framebufferObject->Detach(attachmentType); - PipePublishFramebufferByName(framebufferObject); + PipePublishFramebufferByName(framebufferObject); return; } if (!FramebufferImpl::ValidateRenderbufferName(renderbuffer, false)) return; - auto renderbufferObject = - GetNamedRenderbufferObject_State(renderbuffer, "NamedFramebufferRenderbuffer_State"); + auto renderbufferObject = GetNamedRenderbufferObject_State(renderbuffer, "NamedFramebufferRenderbuffer_State"); if (!renderbufferObject) return; framebufferObject->AttachRenderbuffer(attachmentType, renderbufferObject); @@ -1819,10 +1805,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (!allowDefaultFBOAliases && bufs[i] == GL_FRONT) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", __func__, - std::format("glDrawBuffers cannot use the multi-buffer name {}.", - MG_Util::ConvertGLEnumToString(bufs[i])))); + MakeUnique("MG_Impl/GLImpl", __func__, + std::format("glDrawBuffers cannot use the multi-buffer name {}.", + MG_Util::ConvertGLEnumToString(bufs[i])))); return; } @@ -1918,10 +1903,9 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool isMultiBufferName = src == GL_LEFT || src == GL_RIGHT || src == GL_FRONT_AND_BACK; MG_State::pGLContext->RecordError( isMultiBufferName ? ErrorCode::InvalidOperation : ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("`src` = {} does not name a single readable colour buffer.", - MG_Util::ConvertGLEnumToString(src)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("`src` = {} does not name a single readable colour buffer.", + MG_Util::ConvertGLEnumToString(src)))); return; } @@ -1939,9 +1923,9 @@ namespace MobileGL::MG_Impl::GLImpl { (attType < FramebufferAttachmentType::FrontLeft || attType > FramebufferAttachmentType::BackRight)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("Default framebuffer read buffer {} is not valid.", MG_Util::ConvertGLEnumToString(src)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("Default framebuffer read buffer {} is not valid.", + MG_Util::ConvertGLEnumToString(src)))); return; } if (!isDefaultFBO && attType != FramebufferAttachmentType::None && @@ -1967,9 +1951,9 @@ namespace MobileGL::MG_Impl::GLImpl { void NamedFramebufferDrawBuffers_State(GLuint framebuffer, GLsizei n, const GLenum* bufs) { // Zero names the default framebuffer, whose accepted buffer names are a different set - so // it cannot go through the framebuffer-object lookup, which rejects the name outright. - auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferDrawBuffers_State"); + auto framebufferObject = + framebuffer == 0 ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferDrawBuffers_State"); if (!framebufferObject) return; DrawBuffersForFramebuffer_State(framebufferObject, framebuffer == 0, n, bufs, false); PipePublishFramebufferByName(framebufferObject); @@ -1977,8 +1961,8 @@ namespace MobileGL::MG_Impl::GLImpl { void NamedFramebufferDrawBuffer_State(GLuint framebuffer, GLenum buf) { auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferDrawBuffer_State"); + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferDrawBuffer_State"); if (!framebufferObject) return; if (buf == GL_NONE) { @@ -1992,11 +1976,10 @@ namespace MobileGL::MG_Impl::GLImpl { void NamedFramebufferReadBuffer_State(GLuint framebuffer, GLenum src) { auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferReadBuffer_State"); + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferReadBuffer_State"); if (!framebufferObject) return; - ReadBufferForFramebuffer_State(framebufferObject, framebuffer == 0, src, - "NamedFramebufferReadBuffer_State"); + ReadBufferForFramebuffer_State(framebufferObject, framebuffer == 0, src, "NamedFramebufferReadBuffer_State"); PipePublishFramebufferByName(framebufferObject); } @@ -2035,10 +2018,9 @@ namespace MobileGL::MG_Impl::GLImpl { default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("buffer {} is not accepted for glClearNamedFramebufferfv.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("buffer {} is not accepted for glClearNamedFramebufferfv.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } } @@ -2047,10 +2029,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (buffer != GL_DEPTH_STENCIL) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("buffer {} is not accepted for glClearNamedFramebufferfi.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("buffer {} is not accepted for glClearNamedFramebufferfi.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } if (drawbuffer != 0) { @@ -2070,7 +2051,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ClearNamedFramebufferfi_State(GLuint framebuffer, GLenum buffer, GLint drawbuffer, GLfloat depth, - GLint stencil) { + GLint stencil) { auto framebufferObject = GetFramebufferObjectForNamedClear(framebuffer, "ClearNamedFramebufferfi_State"); if (!framebufferObject) return; if (!ValidateNamedClearfi_State(buffer, drawbuffer, "ClearNamedFramebufferfi_State")) return; @@ -2176,11 +2157,10 @@ namespace MobileGL::MG_Impl::GLImpl { continue; default: MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("{} does not name a buffer of the default framebuffer.", - MG_Util::ConvertGLEnumToString(attachment)))); + ErrorCode::InvalidEnum, MakeUnique( + "MG_Impl/GLImpl", caller, + std::format("{} does not name a buffer of the default framebuffer.", + MG_Util::ConvertGLEnumToString(attachment)))); return false; } } @@ -2217,7 +2197,7 @@ namespace MobileGL::MG_Impl::GLImpl { } SharedPtr GetFramebufferObjectForInvalidate_State(GLenum target, - const char* caller) { + const char* caller) { const FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target == GL_FRAMEBUFFER ? GL_DRAW_FRAMEBUFFER : target); if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return nullptr; @@ -2232,16 +2212,14 @@ namespace MobileGL::MG_Impl::GLImpl { } void InvalidateNamedFramebufferData_State(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments) { - auto framebufferObject = - GetFramebufferObjectForNamedClear(framebuffer, "InvalidateNamedFramebufferData_State"); + auto framebufferObject = GetFramebufferObjectForNamedClear(framebuffer, "InvalidateNamedFramebufferData_State"); if (!framebufferObject) return; ValidateInvalidateAttachments_State(framebufferObject, numAttachments, attachments, "InvalidateNamedFramebufferData_State"); } - void InvalidateNamedFramebufferSubData_State(GLuint framebuffer, GLsizei numAttachments, - const GLenum* attachments, GLint x, GLint y, GLsizei width, - GLsizei height) { + void InvalidateNamedFramebufferSubData_State(GLuint framebuffer, GLsizei numAttachments, const GLenum* attachments, + GLint x, GLint y, GLsizei width, GLsizei height) { (void)x; (void)y; auto framebufferObject = @@ -2355,9 +2333,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (target != GL_FRAMEBUFFER && target != GL_DRAW_FRAMEBUFFER && target != GL_READ_FRAMEBUFFER) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", "CheckNamedFramebufferStatus_State", - std::format("target {} is not accepted.", - MG_Util::ConvertGLEnumToString(target)))); + MakeUnique( + "MG_Impl/GLImpl", "CheckNamedFramebufferStatus_State", + std::format("target {} is not accepted.", MG_Util::ConvertGLEnumToString(target)))); return GL_FRAMEBUFFER_UNDEFINED; } @@ -2502,8 +2480,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool depthStencilAlias = attachment == GL_DEPTH_STENCIL_ATTACHMENT; FramebufferAttachmentType attachmentType = depthStencilAlias - ? FramebufferAttachmentType::Depth - : MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); + ? FramebufferAttachmentType::Depth + : MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment); if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return; Bool depthStencilMismatch = false; @@ -2517,7 +2495,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Bool depthLive = depthAttachment.IsValid() && !depthAttachment.IsEmpty(); const Bool stencilLive = stencilAttachment.IsValid() && !stencilAttachment.IsEmpty(); if (depthLive && stencilLive) { - const Bool sameObject = depthAttachment.IsTexture() == stencilAttachment.IsTexture() && + const Bool sameObject = + depthAttachment.IsTexture() == stencilAttachment.IsTexture() && (!depthAttachment.IsTexture() || depthAttachment.GetTexture() == stencilAttachment.GetTexture()) && (!depthAttachment.IsRenderbuffer() || depthAttachment.GetRenderbuffer() == stencilAttachment.GetRenderbuffer()); @@ -2569,22 +2548,22 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid()) - ? static_cast(attachmentObject->GetTextureLevel()) - : 0; + ? static_cast(attachmentObject->GetTextureLevel()) + : 0; break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: *params = 0; break; case GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid()) - ? static_cast(attachmentObject->GetTextureLayer()) - : 0; + ? static_cast(attachmentObject->GetTextureLayer()) + : 0; break; case GL_FRAMEBUFFER_ATTACHMENT_LAYERED: *params = (attachmentObject != nullptr && attachmentObject->IsTexture() && attachmentObject->IsValid() && attachmentObject->IsLayered()) - ? GL_TRUE - : GL_FALSE; + ? GL_TRUE + : GL_FALSE; break; default: if (TryAnswerAttachmentFormatQuery(attachmentObject, attachmentType, depthStencilAlias, pname, params, @@ -2601,15 +2580,16 @@ namespace MobileGL::MG_Impl::GLImpl { } void GetNamedFramebufferAttachmentParameteriv_State(GLuint framebuffer, GLenum attachment, GLenum pname, - GLint* params) { + GLint* params) { // Zero names the default framebuffer here rather than being rejected: the DSA queries take // it in place of a binding (GL 4.6 core 9.2.3). - auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "GetNamedFramebufferAttachmentParameteriv_State"); + auto framebufferObject = + framebuffer == 0 + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "GetNamedFramebufferAttachmentParameteriv_State"); if (!framebufferObject) return; GetFramebufferAttachmentParameteriv_Object(framebufferObject, attachment, pname, params, - "GetNamedFramebufferAttachmentParameteriv_State"); + "GetNamedFramebufferAttachmentParameteriv_State"); } // The by-target forms resolve the binding; the by-name forms take zero as the default @@ -2645,30 +2625,30 @@ namespace MobileGL::MG_Impl::GLImpl { } void GetNamedFramebufferParameteriv_State(GLuint framebuffer, GLenum pname, GLint* params) { - auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "GetNamedFramebufferParameteriv_State"); + auto framebufferObject = + framebuffer == 0 ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "GetNamedFramebufferParameteriv_State"); if (!framebufferObject) return; GetFramebufferParameteriv_Object(framebufferObject, pname, params, "GetNamedFramebufferParameteriv_State"); } void NamedFramebufferParameteri_State(GLuint framebuffer, GLenum pname, GLint param) { auto framebufferObject = framebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferParameteri_State"); + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(framebuffer, "NamedFramebufferParameteri_State"); if (!framebufferObject) return; FramebufferParameteri_Object(framebufferObject, pname, param, "NamedFramebufferParameteri_State"); } void BlitNamedFramebuffer_State(GLuint readFramebuffer, GLuint drawFramebuffer, GLint srcX0, GLint srcY0, - GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, - GLbitfield mask, GLenum filter) { + GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter) { auto readObject = readFramebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(readFramebuffer, "BlitNamedFramebuffer_State"); + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(readFramebuffer, "BlitNamedFramebuffer_State"); auto drawObject = drawFramebuffer == 0 - ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO - : GetNamedFramebufferObject_State(drawFramebuffer, "BlitNamedFramebuffer_State"); + ? FramebufferImpl::pDefaultFramebufferInfo->defaultFBO + : GetNamedFramebufferObject_State(drawFramebuffer, "BlitNamedFramebuffer_State"); if (!readObject || !drawObject) return; BlitNamedFramebuffer_Backend(readObject, drawObject, srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, @@ -2716,9 +2696,9 @@ namespace MobileGL::MG_Impl::GLImpl { bindingSlot.Bind(framebufferObject); } - void GetRenderbufferParameterivForObject_State(const SharedPtr& - renderbufferObject, - GLenum pname, GLint* params, const char* caller) { + void GetRenderbufferParameterivForObject_State( + const SharedPtr& renderbufferObject, GLenum pname, GLint* params, + const char* caller) { if (!params) return; if (!renderbufferObject) { @@ -2774,7 +2754,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!FramebufferImpl::ValidateRenderbufferTarget(renderbufferTarget)) return; auto& bindingSlot = MG_State::pGLContext->GetRenderbufferBindingSlot(renderbufferTarget); auto& renderbufferObject = bindingSlot.GetBoundObject(); - GetRenderbufferParameterivForObject_State(renderbufferObject, pname, params, "GetRenderbufferParameteriv_State"); + GetRenderbufferParameterivForObject_State(renderbufferObject, pname, params, + "GetRenderbufferParameteriv_State"); } void GetNamedRenderbufferParameteriv_State(GLuint renderbuffer, GLenum pname, GLint* params) { @@ -2782,7 +2763,7 @@ namespace MobileGL::MG_Impl::GLImpl { GetNamedRenderbufferObject_State(renderbuffer, "GetNamedRenderbufferParameteriv_State"); if (!renderbufferObject) return; GetRenderbufferParameterivForObject_State(renderbufferObject, pname, params, - "GetNamedRenderbufferParameteriv_State"); + "GetNamedRenderbufferParameteriv_State"); } void ClearBufferfi_Backend(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { @@ -2839,10 +2820,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (buffer != GL_COLOR && buffer != GL_DEPTH) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", "ValidateClearBufferfv_State", - std::format("buffer {} is not accepted for glClearBufferfv.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", "ValidateClearBufferfv_State", + std::format("buffer {} is not accepted for glClearBufferfv.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } return ValidateClearBufferDrawbuffer_State(buffer, drawbuffer, "ValidateClearBufferfv_State"); @@ -2852,10 +2832,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (buffer != GL_COLOR && buffer != GL_STENCIL) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", "ValidateClearBufferiv_State", - std::format("buffer {} is not accepted for glClearBufferiv.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", "ValidateClearBufferiv_State", + std::format("buffer {} is not accepted for glClearBufferiv.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } return ValidateClearBufferDrawbuffer_State(buffer, drawbuffer, "ValidateClearBufferiv_State"); @@ -2865,10 +2844,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (buffer != GL_COLOR && buffer != GL_STENCIL) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", "ValidateClearBufferuiv_State", - std::format("buffer {} is not accepted for glClearBufferuiv.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", "ValidateClearBufferuiv_State", + std::format("buffer {} is not accepted for glClearBufferuiv.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } return ValidateClearBufferDrawbuffer_State(buffer, drawbuffer, "ValidateClearBufferuiv_State"); @@ -2878,10 +2856,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (buffer != GL_DEPTH_STENCIL) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", "ValidateClearBufferfi_State", - std::format("buffer {} is not accepted for glClearBufferfi.", - MG_Util::ConvertGLEnumToString(buffer)))); + MakeUnique("MG_Impl/GLImpl", "ValidateClearBufferfi_State", + std::format("buffer {} is not accepted for glClearBufferfi.", + MG_Util::ConvertGLEnumToString(buffer)))); return false; } return ValidateClearBufferDrawbuffer_State(buffer, drawbuffer, "ValidateClearBufferfi_State"); @@ -2973,10 +2950,9 @@ namespace MobileGL::MG_Impl::GLImpl { const FramebufferAttachmentType readBuffer = framebufferObject->GetReadBuffer(); if (readBuffer == FramebufferAttachmentType::None || !framebufferObject->GetAttachment(readBuffer).IsValid()) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", "ReadPixels_State", - "No color buffer for color format")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", "ReadPixels_State", + "No color buffer for color format")); return false; } @@ -3042,7 +3018,8 @@ namespace MobileGL::MG_Impl::GLImpl { // Check multisampling const FramebufferAttachmentType readBuffer = framebufferObject->GetReadBuffer(); - if (readBuffer != FramebufferAttachmentType::None && framebufferObject->GetAttachment(readBuffer).IsRenderbuffer()) { + if (readBuffer != FramebufferAttachmentType::None && + framebufferObject->GetAttachment(readBuffer).IsRenderbuffer()) { auto& rbo = framebufferObject->GetAttachment(readBuffer).GetRenderbuffer(); if (rbo && rbo->GetSamples() > 1) { MG_State::pGLContext->RecordError( @@ -3075,18 +3052,16 @@ namespace MobileGL::MG_Impl::GLImpl { // wide, and the skip parameters offset the first texel. The last row is not padded - nothing // follows it to align - which is what makes a tightly-sized destination legal. static SizeT ComputePackedReadSizeInBytes(GLsizei width, GLsizei height, GLenum format, GLenum type) { - const SizeT bytesPerPixel = - MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format), - MG_Util::ConvertGLEnumToTexturePixelDataType(type)); + const SizeT bytesPerPixel = MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format), + MG_Util::ConvertGLEnumToTexturePixelDataType(type)); if (bytesPerPixel == 0 || width <= 0 || height <= 0) return 0; const auto packParam = [](PixelStoreParam param) { return static_cast(std::max(0, MG_State::pGLContext->GetPixelStoreParam(param))); }; - const SizeT rowLengthInPixels = - packParam(PixelStoreParam::PackRowLength) != 0 - ? packParam(PixelStoreParam::PackRowLength) - : static_cast(width); + const SizeT rowLengthInPixels = packParam(PixelStoreParam::PackRowLength) != 0 + ? packParam(PixelStoreParam::PackRowLength) + : static_cast(width); const SizeT alignment = std::max(1, packParam(PixelStoreParam::PackAlignment)); const SizeT unalignedRowBytes = rowLengthInPixels * bytesPerPixel; @@ -3203,8 +3178,8 @@ namespace MobileGL::MG_Impl::GLImpl { NamedRenderbufferStorage_State(renderbuffer, internalformat, width, height); } - void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height) { + void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, + GLsizei height) { NamedRenderbufferStorageMultisample_State(renderbuffer, samples, internalformat, width, height); } @@ -3255,13 +3230,12 @@ namespace MobileGL::MG_Impl::GLImpl { NamedFramebufferTexture2D_State(framebuffer, attachment, textarget, texture, level); } - void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, - GLint level, GLint zoffset) { + void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, + GLint zoffset) { NamedFramebufferTexture3D_State(framebuffer, attachment, textarget, texture, level, zoffset); } - void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, - GLint layer) { + void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer) { NamedFramebufferTextureLayer_State(framebuffer, attachment, texture, level, layer); } @@ -3361,10 +3335,9 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } if (filter != GL_NEAREST && filter != GL_LINEAR) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", functionName, - "filter must be GL_NEAREST or GL_LINEAR.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", functionName, + "filter must be GL_NEAREST or GL_LINEAR.")); return false; } // Depth and stencil have no meaningful interpolation, so GL_LINEAR is rejected outright diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h index c91a496c..7dc6954d 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h @@ -28,8 +28,8 @@ namespace MobileGL::MG_Impl::GLImpl { void GenRenderbuffers(GLsizei n, GLuint* renderbuffers); void CreateRenderbuffers(GLsizei n, GLuint* renderbuffers); void NamedRenderbufferStorage(GLuint renderbuffer, GLenum internalformat, GLsizei width, GLsizei height); - void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat, - GLsizei width, GLsizei height); + void NamedRenderbufferStorageMultisample(GLuint renderbuffer, GLsizei samples, GLenum internalformat, GLsizei width, + GLsizei height); void GetNamedRenderbufferParameteriv(GLuint renderbuffer, GLenum pname, GLint* params); void FramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); void NamedFramebufferRenderbuffer(GLuint framebuffer, GLenum attachment, GLenum renderbuffertarget, @@ -48,8 +48,10 @@ namespace MobileGL::MG_Impl::GLImpl { void FramebufferTexture1D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); void FramebufferTexture(GLenum target, GLenum attachment, GLuint texture, GLint level); void NamedFramebufferTexture(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level); - void NamedFramebufferTexture1D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); - void NamedFramebufferTexture2D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + void NamedFramebufferTexture1D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); + void NamedFramebufferTexture2D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, + GLint level); void NamedFramebufferTexture3D(GLuint framebuffer, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint zoffset); void NamedFramebufferTextureLayer(GLuint framebuffer, GLenum attachment, GLuint texture, GLint level, GLint layer); diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp b/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp index fa05e439..a24e711f 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.cpp @@ -66,10 +66,10 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { const auto index = static_cast(attachment); if (index < first) return true; const auto colorIndex = index - first; - const auto limit = static_cast( - MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters() - .MaxColorAttachments - : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)); + const auto limit = + static_cast(MG_Backend::pActiveBackendObject + ? MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxColorAttachments + : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS)); if (colorIndex >= limit) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -140,10 +140,9 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { if (forSetter) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl/FramebufferImpl", caller, - std::format("pname {} is not settable on a framebuffer.", - MG_Util::ConvertGLEnumToString(pname)))); + MakeUnique("MG_Impl/GLImpl/FramebufferImpl", caller, + std::format("pname {} is not settable on a framebuffer.", + MG_Util::ConvertGLEnumToString(pname)))); return false; } break; @@ -152,8 +151,7 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { ErrorCode::InvalidEnum, MakeUnique( "MG_Impl/GLImpl/FramebufferImpl", caller, - std::format("pname {} is not a framebuffer parameter.", - MG_Util::ConvertGLEnumToString(pname)))); + std::format("pname {} is not a framebuffer parameter.", MG_Util::ConvertGLEnumToString(pname)))); return false; } @@ -162,10 +160,9 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { if (isDefaultFramebuffer && isDefaultParameter) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl/FramebufferImpl", caller, - std::format("pname {} does not apply to the default framebuffer.", - MG_Util::ConvertGLEnumToString(pname)))); + MakeUnique("MG_Impl/GLImpl/FramebufferImpl", caller, + std::format("pname {} does not apply to the default framebuffer.", + MG_Util::ConvertGLEnumToString(pname)))); return false; } return true; @@ -183,8 +180,7 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { } const FramebufferAttachmentType readBuffer = framebufferObject->GetReadBuffer(); - if (readBuffer == FramebufferAttachmentType::None || - !framebufferObject->GetAttachment(readBuffer).IsValid()) { + if (readBuffer == FramebufferAttachmentType::None || !framebufferObject->GetAttachment(readBuffer).IsValid()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl/FramebufferImpl", caller, @@ -201,8 +197,8 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { isMultisampled = readAttachment.GetRenderbuffer()->GetSamples() > 0; } else if (readAttachment.IsTexture() && readAttachment.GetTexture()) { const auto target = readAttachment.GetTexture()->GetTarget(); - isMultisampled = target == TextureTarget::Texture2DMultisample || - target == TextureTarget::Texture2DMultisampleArray; + isMultisampled = + target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray; } if (isMultisampled) { MG_State::pGLContext->RecordError( diff --git a/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.h b/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.h index d4215c57..513c0c79 100644 --- a/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.h +++ b/MobileGL/MG_Impl/GLImpl/Framebuffer/Validators.h @@ -29,6 +29,5 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl { // of the contract: a name outside the table is INVALID_ENUM, and only then is a name that the // DEFAULT framebuffer does not answer INVALID_OPERATION. Testing the framebuffer kind first // would turn GL_FRAMEBUFFER_DEFAULT_WIDTH on framebuffer zero into the wrong error. - Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter, - const char* caller); + Bool ValidateFramebufferParameterPname(GLenum pname, Bool isDefaultFramebuffer, Bool forSetter, const char* caller); } // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 71f0a68f..90fdfc02 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -130,8 +130,7 @@ namespace MobileGL::MG_Impl::GLImpl { kFrontendMaxFragmentUniformBlocks + kFrontendMaxComputeUniformBlocksShare; constexpr GLint kFrontendMaxVaryingComponents = static_cast(MG_Util::ShaderTranspiler::MAX_VARYING_COMPONENTS); - constexpr GLint kFrontendMaxVaryingVectors = - static_cast(MG_Util::ShaderTranspiler::MAX_VARYING_VECTORS); + constexpr GLint kFrontendMaxVaryingVectors = static_cast(MG_Util::ShaderTranspiler::MAX_VARYING_VECTORS); constexpr GLint kFrontendMaxProgramTexelOffset = 7; constexpr GLint kFrontendMinProgramTexelOffset = -8; constexpr GLint kFrontendMaxTransformFeedbackInterleavedComponents = 64; @@ -148,8 +147,7 @@ namespace MobileGL::MG_Impl::GLImpl { // the ceiling here. constexpr GLint kFrontendMinUniformBufferBindings = 84; constexpr GLint kFrontendSubpixelBits = 4; - constexpr GLint kFrontendMaxSamples = - static_cast(MG_Util::ShaderTranspiler::MIN_ADVERTISED_MAX_SAMPLES); + constexpr GLint kFrontendMaxSamples = static_cast(MG_Util::ShaderTranspiler::MIN_ADVERTISED_MAX_SAMPLES); // ARB_shader_subroutine's two limits. NOTHING IMPLEMENTS SUBROUTINES: there is no // glGetSubroutineIndex / glUniformSubroutinesuiv, only the program-interface enum // plumbing. These are answered - with the GL 4.5 core minimums - because the conformance @@ -315,7 +313,7 @@ namespace MobileGL::MG_Impl::GLImpl { : kBackendlessDefaults); } - GLint StageStorageBlockCount(Int MG_Backend::DynamicBackendParameters::*stageLimit) { + GLint StageStorageBlockCount(Int MG_Backend::DynamicBackendParameters::* stageLimit) { static const MG_Backend::DynamicBackendParameters kBackendlessDefaults{}; const MG_Backend::DynamicBackendParameters& parameters = MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters() @@ -367,10 +365,9 @@ namespace MobileGL::MG_Impl::GLImpl { void RecordIndexedOnlyGetterError(const char* functionName, GLenum pname) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", functionName, - "pname " + MG_Util::ConvertGLEnumToString(pname) + - " is only valid with indexed getter entrypoints.")); + MakeUnique("MG_Impl/GLImpl", functionName, + "pname " + MG_Util::ConvertGLEnumToString(pname) + + " is only valid with indexed getter entrypoints.")); } bool ValidateIndexedBufferQueryIndex(GLenum pname, GLuint index, const char* functionName, @@ -393,10 +390,10 @@ namespace MobileGL::MG_Impl::GLImpl { if (index >= GetIndexedBufferQueryPointCount(bufferTarget)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique( - "MG_Impl/GLImpl", functionName, - "index " + std::to_string(index) + " is out of range for indexed buffer query " + - std::to_string(pname) + ".")); + MakeUnique("MG_Impl/GLImpl", functionName, + "index " + std::to_string(index) + + " is out of range for indexed buffer query " + + std::to_string(pname) + ".")); return false; } @@ -408,21 +405,44 @@ namespace MobileGL::MG_Impl::GLImpl { // pname -> target decode, so it lives here instead of being spelled out twice. bool TryDecodeTextureUnitBindingPname(GLenum pname, TextureTarget& outTarget) { switch (pname) { - case GL_TEXTURE_BINDING_1D: outTarget = TextureTarget::Texture1D; return true; - case GL_TEXTURE_BINDING_1D_ARRAY: outTarget = TextureTarget::Texture1DArray; return true; - case GL_TEXTURE_BINDING_2D: outTarget = TextureTarget::Texture2D; return true; - case GL_TEXTURE_BINDING_2D_ARRAY: outTarget = TextureTarget::Texture2DArray; return true; - case GL_TEXTURE_BINDING_2D_MULTISAMPLE: outTarget = TextureTarget::Texture2DMultisample; return true; + case GL_TEXTURE_BINDING_1D: + outTarget = TextureTarget::Texture1D; + return true; + case GL_TEXTURE_BINDING_1D_ARRAY: + outTarget = TextureTarget::Texture1DArray; + return true; + case GL_TEXTURE_BINDING_2D: + outTarget = TextureTarget::Texture2D; + return true; + case GL_TEXTURE_BINDING_2D_ARRAY: + outTarget = TextureTarget::Texture2DArray; + return true; + case GL_TEXTURE_BINDING_2D_MULTISAMPLE: + outTarget = TextureTarget::Texture2DMultisample; + return true; case GL_TEXTURE_BINDING_2D_MULTISAMPLE_ARRAY: outTarget = TextureTarget::Texture2DMultisampleArray; return true; - case GL_TEXTURE_BINDING_3D: outTarget = TextureTarget::Texture3D; return true; - case GL_TEXTURE_BINDING_BUFFER: outTarget = TextureTarget::TextureBuffer; return true; - case GL_TEXTURE_BINDING_CUBE_MAP: outTarget = TextureTarget::TextureCubeMap; return true; - case GL_TEXTURE_BINDING_CUBE_MAP_ARRAY: outTarget = TextureTarget::TextureCubeMapArray; return true; - case GL_TEXTURE_BINDING_RECTANGLE: outTarget = TextureTarget::TextureRectangle; return true; - case GL_TEXTURE_BINDING_EXTERNAL_OES: outTarget = TextureTarget::External; return true; - default: return false; + case GL_TEXTURE_BINDING_3D: + outTarget = TextureTarget::Texture3D; + return true; + case GL_TEXTURE_BINDING_BUFFER: + outTarget = TextureTarget::TextureBuffer; + return true; + case GL_TEXTURE_BINDING_CUBE_MAP: + outTarget = TextureTarget::TextureCubeMap; + return true; + case GL_TEXTURE_BINDING_CUBE_MAP_ARRAY: + outTarget = TextureTarget::TextureCubeMapArray; + return true; + case GL_TEXTURE_BINDING_RECTANGLE: + outTarget = TextureTarget::TextureRectangle; + return true; + case GL_TEXTURE_BINDING_EXTERNAL_OES: + outTarget = TextureTarget::External; + return true; + default: + return false; } } @@ -539,7 +559,7 @@ namespace MobileGL::MG_Impl::GLImpl { // see the note on GetAdvertisedMaxSamples. A zero would be a probe that never ran, so it is // floored rather than trusted. namespace { - GLint AdvertisedCategoryMaxSamples(Int MG_Backend::DynamicBackendParameters::*categoryLimit) { + GLint AdvertisedCategoryMaxSamples(Int MG_Backend::DynamicBackendParameters::* categoryLimit) { if (MG_Backend::pActiveBackendObject == nullptr) { return 1; } @@ -562,8 +582,7 @@ namespace MobileGL::MG_Impl::GLImpl { // Declared in GL_Getter.h, so that the draw path can feed the same number to the reserved // gl_NumSamples stand-in that glGetIntegerv(GL_SAMPLES) reports. GLint ResolveDrawFramebufferSampleCount() { - const auto& drawFbo = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto& drawFbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); if (!drawFbo) return 0; GLint maxSamples = 0; @@ -585,8 +604,8 @@ namespace MobileGL::MG_Impl::GLImpl { // One list for glGetString and glGetStringi, so the two forms always agree. const Vector& ClientSideExtensions() { const Bool images = MG_Impl::EGLImpl::SharedImagesAvailable(); - const Bool esApi = MG_State::pEGLContext && - MG_State::pEGLContext->GetCurrentContextClientAPI() == EGL_OPENGL_ES_API; + const Bool esApi = + MG_State::pEGLContext && MG_State::pEGLContext->GetCurrentContextClientAPI() == EGL_OPENGL_ES_API; const Bool external = images && ClientSideExternalImageExtensionsAllowed() && MG_Impl::EGLImpl::SharedImageYuvAvailable(); return ClientSideExtensionList(images, external, EsIdentityActive(), esApi); @@ -689,14 +708,12 @@ namespace MobileGL::MG_Impl::GLImpl { return list; }; static const Vector lists[16] = { - build(false, false, false, false), build(false, false, false, true), - build(false, false, true, false), build(false, false, true, true), - build(false, true, false, false), build(false, true, false, true), - build(false, true, true, false), build(false, true, true, true), - build(true, false, false, false), build(true, false, false, true), - build(true, false, true, false), build(true, false, true, true), - build(true, true, false, false), build(true, true, false, true), - build(true, true, true, false), build(true, true, true, true)}; + build(false, false, false, false), build(false, false, false, true), build(false, false, true, false), + build(false, false, true, true), build(false, true, false, false), build(false, true, false, true), + build(false, true, true, false), build(false, true, true, true), build(true, false, false, false), + build(true, false, false, true), build(true, false, true, false), build(true, false, true, true), + build(true, true, false, false), build(true, true, false, true), build(true, true, true, false), + build(true, true, true, true)}; return lists[(images ? 8 : 0) + (external ? 4 : 0) + (es ? 2 : 0) + (esApi ? 1 : 0)]; } @@ -746,16 +763,15 @@ namespace MobileGL::MG_Impl::GLImpl { backendName.replace(at, sizeof("OpenGL ES") - 1, "GLES"); } if (EsIdentityActive()) { - versionStr = std::format("OpenGL ES 3.2 {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT, MG_Config::ProjectName, - MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib), - backendName); + versionStr = std::format( + "OpenGL ES 3.2 {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT, MG_Config::ProjectName, + MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib), backendName); return (const GLubyte*)versionStr.c_str(); } - versionStr = - std::format("{} {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT, - rendererInfo.RendererGLInfo.TargetGLVersion.toString(), MG_Config::ProjectName, - MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib), - backendName); + versionStr = std::format( + "{} {} {}, {} Backend, GIT@" GIT_COMMIT_HASH_SHORT, + rendererInfo.RendererGLInfo.TargetGLVersion.toString(), MG_Config::ProjectName, + MG_Config::CoreVersion.toFormattedString(MG_Config::DefaultVersionStringFormatAttrib), backendName); MGLOG_D("versionStr: %s", versionStr.c_str()); return (const GLubyte*)versionStr.c_str(); } @@ -768,9 +784,8 @@ namespace MobileGL::MG_Impl::GLImpl { } case GL_SHADING_LANGUAGE_VERSION: if (EsIdentityActive()) return (const GLubyte*)"OpenGL ES GLSL ES 3.20"; - shadingLanguageVersion = - std::format("{} {}", rendererInfo.RendererGLInfo.TargetGLSLVersion.toString({true, false}), - MG_Config::ProjectName); + shadingLanguageVersion = std::format( + "{} {}", rendererInfo.RendererGLInfo.TargetGLSLVersion.toString({true, false}), MG_Config::ProjectName); MGLOG_D("shadingLanguageVersion: %s", shadingLanguageVersion.c_str()); return (const GLubyte*)shadingLanguageVersion.c_str(); case GL_EXTENSIONS: @@ -796,7 +811,8 @@ namespace MobileGL::MG_Impl::GLImpl { // GL 4.6 core 22.2: an index at or past the list's GL_NUM_* count is INVALID_VALUE, and a // name glGetStringi does not index is INVALID_ENUM; both return NULL. const auto recordError = [](ErrorCode code, const char* message) -> const GLubyte* { - MG_State::pGLContext->RecordError(code, MakeUnique("MG_Impl/GLImpl", "GetStringi", message)); + MG_State::pGLContext->RecordError(code, + MakeUnique("MG_Impl/GLImpl", "GetStringi", message)); return nullptr; }; if (name == GL_SPIR_V_EXTENSIONS) { @@ -903,31 +919,25 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PolygonOffsetFill) ? GL_TRUE : GL_FALSE; return; case GL_PRIMITIVE_RESTART_FIXED_INDEX: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex) - ? GL_TRUE - : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex) ? GL_TRUE + : GL_FALSE; return; case GL_RASTERIZER_DISCARD: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::RasterizerDiscard) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_ALPHA_TO_COVERAGE: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToCoverage) - ? GL_TRUE - : GL_FALSE; + *params = + MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToCoverage) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_ALPHA_TO_ONE: - *params = - MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToOne) ? GL_TRUE : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToOne) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_COVERAGE: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleCoverage) - ? GL_TRUE - : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleCoverage) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_MASK: - *params = - MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleMask) ? GL_TRUE : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleMask) ? GL_TRUE : GL_FALSE; return; case GL_SCISSOR_TEST: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ScissorTest) ? GL_TRUE : GL_FALSE; @@ -1211,8 +1221,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } *data = target == GL_SAMPLER_BINDING - ? QuerySamplerBindingOnUnit(static_cast(index)) - : QueryTextureBindingOnUnit(static_cast(index), textureBindingTarget); + ? QuerySamplerBindingOnUnit(static_cast(index)) + : QueryTextureBindingOnUnit(static_cast(index), textureBindingTarget); return; } @@ -1282,9 +1292,9 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_IMAGE_BINDING_LAYER: case GL_IMAGE_BINDING_ACCESS: case GL_IMAGE_BINDING_FORMAT: { - const auto maxImageUnits = static_cast(std::min( - MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxImageUnits, - MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS)); + const auto maxImageUnits = static_cast( + std::min(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxImageUnits, + MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS)); if (index >= maxImageUnits) { *data = 0; MG_State::pGLContext->RecordError( @@ -1326,15 +1336,13 @@ namespace MobileGL::MG_Impl::GLImpl { if (index >= 3) { *data = 0; MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "Compute work group index is out of range.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, + "Compute work group index is out of range.")); return; } - const GLint minimum = target == GL_MAX_COMPUTE_WORK_GROUP_COUNT - ? GetMinComputeWorkGroupCount(index) - : GetMinComputeWorkGroupSize(index); + const GLint minimum = target == GL_MAX_COMPUTE_WORK_GROUP_COUNT ? GetMinComputeWorkGroupCount(index) + : GetMinComputeWorkGroupSize(index); GLint backendValue = 0; if (getIntegeri) { #if MOBILEGL_BUILD_DISAGGREGATED @@ -1343,7 +1351,7 @@ namespace MobileGL::MG_Impl::GLImpl { // the apply thread before every compute dispatch validation. if (MG_Config::Transport == MG_Config::TransportMode::Monolith) { #endif - MGP_FILL(GetIntegeri_v); + MGP_FILL(GetIntegeri_v); #if MOBILEGL_BUILD_DISAGGREGATED } #endif @@ -1357,7 +1365,8 @@ namespace MobileGL::MG_Impl::GLImpl { *data = 0; MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support indexed integer queries.")); + MakeUnique("MG_Impl/GLImpl", __func__, + "Backend does not support indexed integer queries.")); return; } getIntegeri(target, index, data); @@ -1939,7 +1948,8 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(MG_Util::ConvertCullFaceModeToGLEnum(MG_State::pGLContext->GetCullFaceMode())); return; case GL_FRONT_FACE: - *params = static_cast(MG_Util::ConvertFrontFaceModeToGLEnum(MG_State::pGLContext->GetFrontFaceMode())); + *params = + static_cast(MG_Util::ConvertFrontFaceModeToGLEnum(MG_State::pGLContext->GetFrontFaceMode())); return; case GL_CURRENT_PROGRAM: { const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); @@ -1968,9 +1978,8 @@ namespace MobileGL::MG_Impl::GLImpl { *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DebugOutput) ? GL_TRUE : GL_FALSE; return; case GL_DEBUG_OUTPUT_SYNCHRONOUS: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DebugOutputSynchronous) - ? GL_TRUE - : GL_FALSE; + *params = + MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DebugOutputSynchronous) ? GL_TRUE : GL_FALSE; return; case GL_DITHER: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::Dither) ? GL_TRUE : GL_FALSE; @@ -2001,8 +2010,8 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_DRAW_BUFFER13: case GL_DRAW_BUFFER14: case GL_DRAW_BUFFER15: - if (const auto& fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw) - .GetBoundObject()) { + if (const auto& fbo = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject()) { SizeT drawBufferIndex = 0; const bool decoded = TryDecodeDrawBufferQuery(pname, drawBufferIndex); MOBILEGL_ASSERT(decoded, "Draw buffer query enum should have been decoded already: 0x%X", pname); @@ -2340,7 +2349,8 @@ namespace MobileGL::MG_Impl::GLImpl { *params = MG_State::pGLContext->GetPixelStoreParam(PixelStoreParam::PackSwapBytes); return; case GL_PIXEL_PACK_BUFFER_BINDING: - if (const auto& obj = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject()) { + if (const auto& obj = + MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject()) { *params = static_cast(obj->GetExternalIndex()); } else { *params = 0; @@ -2366,8 +2376,7 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(MG_State::pGLContext->GetPointSpriteCoordOrigin()); return; case GL_PRIMITIVE_RESTART: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ? GL_TRUE - : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ? GL_TRUE : GL_FALSE; return; case GL_PRIMITIVE_RESTART_INDEX: *params = static_cast(MG_State::pGLContext->GetPrimitiveRestartIndex()); @@ -2422,17 +2431,16 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(MG_State::pGLContext->GetHint(pname)); return; case GL_READ_BUFFER: - if (const auto& fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read) - .GetBoundObject()) { - *params = - static_cast(MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(fbo->GetReadBuffer())); + if (const auto& fbo = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject()) { + *params = static_cast(MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(fbo->GetReadBuffer())); } else { *params = 0; } return; case GL_RENDERBUFFER_BINDING: - if (const auto& obj = - MG_State::pGLContext->GetRenderbufferBindingSlot(RenderbufferTarget::Renderbuffer).GetBoundObject()) { + if (const auto& obj = MG_State::pGLContext->GetRenderbufferBindingSlot(RenderbufferTarget::Renderbuffer) + .GetBoundObject()) { *params = static_cast(obj->GetExternalIndex()); } else { *params = 0; @@ -2452,12 +2460,10 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToCoverage) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_ALPHA_TO_ONE: - *params = - MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToOne) ? GL_TRUE : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleAlphaToOne) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_COVERAGE: - *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleCoverage) ? GL_TRUE - : GL_FALSE; + *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleCoverage) ? GL_TRUE : GL_FALSE; return; case GL_SAMPLE_MASK: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::SampleMask) ? GL_TRUE : GL_FALSE; @@ -2504,24 +2510,20 @@ namespace MobileGL::MG_Impl::GLImpl { RecordIndexedOnlyGetterError(__func__, pname); return; case GL_STENCIL_BACK_FAIL: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Back).FailOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Back).FailOp)); return; case GL_STENCIL_BACK_FUNC: *params = static_cast( - MG_Util::ConvertDepthTestFuncToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Back).Func)); + MG_Util::ConvertDepthTestFuncToGLEnum(MG_State::pGLContext->GetStencilState(StencilFace::Back).Func)); return; case GL_STENCIL_BACK_PASS_DEPTH_FAIL: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Back).PassDepthFailOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Back).PassDepthFailOp)); return; case GL_STENCIL_BACK_PASS_DEPTH_PASS: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Back).PassDepthPassOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Back).PassDepthPassOp)); return; case GL_STENCIL_BACK_REF: *params = MG_State::pGLContext->GetStencilState(StencilFace::Back).Ref; @@ -2536,24 +2538,20 @@ namespace MobileGL::MG_Impl::GLImpl { *params = static_cast(MG_State::pGLContext->GetClearStencil()); return; case GL_STENCIL_FAIL: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Front).FailOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Front).FailOp)); return; case GL_STENCIL_FUNC: *params = static_cast( - MG_Util::ConvertDepthTestFuncToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Front).Func)); + MG_Util::ConvertDepthTestFuncToGLEnum(MG_State::pGLContext->GetStencilState(StencilFace::Front).Func)); return; case GL_STENCIL_PASS_DEPTH_FAIL: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Front).PassDepthFailOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Front).PassDepthFailOp)); return; case GL_STENCIL_PASS_DEPTH_PASS: - *params = static_cast( - MG_Util::ConvertStencilOperationToGLEnum( - MG_State::pGLContext->GetStencilState(StencilFace::Front).PassDepthPassOp)); + *params = static_cast(MG_Util::ConvertStencilOperationToGLEnum( + MG_State::pGLContext->GetStencilState(StencilFace::Front).PassDepthPassOp)); return; case GL_STENCIL_REF: *params = MG_State::pGLContext->GetStencilState(StencilFace::Front).Ref; @@ -2767,8 +2765,8 @@ namespace MobileGL::MG_Impl::GLImpl { dynamicParameters.MaxUniformBlockSize); break; case GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS: - *params = std::max(dynamicParameters.MaxComputeWorkGroupInvocations, - kFrontendMaxComputeWorkGroupInvocations); + *params = + std::max(dynamicParameters.MaxComputeWorkGroupInvocations, kFrontendMaxComputeWorkGroupInvocations); break; case GL_MAX_COMPUTE_WORK_GROUP_COUNT: GetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, ¶ms[0]); @@ -2880,12 +2878,14 @@ namespace MobileGL::MG_Impl::GLImpl { // everything its own switch does not handle, does not report INVALID_ENUM for them. case GL_PATCH_DEFAULT_OUTER_LEVEL: { const FloatVec4& outer = MG_State::pGLContext->GetPatchDefaultOuterLevel(); - for (Uint i = 0; i < 4; ++i) params[i] = static_cast(std::lround(outer[i])); + for (Uint i = 0; i < 4; ++i) + params[i] = static_cast(std::lround(outer[i])); break; } case GL_PATCH_DEFAULT_INNER_LEVEL: { const FloatVec2& inner = MG_State::pGLContext->GetPatchDefaultInnerLevel(); - for (Uint i = 0; i < 2; ++i) params[i] = static_cast(std::lround(inner[i])); + for (Uint i = 0; i < 2; ++i) + params[i] = static_cast(std::lround(inner[i])); break; } // GL 4.6 core table 23.66: whether the primitive-restart index terminates a patch. @@ -2937,8 +2937,8 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_MAX_SHADER_STORAGE_BLOCK_SIZE: // 64-bit state (see GetInteger64v); the 32-bit query saturates, per the GL // state-query conversion rules. - *params = static_cast(std::min(dynamicParameters.MaxShaderStorageBlockSize, - static_cast(INT32_MAX))); + *params = static_cast( + std::min(dynamicParameters.MaxShaderStorageBlockSize, static_cast(INT32_MAX))); break; case GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS: // NOT the frontend's binding-point array size: GetIndexedBufferQueryPointCount diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index 96dd005d..14712a73 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -41,14 +41,13 @@ namespace MobileGL::MG_Impl::GLImpl { // entry point handed a program name reported INVALID_VALUE; the conformance suite // reads exactly that code back from glSpecializeShader. const ErrorCode error = (shader != 0 && MG_State::pGLContext->ValidateProgramName(shader)) - ? ErrorCode::InvalidOperation - : ErrorCode::InvalidValue; + ? ErrorCode::InvalidOperation + : ErrorCode::InvalidValue; MG_State::pGLContext->RecordError( - error, - MakeUnique("MG_Impl/GLImpl", __func__, - std::to_string(shader) + - (error == ErrorCode::InvalidOperation ? " is not a shader object." - : " is not a valid name."))); + error, MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(shader) + (error == ErrorCode::InvalidOperation + ? " is not a shader object." + : " is not a valid name."))); return false; } return true; @@ -74,15 +73,13 @@ namespace MobileGL::MG_Impl::GLImpl { // Programs and shaders share one name space: a name that exists but // belongs to a shader is INVALID_OPERATION, a name GL never handed // out is INVALID_VALUE (GL 3.3 core 2.11.x). - const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) - ? ErrorCode::InvalidOperation - : ErrorCode::InvalidValue; + const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) ? ErrorCode::InvalidOperation + : ErrorCode::InvalidValue; MG_State::pGLContext->RecordError( - error, - MakeUnique("MG_Impl/GLImpl", __func__, - std::to_string(program) + - (error == ErrorCode::InvalidOperation ? " is not a program object." - : " is not a valid name."))); + error, MakeUnique("MG_Impl/GLImpl", __func__, + std::to_string(program) + (error == ErrorCode::InvalidOperation + ? " is not a program object." + : " is not a valid name."))); return false; } return true; @@ -103,17 +100,15 @@ namespace MobileGL::MG_Impl::GLImpl { return programObject; } - static const SharedPtr& TryToGetLinkedProgramForInterfaceQuery(GLuint program, - const char* caller) { + static const SharedPtr& TryToGetLinkedProgramForInterfaceQuery( + GLuint program, const char* caller) { static const SharedPtr nullProgramObject = nullptr; if (!MG_State::pGLContext->ValidateProgramName(program)) { - const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) - ? ErrorCode::InvalidOperation - : ErrorCode::InvalidValue; + const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) ? ErrorCode::InvalidOperation + : ErrorCode::InvalidValue; MG_State::pGLContext->RecordError( - error, - MakeUnique("MG_Impl/GLImpl", caller, - std::to_string(program) + " is not a linked program object.")); + error, MakeUnique("MG_Impl/GLImpl", caller, + std::to_string(program) + " is not a linked program object.")); return nullProgramObject; } @@ -137,13 +132,11 @@ namespace MobileGL::MG_Impl::GLImpl { const char* caller) { static const SharedPtr nullProgramObject = nullptr; if (!MG_State::pGLContext->ValidateProgramName(program)) { - const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) - ? ErrorCode::InvalidOperation - : ErrorCode::InvalidValue; + const ErrorCode error = MG_State::pGLContext->ValidateShaderName(program) ? ErrorCode::InvalidOperation + : ErrorCode::InvalidValue; MG_State::pGLContext->RecordError( - error, - MakeUnique("MG_Impl/GLImpl", caller, - std::to_string(program) + " is not a program object.")); + error, MakeUnique("MG_Impl/GLImpl", caller, + std::to_string(program) + " is not a program object.")); return nullProgramObject; } auto& programObject = MG_State::pGLContext->GetProgramObject(program); @@ -189,8 +182,7 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_MAX_NUM_ACTIVE_VARIABLES: valid = programInterface == GL_UNIFORM_BLOCK || programInterface == GL_ATOMIC_COUNTER_BUFFER || - programInterface == GL_SHADER_STORAGE_BLOCK || - programInterface == GL_TRANSFORM_FEEDBACK_BUFFER; + programInterface == GL_SHADER_STORAGE_BLOCK || programInterface == GL_TRANSFORM_FEEDBACK_BUFFER; break; case GL_MAX_NUM_COMPATIBLE_SUBROUTINES: valid = IsSubroutineUniformInterface(programInterface); @@ -202,8 +194,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!valid) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", __func__, - "Unsupported program interface query.")); + MakeUnique("MG_Impl/GLImpl", __func__, "Unsupported program interface query.")); } return valid; } @@ -211,9 +202,8 @@ namespace MobileGL::MG_Impl::GLImpl { static bool ValidateNamedProgramResourceInterface(GLenum programInterface, const char* caller) { if (!ProgramInterface::IsNamedInterface(programInterface)) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", caller, - "Unsupported named program resource interface.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", caller, + "Unsupported named program resource interface.")); return false; } return true; @@ -253,8 +243,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (unit < 0 || unit >= limit) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", functionName, - "Opaque uniform unit is out of range.")); + MakeUnique("MG_Impl/GLImpl", functionName, "Opaque uniform unit is out of range.")); return false; } return true; @@ -298,8 +287,7 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "Shader storage block binding is out of range.")); + MakeUnique("MG_Impl/GLImpl", __func__, "Shader storage block binding is out of range.")); return false; } @@ -409,10 +397,9 @@ namespace MobileGL::MG_Impl::GLImpl { for (GLsizei i = 0; i < count; ++i) { if (!seen.insert(shaders[i]).second) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "shader " + std::to_string(shaders[i]) + - " appears more than once in `shaders`.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, + "shader " + std::to_string(shaders[i]) + + " appears more than once in `shaders`.")); return; } if (!MG_State::pGLContext->ValidateShaderName(shaders[i])) { @@ -433,8 +420,7 @@ namespace MobileGL::MG_Impl::GLImpl { // they reach SPIRV-Cross, which parses rather than validates. ARB_gl_spirv lets an // implementation reject an invalid module at either call; rejecting at the earlier one // means the application's error is reported next to the data that caused it. - if (const auto validated = MG_Util::ShaderTranspiler::ShaderCompiler::ValidateSpirvModule(module); - !validated) { + if (const auto validated = MG_Util::ShaderTranspiler::ShaderCompiler::ValidateSpirvModule(module); !validated) { MGLOG_D("%s: rejected SPIR-V module: %s", __func__, validated.error().log.c_str()); MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -514,8 +500,7 @@ namespace MobileGL::MG_Impl::GLImpl { auto specialized = MG_Util::ShaderTranspiler::ShaderCompiler::SpecializeAndDecompileSpirvModule( shaderObject->GetSpirvBinary(), shaderType, entryPoint, constantIds, constantValues, failure); if (!specialized) { - MGLOG_D("%s: specialization failed for shader %u: %s", __func__, shader, - specialized.error().log.c_str()); + MGLOG_D("%s: specialization failed for shader %u: %s", __func__, shader, specialized.error().log.c_str()); // The two conditions ARB_gl_spirv ENUMERATES are GL errors, and an erroring GL command // must have no other effect - so the shader object is left exactly as it was rather // than being pushed into a failed-compile state. Anything else is a genuine compile @@ -999,10 +984,18 @@ namespace MobileGL::MG_Impl::GLImpl { return; } switch (pname) { - case GL_GEOMETRY_VERTICES_OUT: *params = programObject->GetGeometryVerticesOut(); break; - case GL_GEOMETRY_INPUT_TYPE: *params = static_cast(programObject->GetGeometryInputType()); break; - case GL_GEOMETRY_OUTPUT_TYPE: *params = static_cast(programObject->GetGeometryOutputType()); break; - default: *params = programObject->GetGeometryShaderInvocations(); break; + case GL_GEOMETRY_VERTICES_OUT: + *params = programObject->GetGeometryVerticesOut(); + break; + case GL_GEOMETRY_INPUT_TYPE: + *params = static_cast(programObject->GetGeometryInputType()); + break; + case GL_GEOMETRY_OUTPUT_TYPE: + *params = static_cast(programObject->GetGeometryOutputType()); + break; + default: + *params = programObject->GetGeometryShaderInvocations(); + break; } MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); break; @@ -1035,12 +1028,18 @@ namespace MobileGL::MG_Impl::GLImpl { return; } switch (pname) { - case GL_TESS_GEN_MODE: *params = static_cast(programObject->GetTessGenMode()); break; - case GL_TESS_GEN_SPACING: *params = static_cast(programObject->GetTessGenSpacing()); break; + case GL_TESS_GEN_MODE: + *params = static_cast(programObject->GetTessGenMode()); + break; + case GL_TESS_GEN_SPACING: + *params = static_cast(programObject->GetTessGenSpacing()); + break; case GL_TESS_GEN_VERTEX_ORDER: *params = static_cast(programObject->GetTessGenVertexOrder()); break; - default: *params = programObject->GetTessGenPointMode() ? GL_TRUE : GL_FALSE; break; + default: + *params = programObject->GetTessGenPointMode() ? GL_TRUE : GL_FALSE; + break; } MGLOG_D("%s: %s = %d", __func__, MG_Util::ConvertGLEnumToString(pname).c_str(), *params); break; @@ -1254,8 +1253,8 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT span = UniformStorageSpanInBytes(ttype, size, nativeFloat64); if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || offset + span > programObject->GetUBOSize()) { - MGLOG_E_ONCE("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, - program, location); + MGLOG_E_ONCE("%s: uniform at program %u location %d has no backing storage; returning nothing", + __func__, program, location); return; } @@ -1310,7 +1309,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (pUBO == nullptr || offset == MG_State::GLState::ProgramObject::kInvalidUniformOffset || offset + span > programObject->GetUBOSize()) { MGLOG_E_ONCE("%s: uniform at program %u location %d has no backing storage; returning nothing", __func__, - program, location); + program, location); return; } @@ -1332,8 +1331,7 @@ namespace MobileGL::MG_Impl::GLImpl { // glUniform*d that stored it and not here. if (ttype.isDouble) { const Int columns = ttype.isMatrix ? ttype.matrixCols : 1; - const Int rows = ttype.isMatrix ? ttype.matrixRows - : (ttype.isVector ? ttype.vectorSize : 1); + const Int rows = ttype.isMatrix ? ttype.matrixRows : (ttype.isVector ? ttype.vectorSize : 1); // A non-matrix is one tightly packed run and never reaches the stride at all. const SizeT columnStride = MG_State::GLState::ProgramObject::UniformMatrixColumnStride(ttype, nativeFloat64); @@ -1346,8 +1344,7 @@ namespace MobileGL::MG_Impl::GLImpl { sizeof(GLdouble)); } else { GLfloat narrow = 0.0f; - Memcpy(&narrow, pUBO + offset + column * columnStride + row * componentSize, - sizeof(narrow)); + Memcpy(&narrow, pUBO + offset + column * columnStride + row * componentSize, sizeof(narrow)); component = static_cast(narrow); } if constexpr (std::is_integral_v) { @@ -1438,9 +1435,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetTransformFeedbackProgram().get() == programObject.get()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", __func__, - "The program used by active transform feedback cannot be relinked.")); + MakeUnique("MG_Impl/GLImpl", __func__, + "The program used by active transform feedback cannot be relinked.")); return; } @@ -1489,8 +1485,7 @@ namespace MobileGL::MG_Impl::GLImpl { // The program in use may not change while transform feedback is active - unless // the capture is paused, which is exactly what ARB_transform_feedback2 added the // pause for (GL 4.6 core 7.3). - if (MG_State::pGLContext->IsTransformFeedbackActive() && - !MG_State::pGLContext->IsTransformFeedbackPaused()) { + if (MG_State::pGLContext->IsTransformFeedbackActive() && !MG_State::pGLContext->IsTransformFeedbackPaused()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, @@ -1538,9 +1533,10 @@ namespace MobileGL::MG_Impl::GLImpl { SizeT writeSize = ItemCount * sizeof(T); if (size < writeSize) { // Metadata bug: degrade to a clamped copy instead of killing the process. - MGLOG_E_ONCE("%s: uniform size mismatch at program %u location %u: expected at least %zu bytes, got %zu " - "bytes; clamping", - __func__, programObject.GetExternalIndex(), location, ItemCount * sizeof(T), size); + MGLOG_E_ONCE( + "%s: uniform size mismatch at program %u location %u: expected at least %zu bytes, got %zu " + "bytes; clamping", + __func__, programObject.GetExternalIndex(), location, ItemCount * sizeof(T), size); writeSize = size; } // The uniform shadow's LAYOUT is phase-B data, so a write that lands while the @@ -1560,9 +1556,9 @@ namespace MobileGL::MG_Impl::GLImpl { // Should not happen: linking gives every settable uniform backing // storage. Log and drop the write instead of faulting. MGLOG_E_ONCE("%s: uniform at program %u location %u has no backing storage (ubo=%p offset=%u size=%zu " - "uboSize=%zu); dropping write", - __func__, programObject.GetExternalIndex(), location, static_cast(pUBO), offset, - writeSize, uboSize); + "uboSize=%zu); dropping write", + __func__, programObject.GetExternalIndex(), location, static_cast(pUBO), offset, + writeSize, uboSize); return; } MGLOG_D("%s: program = %d, location = %d, byteOffset = %d", __func__, programObject.GetExternalIndex(), @@ -1659,8 +1655,7 @@ namespace MobileGL::MG_Impl::GLImpl { break; } if (!programObject->IsValidUniformLocation(location + offset)) { - RecordInvalidUniformLocationError(__func__, location + offset, - "program " + std::to_string(program)); + RecordInvalidUniformLocationError(__func__, location + offset, "program " + std::to_string(program)); return; } Uniform_State(*programObject, location + offset, value + offset * ItemCount); @@ -1719,7 +1714,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } Vector narrowed(static_cast(count) * ItemCount); - for (SizeT i = 0; i < narrowed.size(); ++i) narrowed[i] = static_cast(value[i]); + for (SizeT i = 0; i < narrowed.size(); ++i) + narrowed[i] = static_cast(value[i]); Uniformv_State(location, count, narrowed.data()); } @@ -1734,7 +1730,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } Vector narrowed(static_cast(count) * ItemCount); - for (SizeT i = 0; i < narrowed.size(); ++i) narrowed[i] = static_cast(value[i]); + for (SizeT i = 0; i < narrowed.size(); ++i) + narrowed[i] = static_cast(value[i]); ProgramUniformv_State(program, location, count, narrowed.data()); } @@ -1776,9 +1773,15 @@ namespace MobileGL::MG_Impl::GLImpl { } const SizeT byteOffset = static_cast(c) * kColumnStride; switch (rows) { - case 2: Uniform_State<2>(programObject, location + matrix, column, byteOffset); break; - case 3: Uniform_State<3>(programObject, location + matrix, column, byteOffset); break; - default: Uniform_State<4>(programObject, location + matrix, column, byteOffset); break; + case 2: + Uniform_State<2>(programObject, location + matrix, column, byteOffset); + break; + case 3: + Uniform_State<3>(programObject, location + matrix, column, byteOffset); + break; + default: + Uniform_State<4>(programObject, location + matrix, column, byteOffset); + break; } } } @@ -1793,8 +1796,7 @@ namespace MobileGL::MG_Impl::GLImpl { // Minecraft calls thousands of times a frame for a case no shader pack ever takes. template void UniformMatrixdvNative_Object(Program& programObject, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value, Int columns, Int rows, - const String& ownerDescription) { + const GLdouble* value, Int columns, Int rows, const String& ownerDescription) { const SizeT columnStride = rows <= 2 ? 2 * sizeof(GLdouble) : 4 * sizeof(GLdouble); const SizeT componentCount = static_cast(columns) * static_cast(rows); GLdouble column[4] = {}; @@ -1818,9 +1820,15 @@ namespace MobileGL::MG_Impl::GLImpl { } const SizeT byteOffset = static_cast(c) * columnStride; switch (rows) { - case 2: Uniform_State<2>(programObject, location + matrix, column, byteOffset); break; - case 3: Uniform_State<3>(programObject, location + matrix, column, byteOffset); break; - default: Uniform_State<4>(programObject, location + matrix, column, byteOffset); break; + case 2: + Uniform_State<2>(programObject, location + matrix, column, byteOffset); + break; + case 3: + Uniform_State<3>(programObject, location + matrix, column, byteOffset); + break; + default: + Uniform_State<4>(programObject, location + matrix, column, byteOffset); + break; } } } @@ -1843,9 +1851,10 @@ namespace MobileGL::MG_Impl::GLImpl { } const SizeT componentCount = static_cast(columns) * static_cast(rows); Vector narrowed(static_cast(count) * componentCount); - for (SizeT i = 0; i < narrowed.size(); ++i) narrowed[i] = static_cast(value[i]); - UniformMatrixfv_Object(programObject, "glUniformMatrixdv", location, count, transpose, narrowed.data(), - columns, rows, "the current program object"); + for (SizeT i = 0; i < narrowed.size(); ++i) + narrowed[i] = static_cast(value[i]); + UniformMatrixfv_Object(programObject, "glUniformMatrixdv", location, count, transpose, narrowed.data(), columns, + rows, "the current program object"); } // Helper function to transpose a 2x2 matrix @@ -2267,13 +2276,11 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("%s: GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER = %d", __func__, *params); break; case GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER: - *params = - BoolToGLInt(programObject->IsUniformBlockReferencedByStage(blockIndex, EShLangTessControl)); + *params = BoolToGLInt(programObject->IsUniformBlockReferencedByStage(blockIndex, EShLangTessControl)); MGLOG_D("%s: GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_CONTROL_SHADER = %d", __func__, *params); break; case GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER: - *params = - BoolToGLInt(programObject->IsUniformBlockReferencedByStage(blockIndex, EShLangTessEvaluation)); + *params = BoolToGLInt(programObject->IsUniformBlockReferencedByStage(blockIndex, EShLangTessEvaluation)); MGLOG_D("%s: GL_UNIFORM_BLOCK_REFERENCED_BY_TESS_EVALUATION_SHADER = %d", __func__, *params); break; case GL_UNIFORM_BLOCK_REFERENCED_BY_GEOMETRY_SHADER: @@ -2367,8 +2374,7 @@ namespace MobileGL::MG_Impl::GLImpl { const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); // colorNumber is bounded by GL_MAX_DRAW_BUFFERS for index 0, and by // GL_MAX_DUAL_SOURCE_DRAW_BUFFERS (which MobileGL reports as 1) for index 1. - const GLuint colorNumberLimit = - (index == 0) ? static_cast(dynamicParameters.MaxDrawBuffers) : 1u; + const GLuint colorNumberLimit = (index == 0) ? static_cast(dynamicParameters.MaxDrawBuffers) : 1u; if (colorNumber >= colorNumberLimit) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -2771,7 +2777,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2797,7 +2803,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2823,7 +2829,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2849,7 +2855,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2875,7 +2881,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2901,7 +2907,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2927,7 +2933,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2953,7 +2959,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -2979,7 +2985,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, - const GLdouble* value) { + const GLdouble* value) { auto& programObject = TryToGetProgramObject(program); if (!programObject) return; if (!programObject->GetLinkStatus()) { @@ -3335,10 +3341,10 @@ namespace MobileGL::MG_Impl::GLImpl { return; } if (propCount <= 0 || bufSize < 0) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, - "propCount must be positive and bufSize " - "non-negative.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "propCount must be positive and bufSize " + "non-negative.")); return; } if (props == nullptr) return; @@ -3372,7 +3378,8 @@ namespace MobileGL::MG_Impl::GLImpl { } if (params == nullptr) return; const GLsizei written = static_cast(std::min(values.size(), static_cast(bufSize))); - for (GLsizei i = 0; i < written; ++i) params[i] = values[i]; + for (GLsizei i = 0; i < written; ++i) + params[i] = values[i]; if (length) *length = written; } @@ -3384,8 +3391,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!ProgramInterface::InterfaceHasLocations(programInterface)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "Program interface has no locations.")); + MakeUnique("MG_Impl/GLImpl", __func__, "Program interface has no locations.")); return -1; } return ProgramInterface::GetResourceLocation(*programObject, programInterface, name); @@ -3468,7 +3474,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (params == nullptr) return; // GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES is the only multi-value property // here, and the caller sized its array from _ACTIVE_ATOMIC_COUNTERS. - for (SizeT i = 0; i < values.size(); ++i) params[i] = values[i]; + for (SizeT i = 0; i < values.size(); ++i) + params[i] = values[i]; } // GL 4.6 §7.6.2: is an active shader storage block index of @@ -3482,8 +3489,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!programObject || !programObject->GetLinkStatus()) return; if (!ValidateShaderStorageBlockBinding(storageBlockBinding)) return; String blockName; - if (!ProgramInterface::GetResourceName(*programObject, GL_SHADER_STORAGE_BLOCK, storageBlockIndex, - blockName)) { + if (!ProgramInterface::GetResourceName(*programObject, GL_SHADER_STORAGE_BLOCK, storageBlockIndex, blockName)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, @@ -3689,9 +3695,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (varying == nullptr) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique( - "MG_Impl/GLImpl", __func__, - "index is not an active transform feedback varying of the program.")); + MakeUnique("MG_Impl/GLImpl", __func__, + "index is not an active transform feedback varying of the program.")); return; } if (size != nullptr) *size = varying->size; diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h index 461745ee..ebd85a3b 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h @@ -146,8 +146,8 @@ namespace MobileGL::MG_Impl::GLImpl { GLint GetFragDataIndex(GLuint program, const char* name); void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params); GLuint GetProgramResourceIndex(GLuint program, GLenum programInterface, const GLchar* name); - void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, - GLsizei* length, GLchar* name); + void GetProgramResourceName(GLuint program, GLenum programInterface, GLuint index, GLsizei bufSize, GLsizei* length, + GLchar* name); void GetProgramResourceiv(GLuint program, GLenum programInterface, GLuint index, GLsizei propCount, const GLenum* props, GLsizei bufSize, GLsizei* length, GLint* params); GLint GetProgramResourceLocation(GLuint program, GLenum programInterface, const GLchar* name); @@ -171,23 +171,32 @@ namespace MobileGL::MG_Impl::GLImpl { void ProgramUniform4d(GLuint program, GLint location, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3); void ProgramUniform4dv(GLuint program, GLint location, GLsizei count, const GLdouble* value); void UniformMatrix2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix2x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix2x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix2x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix2x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix3x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix3x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix3x4dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix3x4dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix4x2dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix4x2dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void UniformMatrix4x3dv(GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); - void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLdouble* value); + void ProgramUniformMatrix4x3dv(GLuint program, GLint location, GLsizei count, GLboolean transpose, + const GLdouble* value); void GetUniformdv(GLuint program, GLint location, GLdouble* params); void ValidateProgram(GLuint program); void ProgramParameteri(GLuint program, GLenum pname, GLint value); diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp index 3e7fe3e0..4723fd90 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ProgramPipeline.cpp @@ -15,8 +15,8 @@ namespace MobileGL::MG_Impl::GLImpl { namespace { void RecordPipelineError(ErrorCode code, const char* function, String message) { - MG_State::pGLContext->RecordError( - code, MakeUnique("MG_Impl/GLImpl", function, Move(message))); + MG_State::pGLContext->RecordError(code, + MakeUnique("MG_Impl/GLImpl", function, Move(message))); } // GL 4.6 core 7.4 asks only that the name came from GenProgramPipelines and has not been @@ -31,7 +31,7 @@ namespace MobileGL::MG_Impl::GLImpl { // rejected calls' error was left in the queue for the harness to find. One cause, both // symptoms. const SharedPtr* TryGetPipeline(GLuint pipeline, - const char* function) { + const char* function) { const auto& object = MG_State::pGLContext->MaterializeProgramPipelineObject(pipeline); if (!object) { RecordPipelineError(ErrorCode::InvalidOperation, function, @@ -52,19 +52,32 @@ namespace MobileGL::MG_Impl::GLImpl { // GL 4.6 core table 7.1 maps each stage bit onto a shader stage. Bool TryResolveStageBit(GLbitfield bit, ShaderStage& outStage) { switch (bit) { - case GL_VERTEX_SHADER_BIT: outStage = ShaderStage::Vertex; return true; - case GL_TESS_CONTROL_SHADER_BIT: outStage = ShaderStage::TessControl; return true; - case GL_TESS_EVALUATION_SHADER_BIT: outStage = ShaderStage::TessEval; return true; - case GL_GEOMETRY_SHADER_BIT: outStage = ShaderStage::Geometry; return true; - case GL_FRAGMENT_SHADER_BIT: outStage = ShaderStage::Fragment; return true; - case GL_COMPUTE_SHADER_BIT: outStage = ShaderStage::Compute; return true; - default: return false; + case GL_VERTEX_SHADER_BIT: + outStage = ShaderStage::Vertex; + return true; + case GL_TESS_CONTROL_SHADER_BIT: + outStage = ShaderStage::TessControl; + return true; + case GL_TESS_EVALUATION_SHADER_BIT: + outStage = ShaderStage::TessEval; + return true; + case GL_GEOMETRY_SHADER_BIT: + outStage = ShaderStage::Geometry; + return true; + case GL_FRAGMENT_SHADER_BIT: + outStage = ShaderStage::Fragment; + return true; + case GL_COMPUTE_SHADER_BIT: + outStage = ShaderStage::Compute; + return true; + default: + return false; } } constexpr GLbitfield kAllStageBits = GL_VERTEX_SHADER_BIT | GL_TESS_CONTROL_SHADER_BIT | - GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | - GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT; + GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | + GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT; } // namespace void GenProgramPipelines(GLsizei n, GLuint* pipelines) { @@ -126,13 +139,27 @@ namespace MobileGL::MG_Impl::GLImpl { *params = active ? static_cast(active->GetExternalIndex()) : 0; break; } - case GL_VERTEX_SHADER: *params = stageProgramName(ShaderStage::Vertex); break; - case GL_TESS_CONTROL_SHADER: *params = stageProgramName(ShaderStage::TessControl); break; - case GL_TESS_EVALUATION_SHADER: *params = stageProgramName(ShaderStage::TessEval); break; - case GL_GEOMETRY_SHADER: *params = stageProgramName(ShaderStage::Geometry); break; - case GL_FRAGMENT_SHADER: *params = stageProgramName(ShaderStage::Fragment); break; - case GL_COMPUTE_SHADER: *params = stageProgramName(ShaderStage::Compute); break; - case GL_VALIDATE_STATUS: *params = (*pipelineObject)->GetValidateStatus() ? GL_TRUE : GL_FALSE; break; + case GL_VERTEX_SHADER: + *params = stageProgramName(ShaderStage::Vertex); + break; + case GL_TESS_CONTROL_SHADER: + *params = stageProgramName(ShaderStage::TessControl); + break; + case GL_TESS_EVALUATION_SHADER: + *params = stageProgramName(ShaderStage::TessEval); + break; + case GL_GEOMETRY_SHADER: + *params = stageProgramName(ShaderStage::Geometry); + break; + case GL_FRAGMENT_SHADER: + *params = stageProgramName(ShaderStage::Fragment); + break; + case GL_COMPUTE_SHADER: + *params = stageProgramName(ShaderStage::Compute); + break; + case GL_VALIDATE_STATUS: + *params = (*pipelineObject)->GetValidateStatus() ? GL_TRUE : GL_FALSE; + break; case GL_INFO_LOG_LENGTH: { // GL counts the null terminator, and reports 0 rather than 1 for an empty log. const auto& log = (*pipelineObject)->GetInfoLog(); @@ -140,9 +167,9 @@ namespace MobileGL::MG_Impl::GLImpl { break; } default: - RecordPipelineError(ErrorCode::InvalidEnum, __func__, - std::format("pname {} is not a program pipeline parameter.", - MG_Util::ConvertGLEnumToString(pname))); + RecordPipelineError( + ErrorCode::InvalidEnum, __func__, + std::format("pname {} is not a program pipeline parameter.", MG_Util::ConvertGLEnumToString(pname))); break; } } diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp index 742d9e20..4698f91f 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_ShaderObjectsARB.cpp @@ -32,7 +32,11 @@ namespace MobileGL::MG_Impl::GLImpl { constexpr GLenum kObjectTypeArb = 0x8B4E; constexpr GLenum kObjectSubtypeArb = 0x8B4F; - enum class ObjectKind { None, Program, Shader }; + enum class ObjectKind { + None, + Program, + Shader + }; ObjectKind KindOf(GLuint obj) { if (obj == 0) return ObjectKind::None; @@ -52,9 +56,14 @@ namespace MobileGL::MG_Impl::GLImpl { void DeleteObjectARB(GLuint obj) { if (obj == 0) return; switch (KindOf(obj)) { - case ObjectKind::Program: DeleteProgram(obj); return; - case ObjectKind::Shader: DeleteShader(obj); return; - case ObjectKind::None: break; + case ObjectKind::Program: + DeleteProgram(obj); + return; + case ObjectKind::Shader: + DeleteShader(obj); + return; + case ObjectKind::None: + break; } Fail(ErrorCode::InvalidValue, __func__, std::to_string(obj) + " is not a shader or program object."); } @@ -81,18 +90,26 @@ namespace MobileGL::MG_Impl::GLImpl { switch (pname) { case kObjectTypeArb: case GL_DELETE_STATUS: - case GL_INFO_LOG_LENGTH: programParam = shaderParam = true; break; + case GL_INFO_LOG_LENGTH: + programParam = shaderParam = true; + break; case kObjectSubtypeArb: case GL_COMPILE_STATUS: - case GL_SHADER_SOURCE_LENGTH: shaderParam = true; break; + case GL_SHADER_SOURCE_LENGTH: + shaderParam = true; + break; case GL_LINK_STATUS: case GL_VALIDATE_STATUS: case GL_ATTACHED_SHADERS: case GL_ACTIVE_UNIFORMS: case GL_ACTIVE_UNIFORM_MAX_LENGTH: case GL_ACTIVE_ATTRIBUTES: - case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: programParam = true; break; - default: Fail(ErrorCode::InvalidEnum, func, "pname is not a GetObjectParameterARB parameter."); return false; + case GL_ACTIVE_ATTRIBUTE_MAX_LENGTH: + programParam = true; + break; + default: + Fail(ErrorCode::InvalidEnum, func, "pname is not a GetObjectParameterARB parameter."); + return false; } const ObjectKind kind = KindOf(obj); if (kind == ObjectKind::None) { @@ -123,14 +140,20 @@ namespace MobileGL::MG_Impl::GLImpl { void GetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params) { GLint value = 0; - if (QueryObjectParameter(obj, pname, &value, __func__) && params != nullptr) *params = static_cast(value); + if (QueryObjectParameter(obj, pname, &value, __func__) && params != nullptr) + *params = static_cast(value); } void GetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, GLchar* infoLog) { switch (KindOf(obj)) { - case ObjectKind::Program: GetProgramInfoLog(obj, maxLength, length, infoLog); return; - case ObjectKind::Shader: GetShaderInfoLog(obj, maxLength, length, infoLog); return; - case ObjectKind::None: break; + case ObjectKind::Program: + GetProgramInfoLog(obj, maxLength, length, infoLog); + return; + case ObjectKind::Shader: + GetShaderInfoLog(obj, maxLength, length, infoLog); + return; + case ObjectKind::None: + break; } Fail(ErrorCode::InvalidValue, __func__, std::to_string(obj) + " is not a shader or program object."); } diff --git a/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.cpp b/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.cpp index 3ecf1e5f..5059de17 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.cpp @@ -99,8 +99,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // "[0]" the enumeration appends to an array. Bool NamesMatch(const String& resourceName, const String& query) { if (resourceName == query) return true; - if (EndsWithZeroSubscript(resourceName) && - resourceName.compare(0, resourceName.length() - 3, query) == 0) { + if (EndsWithZeroSubscript(resourceName) && resourceName.compare(0, resourceName.length() - 3, query) == 0) { return true; } return EndsWithZeroSubscript(query) && query.compare(0, query.length() - 3, resourceName) == 0; @@ -189,7 +188,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { const bool rowMajor = type.layoutMatrix == static_cast(glslang::ElmRowMajor); const int strideVectorComponents = rowMajor ? type.matrixCols : type.matrixRows; constexpr int scalarSize = 4; - const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize + const int vectorAlignment = (strideVectorComponents <= 1) ? scalarSize : (strideVectorComponents == 2) ? 2 * scalarSize : 4 * scalarSize; return (vectorAlignment + 15) & ~15; @@ -225,8 +224,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // Note the union is used even when it is empty: an array element nobody dereferenced has // no member bits and is genuinely referenced by nobody, which is the whole point - falling // back to the block's own mask there would restore the over-approximation. - Vector BuildBlockStagesFromMembers(const ProgramObject::LinkArtifacts& reflection, - Int blockCount) { + Vector BuildBlockStagesFromMembers(const ProgramObject::LinkArtifacts& reflection, Int blockCount) { Vector stagesByBlock(static_cast(blockCount < 0 ? 0 : blockCount), 0u); const Int uniformCount = static_cast(reflection.uniformReflection.size()); for (Int index = 0; index < uniformCount; ++index) { @@ -313,20 +311,19 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { const Int blockIndex = program.BlockIndexFromGlUniformBlock(static_cast(glIndex)); Resource resource; resource.name = program.GetUniformBlockName(static_cast(blockIndex)); - resource.bufferBinding = static_cast(program.GetUniformBlockBinding(static_cast(blockIndex))); + resource.bufferBinding = + static_cast(program.GetUniformBlockBinding(static_cast(blockIndex))); resource.bufferDataSize = static_cast(program.GetUBOSizeAt(static_cast(blockIndex))); const Int tIndex = program.TProgramBlockIndex(static_cast(blockIndex)); if (tIndex >= 0 && tIndex < blockCount) { - resource.stages = UniformBlockStages(reflection.blockReflection[tIndex], - stagesFromMembers, tIndex); + resource.stages = UniformBlockStages(reflection.blockReflection[tIndex], stagesFromMembers, tIndex); } model.uniformBlocks.push_back(Move(resource)); } } - void BuildUniformsAndBufferVariables(ProgramObject& program, - const ProgramObject::LinkArtifacts& reflection, Model& model, - const Vector& blockKind, + void BuildUniformsAndBufferVariables(ProgramObject& program, const ProgramObject::LinkArtifacts& reflection, + Model& model, const Vector& blockKind, const Vector& blockInterfaceIndex) { // Walks the TPROGRAM uniform space, not the GL one. A buffer variable is not a GL // uniform (GL 4.6 core 7.3.1) and DoReflection therefore keeps it out of the GL @@ -388,8 +385,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // A member of a named uniform block has no location, whatever the // frontend's own location table says (it hands one out to every uniform // so glUniform* can address block members through the global UBO). - resource.location = - resource.blockIndex >= 0 ? -1 : program.GetUniformLocation(refl.name); + resource.location = resource.blockIndex >= 0 ? -1 : program.GetUniformLocation(refl.name); } model.uniforms.push_back(Move(resource)); } @@ -432,7 +428,9 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // program that redeclares `out gl_PerVertex { vec4 gl_Position; }` still carries // gl_PointSize and gl_ClipDistance through the block-unwrapping reflection, and they // are not part of its output interface. - Bool IsHiddenBlockMember(const ProgramObject::TypeFacts& type) { return type.isVoid; } + Bool IsHiddenBlockMember(const ProgramObject::TypeFacts& type) { + return type.isVoid; + } void BuildStageIO(ProgramObject& program, const ProgramObject::LinkArtifacts& reflection, Model& model) { const Int inputCount = static_cast(reflection.pipeInputReflection.size()); @@ -663,12 +661,12 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { programInterface == GL_VERTEX_SUBROUTINE || programInterface == GL_TESS_CONTROL_SUBROUTINE || programInterface == GL_TESS_EVALUATION_SUBROUTINE || programInterface == GL_GEOMETRY_SUBROUTINE || programInterface == GL_FRAGMENT_SUBROUTINE || programInterface == GL_COMPUTE_SUBROUTINE; - const Bool isSubroutineUniform = - programInterface == GL_VERTEX_SUBROUTINE_UNIFORM || - programInterface == GL_TESS_CONTROL_SUBROUTINE_UNIFORM || - programInterface == GL_TESS_EVALUATION_SUBROUTINE_UNIFORM || - programInterface == GL_GEOMETRY_SUBROUTINE_UNIFORM || - programInterface == GL_FRAGMENT_SUBROUTINE_UNIFORM || programInterface == GL_COMPUTE_SUBROUTINE_UNIFORM; + const Bool isSubroutineUniform = programInterface == GL_VERTEX_SUBROUTINE_UNIFORM || + programInterface == GL_TESS_CONTROL_SUBROUTINE_UNIFORM || + programInterface == GL_TESS_EVALUATION_SUBROUTINE_UNIFORM || + programInterface == GL_GEOMETRY_SUBROUTINE_UNIFORM || + programInterface == GL_FRAGMENT_SUBROUTINE_UNIFORM || + programInterface == GL_COMPUTE_SUBROUTINE_UNIFORM; switch (prop) { case GL_NAME_LENGTH: @@ -677,8 +675,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { case GL_ARRAY_SIZE: return programInterface == GL_UNIFORM || programInterface == GL_PROGRAM_INPUT || programInterface == GL_PROGRAM_OUTPUT || programInterface == GL_BUFFER_VARIABLE || - programInterface == GL_TRANSFORM_FEEDBACK_VARYING || - (prop == GL_ARRAY_SIZE && isSubroutineUniform); + programInterface == GL_TRANSFORM_FEEDBACK_VARYING || (prop == GL_ARRAY_SIZE && isSubroutineUniform); case GL_OFFSET: return programInterface == GL_UNIFORM || programInterface == GL_BUFFER_VARIABLE || programInterface == GL_TRANSFORM_FEEDBACK_VARYING; @@ -697,8 +694,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // ends in an error - but INVALID_VALUE for the out-of-range index, not the // INVALID_OPERATION a narrower table would invent. return programInterface == GL_UNIFORM_BLOCK || programInterface == GL_ATOMIC_COUNTER_BUFFER || - programInterface == GL_SHADER_STORAGE_BLOCK || - programInterface == GL_TRANSFORM_FEEDBACK_BUFFER; + programInterface == GL_SHADER_STORAGE_BLOCK || programInterface == GL_TRANSFORM_FEEDBACK_BUFFER; case GL_BUFFER_DATA_SIZE: return programInterface == GL_UNIFORM_BLOCK || programInterface == GL_ATOMIC_COUNTER_BUFFER || programInterface == GL_SHADER_STORAGE_BLOCK; @@ -767,8 +763,7 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { // The layout controls of an interleaved capture are enumerable but not addressable // by name (GL 4.6 §7.3.1.1). if (programInterface == GL_TRANSFORM_FEEDBACK_VARYING && - (query == "gl_NextBuffer" || - (query.size() == 18 && query.compare(0, 17, "gl_SkipComponents") == 0))) { + (query == "gl_NextBuffer" || (query.size() == 18 && query.compare(0, 17, "gl_SkipComponents") == 0))) { return GL_INVALID_INDEX; } for (SizeT i = 0; i < resources.size(); ++i) { @@ -834,7 +829,8 @@ namespace MobileGL::MG_Impl::GLImpl::ProgramInterface { outValues.push_back(static_cast(resource.activeVariables.size())); break; case GL_ACTIVE_VARIABLES: - for (const GLuint variable : resource.activeVariables) outValues.push_back(static_cast(variable)); + for (const GLuint variable : resource.activeVariables) + outValues.push_back(static_cast(variable)); break; case GL_REFERENCED_BY_VERTEX_SHADER: outValues.push_back(referencedBy(EShLangVertex)); diff --git a/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.h b/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.h index 30005266..a107aeda 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.h +++ b/MobileGL/MG_Impl/GLImpl/Program/ProgramInterface.h @@ -11,7 +11,7 @@ namespace MobileGL::MG_State::GLState { class ProgramObject; -} +} // namespace MobileGL::MG_State::GLState // The GL program interface (ARB_program_interface_query / GL 4.3 §7.3.1) as a frontend // resource model. diff --git a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp index 68a6fbed..8a0a366c 100644 --- a/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp +++ b/MobileGL/MG_Impl/GLImpl/Query/GL_Query.cpp @@ -126,8 +126,7 @@ namespace MobileGL::MG_Impl::GLImpl { } void RecordQueryError(ErrorCode code, const char* function, const char* message) { - MG_State::pGLContext->RecordError(code, - MakeUnique("MG_Impl/GLImpl", function, message)); + MG_State::pGLContext->RecordError(code, MakeUnique("MG_Impl/GLImpl", function, message)); } // The by-buffer query getters write the result into a buffer object instead of client @@ -197,9 +196,8 @@ namespace MobileGL::MG_Impl::GLImpl { // stage assembled - a paused span included - for GL_PRIMITIVES_GENERATED. One counter // for both targets would report the clamped written count as the generated one. Uint64 TransformFeedbackCounterForTarget(GLenum target) { - return target == GL_PRIMITIVES_GENERATED - ? MG_State::pGLContext->GetTransformFeedbackGeneratedCounter() - : MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter(); + return target == GL_PRIMITIVES_GENERATED ? MG_State::pGLContext->GetTransformFeedbackGeneratedCounter() + : MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter(); } // The span's CPU accounting delta. Saturating: a snapshot left above its counter (a @@ -308,8 +306,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (queryObject->backendHandle) { const auto getQueryResult64 = MG_Backend::gBackendFunctionsTable.GL.GetQueryResult64; MGP_FILL(GetQueryResult64); - if (getQueryResult64 && - !getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) { + if (getQueryResult64 && !getQueryResult64(queryObject->backendHandle, /*wait=*/true, &result)) { // The backend could not produce the result YET (e.g. a // Vulkan wait refusing to block on a not-yet-submitted // frame serial). Per the documented no-stall tradeoff @@ -479,13 +476,11 @@ namespace MobileGL::MG_Impl::GLImpl { void BeginQuery(GLenum target, GLuint id) { const Bool isTransformFeedbackQuery = target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; - const Bool isOcclusionQuery = - (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || - target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && - MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery); + const Bool isOcclusionQuery = (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || + target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && + MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery); const Bool isPipelineStatisticsQuery = IsPipelineStatisticsQueryTarget(target); - if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery && - !isPipelineStatisticsQuery) { + if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery && !isPipelineStatisticsQuery) { // GL_TIMESTAMP is not a valid BeginQuery target; the occlusion targets // need backend support. RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); @@ -501,15 +496,15 @@ namespace MobileGL::MG_Impl::GLImpl { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "Query object does not exist."); return; } - GLuint& activeQueryId = isPipelineStatisticsQuery - ? g_activePipelineStatisticsQueryIds[target] - : (isTransformFeedbackQuery - ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId - : g_activePrimitivesGeneratedQueryId) - : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId)); + GLuint& activeQueryId = + isPipelineStatisticsQuery + ? g_activePipelineStatisticsQueryIds[target] + : (isTransformFeedbackQuery + ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId + : g_activePrimitivesGeneratedQueryId) + : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId)); if (activeQueryId != 0) { - RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, - "A query is already active on this target."); + RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "A query is already active on this target."); return; } if (queryObject->active) { @@ -541,14 +536,12 @@ namespace MobileGL::MG_Impl::GLImpl { queryObject->counterSnapshot = TransformFeedbackCounterForTarget(target); queryObject->accountedCaptureDrawSnapshot = MG_State::pGLContext->GetTransformFeedbackAccountedCaptureDraws(); - queryObject->geometryCaptureDrawSnapshot = - MG_State::pGLContext->GetTransformFeedbackGeometryCaptureDraws(); + queryObject->geometryCaptureDrawSnapshot = MG_State::pGLContext->GetTransformFeedbackGeometryCaptureDraws(); } else if (isOcclusionQuery) { MGP_FILL(BeginOcclusionQuery); queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery(); } else { - const Bool timerQuerySupported = - MGL_BACKEND_SLOT_CAP(BeginTimeElapsedQuery, MG_Pipe::kCapTimerQuery); + const Bool timerQuerySupported = MGL_BACKEND_SLOT_CAP(BeginTimeElapsedQuery, MG_Pipe::kCapTimerQuery); const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery; MGP_FILL(BeginTimeElapsedQuery); queryObject->backendHandle = @@ -560,23 +553,22 @@ namespace MobileGL::MG_Impl::GLImpl { void EndQuery(GLenum target) { const Bool isTransformFeedbackQuery = target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED; - const Bool isOcclusionQuery = - (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || - target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && - MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery); + const Bool isOcclusionQuery = (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED || + target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) && + MGL_BACKEND_SLOT_CAP(BeginOcclusionQuery, MG_Pipe::kCapOcclusionQuery); const Bool isPipelineStatisticsQuery = IsPipelineStatisticsQueryTarget(target); - if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery && - !isPipelineStatisticsQuery) { + if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery && !isPipelineStatisticsQuery) { RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported."); return; } const std::lock_guard lock(g_queryObjectsMutex); - GLuint& activeQueryId = isPipelineStatisticsQuery - ? g_activePipelineStatisticsQueryIds[target] - : (isTransformFeedbackQuery - ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId - : g_activePrimitivesGeneratedQueryId) - : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId)); + GLuint& activeQueryId = + isPipelineStatisticsQuery + ? g_activePipelineStatisticsQueryIds[target] + : (isTransformFeedbackQuery + ? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId + : g_activePrimitivesGeneratedQueryId) + : (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId)); if (activeQueryId == 0) { RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target."); return; @@ -665,8 +657,7 @@ namespace MobileGL::MG_Impl::GLImpl { ResetQueryObjectLocked(queryObject); // discard any previous result queryObject->target = target; - const Bool timerQuerySupported = - MGL_BACKEND_SLOT_CAP(QueryCounterTimestamp, MG_Pipe::kCapTimerQuery); + const Bool timerQuerySupported = MGL_BACKEND_SLOT_CAP(QueryCounterTimestamp, MG_Pipe::kCapTimerQuery); const auto queryCounterTimestamp = timerQuerySupported ? MG_Backend::gBackendFunctionsTable.GL.QueryCounterTimestamp : nullptr; MGP_FILL(QueryCounterTimestamp); diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index cba9b5fb..c2b266c7 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -46,12 +46,12 @@ namespace MobileGL::MG_Impl::GLImpl { } if (index >= limit) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", functionName, - String(indexName) + " index " + std::to_string(index) + - " is out of range. Max supported is " + std::to_string(limit - 1) + - ".")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", functionName, + String(indexName) + " index " + + std::to_string(index) + + " is out of range. Max supported is " + + std::to_string(limit - 1) + ".")); return false; } @@ -89,8 +89,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", functionName, - "first (" + std::to_string(first) + ") + count (" + - std::to_string(count) + ") exceeds GL_MAX_VIEWPORTS (" + + "first (" + std::to_string(first) + ") + count (" + std::to_string(count) + + ") exceeds GL_MAX_VIEWPORTS (" + std::to_string(RenderStateParameters::MAX_VIEWPORTS) + ").")); return false; } @@ -133,16 +133,15 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } - static Bool TryConvertBlendEquation(GLenum mode, const char* functionName, - ::MobileGL::BlendEquation& outEquation) { + static Bool TryConvertBlendEquation(GLenum mode, const char* functionName, ::MobileGL::BlendEquation& outEquation) { outEquation = MG_Util::ConvertGLEnumToBlendEquation(mode); if (outEquation != ::MobileGL::BlendEquation::Unknown) return true; - MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", functionName, - "Blend equation enum " + MG_Util::ConvertGLEnumToString(mode) + - " is not supported.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", functionName, + "Blend equation enum " + + MG_Util::ConvertGLEnumToString(mode) + + " is not supported.")); return false; } @@ -161,11 +160,11 @@ namespace MobileGL::MG_Impl::GLImpl { applyBack = true; return true; default: - MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", functionName, - "Stencil face enum " + MG_Util::ConvertGLEnumToString(face) + - " is not supported.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", functionName, + "Stencil face enum " + + MG_Util::ConvertGLEnumToString(face) + + " is not supported.")); return false; } } @@ -175,11 +174,11 @@ namespace MobileGL::MG_Impl::GLImpl { outOperation = MG_Util::ConvertGLEnumToStencilOperation(value); if (outOperation != StencilOperation::Unknown) return true; - MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", functionName, - String(paramName) + " enum " + MG_Util::ConvertGLEnumToString(value) + - " is not supported.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", functionName, + String(paramName) + " enum " + + MG_Util::ConvertGLEnumToString(value) + + " is not supported.")); return false; } @@ -234,10 +233,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (!ValidateNonNullArray(v, "DepthRangeArrayv_State")) return; for (GLsizei i = 0; i < count; ++i) { - MG_State::pGLContext->SetDepthRangeIndexed( - first + static_cast(i), - FloatVec2(ClampUnitFloat(static_cast(v[i * 2 + 0])), - ClampUnitFloat(static_cast(v[i * 2 + 1])))); + MG_State::pGLContext->SetDepthRangeIndexed(first + static_cast(i), + FloatVec2(ClampUnitFloat(static_cast(v[i * 2 + 0])), + ClampUnitFloat(static_cast(v[i * 2 + 1])))); } } @@ -455,10 +453,9 @@ namespace MobileGL::MG_Impl::GLImpl { void PointSize_State(GLfloat size) { if (size <= 0.0f) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "PointSize_State", - "Point size must be greater than zero.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "PointSize_State", + "Point size must be greater than zero.")); return; } @@ -502,7 +499,9 @@ namespace MobileGL::MG_Impl::GLImpl { } } - void PointParameterf_State(GLenum pname, GLfloat param) { PointParameter_State(pname, param); } + void PointParameterf_State(GLenum pname, GLfloat param) { + PointParameter_State(pname, param); + } void PointParameteri_State(GLenum pname, GLint param) { PointParameter_State(pname, static_cast(param)); @@ -547,11 +546,11 @@ namespace MobileGL::MG_Impl::GLImpl { void LogicOp_State(GLenum opcode) { LogicOperation logicOp = MG_Util::ConvertGLEnumToLogicOperation(opcode); if (logicOp == LogicOperation::Unknown) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", "LogicOp_State", - "Logic op enum " + MG_Util::ConvertGLEnumToString(opcode) + - " is not supported.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", "LogicOp_State", + "Logic op enum " + + MG_Util::ConvertGLEnumToString(opcode) + + " is not supported.")); return; } @@ -560,10 +559,9 @@ namespace MobileGL::MG_Impl::GLImpl { void LineWidth_State(GLfloat width) { if (width <= 0.0f) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "LineWidth_State", - "Line width must be greater than zero.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "LineWidth_State", + "Line width must be greater than zero.")); return; } @@ -592,8 +590,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (!data) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "GetBooleani_v_State", - "data pointer cannot be null.")); + MakeUnique("MG_Impl/GLImpl", "GetBooleani_v_State", "data pointer cannot be null.")); return; } @@ -601,11 +598,11 @@ namespace MobileGL::MG_Impl::GLImpl { // Indexed per-draw-buffer color writemask writes 4 booleans (R,G,B,A) for draw buffer // `index`. The non-indexed glGetBooleanv(GL_COLOR_WRITEMASK) reports draw buffer 0. if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { - MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", "GetBooleani_v_State", - "Color writemask draw buffer index " + std::to_string(index) + - " is out of range.")); + MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "GetBooleani_v_State", + "Color writemask draw buffer index " + + std::to_string(index) + + " is out of range.")); return; } const BoolVec4 mask = MG_State::pGLContext->GetColorMaskIndexed(index); @@ -629,9 +626,8 @@ namespace MobileGL::MG_Impl::GLImpl { GetIntegeri_v(target, index, values); // The ARB_viewport_array rectangles are the only multi-component indexed state that // reaches here; writing element 0 alone would leave the caller's other three untouched. - const GLsizei components = target == GL_VIEWPORT || target == GL_SCISSOR_BOX - ? 4 - : (target == GL_DEPTH_RANGE ? 2 : 1); + const GLsizei components = + target == GL_VIEWPORT || target == GL_SCISSOR_BOX ? 4 : (target == GL_DEPTH_RANGE ? 2 : 1); for (GLsizei i = 0; i < components; ++i) { data[i] = values[i] != 0 ? GL_TRUE : GL_FALSE; } @@ -927,8 +923,8 @@ namespace MobileGL::MG_Impl::GLImpl { BlendFactor dstRGBM = MG_Util::ConvertGLEnumToBlendFactor(dstRGB); BlendFactor srcAlphaM = MG_Util::ConvertGLEnumToBlendFactor(srcAlpha); BlendFactor dstAlphaM = MG_Util::ConvertGLEnumToBlendFactor(dstAlpha); - if (srcRGBM == BlendFactor::Unknown || dstRGBM == BlendFactor::Unknown || - srcAlphaM == BlendFactor::Unknown || dstAlphaM == BlendFactor::Unknown) { + if (srcRGBM == BlendFactor::Unknown || dstRGBM == BlendFactor::Unknown || srcAlphaM == BlendFactor::Unknown || + dstAlphaM == BlendFactor::Unknown) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "BlendFuncSeparatei_State", diff --git a/MobileGL/MG_Impl/GLImpl/Sampler/GL_Sampler.cpp b/MobileGL/MG_Impl/GLImpl/Sampler/GL_Sampler.cpp index b2c2ad2f..b3dd3cc5 100644 --- a/MobileGL/MG_Impl/GLImpl/Sampler/GL_Sampler.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sampler/GL_Sampler.cpp @@ -119,8 +119,8 @@ namespace MobileGL::MG_Impl::GLImpl { // both to the same setter - which is what this file used to do - meant glSamplerParameteriv // stored raw integers (so a border of 255 became float 255.0 instead of the spec's ~1.19e-7) // and glSamplerParameterIiv lost the fact that it was ever an integer at all. - void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat, - bool isUnsignedInteger, bool isIntegerCommand) { + void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat, bool isUnsignedInteger, + bool isIntegerCommand) { if (param == nullptr) return; if (!SamplerImpl::ValidateSamplerName(sampler)) return; @@ -203,8 +203,8 @@ namespace MobileGL::MG_Impl::GLImpl { } } - void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat, - bool isUnsignedInteger, bool isIntegerCommand) { + void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat, bool isUnsignedInteger, + bool isIntegerCommand) { if (params == nullptr) return; if (!SamplerImpl::ValidateSamplerName(sampler)) return; @@ -239,14 +239,14 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertSamplerWrapModeToGLEnum(samplerObj->GetWrapR())); break; case GL_TEXTURE_MIN_FILTER: - StoreSamplerEnum(params, isFloat, isUnsignedInteger, - MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObj->GetMinFilter(), - samplerObj->GetMipmapMode())); + StoreSamplerEnum( + params, isFloat, isUnsignedInteger, + MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObj->GetMinFilter(), samplerObj->GetMipmapMode())); break; case GL_TEXTURE_MAG_FILTER: - StoreSamplerEnum(params, isFloat, isUnsignedInteger, - MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObj->GetMagFilter(), - SamplerMipmapMode::None)); + StoreSamplerEnum( + params, isFloat, isUnsignedInteger, + MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObj->GetMagFilter(), SamplerMipmapMode::None)); break; case GL_TEXTURE_MIN_LOD: StoreSamplerScalar(params, isFloat, isUnsignedInteger, samplerObj->GetMinLod()); diff --git a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp index 75cbe127..0bde1516 100644 --- a/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp +++ b/MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.cpp @@ -196,8 +196,8 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_SYNC_STATUS: { const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus; MGP_FILL(GetSyncStatus); - const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle || - backendGetSyncStatus(syncObject->backendHandle); + const Bool signaled = + !backendGetSyncStatus || !syncObject->backendHandle || backendGetSyncStatus(syncObject->backendHandle); value = signaled ? GL_SIGNALED : GL_UNSIGNALED; break; } diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp index 274fa9b3..9344550f 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp @@ -50,7 +50,10 @@ // SyncGpuWrites, exactly as glMapBufferRange and glGetBufferSubData already do. Monolith keeps its // read as it was: its ReadPixels maps the driver PBO back into the shadow inside the call, and the // pull build compiles this to nothing (G1). -#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) do { (buffer)->SyncGpuWrites(); } while (0) +#define MGL_SYNC_PIXEL_UNPACK_SOURCE(buffer) \ + do { \ + (buffer)->SyncGpuWrites(); \ + } while (0) namespace MobileGL::MG_Impl::GLImpl { static SharedPtr nullTextureObject; @@ -69,10 +72,9 @@ namespace MobileGL::MG_Impl::GLImpl { // equation 2.3; the two live in one header so they cannot drift apart. void SetTextureBorderColorFromInts(const SharedPtr& textureObject, const GLint* params) { - textureObject->SetBorderColor(FloatVec4(MG_Util::SignedNormalizedInt32ToFloat(params[0]), - MG_Util::SignedNormalizedInt32ToFloat(params[1]), - MG_Util::SignedNormalizedInt32ToFloat(params[2]), - MG_Util::SignedNormalizedInt32ToFloat(params[3]))); + textureObject->SetBorderColor(FloatVec4( + MG_Util::SignedNormalizedInt32ToFloat(params[0]), MG_Util::SignedNormalizedInt32ToFloat(params[1]), + MG_Util::SignedNormalizedInt32ToFloat(params[2]), MG_Util::SignedNormalizedInt32ToFloat(params[3]))); } void SetTextureBorderColorFromIntegerInts(const SharedPtr& textureObject, @@ -158,7 +160,7 @@ namespace MobileGL::MG_Impl::GLImpl { // a live unit for the rest of the context's life. template void WithTemporarilyBoundNamedTexture(const SharedPtr& textureObject, - Fn&& fn) { + Fn&& fn) { if (!textureObject) return; const Int activeUnitIndex = MG_State::pGLContext->GetActiveTextureUnit(); @@ -258,8 +260,7 @@ namespace MobileGL::MG_Impl::GLImpl { void SeedImmutableViewState(const SharedPtr& textureObject, Uint levels) { if (!textureObject) return; textureObject->SetViewLevelLayerRange( - 0, levels, 0, - LayerCountOfImmutableTexture(textureObject->GetTarget(), textureObject->GetBaseSize())); + 0, levels, 0, LayerCountOfImmutableTexture(textureObject->GetTarget(), textureObject->GetBaseSize())); } Bool ValidateTextureMutable(const SharedPtr& textureObject, @@ -443,9 +444,9 @@ namespace MobileGL::MG_Impl::GLImpl { } GLuint GetAdvertisedImageUnitCount() { - return static_cast(std::min( - MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxImageUnits, - MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS)); + return static_cast( + std::min(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxImageUnits, + MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS)); } // How many components of a GL-space texel size actually halve down the mip chain. @@ -543,9 +544,8 @@ namespace MobileGL::MG_Impl::GLImpl { // MAX_LEVEL admit, and leaves every other level exactly as it was (GL 4.6 core 8.17). Both // transports take this one path: the window, and the refusal to redefine the levels around // it, are properties of the GL call rather than of how the backend is reached. - Bool EnsureGeneratedMipmapStorageAllocated( - MG_State::GLState::TextureObjectMipmap& texture, - TextureUploadTarget uploadTarget) { + Bool EnsureGeneratedMipmapStorageAllocated(MG_State::GLState::TextureObjectMipmap& texture, + TextureUploadTarget uploadTarget) { const auto plan = ComputeMipmapGenerationRange(texture, uploadTarget); if (plan.End <= plan.Base) return false; // Immutable textures and views already own every level generation can write. @@ -584,8 +584,7 @@ namespace MobileGL::MG_Impl::GLImpl { } Bool IsMultisampleTextureTarget(TextureTarget target) { - return target == TextureTarget::Texture2DMultisample || - target == TextureTarget::Texture2DMultisampleArray; + return target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray; } // The largest count the backend actually probed for this format on this target, or 0 when @@ -616,8 +615,7 @@ namespace MobileGL::MG_Impl::GLImpl { // application wrote per-sample data it could never read back. Raising INVALID_OPERATION // is what a real driver does, and it is what makes that silent squeeze unreachable for // application-visible storage. - Int GetMaxSupportedTextureSamples(TextureTarget textureTarget, - TextureInternalFormat textureInternalFormat) { + Int GetMaxSupportedTextureSamples(TextureTarget textureTarget, TextureInternalFormat textureInternalFormat) { if (MG_Backend::pActiveBackendObject == nullptr) { return std::numeric_limits::max(); } @@ -626,8 +624,7 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::IsStencilFormatInternalFormat(textureInternalFormat); Bool isIntegerFormat = false; if (!isDepthOrStencil) { - GLenum normalizedInternalFormat = - MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat); + GLenum normalizedInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat); GLenum normalizedFormat = GL_RGBA; GLenum normalizedType = GL_UNSIGNED_BYTE; MG_Util::TextureFormatProcessor::NormalizePixelFormat( @@ -636,7 +633,7 @@ namespace MobileGL::MG_Impl::GLImpl { isIntegerFormat = normalizedFormat == GL_RED_INTEGER || normalizedFormat == GL_RG_INTEGER || normalizedFormat == GL_RGB_INTEGER || normalizedFormat == GL_RGBA_INTEGER; } - const Int categoryMaxSamples = isDepthOrStencil ? GetAdvertisedDepthTextureMaxSamples() + const Int categoryMaxSamples = isDepthOrStencil ? GetAdvertisedDepthTextureMaxSamples() : isIntegerFormat ? GetAdvertisedIntegerMaxSamples() : GetAdvertisedColorTextureMaxSamples(); @@ -657,8 +654,8 @@ namespace MobileGL::MG_Impl::GLImpl { } Bool ValidateTextureMultisampleStorage(TextureTarget textureTarget, GLsizei samples, GLsizei width, - GLsizei height, GLsizei depth, TextureInternalFormat textureInternalFormat, - const char* caller) { + GLsizei height, GLsizei depth, + TextureInternalFormat textureInternalFormat, const char* caller) { if (!IsMultisampleTextureTarget(textureTarget)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, @@ -680,9 +677,8 @@ namespace MobileGL::MG_Impl::GLImpl { } if (textureTarget == TextureTarget::Texture2DMultisample && depth != 1) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", caller, - "2D multisample textures must use depth 1.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", caller, + "2D multisample textures must use depth 1.")); return false; } // Zero layers is NOT an error for multisample arrays: depth == 0 (like width/height @@ -723,8 +719,7 @@ namespace MobileGL::MG_Impl::GLImpl { // every unit the reset touched, and reaches an ES glTexStorage*Multisample(..., 0, 0) // that ES 3.1 8.19 makes INVALID_VALUE on every driver there is. A proxy target holds // no image at all, only the query result, so it keeps recording what was asked for. - if ((width <= 0 || height <= 0 || depth <= 0) && - !TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { + if ((width <= 0 || height <= 0 || depth <= 0) && !TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { textureObject->SetInternalFormat(TextureInternalFormat::Unknown); textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, 0); return; @@ -781,9 +776,8 @@ namespace MobileGL::MG_Impl::GLImpl { // have no compressed upload path yet. void RecordUnsupportedCompressedFormat(const char* caller) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", caller, - "Compressed texture formats are not supported.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", caller, + "Compressed texture formats are not supported.")); } // The ETC2/EAC arm of the compressed uploads: the blocks are decoded into the level's @@ -808,8 +802,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto* shadow = static_cast(texture->MapMipmapData(uploadTarget, level)); if (shadow == nullptr) return false; MG_Util::EtcDecoder::DecodeRegion(format, static_cast(blocks), size.x(), size.y(), size.z(), - shadow, levelSize.x(), levelSize.y(), levelDepth, offset.x(), - offset.y(), offset.z()); + shadow, levelSize.x(), levelSize.y(), levelDepth, offset.x(), offset.y(), + offset.z()); texture->MarkStorageDirtyRegion(uploadTarget, level, offset, size); return true; } @@ -827,8 +821,7 @@ namespace MobileGL::MG_Impl::GLImpl { // A format with no known footprint has no texel count to report; answering 0 beats // dividing by it. if (texelByteSize == 0) return 0; - const auto* bufferTextureObject = - static_cast(textureObject); + const auto* bufferTextureObject = static_cast(textureObject); const SizeT texelCount = bufferTextureObject->GetBufferRangeSizeInBytes() / texelByteSize; const SizeT maxTexelCount = static_cast( std::max(0, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxTextureBufferSize)); @@ -852,13 +845,12 @@ namespace MobileGL::MG_Impl::GLImpl { // for the compressed-format path. void RecordUnsupportedLevelQueryStorage(const char* caller, GLenum pname) { MGLOG_W_ONCE("%s: glGetTexLevelParameter(pname=%s) is not implemented for this texture's " - "storage class; recording GL_INVALID_OPERATION instead of terminating", - caller, MG_Util::ConvertGLEnumToString(pname).c_str()); + "storage class; recording GL_INVALID_OPERATION instead of terminating", + caller, MG_Util::ConvertGLEnumToString(pname).c_str()); MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique( - "MG_Impl/GLImpl", caller, - "Level queries are not supported for this texture's storage class.")); + MakeUnique("MG_Impl/GLImpl", caller, + "Level queries are not supported for this texture's storage class.")); } } // namespace @@ -904,8 +896,7 @@ namespace MobileGL::MG_Impl::GLImpl { namespace { void RecordClearTextureError(const char* caller, ErrorCode code, const String& message) { - MG_State::pGLContext->RecordError( - code, MakeUnique("MG_Impl/GLImpl", caller, message)); + MG_State::pGLContext->RecordError(code, MakeUnique("MG_Impl/GLImpl", caller, message)); } SharedPtr GetClearTextureObject(GLuint texture, GLint level, @@ -948,21 +939,20 @@ namespace MobileGL::MG_Impl::GLImpl { (mipmapTexture->GetMipmapCompressedFormat(uploadTargets[0], static_cast(level)) != GL_NONE || mipmapTexture->GetMipmapRequestedCompressedFormat(uploadTargets[0], static_cast(level)) != GL_NONE)) { - RecordClearTextureError(caller, ErrorCode::InvalidOperation, - "Compressed textures cannot be cleared."); + RecordClearTextureError(caller, ErrorCode::InvalidOperation, "Compressed textures cannot be cleared."); return nullptr; } return mipmapTexture; } - Bool BuildClearPixel(const SharedPtr& textureObject, - GLenum format, GLenum type, const void* data, Vector& clearPixel) { + Bool BuildClearPixel(const SharedPtr& textureObject, GLenum format, + GLenum type, const void* data, Vector& clearPixel) { const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); const TexturePixelDataType inputType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); if (!TextureImpl::ValidateTextureInputFormat(inputFormat) || !TextureImpl::ValidateTexturePixelDataType(inputType) || - !TextureImpl::ValidateTextureInternalFormatCompatibleWithInput( - inputFormat, textureObject->GetFormat(), inputType)) { + !TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(inputFormat, textureObject->GetFormat(), + inputType)) { return false; } @@ -977,8 +967,8 @@ namespace MobileGL::MG_Impl::GLImpl { clearPixelStore.Alignment = 1; SizeT clearPixelSize = 0; void* converted = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack( - data, clearPixelStore, textureObject->GetFormat(), inputFormat, inputType, - {1, 1, 1}, false, clearPixelSize); + data, clearPixelStore, textureObject->GetFormat(), inputFormat, inputType, {1, 1, 1}, false, + clearPixelSize); if (!converted || clearPixelSize == 0) { if (converted) free(converted); return false; @@ -997,9 +987,8 @@ namespace MobileGL::MG_Impl::GLImpl { // the next sync. Full-level clears (glClearTexImage, or a sub-clear covering the // level) rewrite the entire shadow and are always correct. Bool ClearMipmapRegion(const SharedPtr& textureObject, - TextureUploadTarget uploadTarget, GLint level, - GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, + TextureUploadTarget uploadTarget, GLint level, GLint xoffset, GLint yoffset, + GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, const Vector& clearPixel, const char* caller) { const IntVec3 texelSize = textureObject->GetMipmapTexelSize(uploadTarget, static_cast(level)); if (texelSize.x() <= 0 || texelSize.y() <= 0 || texelSize.z() <= 0) { @@ -1007,10 +996,8 @@ namespace MobileGL::MG_Impl::GLImpl { "The requested texture level has no storage."); return false; } - if (xoffset < 0 || yoffset < 0 || zoffset < 0 || - width < 0 || height < 0 || depth < 0 || - width > texelSize.x() - xoffset || - height > texelSize.y() - yoffset || + if (xoffset < 0 || yoffset < 0 || zoffset < 0 || width < 0 || height < 0 || depth < 0 || + width > texelSize.x() - xoffset || height > texelSize.y() - yoffset || depth > texelSize.z() - zoffset) { RecordClearTextureError(caller, ErrorCode::InvalidValue, "The clear region lies outside the requested texture level."); @@ -1018,8 +1005,7 @@ namespace MobileGL::MG_Impl::GLImpl { } if (width == 0 || height == 0 || depth == 0) return true; - const SizeT texelCount = static_cast(texelSize.x()) * - static_cast(texelSize.y()) * + const SizeT texelCount = static_cast(texelSize.x()) * static_cast(texelSize.y()) * static_cast(texelSize.z()); const SizeT byteSize = textureObject->GetMipmapByteSize(uploadTarget, static_cast(level)); if (byteSize == 0 || byteSize % texelCount != 0) { @@ -1037,8 +1023,8 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } - auto* destination = static_cast( - textureObject->MapMipmapData(uploadTarget, static_cast(level))); + auto* destination = + static_cast(textureObject->MapMipmapData(uploadTarget, static_cast(level))); if (!destination) { RecordClearTextureError(caller, ErrorCode::InvalidOperation, "The requested texture level could not be mapped."); @@ -1052,8 +1038,7 @@ namespace MobileGL::MG_Impl::GLImpl { for (GLsizei z = 0; z < depth; ++z) { for (GLsizei y = 0; y < height; ++y) { - Uint8* row = destination + - static_cast(zoffset + z) * fullSliceBytes + + Uint8* row = destination + static_cast(zoffset + z) * fullSliceBytes + static_cast(yoffset + y) * fullRowBytes + static_cast(xoffset) * bytesPerTexel; if (firstClearRow) { @@ -1104,10 +1089,9 @@ namespace MobileGL::MG_Impl::GLImpl { private: // PackAlignment must be first: it is the one that resets to 1 rather than 0. static constexpr Array kParams{ - PixelStoreParam::PackAlignment, PixelStoreParam::PackRowLength, - PixelStoreParam::PackImageHeight, PixelStoreParam::PackSkipRows, - PixelStoreParam::PackSkipPixels, PixelStoreParam::PackSkipImages, - PixelStoreParam::PackSwapBytes, PixelStoreParam::PackLSBFirst}; + PixelStoreParam::PackAlignment, PixelStoreParam::PackRowLength, PixelStoreParam::PackImageHeight, + PixelStoreParam::PackSkipRows, PixelStoreParam::PackSkipPixels, PixelStoreParam::PackSkipImages, + PixelStoreParam::PackSwapBytes, PixelStoreParam::PackLSBFirst}; Array m_saved{}; SharedPtr m_savedPackBuffer; }; @@ -1139,12 +1123,11 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum type = GL_UNSIGNED_BYTE; MG_Util::TextureFormatProcessor::NormalizePixelFormat(glInternalFormat, PixelFormatNormalizeOptionBit::None, &realInternalFormat, &format, &type); - const SizeT readBytesPerTexel = - MG_Util::GetInputBytesPerPixel(MG_Util::ConvertGLEnumToTextureInputFormat(format), - MG_Util::ConvertGLEnumToTexturePixelDataType(type)); + const SizeT readBytesPerTexel = MG_Util::GetInputBytesPerPixel( + MG_Util::ConvertGLEnumToTextureInputFormat(format), MG_Util::ConvertGLEnumToTexturePixelDataType(type)); if (readBytesPerTexel != bytesPerTexel) { MGLOG_W_ONCE("%s: cannot copy into a %zu-byte texel from a %zu-byte readback layout", caller, - bytesPerTexel, readBytesPerTexel); + bytesPerTexel, readBytesPerTexel); return false; } @@ -1182,16 +1165,15 @@ namespace MobileGL::MG_Impl::GLImpl { for (TextureUploadTarget uploadTarget : textureObject->GetUploadTargets()) { const IntVec3 size = textureObject->GetMipmapTexelSize(uploadTarget, static_cast(level)); - if (!ClearMipmapRegion(textureObject, uploadTarget, level, 0, 0, 0, - size.x(), size.y(), size.z(), clearPixel, __func__)) { + if (!ClearMipmapRegion(textureObject, uploadTarget, level, 0, 0, 0, size.x(), size.y(), size.z(), + clearPixel, __func__)) { return; } } } - void ClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, - const void* data) { + void ClearTexSubImage(GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) { auto textureObject = GetClearTextureObject(texture, level, __func__); if (!textureObject) return; @@ -1200,16 +1182,15 @@ namespace MobileGL::MG_Impl::GLImpl { const auto& uploadTargets = textureObject->GetUploadTargets(); if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) { - if (zoffset < 0 || depth < 0 || - static_cast(zoffset) > uploadTargets.size() || + if (zoffset < 0 || depth < 0 || static_cast(zoffset) > uploadTargets.size() || static_cast(depth) > uploadTargets.size() - static_cast(zoffset)) { RecordClearTextureError(__func__, ErrorCode::InvalidValue, "The cube-map clear region selects invalid faces."); return; } for (GLsizei face = 0; face < depth; ++face) { - if (!ClearMipmapRegion(textureObject, uploadTargets[static_cast(zoffset + face)], level, - xoffset, yoffset, 0, width, height, 1, clearPixel, __func__)) { + if (!ClearMipmapRegion(textureObject, uploadTargets[static_cast(zoffset + face)], level, xoffset, + yoffset, 0, width, height, 1, clearPixel, __func__)) { return; } } @@ -1221,8 +1202,8 @@ namespace MobileGL::MG_Impl::GLImpl { "The requested texture has no upload target."); return; } - ClearMipmapRegion(textureObject, uploadTargets.front(), level, xoffset, yoffset, zoffset, - width, height, depth, clearPixel, __func__); + ClearMipmapRegion(textureObject, uploadTargets.front(), level, xoffset, yoffset, zoffset, width, height, depth, + clearPixel, __func__); } Bool ValidateTextureParameterForTarget(const SharedPtr& textureObject, @@ -1233,18 +1214,16 @@ namespace MobileGL::MG_Impl::GLImpl { } if ((pname == GL_TEXTURE_BASE_LEVEL || pname == GL_TEXTURE_MAX_LEVEL) && param < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", caller, "Texture level parameter must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", caller, + "Texture level parameter must be non-negative.")); return false; } - if ((target == TextureTarget::Texture2DMultisample || - target == TextureTarget::Texture2DMultisampleArray) && + if ((target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray) && pname == GL_TEXTURE_BASE_LEVEL && param != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, - "Multisample texture base level must be zero.")); + MakeUnique("MG_Impl/GLImpl", caller, "Multisample texture base level must be zero.")); return false; } @@ -1253,7 +1232,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (pname == GL_TEXTURE_BASE_LEVEL && param != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, "External texture base level must be zero.")); + MakeUnique("MG_Impl/GLImpl", caller, + "External texture base level must be zero.")); return false; } if (((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T || pname == GL_TEXTURE_WRAP_R) && @@ -1274,8 +1254,7 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } - if ((target == TextureTarget::Texture2DMultisample || - target == TextureTarget::Texture2DMultisampleArray) && + if ((target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray) && (pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T || pname == GL_TEXTURE_WRAP_R || pname == GL_TEXTURE_MIN_FILTER || pname == GL_TEXTURE_MAG_FILTER || pname == GL_TEXTURE_MIN_LOD || pname == GL_TEXTURE_MAX_LOD || pname == GL_TEXTURE_LOD_BIAS || pname == GL_TEXTURE_COMPARE_MODE || @@ -1320,15 +1299,13 @@ namespace MobileGL::MG_Impl::GLImpl { (param == GL_MIRROR_CLAMP_TO_EDGE || param == GL_MIRRORED_REPEAT || param == GL_REPEAT)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", caller, - "Invalid wrap mode for rectangle texture.")); + MakeUnique("MG_Impl/GLImpl", caller, "Invalid wrap mode for rectangle texture.")); return false; } if (pname == GL_TEXTURE_MIN_FILTER && param != GL_NEAREST && param != GL_LINEAR) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", caller, - "Invalid min filter for rectangle texture.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", caller, + "Invalid min filter for rectangle texture.")); return false; } } @@ -1458,8 +1435,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLfloat param, const char* caller) { if (!textureObject) return; if (pname == GL_TEXTURE_MAX_ANISOTROPY_EXT && !ValidateMaxAnisotropy(param, caller)) return; - const GLint validationParam = - pname == GL_TEXTURE_MAX_ANISOTROPY_EXT ? 1 : static_cast(param); + const GLint validationParam = pname == GL_TEXTURE_MAX_ANISOTROPY_EXT ? 1 : static_cast(param); if (!ValidateTextureParameterForTarget(textureObject, pname, validationParam, caller)) return; switch (pname) { @@ -1560,8 +1536,8 @@ namespace MobileGL::MG_Impl::GLImpl { switch (pname) { case GL_TEXTURE_MAG_FILTER: - *params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum(textureObject->GetSamplerObject()->GetMagFilter(), - SamplerMipmapMode::None); + *params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum( + textureObject->GetSamplerObject()->GetMagFilter(), SamplerMipmapMode::None); break; case GL_TEXTURE_MIN_FILTER: *params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum( @@ -1646,8 +1622,7 @@ namespace MobileGL::MG_Impl::GLImpl { ErrorCode::InvalidEnum, MakeUnique( "MG_Impl/GLImpl", caller, - std::format("pname {} is not a valid texture parameter.", - MG_Util::ConvertGLEnumToString(pname)))); + std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname)))); return; } } @@ -1655,8 +1630,7 @@ namespace MobileGL::MG_Impl::GLImpl { const SharedPtr& GetTextureObjectByTarget( TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) { if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { - auto& textureObject = - TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget); + auto& textureObject = TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget); if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject; return textureObject; } else { @@ -1724,10 +1698,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (IsLegalTextureParameterTarget(effectiveTarget)) return true; MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", caller, - std::format("The effective target {} does not accept texture parameters.", - MG_Util::ConvertGLEnumToString(effectiveTarget)))); + MakeUnique("MG_Impl/GLImpl", caller, + std::format("The effective target {} does not accept texture parameters.", + MG_Util::ConvertGLEnumToString(effectiveTarget)))); return false; } @@ -1883,8 +1856,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height, zoffset, depth)) return; - if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(), - texturePixelDataType)) + if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput( + textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) return; if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, height, depth}, __func__)) @@ -1905,8 +1878,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } if (!originalPixels) { MG_State::pGLContext->RecordError( @@ -1945,10 +1918,9 @@ namespace MobileGL::MG_Impl::GLImpl { if (destData) { for (GLsizei z = 0; z < depth; ++z) { for (GLsizei y = 0; y < height; ++y) { - const SizeT destRowOffset = - static_cast(zoffset + z) * destSliceSize + - static_cast(yoffset + y) * destRowSize + - static_cast(xoffset) * internalBpp; + const SizeT destRowOffset = static_cast(zoffset + z) * destSliceSize + + static_cast(yoffset + y) * destRowSize + + static_cast(xoffset) * internalBpp; const SizeT srcRowOffset = static_cast(z) * srcSliceSize + static_cast(y) * srcRowSize; Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize); @@ -2028,8 +2000,8 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("TexSubImage2D_State: Using Pixel Unpack Buffer Object ID: %u", pixelUnpackBufferObject->GetExternalIndex()); MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } if (!originalPixels) { @@ -2045,8 +2017,8 @@ namespace MobileGL::MG_Impl::GLImpl { {width, height, 1}, false, inputSize); if (!processedPixels || inputSize == 0) { - MGLOG_E_ONCE("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", width, - height); + MGLOG_E_ONCE("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", + width, height); if (processedPixels) free(processedPixels); return; } @@ -2103,8 +2075,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& textureObject = bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width)) return; - if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(), - texturePixelDataType)) + if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput( + textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) return; if (!ValidatePixelUnpackBufferSource(pixels, textureInputFormat, texturePixelDataType, {width, 1, 1}, __func__)) return; @@ -2118,8 +2090,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } if (!originalPixels) { MG_State::pGLContext->RecordError( @@ -2622,8 +2594,8 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__, pixelUnpackBufferObject->GetExternalIndex()); MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), @@ -2671,8 +2643,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { MGLOG_W_ONCE("%s: Processed pixel data size (%zu) does not match expected size (%zu). " - "This may indicate an alignment or processing issue.", - __func__, imageSize, internalBytes); + "This may indicate an alignment or processing issue.", + __func__, imageSize, internalBytes); } const SizeT copySize = std::min(imageSize, internalBytes); @@ -2770,8 +2742,8 @@ namespace MobileGL::MG_Impl::GLImpl { MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__, pixelUnpackBufferObject->GetExternalIndex()); MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), @@ -2784,8 +2756,7 @@ namespace MobileGL::MG_Impl::GLImpl { } else { MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level); DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level); - textureMipmapObject->AllocateStorage(textureUploadTarget, level, - {{width, height, 1}, internalBytes}); + textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, 1}, internalBytes}); // GL 4.6 core 8.5: a SPECIFIC compressed internalformat (unlike a generic // GL_COMPRESSED_* one, where the implementation is free to choose) commits the // level to that format - GL_TEXTURE_COMPRESSED must then answer true for it and @@ -2825,8 +2796,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { MGLOG_W_ONCE("TexImage2D_State: Processed pixel data size (%zu) does not match expected size (%zu). " - "This may indicate an alignment or processing issue.", - imageSize, internalBytes); + "This may indicate an alignment or processing issue.", + imageSize, internalBytes); } const SizeT copySize = std::min(imageSize, internalBytes); @@ -2889,8 +2860,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), @@ -3038,8 +3009,8 @@ namespace MobileGL::MG_Impl::GLImpl { MakeUnique("MG_Impl/GLImpl", caller, "size must be greater than zero.")); return; } - const Int alignment = std::max( - 1, MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment); + const Int alignment = + std::max(1, MG_Backend::pActiveBackendObject->GetDynamicParameters().TextureBufferOffsetAlignment); if (offset % alignment != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -3146,9 +3117,9 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy + ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; @@ -3178,9 +3149,9 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy + ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; @@ -3206,9 +3177,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return false; @@ -3314,8 +3284,8 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_SWIZZLE_R: if (params) { - *params = static_cast( - MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Red))); + *params = static_cast(MG_Util::ConvertTextureSwizzleParamToGLEnum( + textureObject->GetSwizzleParam(TextureSwizzleParam::Red))); } break; case GL_TEXTURE_SWIZZLE_G: @@ -3326,8 +3296,8 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_SWIZZLE_B: if (params) { - *params = static_cast( - MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Blue))); + *params = static_cast(MG_Util::ConvertTextureSwizzleParamToGLEnum( + textureObject->GetSwizzleParam(TextureSwizzleParam::Blue))); } break; case GL_TEXTURE_SWIZZLE_A: @@ -3404,9 +3374,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; @@ -3487,8 +3456,8 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_SWIZZLE_R: if (params) { - *params = static_cast( - MG_Util::ConvertTextureSwizzleParamToGLEnum(textureObject->GetSwizzleParam(TextureSwizzleParam::Red))); + *params = static_cast(MG_Util::ConvertTextureSwizzleParamToGLEnum( + textureObject->GetSwizzleParam(TextureSwizzleParam::Red))); } break; case GL_TEXTURE_SWIZZLE_G: @@ -3595,9 +3564,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; @@ -3696,9 +3664,8 @@ namespace MobileGL::MG_Impl::GLImpl { break; case GL_TEXTURE_COMPRESSED: if (params) { - *params = - (GetCompressedLevelFormat(textureObject, textureUploadTarget, level) != GL_NONE) ? GL_TRUE - : GL_FALSE; + *params = (GetCompressedLevelFormat(textureObject, textureUploadTarget, level) != GL_NONE) ? GL_TRUE + : GL_FALSE; } break; case GL_TEXTURE_COMPRESSED_IMAGE_SIZE: { @@ -3771,9 +3738,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; @@ -4066,7 +4032,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (mipmapTexture == nullptr) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, "The destination texture has no mipmap storage.")); + MakeUnique("MG_Impl/GLImpl", caller, + "The destination texture has no mipmap storage.")); return false; } const IntVec3 levelSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, static_cast(level)); @@ -4076,8 +4043,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (levelSize.x() <= 0 || levelSize.y() <= 0 || levelSize.z() <= 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, - "The requested texture level has no storage.")); + MakeUnique("MG_Impl/GLImpl", caller, "The requested texture level has no storage.")); return false; } // Signed 64-bit sums: xoffset and width are both GLint and an application may pass values @@ -4091,8 +4057,8 @@ namespace MobileGL::MG_Impl::GLImpl { ErrorCode::InvalidValue, MakeUnique( "MG_Impl/GLImpl", caller, - std::format("The destination region does not lie inside level {} ({}x{}x{}).", level, - levelSize.x(), levelSize.y(), levelSize.z()))); + std::format("The destination region does not lie inside level {} ({}x{}x{}).", level, levelSize.x(), + levelSize.y(), levelSize.z()))); return false; } return true; @@ -4131,18 +4097,18 @@ namespace MobileGL::MG_Impl::GLImpl { if (zoffset < 0 || static_cast(zoffset) >= faceCount) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique( - "MG_Impl/GLImpl", caller, - "zoffset selects the cube map face and must be in [0, " + std::to_string(faceCount) + ").")); + MakeUnique("MG_Impl/GLImpl", caller, + "zoffset selects the cube map face and must be in [0, " + + std::to_string(faceCount) + ").")); return; } - uploadTarget = static_cast( - static_cast(TextureUploadTarget::CubeMapPositiveX) + static_cast(zoffset)); + uploadTarget = static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + + static_cast(zoffset)); sliceOffset = 0; } - if (!ValidateCopySubImageRegionAtLevel(textureObject, uploadTarget, level, xoffset, yoffset, sliceOffset, - width, height, /*depth=*/1, caller)) { + if (!ValidateCopySubImageRegionAtLevel(textureObject, uploadTarget, level, xoffset, yoffset, sliceOffset, width, + height, /*depth=*/1, caller)) { return; } if (!FramebufferImpl::ValidateReadFramebufferForCopy(caller)) return; @@ -4222,9 +4188,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); } - void CopyImageSubData_Backend(const MG_Backend::CopyImageEndpoint& src, - GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, - const MG_Backend::CopyImageEndpoint& dst, + void CopyImageSubData_Backend(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, + GLint srcX, GLint srcY, GLint srcZ, const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { auto copyImageSubData = MG_Backend::gBackendFunctionsTable.GL.CopyImageSubData; @@ -4236,8 +4201,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } MGP_FILL(CopyImageSubData); - copyImageSubData(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX, - dstY, dstZ, srcWidth, srcHeight, srcDepth); + copyImageSubData(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX, dstY, dstZ, + srcWidth, srcHeight, srcDepth); } namespace { @@ -4263,10 +4228,9 @@ namespace MobileGL::MG_Impl::GLImpl { } MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", "ValidateCopyImageSubData_State", - std::format("{} is not a target glCopyImageSubData accepts as the {}.", - MG_Util::ConvertGLEnumToString(target), endpointName))); + MakeUnique("MG_Impl/GLImpl", "ValidateCopyImageSubData_State", + std::format("{} is not a target glCopyImageSubData accepts as the {}.", + MG_Util::ConvertGLEnumToString(target), endpointName))); return false; } @@ -4283,8 +4247,7 @@ namespace MobileGL::MG_Impl::GLImpl { // the ~30 entry points that reach it through a BOUND object (where the name was never // in question and the fault is the binding), so this is a local rule rather than a // change to the helper. - Bool ValidateCopyImageObjectExists(const MG_Backend::CopyImageEndpoint& endpoint, - const char* endpointName) { + Bool ValidateCopyImageObjectExists(const MG_Backend::CopyImageEndpoint& endpoint, const char* endpointName) { if (endpoint.Exists()) return true; MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -4451,13 +4414,11 @@ namespace MobileGL::MG_Impl::GLImpl { } } // namespace - Bool ValidateCopyImageSubData_State(const MG_Backend::CopyImageEndpoint& src, - GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, - const MG_Backend::CopyImageEndpoint& dst, + Bool ValidateCopyImageSubData_State(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, + GLint srcX, GLint srcY, GLint srcZ, const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { - if (!ValidateCopyImageObjectExists(src, "source") || - !ValidateCopyImageObjectExists(dst, "destination")) { + if (!ValidateCopyImageObjectExists(src, "source") || !ValidateCopyImageObjectExists(dst, "destination")) { return false; } // GL_RENDERBUFFER has no TextureTarget to convert to, and it needs none: it is its own @@ -4480,8 +4441,7 @@ namespace MobileGL::MG_Impl::GLImpl { !ValidateCopyImageTargetMatchesObject(dst.Texture, dstTextureTarget, "destination")) { return false; } - if (!TextureImpl::ValidateTextureLevelNumber(srcLevel) || - !TextureImpl::ValidateTextureLevelNumber(dstLevel)) { + if (!TextureImpl::ValidateTextureLevelNumber(srcLevel) || !TextureImpl::ValidateTextureLevelNumber(dstLevel)) { return false; } // ValidateTextureLevelNumber only bounds the index by GL_MAX_TEXTURE_SIZE; it cannot @@ -4498,8 +4458,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (srcWidth < 0 || srcHeight < 0 || srcDepth < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "Copy dimensions must be non-negative.")); + MakeUnique("MG_Impl/GLImpl", __func__, "Copy dimensions must be non-negative.")); return false; } if (srcWidth == 0 || srcHeight == 0 || srcDepth == 0) { @@ -4515,8 +4474,7 @@ namespace MobileGL::MG_Impl::GLImpl { ErrorCode::InvalidOperation, MakeUnique( "MG_Impl/GLImpl", __func__, - std::format("The two images have different sample counts ({} vs. {}).", - srcSamples, dstSamples))); + std::format("The two images have different sample counts ({} vs. {}).", srcSamples, dstSamples))); return false; } // 18.3.2: both images must be complete. An incomplete one has no defined texels to copy @@ -4543,10 +4501,10 @@ namespace MobileGL::MG_Impl::GLImpl { } const IntVec3 srcLevelSize = GetCopyImageEndpointLevelSize(src, srcUploadTarget, srcLevel); const IntVec3 dstLevelSize = GetCopyImageEndpointLevelSize(dst, dstUploadTarget, dstLevel); - if (!TextureImpl::ValidateCopyImageBlockAlignment(srcBlock, srcX, srcY, srcWidth, srcHeight, - srcLevelSize.x(), srcLevelSize.y(), "source") || - !TextureImpl::ValidateCopyImageBlockAlignment(dstBlock, dstX, dstY, srcWidth, srcHeight, - dstLevelSize.x(), dstLevelSize.y(), "destination")) { + if (!TextureImpl::ValidateCopyImageBlockAlignment(srcBlock, srcX, srcY, srcWidth, srcHeight, srcLevelSize.x(), + srcLevelSize.y(), "source") || + !TextureImpl::ValidateCopyImageBlockAlignment(dstBlock, dstX, dstY, srcWidth, srcHeight, dstLevelSize.x(), + dstLevelSize.y(), "destination")) { return false; } // One region extent, measured against both images: GL 4.6 core 18.3.2 gives the copy a @@ -4566,9 +4524,8 @@ namespace MobileGL::MG_Impl::GLImpl { const auto textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); if (textureTarget != TextureTarget::Texture1D) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "glCopyTexSubImage1D requires GL_TEXTURE_1D.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "glCopyTexSubImage1D requires GL_TEXTURE_1D.")); return; } const auto textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); @@ -4694,18 +4651,16 @@ namespace MobileGL::MG_Impl::GLImpl { // accepts one. if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "A proxy target has no texture image to modify.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "A proxy target has no texture image to modify.")); return; } if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; if (width < 0 || height < 0 || depth < 0) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "width, height and depth must be non-negative.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", __func__, + "width, height and depth must be non-negative.")); return; } if (compressedInfo.blockWidth == 0) { @@ -4735,7 +4690,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } - const IntVec3 levelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); + const IntVec3 levelSize = + textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); const Int levelDepth = std::max(levelSize.z(), 1); // Subtractions rather than sums for the reason CompressedTexSubImage2D_State spells out: // offset + extent are both application-supplied GLints and a signed overflow is undefined. @@ -4753,10 +4709,10 @@ namespace MobileGL::MG_Impl::GLImpl { // each slice is its own block grid. const Int blockWidth = static_cast(compressedInfo.blockWidth); const Int blockHeight = static_cast(compressedInfo.blockHeight); - const Bool alignedX = (xoffset % blockWidth == 0) && - (width % blockWidth == 0 || xoffset + width == levelSize.x()); - const Bool alignedY = (yoffset % blockHeight == 0) && - (height % blockHeight == 0 || yoffset + height == levelSize.y()); + const Bool alignedX = + (xoffset % blockWidth == 0) && (width % blockWidth == 0 || xoffset + width == levelSize.x()); + const Bool alignedY = + (yoffset % blockHeight == 0) && (height % blockHeight == 0 || yoffset + height == levelSize.y()); if (!alignedX || !alignedY) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -4812,16 +4768,16 @@ namespace MobileGL::MG_Impl::GLImpl { Memcpy(blob.data(), existing, blobSize); const SizeT blockByteSize = compressedInfo.blockByteSize; - const SizeT levelBlocksX = (static_cast(levelSize.x()) + compressedInfo.blockWidth - 1) / - compressedInfo.blockWidth; - const SizeT levelBlocksY = (static_cast(levelSize.y()) + compressedInfo.blockHeight - 1) / - compressedInfo.blockHeight; + const SizeT levelBlocksX = + (static_cast(levelSize.x()) + compressedInfo.blockWidth - 1) / compressedInfo.blockWidth; + const SizeT levelBlocksY = + (static_cast(levelSize.y()) + compressedInfo.blockHeight - 1) / compressedInfo.blockHeight; const SizeT levelRowBytes = levelBlocksX * blockByteSize; const SizeT levelSliceBytes = levelRowBytes * levelBlocksY; - const SizeT regionBlocksX = (static_cast(width) + compressedInfo.blockWidth - 1) / - compressedInfo.blockWidth; - const SizeT regionBlocksY = (static_cast(height) + compressedInfo.blockHeight - 1) / - compressedInfo.blockHeight; + const SizeT regionBlocksX = + (static_cast(width) + compressedInfo.blockWidth - 1) / compressedInfo.blockWidth; + const SizeT regionBlocksY = + (static_cast(height) + compressedInfo.blockHeight - 1) / compressedInfo.blockHeight; const SizeT firstBlockX = static_cast(xoffset) / compressedInfo.blockWidth; const SizeT firstBlockY = static_cast(yoffset) / compressedInfo.blockHeight; const SizeT regionRowBytes = regionBlocksX * blockByteSize; @@ -4864,9 +4820,8 @@ namespace MobileGL::MG_Impl::GLImpl { // accepts one. if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "A proxy target has no texture image to modify.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "A proxy target has no texture image to modify.")); return; } if (!TextureImpl::ValidateTextureLevelNumber(level)) return; @@ -4904,7 +4859,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; } - const IntVec3 levelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); + const IntVec3 levelSize = + textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, static_cast(level)); // Written as a subtraction rather than `xoffset + width > levelSize.x()`: both operands // are application-supplied GLints, so the sum is free to overflow, and a signed overflow // is undefined behaviour that a compiler may resolve by assuming the check passes. @@ -4921,10 +4877,10 @@ namespace MobileGL::MG_Impl::GLImpl { // and must either be a whole number of blocks wide/high or run to the image's edge. const Int blockWidth = static_cast(compressedInfo.blockWidth); const Int blockHeight = static_cast(compressedInfo.blockHeight); - const Bool alignedX = (xoffset % blockWidth == 0) && - (width % blockWidth == 0 || xoffset + width == levelSize.x()); - const Bool alignedY = (yoffset % blockHeight == 0) && - (height % blockHeight == 0 || yoffset + height == levelSize.y()); + const Bool alignedX = + (xoffset % blockWidth == 0) && (width % blockWidth == 0 || xoffset + width == levelSize.x()); + const Bool alignedY = + (yoffset % blockHeight == 0) && (height % blockHeight == 0 || yoffset + height == levelSize.y()); if (!alignedX || !alignedY) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -4989,13 +4945,13 @@ namespace MobileGL::MG_Impl::GLImpl { Memcpy(blob.data(), existing, blobSize); const SizeT blockByteSize = compressedInfo.blockByteSize; - const SizeT levelBlocksX = (static_cast(levelSize.x()) + compressedInfo.blockWidth - 1) / - compressedInfo.blockWidth; + const SizeT levelBlocksX = + (static_cast(levelSize.x()) + compressedInfo.blockWidth - 1) / compressedInfo.blockWidth; const SizeT levelRowBytes = levelBlocksX * blockByteSize; - const SizeT regionBlocksX = (static_cast(width) + compressedInfo.blockWidth - 1) / - compressedInfo.blockWidth; - const SizeT regionBlocksY = (static_cast(height) + compressedInfo.blockHeight - 1) / - compressedInfo.blockHeight; + const SizeT regionBlocksX = + (static_cast(width) + compressedInfo.blockWidth - 1) / compressedInfo.blockWidth; + const SizeT regionBlocksY = + (static_cast(height) + compressedInfo.blockHeight - 1) / compressedInfo.blockHeight; const SizeT firstBlockX = static_cast(xoffset) / compressedInfo.blockWidth; const SizeT firstBlockY = static_cast(yoffset) / compressedInfo.blockHeight; const SizeT regionRowBytes = regionBlocksX * blockByteSize; @@ -5257,8 +5213,9 @@ namespace MobileGL::MG_Impl::GLImpl { // layer's MAX_TEXTURE_IMAGE_UNITS capacity. Int maxCombinedUnits = static_cast(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS); if (MG_Backend::pActiveBackendObject) { - maxCombinedUnits = std::min( - maxCombinedUnits, MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxCombinedTextureImageUnits); + maxCombinedUnits = + std::min(maxCombinedUnits, + MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxCombinedTextureImageUnits); } if (texture < GL_TEXTURE0 || static_cast(texture - GL_TEXTURE0) >= maxCombinedUnits) { MG_State::pGLContext->RecordError( @@ -5300,8 +5257,7 @@ namespace MobileGL::MG_Impl::GLImpl { // single-face read has any reason to pass. Bool ValidateTextureImageQuery(const SharedPtr& textureObject, GLint level, TextureInputFormat textureInputFormat, TexturePixelDataType texturePixelDataType, - GLsizei bufSize, const void* pixels, const char* caller, - SizeT imagesQueried = 0) { + GLsizei bufSize, const void* pixels, const char* caller, SizeT imagesQueried = 0) { if (!TextureImpl::ValidateTextureObject(textureObject)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -5316,8 +5272,7 @@ namespace MobileGL::MG_Impl::GLImpl { target == TextureTarget::TextureBuffer) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, - "Texture target has no image to read back.")); + MakeUnique("MG_Impl/GLImpl", caller, "Texture target has no image to read back.")); return false; } @@ -5328,8 +5283,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (target == TextureTarget::TextureRectangle && level != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", caller, - "Level must be zero for rectangle textures")); + MakeUnique("MG_Impl/GLImpl", caller, "Level must be zero for rectangle textures")); return false; } @@ -5422,9 +5376,9 @@ namespace MobileGL::MG_Impl::GLImpl { // imagesQueried says. Pack pixel-store state only ever grows this, so a request // rejected here could not have fit under any packing. const SizeT imageCount = imagesQueried != 0 ? imagesQueried : uploadTargets.size(); - const SizeT required = MG_Util::CalculateInputTextureImageSize(textureInputFormat, - texturePixelDataType, texelSize) * - imageCount; + const SizeT required = + MG_Util::CalculateInputTextureImageSize(textureInputFormat, texturePixelDataType, texelSize) * + imageCount; if (bufSize >= 0 && static_cast(bufSize) < required) { MG_State::pGLContext->RecordError( @@ -5493,9 +5447,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); - auto& textureObject = - isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) - : bindingSlot.GetBoundObject(); + auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->GetProxyTextureObject(textureUploadTarget) + : bindingSlot.GetBoundObject(); // glGetTexImage has no bufSize argument: -1 stands for "no client-side limit". That skips // the destination-size branch but NOT the pixel-pack-buffer one, which measures the same @@ -5503,8 +5456,7 @@ namespace MobileGL::MG_Impl::GLImpl { // one image here too, or a PBO sized for the one face this call packs is refused as too // small while the copy that follows writes exactly that much into it. return ValidateTextureImageQuery(textureObject, level, textureInputFormat, texturePixelDataType, -1, pixels, - "GetTexImage_State", - IsCubeMapFaceUploadTarget(textureUploadTarget) ? 1u : 0u); + "GetTexImage_State", IsCubeMapFaceUploadTarget(textureUploadTarget) ? 1u : 0u); } // What this helper can and cannot answer. @@ -5559,8 +5511,7 @@ namespace MobileGL::MG_Impl::GLImpl { const SizeT rowBytes = static_cast(std::max(width, 0)) * clientTexelSize; const Bool tightRows = (rowBytes % alignment) == 0; const Bool noOverrides = packParams.RowLength == 0 && packParams.ImageHeight == 0 && - packParams.SkipPixels == 0 && packParams.SkipRows == 0 && - packParams.SkipImages == 0; + packParams.SkipPixels == 0 && packParams.SkipRows == 0 && packParams.SkipImages == 0; if (!tightRows || !noOverrides) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, @@ -5574,8 +5525,8 @@ namespace MobileGL::MG_Impl::GLImpl { } void CopyTextureImageToClientOrPBO_State(const SharedPtr& textureObject, - TextureUploadTarget textureUploadTarget, GLint level, GLenum format, - GLenum type, GLsizei bufSize, void* pixels, const char* caller) { + TextureUploadTarget textureUploadTarget, GLint level, GLenum format, + GLenum type, GLsizei bufSize, void* pixels, const char* caller) { if (!textureObject) return; TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); @@ -5692,8 +5643,8 @@ namespace MobileGL::MG_Impl::GLImpl { // Shared front half of glTextureStorage1D/2D/3D. `dimension` selects which set of targets the // entry point accepts; `depth` is 1 for the lower-dimensional forms. static Bool ValidateTextureStorageShape(const SharedPtr& textureObject, - int dimension, GLsizei levels, GLsizei width, GLsizei height, - GLsizei depth, const char* caller) { + int dimension, GLsizei levels, GLsizei width, GLsizei height, GLsizei depth, + const char* caller) { if (levels < 1) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, @@ -5756,8 +5707,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (IsCompressedGLInternalFormat(internalformat)) { // After AllocateStorage, which clears the tag. See TexImage1D_State: no compressed // format has a 1D block layout, but glClearTexImage still has to refuse the request. - textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, - static_cast(level), internalformat); + textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, static_cast(level), + internalformat); } } // Immutable storage defines exactly `levels` levels; AllocateStorage only grows, so a @@ -5783,8 +5734,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (textureObject->GetTarget() == TextureTarget::TextureCubeMap && width != height) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", __func__, - "Cube map immutable storage must be square.")); + MakeUnique("MG_Impl/GLImpl", __func__, "Cube map immutable storage must be square.")); return; } @@ -5821,24 +5771,21 @@ namespace MobileGL::MG_Impl::GLImpl { // (max(1, width >> i), height). Shrinking it made every mipmapped 1D array // level report fewer layers than it has. const Bool heightIsLayerCount = textureObject->GetTarget() == TextureTarget::Texture1DArray; - const GLsizei levelHeight = - heightIsLayerCount ? height : std::max(1, height >> level); - const SizeT byteSize = - static_cast(levelWidth) * static_cast(levelHeight) * bytesPerPixel; + const GLsizei levelHeight = heightIsLayerCount ? height : std::max(1, height >> level); + const SizeT byteSize = static_cast(levelWidth) * static_cast(levelHeight) * bytesPerPixel; textureMipmapObject->AllocateStorage(uploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize}); textureMipmapObject->MarkStorageDirty(uploadTarget, level, false); if (compressedInfo.blockWidth != 0) { // After AllocateStorage, which clears the tag. textureMipmapObject->SetMipmapCompressedImage( uploadTarget, static_cast(level), internalformat, nullptr, - MG_Util::CalculateCompressedTextureImageSize(compressedInfo, - {levelWidth, levelHeight, 1})); + MG_Util::CalculateCompressedTextureImageSize(compressedInfo, {levelWidth, levelHeight, 1})); } if (IsCompressedGLInternalFormat(internalformat)) { // Also after AllocateStorage. The generic enums land here and nowhere above, // and glClearTexImage has to refuse them too (GL 4.6 core 8.19). - textureMipmapObject->SetMipmapRequestedCompressedFormat(uploadTarget, - static_cast(level), internalformat); + textureMipmapObject->SetMipmapRequestedCompressedFormat(uploadTarget, static_cast(level), + internalformat); } } // See TextureStorage1D. @@ -5852,8 +5799,7 @@ namespace MobileGL::MG_Impl::GLImpl { GLsizei depth) { auto textureObject = GetTextureObjectByName(texture, __func__); if (!textureObject) return; - if (textureObject->GetTarget() == TextureTarget::Texture3D && - IsCompressedGLInternalFormat(internalformat)) { + if (textureObject->GetTarget() == TextureTarget::Texture3D && IsCompressedGLInternalFormat(internalformat)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique( @@ -5892,8 +5838,8 @@ namespace MobileGL::MG_Impl::GLImpl { const GLsizei levelWidth = std::max(1, width >> level); const GLsizei levelHeight = std::max(1, height >> level); const GLsizei levelDepth = depthMips ? std::max(1, depth >> level) : depth; - const SizeT byteSize = ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, levelHeight, - levelDepth); + const SizeT byteSize = + ComputeTextureStorageByteSize(textureInternalFormat, levelWidth, levelHeight, levelDepth); textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, levelHeight, levelDepth}, byteSize}); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false); @@ -5907,8 +5853,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (IsCompressedGLInternalFormat(internalformat)) { // Also after AllocateStorage. The generic enums land here and nowhere above, // and glClearTexImage has to refuse them too (GL 4.6 core 8.19). - textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, - static_cast(level), internalformat); + textureMipmapObject->SetMipmapRequestedCompressedFormat(textureUploadTarget, static_cast(level), + internalformat); } } // See TextureStorage1D. @@ -6045,8 +5991,7 @@ namespace MobileGL::MG_Impl::GLImpl { // which is indistinguishable from success at the call site and renders garbage. MGLOG_W_ONCE("glTextureView: the active backend has no texture-view support " "(GL_EXT_texture_view / GL_OES_texture_view absent); raising GL_INVALID_OPERATION"); - RecordTextureViewError(ErrorCode::InvalidOperation, - "The active backend does not support texture views."); + RecordTextureViewError(ErrorCode::InvalidOperation, "The active backend does not support texture views."); return; } @@ -6137,8 +6082,7 @@ namespace MobileGL::MG_Impl::GLImpl { MG_Util::ConvertGLEnumToString(origFormat))); return; } - const TextureInternalFormat viewInternalFormat = - MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); + const TextureInternalFormat viewInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); if (!TextureImpl::ValidateTextureInternalFormat(viewInternalFormat)) return; // i)/j) the range checks, both against the ORIGINAL's view state rather than its raw @@ -6188,9 +6132,9 @@ namespace MobileGL::MG_Impl::GLImpl { const Uint composedMinLayer = minlayer + origTextureObject->GetViewMinLayer(); const Uint composedNumLayers = std::min(numlayers, origNumLayers - minlayer); - const auto& viewObject = MG_State::pGLContext->CreateTextureViewObject( - texture, viewTarget, storageOwner, composedMinLevel, composedNumLevels, composedMinLayer, - composedNumLayers); + const auto& viewObject = + MG_State::pGLContext->CreateTextureViewObject(texture, viewTarget, storageOwner, composedMinLevel, + composedNumLevels, composedMinLayer, composedNumLayers); if (!viewObject) { RecordTextureViewError(ErrorCode::InvalidOperation, "Failed to create the texture view object."); return; @@ -6309,8 +6253,8 @@ namespace MobileGL::MG_Impl::GLImpl { SeedImmutableViewState(textureObject, 1); } - void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLboolean fixedsamplelocations) { + void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, + GLboolean fixedsamplelocations) { const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); if (!ValidateTextureMutable(activeUnit.GetBindingSlot(textureTarget).GetBoundObject(), __func__)) return; @@ -6320,16 +6264,16 @@ namespace MobileGL::MG_Impl::GLImpl { __func__); } - void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) { + void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLboolean fixedsamplelocations) { const TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); if (!ValidateTextureMutable(activeUnit.GetBindingSlot(textureTarget).GetBoundObject(), __func__)) return; if (!ValidateTexStorageMultisampleSize(width, height, depth, __func__)) return; - TexStorageMultisample_State(target, - TexImage3DMultisample_State(target, samples, internalformat, width, height, depth, - fixedsamplelocations), - __func__); + TexStorageMultisample_State( + target, + TexImage3DMultisample_State(target, samples, internalformat, width, height, depth, fixedsamplelocations), + __func__); } void TextureSubImage1D(GLuint texture, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, @@ -6360,8 +6304,8 @@ namespace MobileGL::MG_Impl::GLImpl { auto textureUploadTarget = GetPrimaryUploadTarget(textureObject); if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; - if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureObject->GetFormat(), - texturePixelDataType)) + if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput( + textureInputFormat, textureObject->GetFormat(), texturePixelDataType)) return; auto* textureMipmapObject = static_cast(textureObject.get()); @@ -6383,8 +6327,8 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGL_SYNC_PIXEL_UNPACK_SOURCE(pixelUnpackBufferObject); - originalPixels = reinterpret_cast(pixelUnpackBufferObject->MappedData()) + - reinterpret_cast(pixels); + originalPixels = + reinterpret_cast(pixelUnpackBufferObject->MappedData()) + reinterpret_cast(pixels); } if (!originalPixels) { MG_State::pGLContext->RecordError( @@ -6412,8 +6356,8 @@ namespace MobileGL::MG_Impl::GLImpl { Uint8* destData = static_cast(textureMipmapObject->MapMipmapData(textureUploadTarget, level)); if (destData) { for (GLsizei y = 0; y < height; ++y) { - const SizeT destRowOffset = static_cast(yoffset + y) * destRowSize + - static_cast(xoffset) * internalBpp; + const SizeT destRowOffset = + static_cast(yoffset + y) * destRowSize + static_cast(xoffset) * internalBpp; const SizeT srcRowOffset = static_cast(y) * srcRowSize; Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize); } @@ -6474,32 +6418,32 @@ namespace MobileGL::MG_Impl::GLImpl { if (!params) return; ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameterfv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { TexParameterfv_State(target, pname, params); }); } void TextureParameteriv(GLuint texture, GLenum pname, const GLint* params) { if (!params) return; ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { TexParameteriv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { TexParameteriv_State(target, pname, params); }); } void TextureParameterIiv(GLuint texture, GLenum pname, const GLint* params) { if (!params) return; ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { - TexParameterIiv_State(target, pname, params); - }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { TexParameterIiv_State(target, pname, params); }); } void TextureParameterIuiv(GLuint texture, GLenum pname, const GLuint* params) { if (!params) return; ScopedNamedTextureParameterCall namedCall; auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { - TexParameterIuiv_State(target, pname, params); - }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { TexParameterIuiv_State(target, pname, params); }); } void BindTextureUnit(GLuint unit, GLuint texture) { @@ -6570,7 +6514,7 @@ namespace MobileGL::MG_Impl::GLImpl { // Deliberately PER ELEMENT: the extension defines these calls as a loop, so a bad entry // costs its own unit and leaves the rest of the range bound. SharedPtr ResolveMultiBindTexture(GLuint texture, GLsizei index, - const char* funcName) { + const char* funcName) { SharedPtr textureObject = MG_State::pGLContext->GetTextureObject(texture); if (!textureObject) { @@ -6667,8 +6611,8 @@ namespace MobileGL::MG_Impl::GLImpl { TextureUploadTarget uploadTarget, GLint level, GLenum format, GLenum type, GLsizei bufSize, void* pixels, const char* caller) { MGP_FILL(GetTextureImage); - MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, - bufSize, pixels); + MG_Backend::gBackendFunctionsTable.GL.GetTextureImage(textureObject, uploadTarget, level, format, type, bufSize, + pixels); return; } @@ -6676,16 +6620,13 @@ namespace MobileGL::MG_Impl::GLImpl { auto textureObject = GetTextureObjectByName(texture, __func__); if (!textureObject) return; if (!ValidateTextureImageQuery(textureObject, level, MG_Util::ConvertGLEnumToTextureInputFormat(format), - MG_Util::ConvertGLEnumToTexturePixelDataType(type), bufSize, pixels, - __func__)) { + MG_Util::ConvertGLEnumToTexturePixelDataType(type), bufSize, pixels, __func__)) { return; } auto uploadTarget = GetPrimaryUploadTarget(textureObject); // Unlike glGetTexImage's face target, this DSA call reads all six faces. - if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) - uploadTarget = TextureUploadTarget::Unknown; - GetTextureImageForUploadTarget(textureObject, uploadTarget, level, format, type, - bufSize, pixels, __func__); + if (textureObject->GetTarget() == TextureTarget::TextureCubeMap) uploadTarget = TextureUploadTarget::Unknown; + GetTextureImageForUploadTarget(textureObject, uploadTarget, level, format, type, bufSize, pixels, __func__); } void GetCompressedTextureImage(GLuint texture, GLint level, GLsizei bufSize, void* pixels) { @@ -6708,9 +6649,8 @@ namespace MobileGL::MG_Impl::GLImpl { // height, a 2D array's or cube map array's layer(-face)s its depth, and a cube map's six faces // its depth too. static Bool ValidateTextureSubImageRegion(const SharedPtr& textureObject, - GLint level, GLint xoffset, GLint yoffset, GLint zoffset, - GLsizei width, GLsizei height, GLsizei depth, IntVec3& levelExtent, - const char* caller) { + GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, + GLsizei height, GLsizei depth, IntVec3& levelExtent, const char* caller) { const auto recordError = [caller](ErrorCode code, const char* message) { MG_State::pGLContext->RecordError(code, MakeUnique("MG_Impl/GLImpl", caller, message)); return false; @@ -6718,8 +6658,7 @@ namespace MobileGL::MG_Impl::GLImpl { const auto target = textureObject->GetTarget(); if (textureObject->GetStorageType() != TextureStorageType::Mipmap || target == TextureTarget::Texture2DMultisample || target == TextureTarget::Texture2DMultisampleArray) { - return recordError(ErrorCode::InvalidOperation, - "The effective target is a buffer or multisample texture."); + return recordError(ErrorCode::InvalidOperation, "The effective target is a buffer or multisample texture."); } if (level < 0 || xoffset < 0 || yoffset < 0 || zoffset < 0 || width < 0 || height < 0 || depth < 0) { return recordError(ErrorCode::InvalidValue, "Texture sub-image range is invalid."); @@ -6737,8 +6676,8 @@ namespace MobileGL::MG_Impl::GLImpl { return recordError(ErrorCode::InvalidValue, "This texture target needs zoffset 0 and depth 1."); } - levelExtent = textureMipmapObject->GetMipmapTexelSize(GetPrimaryUploadTarget(textureObject), - static_cast(level)); + levelExtent = + textureMipmapObject->GetMipmapTexelSize(GetPrimaryUploadTarget(textureObject), static_cast(level)); // A cube map's level is stored per face, so its own extent reads z = 1; the six faces are // what zoffset/depth address. if (target == TextureTarget::TextureCubeMap) levelExtent.z() = 6; @@ -6778,8 +6717,8 @@ namespace MobileGL::MG_Impl::GLImpl { TextureUploadTarget readUploadTarget = GetPrimaryUploadTarget(textureObject); if (isSingleCubeFaceRead) { - readUploadTarget = static_cast( - static_cast(TextureUploadTarget::CubeMapPositiveX) + zoffset); + readUploadTarget = + static_cast(static_cast(TextureUploadTarget::CubeMapPositiveX) + zoffset); } else if (isCubeMap) { readUploadTarget = TextureUploadTarget::Unknown; // all six faces, as glGetTextureImage reads them } @@ -6788,8 +6727,7 @@ namespace MobileGL::MG_Impl::GLImpl { isSingleCubeFaceRead ? 1u : 0u)) { return; } - GetTextureImageForUploadTarget(textureObject, readUploadTarget, level, format, type, bufSize, pixels, - __func__); + GetTextureImageForUploadTarget(textureObject, readUploadTarget, level, format, type, bufSize, pixels, __func__); } // GL 4.6 core 8.11.4. Like glGetCompressedTextureImage this answers out of the CPU shadow of the @@ -6888,39 +6826,41 @@ namespace MobileGL::MG_Impl::GLImpl { void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) { auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return; - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { GetTexParameteriv_State(target, pname, params); }); } void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) { auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return; - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { GetTexParameterfv_State(target, pname, params); }); } void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) { auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return; - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); }); } void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) { auto textureObject = GetTextureObjectByName(texture, __func__); if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return; - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); }); + WithTemporarilyBoundNamedTexture(textureObject, + [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); }); } void GetTextureLevelParameteriv(GLuint texture, GLint level, GLenum pname, GLint* params) { auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { - GetTexLevelParameteriv_State(target, level, pname, params); - }); + WithTemporarilyBoundNamedTexture( + textureObject, [&](GLenum target) { GetTexLevelParameteriv_State(target, level, pname, params); }); } void GetTextureLevelParameterfv(GLuint texture, GLint level, GLenum pname, GLfloat* params) { auto textureObject = GetTextureObjectByName(texture, __func__); - WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { - GetTexLevelParameterfv_State(target, level, pname, params); - }); + WithTemporarilyBoundNamedTexture( + textureObject, [&](GLenum target) { GetTexLevelParameterfv_State(target, level, pname, params); }); } void BindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum access, @@ -7069,7 +7009,8 @@ namespace MobileGL::MG_Impl::GLImpl { GLenum preferredInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(textureInternalFormat); GLenum imageFormat = GL_RGBA; GLenum imageType = GL_UNSIGNED_BYTE; - MG_Util::TextureFormatProcessor::NormalizePixelFormat(preferredInternalFormat, PixelFormatNormalizeOptionBit::None, + MG_Util::TextureFormatProcessor::NormalizePixelFormat(preferredInternalFormat, + PixelFormatNormalizeOptionBit::None, &preferredInternalFormat, &imageFormat, &imageType); auto writeValues = [&](std::initializer_list values) { @@ -7231,9 +7172,8 @@ namespace MobileGL::MG_Impl::GLImpl { return; default: MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "pname is not supported by GetInternalformativ.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "pname is not supported by GetInternalformativ.")); return; } } @@ -7263,8 +7203,8 @@ namespace MobileGL::MG_Impl::GLImpl { // answering the pixel centre. Float position[2] = {0.5f, 0.5f}; if (MG_Backend::pActiveBackendObject != nullptr) { - MG_Backend::pActiveBackendObject->GetDynamicParameters().GetSamplePosition( - samples, static_cast(index), position); + MG_Backend::pActiveBackendObject->GetDynamicParameters().GetSamplePosition(samples, static_cast(index), + position); } val[0] = position[0]; val[1] = position[1]; @@ -7578,12 +7518,12 @@ namespace MobileGL::MG_Impl::GLImpl { }; const MG_Backend::CopyImageEndpoint src = resolveEndpoint(srcName, srcTarget); const MG_Backend::CopyImageEndpoint dst = resolveEndpoint(dstName, dstTarget); - if (!ValidateCopyImageSubData_State(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, - dstLevel, dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth)) { + if (!ValidateCopyImageSubData_State(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX, + dstY, dstZ, srcWidth, srcHeight, srcDepth)) { return; } - CopyImageSubData_Backend(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, - dstX, dstY, dstZ, srcWidth, srcHeight, srcDepth); + CopyImageSubData_Backend(src, srcTarget, srcLevel, srcX, srcY, srcZ, dst, dstTarget, dstLevel, dstX, dstY, dstZ, + srcWidth, srcHeight, srcDepth); } void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, diff --git a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h index 7993e70d..f3af9361 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.h @@ -88,10 +88,10 @@ namespace MobileGL::MG_Impl::GLImpl { void TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); void TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth); - void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLboolean fixedsamplelocations); - void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, - GLsizei height, GLsizei depth, GLboolean fixedsamplelocations); + void TexStorage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, + GLboolean fixedsamplelocations); + void TexStorage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, + GLsizei depth, GLboolean fixedsamplelocations); void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels); void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, diff --git a/MobileGL/MG_Impl/GLImpl/Texture/MipmapGenerationPlan.h b/MobileGL/MG_Impl/GLImpl/Texture/MipmapGenerationPlan.h index 59677d12..5d342f36 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/MipmapGenerationPlan.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/MipmapGenerationPlan.h @@ -20,8 +20,8 @@ namespace MobileGL::MG_Impl::GLImpl { Uint End = 0; // logical end-exclusive, including the source base level }; - inline MipmapGenerationRange ComputeMipmapGenerationRange( - const MG_State::GLState::TextureObjectMipmap& texture, TextureUploadTarget uploadTarget) { + inline MipmapGenerationRange ComputeMipmapGenerationRange(const MG_State::GLState::TextureObjectMipmap& texture, + TextureUploadTarget uploadTarget) { const auto range = texture.GetLevelRange(); MipmapGenerationRange result{range.x(), range.x()}; const Uint available = texture.GetMipmapLevelCount(); @@ -29,18 +29,19 @@ namespace MobileGL::MG_Impl::GLImpl { const auto extent = texture.GetMipmapTexelSize(uploadTarget, result.Base); if (extent.x() <= 0 || extent.y() <= 0 || extent.z() <= 0) return result; Int largest = extent.x(); - if (texture.GetTarget() != TextureTarget::Texture1D && - texture.GetTarget() != TextureTarget::Texture1DArray) + if (texture.GetTarget() != TextureTarget::Texture1D && texture.GetTarget() != TextureTarget::Texture1DArray) largest = std::max(largest, extent.y()); if (texture.GetTarget() == TextureTarget::Texture3D) largest = std::max(largest, extent.z()); Uint levels = 1; - while (largest > 1) { largest /= 2; ++levels; } - Uint64 end = std::min(static_cast(result.Base) + levels, - static_cast(range.y()) + 1); + while (largest > 1) { + largest /= 2; + ++levels; + } + Uint64 end = std::min(static_cast(result.Base) + levels, static_cast(range.y()) + 1); // A view's level count is already relative to its own window. Neither // immutable storage nor a view may acquire levels beyond that allocation. if (texture.IsImmutable()) end = std::min(end, static_cast(available)); result.End = static_cast(end); return result; } -} +} // namespace MobileGL::MG_Impl::GLImpl diff --git a/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp b/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp index e8acf306..0ebe0479 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/Texture/Validators.cpp @@ -208,53 +208,53 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { Bool IsIntegerColorInternalFormat(TextureInternalFormat internalFormat) { switch (internalFormat) { - case TextureInternalFormat::R8I: - case TextureInternalFormat::R8UI: - case TextureInternalFormat::R16I: - case TextureInternalFormat::R16UI: - case TextureInternalFormat::R32I: - case TextureInternalFormat::R32UI: - case TextureInternalFormat::RG8I: - case TextureInternalFormat::RG8UI: - case TextureInternalFormat::RG16I: - case TextureInternalFormat::RG16UI: - case TextureInternalFormat::RG32I: - case TextureInternalFormat::RG32UI: - case TextureInternalFormat::RGB8I: - case TextureInternalFormat::RGB8UI: - case TextureInternalFormat::RGB16I: - case TextureInternalFormat::RGB16UI: - case TextureInternalFormat::RGB32I: - case TextureInternalFormat::RGB32UI: - case TextureInternalFormat::RGBA8I: - case TextureInternalFormat::RGBA8UI: - case TextureInternalFormat::RGBA16I: - case TextureInternalFormat::RGBA16UI: - case TextureInternalFormat::RGBA32I: - case TextureInternalFormat::RGBA32UI: - case TextureInternalFormat::RGB10A2UI: - return true; - default: - return false; + case TextureInternalFormat::R8I: + case TextureInternalFormat::R8UI: + case TextureInternalFormat::R16I: + case TextureInternalFormat::R16UI: + case TextureInternalFormat::R32I: + case TextureInternalFormat::R32UI: + case TextureInternalFormat::RG8I: + case TextureInternalFormat::RG8UI: + case TextureInternalFormat::RG16I: + case TextureInternalFormat::RG16UI: + case TextureInternalFormat::RG32I: + case TextureInternalFormat::RG32UI: + case TextureInternalFormat::RGB8I: + case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::RGB16I: + case TextureInternalFormat::RGB16UI: + case TextureInternalFormat::RGB32I: + case TextureInternalFormat::RGB32UI: + case TextureInternalFormat::RGBA8I: + case TextureInternalFormat::RGBA8UI: + case TextureInternalFormat::RGBA16I: + case TextureInternalFormat::RGBA16UI: + case TextureInternalFormat::RGBA32I: + case TextureInternalFormat::RGBA32UI: + case TextureInternalFormat::RGB10A2UI: + return true; + default: + return false; } } static Bool IsDepthLikeInternalFormat(TextureInternalFormat internalFormat) { switch (internalFormat) { - case TextureInternalFormat::DepthComponent: - case TextureInternalFormat::DepthComponent16: - case TextureInternalFormat::DepthComponent24: - case TextureInternalFormat::DepthComponent32: // not core, kept for Minecraft 1.21.5+ - case TextureInternalFormat::DepthComponent32F: - case TextureInternalFormat::Depth24Stencil8: - case TextureInternalFormat::Depth32FStencil8: - case TextureInternalFormat::DepthStencil: - // Stencil-only is not a colour format either: a colour client format read against a - // STENCIL_INDEX8 texture has to be the same INVALID_OPERATION as against a depth one. - case TextureInternalFormat::StencilIndex8: - return true; - default: - return false; + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent16: + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: // not core, kept for Minecraft 1.21.5+ + case TextureInternalFormat::DepthComponent32F: + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: + case TextureInternalFormat::DepthStencil: + // Stencil-only is not a colour format either: a colour client format read against a + // STENCIL_INDEX8 texture has to be the same INVALID_OPERATION as against a depth one. + case TextureInternalFormat::StencilIndex8: + return true; + default: + return false; } } @@ -339,8 +339,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { // pairs with stencil-only storage: against a depth, depth-stencil or colour internal format // STENCIL_INDEX keeps the pre-4.4 answer (GL CTS packed_pixels feeds exactly that pairing // and expects INVALID_OPERATION). - if (format == TextureInputFormat::StencilIndex && - internalFormat != TextureInternalFormat::StencilIndex8) { + if (format == TextureInputFormat::StencilIndex && internalFormat != TextureInternalFormat::StencilIndex8) { return recordInvalidOperation("STENCIL_INDEX requires a stencil-only internal format"); } @@ -396,8 +395,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { // dereference a backend texture it never created. MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, - "Texture has no mipmap levels to address.")); + MakeUnique("MG_Impl/GLImpl", caller, "Texture has no mipmap levels to address.")); return false; } @@ -423,15 +421,13 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { // copy names is an incomplete texture. MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, - MakeUnique("MG_Impl/GLImpl", caller, - "Texture has no image defined at any level.")); + MakeUnique("MG_Impl/GLImpl", caller, "Texture has no image defined at any level.")); return false; } if (level < 0 || static_cast(level) >= levelCount) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", caller, - "Texture level does not exist in this texture.")); + ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", caller, + "Texture level does not exist in this texture.")); return false; } return true; @@ -592,8 +588,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { MakeUnique( "MG_Impl/GLImpl", "ValidateCopyImageFormatCompatibility", std::format("The two compressed images have different block dimensions ({}x{} vs. {}x{}).", - srcBlock.blockWidth, srcBlock.blockHeight, dstBlock.blockWidth, - dstBlock.blockHeight))); + srcBlock.blockWidth, srcBlock.blockHeight, dstBlock.blockWidth, dstBlock.blockHeight))); return false; } return true; diff --git a/MobileGL/MG_Impl/GLImpl/Texture/Validators.h b/MobileGL/MG_Impl/GLImpl/Texture/Validators.h index ef2fe37f..e48e45ad 100644 --- a/MobileGL/MG_Impl/GLImpl/Texture/Validators.h +++ b/MobileGL/MG_Impl/GLImpl/Texture/Validators.h @@ -76,8 +76,7 @@ namespace MobileGL::MG_Impl::GLImpl::TextureImpl { // blocks are the same SIZE - not that they share a base internal format. RGBA32UI into // RGBA32F is legal (both 128-bit) while RGBA8 into RGBA32F is not, and a compressed image // pairs with an uncompressed one whose texel is as big as the compressed block. - Bool ValidateCopyImageFormatCompatibility(const CopyImageTexelBlock& srcBlock, - const CopyImageTexelBlock& dstBlock); + Bool ValidateCopyImageFormatCompatibility(const CopyImageTexelBlock& srcBlock, const CopyImageTexelBlock& dstBlock); // GL 4.6 core 18.3.2: for a compressed image the region's origin must sit on a block // boundary and its size must be a whole number of blocks - unless the edge it runs to is // the edge of the image. diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp index 29b07372..5d07ce64 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/GL_VertexArray.cpp @@ -26,19 +26,19 @@ namespace MobileGL::MG_Impl::GLImpl { // form here -- it is not GL 3.3 Core. Unsigned normalization (f = c/(2^b-1)) is unchanged. // The bit width fixes the arithmetic type: 8/16-bit stay exact in int/float, but the 32-bit forms // must use double because 2*INT_MAX overflows int32 and neither 2^32-1 nor 2^31-1 is float-exact. - constexpr GLfloat NormalizeSignedByte(GLbyte c) { // b = 8, divisor 2^8 - 1 = 255 + constexpr GLfloat NormalizeSignedByte(GLbyte c) { // b = 8, divisor 2^8 - 1 = 255 return (2 * static_cast(c) + 1) / 255.0f; } constexpr GLfloat NormalizeSignedShort(GLshort c) { // b = 16, divisor 2^16 - 1 = 65535 return (2 * static_cast(c) + 1) / 65535.0f; } - constexpr GLfloat NormalizeSignedInt(GLint c) { // b = 32, divisor 2^32 - 1 (double!) + constexpr GLfloat NormalizeSignedInt(GLint c) { // b = 32, divisor 2^32 - 1 (double!) return static_cast((2.0 * static_cast(c) + 1.0) / 4294967295.0); } constexpr GLfloat NormalizeUnsignedShort(GLushort c) { // b = 16 return static_cast(c) / 65535.0f; } - constexpr GLfloat NormalizeUnsignedInt(GLuint c) { // b = 32 (double!) + constexpr GLfloat NormalizeUnsignedInt(GLuint c) { // b = 32 (double!) return static_cast(static_cast(c) / 4294967295.0); } // (b = 8 unsigned normalization is VertexAttrib4Nub's x * (1/255).) @@ -232,8 +232,7 @@ namespace MobileGL::MG_Impl::GLImpl { } } // namespace - SharedPtr GetNamedVertexArrayObject_State(GLuint vaobj, - const char* caller) { + SharedPtr GetNamedVertexArrayObject_State(GLuint vaobj, const char* caller) { // Name zero is not a vertex array object in a core profile: it names the default vertex // array, which the by-name (direct state access) entry points never accept. MobileGL keeps a // real object at index 0 for the compatibility paths, so the generic name validation below @@ -326,7 +325,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return; DataType dataType = MG_Util::ConvertGLEnumToDataType(type); - if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false)) + if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, + false)) return; auto& vao = MG_State::pGLContext->GetBoundVertexArray(); @@ -457,8 +457,7 @@ namespace MobileGL::MG_Impl::GLImpl { if (static_cast(stride) > VertexArrayImpl::GetMaxVertexAttribStride()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, - MakeUnique("MG_Impl/GLImpl", caller, - "stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE.")); + MakeUnique("MG_Impl/GLImpl", caller, "stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE.")); return; } auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller); @@ -505,8 +504,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return; const Bool isBgra = (size == static_cast(GL_BGRA)); - vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger, - relativeoffset, isBgra); + vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger, relativeoffset, + isBgra); } // The long (64-bit) attribute format: the values reach the shader as doubles, unconverted @@ -526,8 +525,7 @@ namespace MobileGL::MG_Impl::GLImpl { // MG_Util/SelfTest/DriverPost.cpp; the draw-side narrowing is DirectGLES/Managers.cpp and, on // DirectVulkan, VertexInputStateFactory's Float64 case. static void VertexAttribLFormatSeparate_State(const SharedPtr& vao, - GLuint attribindex, GLint size, GLenum type, - GLuint relativeoffset) { + GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) { if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return; if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return; if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return; @@ -535,11 +533,11 @@ namespace MobileGL::MG_Impl::GLImpl { if (!MG_Backend::pActiveBackendObject || !MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) { MGLOG_W_ONCE("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this " - "backend has no double-precision vertex attribute support - the format is recorded " - "and queryable, and the array is FETCHED AT FLOAT32 PRECISION at draw (the same " - "narrowing the shader's dvec inputs already get); see the \"64-bit vertex " - "attributes\" / \"shaderFloat64\" POST row for what that costs", - attribindex); + "backend has no double-precision vertex attribute support - the format is recorded " + "and queryable, and the array is FETCHED AT FLOAT32 PRECISION at draw (the same " + "narrowing the shader's dvec inputs already get); see the \"64-bit vertex " + "attributes\" / \"shaderFloat64\" POST row for what that costs", + attribindex); } vao->SetAttributeFormatSeparate(attribindex, size, MG_Util::ConvertGLEnumToDataType(type), @@ -547,8 +545,8 @@ namespace MobileGL::MG_Impl::GLImpl { /*isBgra: */ false, /*isLong: */ true); } - void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, - GLboolean normalized, GLuint relativeoffset) { + void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, + GLuint relativeoffset) { auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribFormat_State"); if (!vao) return; VertexAttribFormatSeparate_State(vao, attribindex, size, type, normalized, relativeoffset, false, @@ -693,8 +691,8 @@ namespace MobileGL::MG_Impl::GLImpl { // path), so they take the float current-value funnel. The single packed word is always fully // decoded, but only the first `componentCount` components are written; the rest keep the generic // attribute defaults (0, 0, 0, 1). type must be one of the two 2_10_10_10_REV packed enums. - static void VertexAttribP_Common(GLuint index, GLenum type, GLboolean normalized, GLuint value, - int componentCount, const char* funcName) { + static void VertexAttribP_Common(GLuint index, GLenum type, GLboolean normalized, GLuint value, int componentCount, + const char* funcName) { if (!ValidateCurrentVertexAttribIndex(index, funcName)) return; bool signedType; @@ -714,7 +712,8 @@ namespace MobileGL::MG_Impl::GLImpl { const Array decoded = DecodePacked2101010(value, signedType, normalized == GL_TRUE); Array out = {0.0f, 0.0f, 0.0f, 1.0f}; - for (int i = 0; i < componentCount; ++i) out[i] = decoded[i]; + for (int i = 0; i < componentCount; ++i) + out[i] = decoded[i]; MG_State::pGLContext->SetCurrentVertexAttributeFloat(index, out); } @@ -802,14 +801,16 @@ namespace MobileGL::MG_Impl::GLImpl { // components are 0, unspecified w is 1 (integer 1 for the I* forms). #define MG_ATTRIB_NULL_GUARD(ptr) \ if (!(ptr)) { \ - MG_State::pGLContext->RecordError( \ + MG_State::pGLContext->RecordError( \ ErrorCode::InvalidValue, \ - MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); \ + MakeUnique("MG_Impl/GLImpl", __func__, "value pointer cannot be null.")); \ return; \ } // Family A -- GLdouble, value-preserving narrowing to float. - void VertexAttrib1d(GLuint index, GLdouble x) { VertexAttrib4f(index, static_cast(x), 0.0f, 0.0f, 1.0f); } + void VertexAttrib1d(GLuint index, GLdouble x) { + VertexAttrib4f(index, static_cast(x), 0.0f, 0.0f, 1.0f); + } void VertexAttrib2d(GLuint index, GLdouble x, GLdouble y) { VertexAttrib4f(index, static_cast(x), static_cast(y), 0.0f, 1.0f); } @@ -820,8 +821,12 @@ namespace MobileGL::MG_Impl::GLImpl { VertexAttrib4f(index, static_cast(x), static_cast(y), static_cast(z), static_cast(w)); } - void VertexAttrib1dv(GLuint index, const GLdouble* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib1d(index, v[0]); } - void VertexAttrib2dv(GLuint index, const GLdouble* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib2d(index, v[0], v[1]); } + void VertexAttrib1dv(GLuint index, const GLdouble* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttrib1d(index, v[0]); + } + void VertexAttrib2dv(GLuint index, const GLdouble* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttrib2d(index, v[0], v[1]); + } void VertexAttrib3dv(GLuint index, const GLdouble* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib3d(index, v[0], v[1], v[2]); } @@ -830,7 +835,9 @@ namespace MobileGL::MG_Impl::GLImpl { } // Family A -- GLshort, value-preserving (sign kept), NOT normalized. - void VertexAttrib1s(GLuint index, GLshort x) { VertexAttrib4f(index, static_cast(x), 0.0f, 0.0f, 1.0f); } + void VertexAttrib1s(GLuint index, GLshort x) { + VertexAttrib4f(index, static_cast(x), 0.0f, 0.0f, 1.0f); + } void VertexAttrib2s(GLuint index, GLshort x, GLshort y) { VertexAttrib4f(index, static_cast(x), static_cast(y), 0.0f, 1.0f); } @@ -841,8 +848,12 @@ namespace MobileGL::MG_Impl::GLImpl { VertexAttrib4f(index, static_cast(x), static_cast(y), static_cast(z), static_cast(w)); } - void VertexAttrib1sv(GLuint index, const GLshort* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib1s(index, v[0]); } - void VertexAttrib2sv(GLuint index, const GLshort* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib2s(index, v[0], v[1]); } + void VertexAttrib1sv(GLuint index, const GLshort* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttrib1s(index, v[0]); + } + void VertexAttrib2sv(GLuint index, const GLshort* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttrib2s(index, v[0], v[1]); + } void VertexAttrib3sv(GLuint index, const GLshort* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttrib3s(index, v[0], v[1], v[2]); } @@ -890,8 +901,8 @@ namespace MobileGL::MG_Impl::GLImpl { } void VertexAttrib4Nusv(GLuint index, const GLushort* v) { MG_ATTRIB_NULL_GUARD(v) - VertexAttrib4f(index, NormalizeUnsignedShort(v[0]), NormalizeUnsignedShort(v[1]), - NormalizeUnsignedShort(v[2]), NormalizeUnsignedShort(v[3])); + VertexAttrib4f(index, NormalizeUnsignedShort(v[0]), NormalizeUnsignedShort(v[1]), NormalizeUnsignedShort(v[2]), + NormalizeUnsignedShort(v[3])); } void VertexAttrib4Nuiv(GLuint index, const GLuint* v) { MG_ATTRIB_NULL_GUARD(v) @@ -901,18 +912,36 @@ namespace MobileGL::MG_Impl::GLImpl { // Family C -- pure integer forms. Signed -> VertexAttribI4i (sign-extend), unsigned -> // VertexAttribI4ui (zero-extend). w defaults to the integer 1 / 1u. Never touches the float view. - void VertexAttribI1i(GLuint index, GLint x) { VertexAttribI4i(index, x, 0, 0, 1); } - void VertexAttribI2i(GLuint index, GLint x, GLint y) { VertexAttribI4i(index, x, y, 0, 1); } - void VertexAttribI3i(GLuint index, GLint x, GLint y, GLint z) { VertexAttribI4i(index, x, y, z, 1); } - void VertexAttribI1ui(GLuint index, GLuint x) { VertexAttribI4ui(index, x, 0u, 0u, 1u); } - void VertexAttribI2ui(GLuint index, GLuint x, GLuint y) { VertexAttribI4ui(index, x, y, 0u, 1u); } - void VertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z) { VertexAttribI4ui(index, x, y, z, 1u); } - void VertexAttribI1iv(GLuint index, const GLint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI1i(index, v[0]); } - void VertexAttribI2iv(GLuint index, const GLint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI2i(index, v[0], v[1]); } + void VertexAttribI1i(GLuint index, GLint x) { + VertexAttribI4i(index, x, 0, 0, 1); + } + void VertexAttribI2i(GLuint index, GLint x, GLint y) { + VertexAttribI4i(index, x, y, 0, 1); + } + void VertexAttribI3i(GLuint index, GLint x, GLint y, GLint z) { + VertexAttribI4i(index, x, y, z, 1); + } + void VertexAttribI1ui(GLuint index, GLuint x) { + VertexAttribI4ui(index, x, 0u, 0u, 1u); + } + void VertexAttribI2ui(GLuint index, GLuint x, GLuint y) { + VertexAttribI4ui(index, x, y, 0u, 1u); + } + void VertexAttribI3ui(GLuint index, GLuint x, GLuint y, GLuint z) { + VertexAttribI4ui(index, x, y, z, 1u); + } + void VertexAttribI1iv(GLuint index, const GLint* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttribI1i(index, v[0]); + } + void VertexAttribI2iv(GLuint index, const GLint* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttribI2i(index, v[0], v[1]); + } void VertexAttribI3iv(GLuint index, const GLint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI3i(index, v[0], v[1], v[2]); } - void VertexAttribI1uiv(GLuint index, const GLuint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI1ui(index, v[0]); } + void VertexAttribI1uiv(GLuint index, const GLuint* v) { + MG_ATTRIB_NULL_GUARD(v) VertexAttribI1ui(index, v[0]); + } void VertexAttribI2uiv(GLuint index, const GLuint* v) { MG_ATTRIB_NULL_GUARD(v) VertexAttribI2ui(index, v[0], v[1]); } @@ -1155,9 +1184,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return; if (pname != GL_VERTEX_ATTRIB_ARRAY_POINTER) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "pname must be GL_VERTEX_ATTRIB_ARRAY_POINTER.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "pname must be GL_VERTEX_ATTRIB_ARRAY_POINTER.")); return; } @@ -1232,9 +1260,8 @@ namespace MobileGL::MG_Impl::GLImpl { if (!vao || !param) return; if (pname != GL_ELEMENT_ARRAY_BUFFER_BINDING) { MG_State::pGLContext->RecordError( - ErrorCode::InvalidEnum, - MakeUnique("MG_Impl/GLImpl", __func__, - "pname must be GL_ELEMENT_ARRAY_BUFFER_BINDING.")); + ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, + "pname must be GL_ELEMENT_ARRAY_BUFFER_BINDING.")); return; } const auto& indexBuffer = vao->GetIndexBufferBindingSlot().GetBoundObject(); diff --git a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp index adeae59f..9e340124 100644 --- a/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp +++ b/MobileGL/MG_Impl/GLImpl/VertexArray/Validators.cpp @@ -235,17 +235,15 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique( - "MG_Impl/GLImpl", fn, - std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index))); + "MG_Impl/GLImpl", fn, std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index))); return false; } // GL 4.6 core 10.3.2: the long form takes GL_DOUBLE and nothing else. if (type != GL_DOUBLE) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, - MakeUnique( - "MG_Impl/GLImpl", fn, - std::format("Type 0x{:X} is not GL_DOUBLE (attribute {}).", type, index))); + MakeUnique("MG_Impl/GLImpl", fn, + std::format("Type 0x{:X} is not GL_DOUBLE (attribute {}).", type, index))); return false; } return true; diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp index f7b0d96f..f3ccc74c 100644 --- a/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/Definitions.cpp @@ -64,8 +64,7 @@ MOBILEGL_GLX_API void** glXGetFBConfigs(Display* dpy, int screen, int* nelements return GLXImpl::GetFBConfigs(dpy, screen, nelements); } -MOBILEGL_GLX_API void** glXChooseFBConfig(Display* dpy, int screen, const int* attribList, - int* nelements) { +MOBILEGL_GLX_API void** glXChooseFBConfig(Display* dpy, int screen, const int* attribList, int* nelements) { GLXStreamLock streamLock; return GLXImpl::ChooseFBConfig(dpy, screen, attribList, nelements); } @@ -95,14 +94,13 @@ MOBILEGL_GLX_API void* glXCreateContext(Display* dpy, void* visualInfo, void* sh return GLXImpl::CreateContext(dpy, visualInfo, shareList, direct); } -MOBILEGL_GLX_API void* glXCreateNewContext(Display* dpy, void* config, int renderType, - void* shareList, int direct) { +MOBILEGL_GLX_API void* glXCreateNewContext(Display* dpy, void* config, int renderType, void* shareList, int direct) { GLXStreamLock streamLock; return GLXImpl::CreateNewContext(dpy, config, renderType, shareList, direct); } -MOBILEGL_GLX_API void* glXCreateContextAttribsARB(Display* dpy, void* config, void* shareContext, - int direct, const int* attribList) { +MOBILEGL_GLX_API void* glXCreateContextAttribsARB(Display* dpy, void* config, void* shareContext, int direct, + const int* attribList) { GLXStreamLock streamLock; return GLXImpl::CreateContextAttribsARB(dpy, config, shareContext, direct, attribList); } @@ -117,8 +115,7 @@ MOBILEGL_GLX_API int glXMakeCurrent(Display* dpy, unsigned long drawable, void* return GLXImpl::MakeCurrent(dpy, drawable, context); } -MOBILEGL_GLX_API int glXMakeContextCurrent(Display* dpy, unsigned long draw, unsigned long read, - void* context) { +MOBILEGL_GLX_API int glXMakeContextCurrent(Display* dpy, unsigned long draw, unsigned long read, void* context) { GLXStreamLock streamLock; return GLXImpl::MakeContextCurrent(dpy, draw, read, context); } @@ -179,8 +176,7 @@ MOBILEGL_GLX_API int glXQueryContext(Display* dpy, void* context, int attribute, return GLXImpl::QueryContext(dpy, context, attribute, value); } -MOBILEGL_GLX_API void glXQueryDrawable(Display* dpy, unsigned long drawable, int attribute, - unsigned int* value) { +MOBILEGL_GLX_API void glXQueryDrawable(Display* dpy, unsigned long drawable, int attribute, unsigned int* value) { GLXStreamLock streamLock; GLXImpl::QueryDrawable(dpy, drawable, attribute, value); } diff --git a/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp b/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp index 3a2b75aa..cae4307f 100644 --- a/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp +++ b/MobileGL/MG_Impl/GLXImpl/Exporting/GlvndVendor.cpp @@ -68,7 +68,9 @@ namespace { const GLXApiExports* g_glxExports = nullptr; GLXVendorInfo* g_glxVendor = nullptr; - XBool VendorIsScreenSupported(Display*, int) { return 1; } + XBool VendorIsScreenSupported(Display*, int) { + return 1; + } // GLX names are the exported entry points (each holds the stream lock); GL names are the // implementation's, as eglGetProcAddress hands them out. diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp index c2326ec4..f506c610 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.cpp @@ -136,8 +136,8 @@ namespace MobileGL::MG_Impl::GLXImpl { int (*Sync)(Display*, int) = nullptr; Bool Valid() const { - return GetGeometry && GetVisualInfo && GetDefaultScreen && GetDefaultVisual && - VisualIDFromVisual && Free; + return GetGeometry && GetVisualInfo && GetDefaultScreen && GetDefaultVisual && VisualIDFromVisual && + Free; } }; @@ -151,16 +151,16 @@ namespace MobileGL::MG_Impl::GLXImpl { } } if (fns->Library) { - fns->GetGeometry = reinterpret_castGetGeometry)>( - dlsym(fns->Library, "XGetGeometry")); - fns->GetVisualInfo = reinterpret_castGetVisualInfo)>( - dlsym(fns->Library, "XGetVisualInfo")); - fns->GetDefaultScreen = reinterpret_castGetDefaultScreen)>( - dlsym(fns->Library, "XDefaultScreen")); - fns->GetDefaultVisual = reinterpret_castGetDefaultVisual)>( - dlsym(fns->Library, "XDefaultVisual")); - fns->VisualIDFromVisual = reinterpret_castVisualIDFromVisual)>( - dlsym(fns->Library, "XVisualIDFromVisual")); + fns->GetGeometry = + reinterpret_castGetGeometry)>(dlsym(fns->Library, "XGetGeometry")); + fns->GetVisualInfo = + reinterpret_castGetVisualInfo)>(dlsym(fns->Library, "XGetVisualInfo")); + fns->GetDefaultScreen = + reinterpret_castGetDefaultScreen)>(dlsym(fns->Library, "XDefaultScreen")); + fns->GetDefaultVisual = + reinterpret_castGetDefaultVisual)>(dlsym(fns->Library, "XDefaultVisual")); + fns->VisualIDFromVisual = + reinterpret_castVisualIDFromVisual)>(dlsym(fns->Library, "XVisualIDFromVisual")); fns->Free = reinterpret_castFree)>(dlsym(fns->Library, "XFree")); fns->Sync = reinterpret_castSync)>(dlsym(fns->Library, "XSync")); } @@ -182,8 +182,8 @@ namespace MobileGL::MG_Impl::GLXImpl { Uint32 (*GenerateId)(void*) = nullptr; XcbVoidCookie (*CreateGC)(void*, Uint32, Uint32, Uint32, const void*) = nullptr; XcbVoidCookie (*FreeGC)(void*, Uint32) = nullptr; - XcbVoidCookie (*PutImage)(void*, Uint8, Uint32, Uint32, Uint16, Uint16, Int16, Int16, Uint8, Uint8, - Uint32, const Uint8*) = nullptr; + XcbVoidCookie (*PutImage)(void*, Uint8, Uint32, Uint32, Uint16, Uint16, Int16, Int16, Uint8, Uint8, Uint32, + const Uint8*) = nullptr; Uint32 (*MaximumRequestLength)(void*) = nullptr; int (*Flush)(void*) = nullptr; @@ -415,9 +415,9 @@ namespace MobileGL::MG_Impl::GLXImpl { X11Present::CreateXcbConnection(connection, static_cast(drawable), caps); if (surface.PresentConnection) { surface.Images = MakeUnique(); - surface.Chain = MakeUnique( - *surface.PresentConnection, *surface.Images, surface.Depth, static_cast(surface.Width), - static_cast(surface.Height), &SteadyNowMs); + surface.Chain = MakeUnique(*surface.PresentConnection, *surface.Images, + surface.Depth, static_cast(surface.Width), + static_cast(surface.Height), &SteadyNowMs); } else { caps.Dri3Major = 0; surface.Path = X11Present::SelectPath(caps, surface.Depth, override); @@ -481,9 +481,11 @@ namespace MobileGL::MG_Impl::GLXImpl { const EGLint height = static_cast(surface.Height); const SizeT rowBytes = static_cast(width) * 4; if (surface.Shm) { - const Bool put = surface.Shm->Put( - static_cast(drawable), surface.Gc, surface.Depth, width, height, - [&](Uint8* rows, SizeT stride) { EGLImpl::ReadBackFrameBGRA(width, height, surface.Scratch, rows, stride); }); + const Bool put = + surface.Shm->Put(static_cast(drawable), surface.Gc, surface.Depth, width, height, + [&](Uint8* rows, SizeT stride) { + EGLImpl::ReadBackFrameBGRA(width, height, surface.Scratch, rows, stride); + }); if (put) return; MGLOG_I("glx: window 0x%lx: MIT-SHM put failed; PutImage from now on", drawable); surface.Shm.reset(); @@ -523,8 +525,7 @@ namespace MobileGL::MG_Impl::GLXImpl { // owns size discovery and pushes changes through the internal resize hook // (same contract as the WGL and CGL layers). Polling XGetGeometry is a // server round trip, so throttle steady-state swaps. - void SyncSurfaceSize(Display* dpy, GLXDrawableHandle drawable, DrawableSurface& surface, - Bool force = false) { + void SyncSurfaceSize(Display* dpy, GLXDrawableHandle drawable, DrawableSurface& surface, Bool force = false) { const auto now = std::chrono::steady_clock::now(); if (!force && now - surface.LastSizePoll < std::chrono::milliseconds(250)) { return; @@ -539,8 +540,7 @@ namespace MobileGL::MG_Impl::GLXImpl { return; } // A remote X drawable is a pbuffer; resizing it gives it a new one at the new size. - if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, - static_cast(width), + if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, static_cast(width), static_cast(height))) { surface.Width = width; surface.Height = height; @@ -549,8 +549,7 @@ namespace MobileGL::MG_Impl::GLXImpl { } } - DrawableSurface* EnsureDrawableSurface(Display* dpy, GLXDrawableHandle drawable, - const ContextObject& context) { + DrawableSurface* EnsureDrawableSurface(Display* dpy, GLXDrawableHandle drawable, const ContextObject& context) { auto& surfaces = DrawableSurfaces(); auto it = surfaces.find(drawable); if (it != surfaces.end()) { @@ -567,21 +566,19 @@ namespace MobileGL::MG_Impl::GLXImpl { } const EGLAttrib attribs[] = { - EGL_WIDTH, static_cast(width), - EGL_HEIGHT, static_cast(height), - EGL_NONE, + EGL_WIDTH, static_cast(width), EGL_HEIGHT, static_cast(height), EGL_NONE, }; const Bool remotePbuffer = MG_Config::Transport == MG_Config::TransportMode::Spawn || MG_Config::Transport == MG_Config::TransportMode::UnixSocket; - const EGLint pbufferAttribs[] = {EGL_WIDTH, static_cast(width), - EGL_HEIGHT, static_cast(height), EGL_NONE}; + const EGLint pbufferAttribs[] = {EGL_WIDTH, static_cast(width), EGL_HEIGHT, + static_cast(height), EGL_NONE}; EGLSurface surface = remotePbuffer - ? EGLImpl::CreatePbufferSurface(context.Display, context.Config, pbufferAttribs) - : EGLImpl::CreatePlatformWindowSurface( - context.Display, context.Config, reinterpret_cast(drawable), attribs); + ? EGLImpl::CreatePbufferSurface(context.Display, context.Config, pbufferAttribs) + : EGLImpl::CreatePlatformWindowSurface(context.Display, context.Config, + reinterpret_cast(drawable), attribs); if (surface == EGL_NO_SURFACE) { - MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable, - width, height); + MGLOG_E_ONCE("glx: failed to create window surface for drawable 0x%lx (%ux%u)", drawable, width, + height); return nullptr; } @@ -598,8 +595,7 @@ namespace MobileGL::MG_Impl::GLXImpl { return &inserted->second; } - GLXContextHandle CreateContextFromEGLAttribs(Display* dpy, const FBConfigInfo* fbconfig, - GLXContextHandle share, + GLXContextHandle CreateContextFromEGLAttribs(Display* dpy, const FBConfigInfo* fbconfig, GLXContextHandle share, const EGLint* contextAttribs) { const std::lock_guard lock(RegistryMutex()); EGLDisplay display = EnsureDisplay(); @@ -619,20 +615,27 @@ namespace MobileGL::MG_Impl::GLXImpl { } const EGLint configAttribs[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 24, - EGL_STENCIL_SIZE, fbconfig->StencilBits, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 24, + EGL_STENCIL_SIZE, + fbconfig->StencilBits, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, EGL_NONE, }; EGLConfig config = nullptr; EGLint configCount = 0; - if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) || - configCount <= 0) { + if (!EGLImpl::ChooseConfig(display, configAttribs, &config, 1, &configCount) || configCount <= 0) { MGLOG_E_ONCE("glx: eglChooseConfig failed"); return nullptr; } @@ -651,8 +654,7 @@ namespace MobileGL::MG_Impl::GLXImpl { object.FBConfig = fbconfig; const auto handle = EncodeContext(NextContextHandle()++); Contexts()[handle] = object; - MGLOG_I("glx: created context %p (EGL context %p, stencil %d)", handle, eglContext, - fbconfig->StencilBits); + MGLOG_I("glx: created context %p (EGL context %p, stencil %d)", handle, eglContext, fbconfig->StencilBits); return handle; } @@ -777,8 +779,8 @@ namespace MobileGL::MG_Impl::GLXImpl { tmpl.depth = 24; tmpl.c_class = kTrueColor; int count = 0; - XVisualInfoCompat* info = x11.GetVisualInfo( - dpy, kVisualScreenMask | kVisualDepthMask | kVisualClassMask, &tmpl, &count); + XVisualInfoCompat* info = + x11.GetVisualInfo(dpy, kVisualScreenMask | kVisualDepthMask | kVisualClassMask, &tmpl, &count); if (info && count < 1) { x11.Free(info); return nullptr; @@ -829,8 +831,7 @@ namespace MobileGL::MG_Impl::GLXImpl { } GLXFBConfigHandle* GetFBConfigs(Display*, int, int* nelements) { - auto** configs = static_cast( - std::malloc(sizeof(GLXFBConfigHandle) * kFBConfigCount)); + auto** configs = static_cast(std::malloc(sizeof(GLXFBConfigHandle) * kFBConfigCount)); if (!configs) { if (nelements) { *nelements = 0; @@ -938,8 +939,7 @@ namespace MobileGL::MG_Impl::GLXImpl { } return nullptr; } - auto** configs = - static_cast(std::malloc(sizeof(GLXFBConfigHandle) * count)); + auto** configs = static_cast(std::malloc(sizeof(GLXFBConfigHandle) * count)); if (!configs) { if (nelements) { *nelements = 0; @@ -1048,32 +1048,38 @@ namespace MobileGL::MG_Impl::GLXImpl { // A legacy GLX context is a compatibility-profile context; MobileGL keys // its relaxed-semantics mode off the explicit compatibility bit. const EGLint attribs[] = { - EGL_CONTEXT_MAJOR_VERSION, 3, - EGL_CONTEXT_MINOR_VERSION, 3, - EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, EGL_NONE, }; return CreateContextFromEGLAttribs(dpy, &kFBConfigs[0], share, attribs); } - GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, - GLXContextHandle share, int) { + GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, GLXContextHandle share, + int) { EnsureInitialized(); const auto* info = TryGetFBConfig(config); if (!info || renderType != GLX_RGBA_TYPE) { return nullptr; } const EGLint attribs[] = { - EGL_CONTEXT_MAJOR_VERSION, 3, - EGL_CONTEXT_MINOR_VERSION, 3, - EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, EGL_NONE, }; return CreateContextFromEGLAttribs(dpy, info, share, attribs); } - GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, - GLXContextHandle share, int, const int* attribList) { + GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, GLXContextHandle share, int, + const int* attribList) { EnsureInitialized(); const auto* info = TryGetFBConfig(config); if (!info) { @@ -1105,21 +1111,22 @@ namespace MobileGL::MG_Impl::GLXImpl { // Accepted and ignored: MobileGL always validates. break; default: - MGLOG_D("glXCreateContextAttribsARB: ignoring attrib 0x%04x = 0x%x", attrib, - value); + MGLOG_D("glXCreateContextAttribsARB: ignoring attrib 0x%04x = 0x%x", attrib, value); break; } } } - if (major < 1 || (profileMask & ~(GLX_CONTEXT_CORE_PROFILE_BIT_ARB | - GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { + if (major < 1 || + (profileMask & ~(GLX_CONTEXT_CORE_PROFILE_BIT_ARB | GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { return nullptr; } Vector attribs = { - EGL_CONTEXT_MAJOR_VERSION, major, - EGL_CONTEXT_MINOR_VERSION, minor, + EGL_CONTEXT_MAJOR_VERSION, + major, + EGL_CONTEXT_MINOR_VERSION, + minor, }; const Bool wantsCompat = (profileMask & GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0; if (major > 3 || (major == 3 && minor >= 2) || profileMask != 0) { @@ -1186,8 +1193,7 @@ namespace MobileGL::MG_Impl::GLXImpl { return 0; } - if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, - object->Context)) { + if (!EGLImpl::MakeCurrent(object->Display, surface->Surface, surface->Surface, object->Context)) { MGLOG_E_ONCE("glx: eglMakeCurrent failed (drawable=0x%lx, ctx=%p)", drawable, context); return 0; } @@ -1195,13 +1201,11 @@ namespace MobileGL::MG_Impl::GLXImpl { return 1; } - int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, - GLXContextHandle context) { + int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, GLXContextHandle context) { if (context && draw != read) { // MobileGL's backends reject split draw/read surfaces; bind the draw // drawable for both, which is what every real caller here needs. - MGLOG_W_ONCE("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw, - read); + MGLOG_W_ONCE("glx: glXMakeContextCurrent draw 0x%lx != read 0x%lx, using draw for both", draw, read); } const int result = MakeCurrent(dpy, draw, context); if (result && context) { @@ -1228,15 +1232,14 @@ namespace MobileGL::MG_Impl::GLXImpl { // next throttled geometry poll. Int32 configuredWidth = 0; Int32 configuredHeight = 0; - const Bool configured = - it->second.Chain && it->second.Chain->TakeConfiguredSize(&configuredWidth, &configuredHeight) && - (static_cast(configuredWidth) != it->second.Width || - static_cast(configuredHeight) != it->second.Height); + const Bool configured = it->second.Chain && + it->second.Chain->TakeConfiguredSize(&configuredWidth, &configuredHeight) && + (static_cast(configuredWidth) != it->second.Width || + static_cast(configuredHeight) != it->second.Height); SyncSurfaceSize(dpy, drawable, it->second, configured); } - GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window, - const int*) { + GLXDrawableHandle CreateWindow(Display*, GLXFBConfigHandle config, GLXDrawableHandle window, const int*) { EnsureInitialized(); const auto* info = TryGetFBConfig(config); if (!info || window == 0) { diff --git a/MobileGL/MG_Impl/GLXImpl/GLXImpl.h b/MobileGL/MG_Impl/GLXImpl/GLXImpl.h index 7eac092f..56758809 100644 --- a/MobileGL/MG_Impl/GLXImpl/GLXImpl.h +++ b/MobileGL/MG_Impl/GLXImpl/GLXImpl.h @@ -33,14 +33,13 @@ namespace MobileGL::MG_Impl::GLXImpl { int GetConfig(Display* dpy, void* visualInfo, int attribute, int* value); GLXContextHandle CreateContext(Display* dpy, void* visualInfo, GLXContextHandle share, int direct); - GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, - GLXContextHandle share, int direct); - GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, GLXContextHandle share, - int direct, const int* attribList); + GLXContextHandle CreateNewContext(Display* dpy, GLXFBConfigHandle config, int renderType, GLXContextHandle share, + int direct); + GLXContextHandle CreateContextAttribsARB(Display* dpy, GLXFBConfigHandle config, GLXContextHandle share, int direct, + const int* attribList); void DestroyContext(Display* dpy, GLXContextHandle context); int MakeCurrent(Display* dpy, GLXDrawableHandle drawable, GLXContextHandle context); - int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, - GLXContextHandle context); + int MakeContextCurrent(Display* dpy, GLXDrawableHandle draw, GLXDrawableHandle read, GLXContextHandle context); void SwapBuffers(Display* dpy, GLXDrawableHandle drawable); GLXDrawableHandle CreateWindow(Display* dpy, GLXFBConfigHandle config, GLXDrawableHandle window, diff --git a/MobileGL/MG_Impl/GLXImpl/X11Present.cpp b/MobileGL/MG_Impl/GLXImpl/X11Present.cpp index d0ff3d28..09e6d911 100644 --- a/MobileGL/MG_Impl/GLXImpl/X11Present.cpp +++ b/MobileGL/MG_Impl/GLXImpl/X11Present.cpp @@ -51,9 +51,9 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { Path SelectPath(const ConnectionCaps& caps, Uint32 depth, const char* overrideValue) { const Bool putImageOnly = overrideValue != nullptr && std::strcmp(overrideValue, "putimage") == 0; const Bool noDri3 = putImageOnly || (overrideValue != nullptr && (std::strcmp(overrideValue, "readback") == 0 || - std::strcmp(overrideValue, "shm") == 0)); - if (!noDri3 && caps.SharedImages && caps.LocalConnection && FourccForDepth(depth) != 0 && - caps.Dri3Major >= 1 && caps.PresentMajor >= 1) { + std::strcmp(overrideValue, "shm") == 0)); + if (!noDri3 && caps.SharedImages && caps.LocalConnection && FourccForDepth(depth) != 0 && caps.Dri3Major >= 1 && + caps.PresentMajor >= 1) { return Path::Dri3Present; } // MIT-SHM 1.2 is the first with a client-made segment passed as a descriptor (no SysV @@ -71,7 +71,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { } Chain::~Chain() { - for (Buffer& buffer : m_buffers) Drop(buffer); + for (Buffer& buffer : m_buffers) + Drop(buffer); m_connection.Flush(); } @@ -83,13 +84,15 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { SizeT Chain::AllocatedBuffers() const { SizeT count = 0; - for (const Buffer& buffer : m_buffers) count += buffer.Pixmap != 0 ? 1 : 0; + for (const Buffer& buffer : m_buffers) + count += buffer.Pixmap != 0 ? 1 : 0; return count; } SizeT Chain::BusyBuffers() const { SizeT count = 0; - for (const Buffer& buffer : m_buffers) count += buffer.Pixmap != 0 && buffer.Busy ? 1 : 0; + for (const Buffer& buffer : m_buffers) + count += buffer.Pixmap != 0 && buffer.Busy ? 1 : 0; return count; } @@ -112,7 +115,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { Break("the server allocated no shared image for the window"); return false; } - if (image.Fd < 0 || image.Width != static_cast(m_width) || image.Height != static_cast(m_height)) { + if (image.Fd < 0 || image.Width != static_cast(m_width) || + image.Height != static_cast(m_height)) { CloseDescriptor(image.Fd); m_images.Release(image.Id); Break("a shared image came back without a descriptor or at another size"); @@ -164,7 +168,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { void Chain::DrainEvents() { Event event; - while (m_connection.PollEvent(&event)) Handle(event); + while (m_connection.PollEvent(&event)) + Handle(event); } void Chain::WaitForPacing(Int swapInterval) { diff --git a/MobileGL/MG_Impl/GLXImpl/X11Present.h b/MobileGL/MG_Impl/GLXImpl/X11Present.h index 14d36a21..9241237e 100644 --- a/MobileGL/MG_Impl/GLXImpl/X11Present.h +++ b/MobileGL/MG_Impl/GLXImpl/X11Present.h @@ -49,11 +49,15 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { // ---- which path a window's frames take ------------------------------------------------------- - enum class Path { Dri3Present, ShmPutImage, PutImage }; + enum class Path { + Dri3Present, + ShmPutImage, + PutImage + }; const char* PathName(Path path); struct ConnectionCaps { - Bool SharedImages = false; // the backend allocates shared images (a split client) + Bool SharedImages = false; // the backend allocates shared images (a split client) Bool LocalConnection = false; // a unix socket: descriptors can be passed Uint32 Dri3Major = 0, Dri3Minor = 0; Uint32 PresentMajor = 0, PresentMinor = 0; @@ -94,7 +98,12 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { }; struct Event { - enum class Kind { None, Idle, Complete, Configure }; + enum class Kind { + None, + Idle, + Complete, + Configure + }; Kind Type = Kind::None; Uint32 Serial = 0; Uint32 Pixmap = 0; diff --git a/MobileGL/MG_Impl/GLXImpl/X11PresentXcb.cpp b/MobileGL/MG_Impl/GLXImpl/X11PresentXcb.cpp index f22609d3..837d654e 100644 --- a/MobileGL/MG_Impl/GLXImpl/X11PresentXcb.cpp +++ b/MobileGL/MG_Impl/GLXImpl/X11PresentXcb.cpp @@ -123,16 +123,14 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { void* xfixes = OpenLibrary("libxcb-xfixes.so.0"); a->xfixesId = xfixes != nullptr ? static_cast(dlsym(xfixes, "xcb_xfixes_id")) : nullptr; - a->xfixes = a->xfixesId != nullptr && R(xfixes, xcb_xfixes_query_version) & - R(xfixes, xcb_xfixes_query_version_reply) & - R(xfixes, xcb_xfixes_create_region) & - R(xfixes, xcb_xfixes_destroy_region); + a->xfixes = a->xfixesId != nullptr && + R(xfixes, xcb_xfixes_query_version) & R(xfixes, xcb_xfixes_query_version_reply) & + R(xfixes, xcb_xfixes_create_region) & R(xfixes, xcb_xfixes_destroy_region); void* shm = OpenLibrary("libxcb-shm.so.0"); a->shmId = shm != nullptr ? static_cast(dlsym(shm, "xcb_shm_id")) : nullptr; - a->shm = a->shmId != nullptr && R(shm, xcb_shm_query_version) & - R(shm, xcb_shm_query_version_reply) & - R(shm, xcb_shm_attach_fd_checked) & R(shm, xcb_shm_detach) & - R(shm, xcb_shm_put_image); + a->shm = a->shmId != nullptr && + R(shm, xcb_shm_query_version) & R(shm, xcb_shm_query_version_reply) & + R(shm, xcb_shm_attach_fd_checked) & R(shm, xcb_shm_detach) & R(shm, xcb_shm_put_image); } #undef R return a; @@ -140,7 +138,9 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { return *api; } - xcb_connection_t* Xcb(void* connection) { return static_cast(connection); } + xcb_connection_t* Xcb(void* connection) { + return static_cast(connection); + } Bool ExtensionPresent(xcb_connection_t* c, xcb_extension_t* id) { const XcbApi& api = Api(); @@ -196,7 +196,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { } // XFixes demands its version be asked before any other request of it. if (api.xfixes && ExtensionPresent(c, api.xfixesId)) { - if (auto* reply = api.xcb_xfixes_query_version_reply(c, api.xcb_xfixes_query_version(c, 5, 0), nullptr)) { + if (auto* reply = + api.xcb_xfixes_query_version_reply(c, api.xcb_xfixes_query_version(c, 5, 0), nullptr)) { result.XFixes = reply->major_version >= 2; std::free(reply); } @@ -295,8 +296,9 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { if (m_buffers12 && api.xcb_dri3_pixmap_from_buffers_checked != nullptr) { int32_t fds[1] = {fd}; cookie = api.xcb_dri3_pixmap_from_buffers_checked( - m_c, pixmap, m_window, 1, static_cast(image.Width), static_cast(image.Height), - image.Stride, image.Offset, 0, 0, 0, 0, 0, 0, static_cast(depth), 32, image.Modifier, fds); + m_c, pixmap, m_window, 1, static_cast(image.Width), + static_cast(image.Height), image.Stride, image.Offset, 0, 0, 0, 0, 0, 0, + static_cast(depth), 32, image.Modifier, fds); } else { if (image.Offset != 0 || image.Stride > 0xffffu) { ::close(fd); @@ -308,8 +310,9 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { static_cast(depth), 32, fd); } if (xcb_generic_error_t* error = api.xcb_request_check(m_c, cookie)) { - MGLOG_I("glx: the X server refused a %ux%u shared image as a pixmap (X error %u, major %u minor %u)", - image.Width, image.Height, error->error_code, error->major_code, error->minor_code); + MGLOG_I( + "glx: the X server refused a %ux%u shared image as a pixmap (X error %u, major %u minor %u)", + image.Width, image.Height, error->error_code, error->major_code, error->minor_code); std::free(error); return 0; } @@ -339,9 +342,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { } // An earlier present whose answer never came is given up on, not left queued. if (m_hasPendingPresent) api.xcb_discard_reply(m_c, m_pendingPresent); - const xcb_void_cookie_t cookie = - api.xcb_present_pixmap_checked(m_c, m_window, pixmap, serial, 0, region, 0, 0, 0, 0, 0, options, - targetMsc, 0, 0, 0, nullptr); + const xcb_void_cookie_t cookie = api.xcb_present_pixmap_checked( + m_c, m_window, pixmap, serial, 0, region, 0, 0, 0, 0, 0, options, targetMsc, 0, 0, 0, nullptr); m_pendingPresent = cookie.sequence; m_hasPendingPresent = true; if (region != 0) api.xcb_xfixes_destroy_region(m_c, region); @@ -412,7 +414,8 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { public: explicit XcbShmPresenter(xcb_connection_t* c) : m_c(c) {} ~XcbShmPresenter() override { - for (Segment& segment : m_segments) Release(segment); + for (Segment& segment : m_segments) + Release(segment); Api().xcb_flush(m_c); } @@ -492,7 +495,9 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { }; } // namespace - ConnectionCaps QueryConnectionCaps(void* xcbConnection) { return Query(xcbConnection).Caps; } + ConnectionCaps QueryConnectionCaps(void* xcbConnection) { + return Query(xcbConnection).Caps; + } UniquePtr CreateXcbConnection(void* xcbConnection, Uint32 window, const ConnectionCaps& caps) { const XcbApi& api = Api(); @@ -514,9 +519,15 @@ namespace MobileGL::MG_Impl::GLXImpl::X11Present { #else // !MOBILEGL_GLX_XCB_PRESENT namespace MobileGL::MG_Impl::GLXImpl::X11Present { - ConnectionCaps QueryConnectionCaps(void*) { return {}; } - UniquePtr CreateXcbConnection(void*, Uint32, const ConnectionCaps&) { return nullptr; } - UniquePtr CreateShmPresenter(void*) { return nullptr; } + ConnectionCaps QueryConnectionCaps(void*) { + return {}; + } + UniquePtr CreateXcbConnection(void*, Uint32, const ConnectionCaps&) { + return nullptr; + } + UniquePtr CreateShmPresenter(void*) { + return nullptr; + } } // namespace MobileGL::MG_Impl::GLXImpl::X11Present #endif diff --git a/MobileGL/MG_Impl/GetProcAddress.cpp b/MobileGL/MG_Impl/GetProcAddress.cpp index 7c4cddf1..97a95af8 100644 --- a/MobileGL/MG_Impl/GetProcAddress.cpp +++ b/MobileGL/MG_Impl/GetProcAddress.cpp @@ -16,12 +16,10 @@ extern "C" EGLDisplay eglGetPlatformDisplayEXT(EGLenum platform, void* native_display, const EGLint* attrib_list); extern "C" EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, - const EGLint* attrib_list); + const EGLint* attrib_list); extern "C" EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImage image); -extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, - EGLint n_rects); -extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, - EGLint n_rects); +extern "C" EGLBoolean eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); +extern "C" EGLBoolean eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface draw, const EGLint* rects, EGLint n_rects); extern "C" EGLBoolean eglSetDamageRegionKHR(EGLDisplay dpy, EGLSurface surface, EGLint* rects, EGLint n_rects); extern "C" EGLBoolean eglQueryDmaBufFormatsEXT(EGLDisplay dpy, EGLint max_formats, EGLint* formats, EGLint* num_formats); diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp index a6f0c219..ea467c72 100644 --- a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.cpp @@ -70,8 +70,9 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } id SendIdCGLPixelFormat(id receiver, const char* selector, CGLPixelFormatObj value) { - return receiver ? ObjcMsgSend()(receiver, sel_registerName(selector), value) - : nil; + return receiver + ? ObjcMsgSend()(receiver, sel_registerName(selector), value) + : nil; } void SendVoidCGRect(id receiver, const char* selector, CGRect value) { @@ -306,9 +307,9 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } SetAssoc(self, &kContextViewKey, view, kAssociationAssign); SetAssoc(self, &kContextLayerKey, layer, kAssociationRetain); - const auto error = CGLImpl::AttachDrawable(context, view, layer, - static_cast(geometry.DrawableSize.width), - static_cast(geometry.DrawableSize.height)); + const auto error = + CGLImpl::AttachDrawable(context, view, layer, static_cast(geometry.DrawableSize.width), + static_cast(geometry.DrawableSize.height)); if (error != kCGLNoError) { MGLOG_E_ONCE("NSOpenGLImpl: failed to attach drawable: %s", CGLImpl::ErrorString(error)); } @@ -373,9 +374,9 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { } const auto geometry = GetDrawableGeometry(view); ConfigureMetalLayerForView(layer, view, geometry); - const auto error = CGLImpl::AttachDrawable(context, view, layer, - static_cast(geometry.DrawableSize.width), - static_cast(geometry.DrawableSize.height)); + const auto error = + CGLImpl::AttachDrawable(context, view, layer, static_cast(geometry.DrawableSize.width), + static_cast(geometry.DrawableSize.height)); if (error != kCGLNoError) { MGLOG_E_ONCE("NSOpenGLImpl: update failed to attach drawable: %s", CGLImpl::ErrorString(error)); return; @@ -448,8 +449,8 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { return false; } - ReplaceInstanceMethod(pixelFormatClass, "initWithAttributes:", - reinterpret_cast(PixelFormatInitWithAttributes)); + ReplaceInstanceMethod(pixelFormatClass, + "initWithAttributes:", reinterpret_cast(PixelFormatInitWithAttributes)); ReplaceInstanceMethod(pixelFormatClass, "initWithCGLPixelFormatObj:", reinterpret_cast(PixelFormatInitWithCGLPixelFormatObj)); ReplaceInstanceMethod(pixelFormatClass, "getValues:forAttribute:forVirtualScreen:", @@ -461,10 +462,10 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { ReplaceInstanceMethod(pixelFormatClass, "dealloc", reinterpret_cast(PixelFormatDealloc), &g_pixelFormatDealloc); - ReplaceInstanceMethod(contextClass, "initWithFormat:shareContext:", - reinterpret_cast(ContextInitWithFormatShare)); - ReplaceInstanceMethod(contextClass, "initWithCGLContextObj:", - reinterpret_cast(ContextInitWithCGLContextObj)); + ReplaceInstanceMethod(contextClass, + "initWithFormat:shareContext:", reinterpret_cast(ContextInitWithFormatShare)); + ReplaceInstanceMethod(contextClass, + "initWithCGLContextObj:", reinterpret_cast(ContextInitWithCGLContextObj)); ReplaceInstanceMethod(contextClass, "setView:", reinterpret_cast(ContextSetView)); ReplaceInstanceMethod(contextClass, "view", reinterpret_cast(ContextView)); ReplaceInstanceMethod(contextClass, "makeCurrentContext", reinterpret_cast(ContextMakeCurrent)); diff --git a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h index 1184d5bf..434e8416 100644 --- a/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h +++ b/MobileGL/MG_Impl/NSOpenGLImpl/NSOpenGLImpl.h @@ -11,4 +11,4 @@ namespace MobileGL::MG_Impl::NSOpenGLImpl { void InstallHooks(); -} +} // namespace MobileGL::MG_Impl::NSOpenGLImpl diff --git a/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h b/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h index 89c55481..5a00c190 100644 --- a/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h +++ b/MobileGL/MG_Impl/Pipe/ClientFetchPlan.h @@ -65,9 +65,9 @@ namespace MobileGL::MG_Pipe { // distinguished because the two roles resolve them from different places and a reader // that answered one for the other would size an upload off the wrong bytes. enum class MGPipeClientBufferKind : Uint8 { - Element, // the bound GL_ELEMENT_ARRAY_BUFFER, for the index stream - IndirectCommand,// the bound GL_DRAW_INDIRECT_BUFFER, for the command block - IndirectCount, // the bound GL_PARAMETER_BUFFER, for an *IndirectCount form's count + Element, // the bound GL_ELEMENT_ARRAY_BUFFER, for the index stream + IndirectCommand, // the bound GL_DRAW_INDIRECT_BUFFER, for the command block + IndirectCount, // the bound GL_PARAMETER_BUFFER, for an *IndirectCount form's count }; // Reads `size` bytes at `offset` of the named buffer on the CPU. False when the bytes do @@ -136,7 +136,8 @@ namespace MobileGL::MG_Pipe { if (!fetch.Range.Count || !fetch.Instances) continue; const Uint64 first = fetch.BaseInstance; const Uint64 last = first + (fetch.Instances - 1u) / divisor; - for (Uint64 at = first; at <= last; ++at) out.push_back(at); + for (Uint64 at = first; at <= last; ++at) + out.push_back(at); } std::sort(out.begin(), out.end()); out.erase(std::unique(out.begin(), out.end()), out.end()); @@ -151,9 +152,8 @@ namespace MobileGL::MG_Pipe { Uint32 count = indirect.DrawCount; if (indirect.HasParameterBuffer) { Uint32 actual = 0; - if (reader == nullptr || - !reader(user, MGPipeClientBufferKind::IndirectCount, indirect.ParameterOffset, sizeof(actual), - &actual)) { + if (reader == nullptr || !reader(user, MGPipeClientBufferKind::IndirectCount, indirect.ParameterOffset, + sizeof(actual), &actual)) { return false; } count = std::min(count, actual); @@ -165,9 +165,8 @@ namespace MobileGL::MG_Pipe { const Uint64 displacement = static_cast(i) * stride; if (displacement > std::numeric_limits::max() - indirect.Offset) return false; Uint32 words[5]{}; - if (reader == nullptr || - !reader(user, MGPipeClientBufferKind::IndirectCommand, indirect.Offset + displacement, - commandBytes, words)) { + if (reader == nullptr || !reader(user, MGPipeClientBufferKind::IndirectCommand, + indirect.Offset + displacement, commandBytes, words)) { return false; } Int32 bias = 0; @@ -199,8 +198,7 @@ namespace MobileGL::MG_Pipe { } else { indexBytes.resize(static_cast(size)); if (reader == nullptr || - !reader(user, MGPipeClientBufferKind::Element, offset, indexBytes.size(), - indexBytes.data())) { + !reader(user, MGPipeClientBufferKind::Element, offset, indexBytes.size(), indexBytes.data())) { return false; } data = indexBytes.data(); diff --git a/MobileGL/MG_Impl/Pipe/CompositeResolver.h b/MobileGL/MG_Impl/Pipe/CompositeResolver.h index 8411af12..26963de3 100644 --- a/MobileGL/MG_Impl/Pipe/CompositeResolver.h +++ b/MobileGL/MG_Impl/Pipe/CompositeResolver.h @@ -64,8 +64,8 @@ namespace MobileGL::MG_Pipe { // one death helper and in its fixed order. That is the pipeline-cache release path; the // composite's own destructor is the other one and the second of the two is the proven // no-op. - MGPipeHandle Observe(Uint64 contextId, const ProgramPipelineObject& pipeline, - const ProgramObject& composite, MGPipeHandle handle) { + MGPipeHandle Observe(Uint64 contextId, const ProgramPipelineObject& pipeline, const ProgramObject& composite, + MGPipeHandle handle) { const DrawProgramSignature signature = pipeline.ComputeDrawProgramSignature(); const Uint pipelineName = pipeline.GetExternalIndex(); Entry* entry = Find(contextId, pipelineName); diff --git a/MobileGL/MG_Impl/Pipe/CsoCache.h b/MobileGL/MG_Impl/Pipe/CsoCache.h index 1f47718e..e7fb8817 100644 --- a/MobileGL/MG_Impl/Pipe/CsoCache.h +++ b/MobileGL/MG_Impl/Pipe/CsoCache.h @@ -34,7 +34,7 @@ namespace MobileGL::MG_Pipe { class MGPipeCsoCache { public: struct Counters { - Uint64 Mints = 0; // create_render_state emissions + Uint64 Mints = 0; // create_render_state emissions // bind_render_state emissions, mint or reuse. Counted in Acquire because Acquire // has exactly ONE caller (PipeFill.cpp's EmitRenderState) and that caller binds // immediately after every call - so "acquisitions" and "binds" are the same @@ -42,9 +42,9 @@ namespace MobileGL::MG_Pipe { // remembering to tick it. mints/binds is the cache's hit rate and it is the // number the CSO content-addressing negative control moves. Uint64 Binds = 0; - Uint64 Hits = 0; // a probe that found a live entry and passed the memcmp + Uint64 Hits = 0; // a probe that found a live entry and passed the memcmp Uint64 Collisions = 0; // a hash hit the memcmp REJECTED - the reason it exists - Uint64 Evictions = 0; // LRU evictions, each one a delete_render_state + Uint64 Evictions = 0; // LRU evictions, each one a delete_render_state }; // The handle for `params`' pipeline subset. Mints and emits create_render_state on a @@ -55,11 +55,10 @@ namespace MobileGL::MG_Pipe { MGPipeGatherPipelineBytes(params, bytes.data()); ++m_counters.Binds; - const Bool contentAddressed = - (MG_Config::Features.PipePush & kMGPipeBehaviourNoCsoContentAddressing) == 0; + const Bool contentAddressed = (MG_Config::Features.PipePush & kMGPipeBehaviourNoCsoContentAddressing) == 0; if (contentAddressed) { - const Uint64 hash = s_hashForTest != nullptr ? s_hashForTest(bytes.data()) - : MGPipeHashPipelineBytes(bytes.data()); + const Uint64 hash = + s_hashForTest != nullptr ? s_hashForTest(bytes.data()) : MGPipeHashPipelineBytes(bytes.data()); for (SizeT i = 0; i < m_entries.size(); ++i) { if (m_entries[i].Hash != hash) continue; if (std::memcmp(m_entries[i].Bytes.data(), bytes.data(), bytes.size()) != 0) { @@ -87,7 +86,8 @@ namespace MobileGL::MG_Pipe { // is reset alongside, and a delete for a record that is about to be dropped anyway // would be a wire message with no reader. void Reset() { - for (auto& entry : m_entries) MGPipeSlots().Free(MGPipeKind::RenderStateCso, entry.Cso); + for (auto& entry : m_entries) + MGPipeSlots().Free(MGPipeKind::RenderStateCso, entry.Cso); m_entries.clear(); m_clock = 0; } @@ -114,8 +114,7 @@ namespace MobileGL::MG_Pipe { Array Bytes{}; }; - MGPipeHandle Mint(Uint64 hash, const Array& bytes, - Uint64& payloadBytes) { + MGPipeHandle Mint(Uint64 hash, const Array& bytes, Uint64& payloadBytes) { if (m_entries.size() >= kMGPipeCsoCacheCapacity) { SizeT victim = 0; for (SizeT i = 1; i < m_entries.size(); ++i) { @@ -161,8 +160,7 @@ namespace MobileGL::MG_Pipe { ++m_counters.Evictions; } - static constexpr Uint32 kAllPipelineChunks = - static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1); + static constexpr Uint32 kAllPipelineChunks = static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1); Vector m_entries; Uint64 m_clock = 0; diff --git a/MobileGL/MG_Impl/Pipe/FramebufferEmit.h b/MobileGL/MG_Impl/Pipe/FramebufferEmit.h index 88e896f5..e4161a02 100644 --- a/MobileGL/MG_Impl/Pipe/FramebufferEmit.h +++ b/MobileGL/MG_Impl/Pipe/FramebufferEmit.h @@ -268,10 +268,8 @@ namespace MobileGL::MG_Pipe { // that "nothing moved" costs one compare rather than two. const Uint64 drawHash = drawOk ? drawState.ContentHash : 0; const Uint64 readHash = readOk ? readState.ContentHash : 0; - const Uint64 combined = - MGPipeMixShutter(MGPipeMixShutter(drawHash, readHash), shared ? 1u : 0u); - if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlot::SetFramebufferState, - combined)) { + const Uint64 combined = MGPipeMixShutter(MGPipeMixShutter(drawHash, readHash), shared ? 1u : 0u); + if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlot::SetFramebufferState, combined)) { return 0; } @@ -405,7 +403,8 @@ namespace MobileGL::MG_Pipe { void ResetForTest() { Reset(); ResetCounters(); - for (auto& memo : m_buildMemo) memo.Valid = false; + for (auto& memo : m_buildMemo) + memo.Valid = false; m_lastDraw = MGPFramebufferState{}; m_lastRead = MGPFramebufferState{}; m_lastNamed = MGPFramebufferState{}; @@ -453,15 +452,14 @@ namespace MobileGL::MG_Pipe { // truncating it silently is exactly the bug class this phase is closing. The // backend half of the same refusal (bit 9 declined at its first lookup, with one // ERROR naming the cap) rides ResolveFramebufferSubsystemArm. - for (Int point = static_cast(FramebufferAttachmentType::Color0) + - static_cast(kMGPipeMaxColorAttachments); + for (Int point = + static_cast(FramebufferAttachmentType::Color0) + static_cast(kMGPipeMaxColorAttachments); point <= static_cast(FramebufferAttachmentType::ColorMax); ++point) { if (fbo.GetAttachment(static_cast(point)).IsEmpty()) continue; MGLOG_E_ONCE("MGPipe: framebuffer %u has an attachment at colour point %d, which is at or " "above the wire width of %u - set_framebuffer_state is refused rather than " "truncated and the legacy arm runs", - fbo.GetExternalIndex(), - point - static_cast(FramebufferAttachmentType::Color0), + fbo.GetExternalIndex(), point - static_cast(FramebufferAttachmentType::Color0), static_cast(kMGPipeMaxColorAttachments)); ++m_refusals; return false; @@ -478,8 +476,7 @@ namespace MobileGL::MG_Pipe { "at or above the wire width of %u - set_framebuffer_state is refused " "rather than truncated and the legacy arm runs", fbo.GetExternalIndex(), - static_cast(tokens[i]) - - static_cast(FramebufferAttachmentType::Color0), + static_cast(tokens[i]) - static_cast(FramebufferAttachmentType::Color0), static_cast(i), static_cast(kMGPipeMaxColorAttachments)); ++m_refusals; return false; @@ -505,9 +502,9 @@ namespace MobileGL::MG_Pipe { // the draw-buffer indices below still map BackLeft to slot 0. if (out.IsDefault == 0) { for (SizeT i = 0; i < kMGPipeMaxColorAttachments; ++i) { - out.Color[i] = SurfaceOf(fbo, static_cast( - static_cast(FramebufferAttachmentType::Color0) + - static_cast(i))); + out.Color[i] = + SurfaceOf(fbo, static_cast( + static_cast(FramebufferAttachmentType::Color0) + static_cast(i))); } out.Depth = SurfaceOf(fbo, FramebufferAttachmentType::Depth); out.Stencil = SurfaceOf(fbo, FramebufferAttachmentType::Stencil); @@ -594,8 +591,7 @@ namespace MobileGL::MG_Pipe { // DEPTH_STENCIL, the two sticky bind bits nothing set before P4a. Sticky and ORed, // so a texture that was ever a colour attachment keeps saying so, and the mask is // republished on the resource's next respecify. - const Uint16 bit = (type == FramebufferAttachmentType::Depth || - type == FramebufferAttachmentType::Stencil) + const Uint16 bit = (type == FramebufferAttachmentType::Depth || type == FramebufferAttachmentType::Stencil) ? static_cast(kMGPipeBindDepthStencil) : static_cast(kMGPipeBindRenderTarget); MGPipeHandle res = kMGPipeNullHandle; @@ -622,15 +618,13 @@ namespace MobileGL::MG_Pipe { if (!found) { out.Width = static_cast(std::clamp(size.x(), 0, 0xFFFF)); out.Height = static_cast(std::clamp(size.y(), 0, 0xFFFF)); - out.Layers = static_cast( - attachment.IsLayered() ? std::clamp(size.z(), 1, 0xFFFF) : 1); + out.Layers = static_cast(attachment.IsLayered() ? std::clamp(size.z(), 1, 0xFFFF) : 1); if (attachment.IsTexture()) { const auto& texture = attachment.GetTexture(); out.Samples = static_cast(std::max(texture->GetSamples(), 0)); out.FixedSampleLocations = texture->HasFixedSampleLocations() ? 1 : 0; } else { - out.Samples = static_cast( - std::max(attachment.GetRenderbuffer()->GetSamples(), 0)); + out.Samples = static_cast(std::max(attachment.GetRenderbuffer()->GetSamples(), 0)); out.FixedSampleLocations = 1; } found = true; @@ -672,8 +666,7 @@ namespace MobileGL::MG_Pipe { if (entry.IsTexture) { const auto& texture = attachment.GetTexture(); return texture->GetLifetimeId() == entry.LifetimeId && - texture->GetShapeVersion() == entry.ShapeVersion && - texture->GetSamples() == entry.Samples && + texture->GetShapeVersion() == entry.ShapeVersion && texture->GetSamples() == entry.Samples && texture->HasFixedSampleLocations() == entry.FixedSampleLocations; } const auto& renderbuffer = attachment.GetRenderbuffer(); diff --git a/MobileGL/MG_Impl/Pipe/ImageEmit.h b/MobileGL/MG_Impl/Pipe/ImageEmit.h index c4dddb71..45dd0dd6 100644 --- a/MobileGL/MG_Impl/Pipe/ImageEmit.h +++ b/MobileGL/MG_Impl/Pipe/ImageEmit.h @@ -145,7 +145,7 @@ namespace MobileGL::MG_Pipe { entry.InternalFormat = static_cast(binding.Format); entry.Layer = static_cast(binding.Layer); entry.Level = MGPipeImageBindingIsAccessible(binding) ? static_cast(binding.Level) - : kMGPipeImageLevelNone; + : kMGPipeImageLevelNone; entry.Layered = binding.Layered != GL_FALSE ? 1 : 0; entry.Access = static_cast(MGPipeEncodeImageAccess(binding.Access)); } diff --git a/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h b/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h index 774bad1c..d26edb39 100644 --- a/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h +++ b/MobileGL/MG_Impl/Pipe/OwnedDrawInputs.h @@ -31,8 +31,7 @@ namespace MobileGL::MG_Pipe { ~MGPipeOwnedDrawInputs() { if (m_vertexBindingsChanged) MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, m_baseInstance); - if (m_indexBindingChanged) - MGPipeVertexInputEmitterInstance().EmitIndexBuffer(m_context); + if (m_indexBindingChanged) MGPipeVertexInputEmitterInstance().EmitIndexBuffer(m_context); // The bindings no longer name them: the buffers go back to the share group for the // next client-array draw. Their next respecify orphans the store this draw reads. auto& group = *m_context.GetShareGroup(); @@ -41,8 +40,8 @@ namespace MobileGL::MG_Pipe { if (m_indexBuffer) group.ReturnClientArrayBuffer(std::move(m_indexBuffer)); } - Bool Prepare(MGPDrawInfo& info, const MGPDrawRange* ranges, Uint32 rangeCount, - const void* clientIndices, Uint64 clientIndexBytes, const MGPDrawIndirect* indirect) { + Bool Prepare(MGPDrawInfo& info, const MGPDrawRange* ranges, Uint32 rangeCount, const void* clientIndices, + Uint64 clientIndexBytes, const MGPDrawIndirect* indirect) { m_baseInstance = info.StartInstance; const auto& vao = m_context.GetBoundVertexArray(); if (!vao) return (info.Flags & kDrawClientArrays) == 0 && clientIndexBytes == 0; @@ -87,9 +86,10 @@ namespace MobileGL::MG_Pipe { inputs.BaseInstance = info.StartInstance; inputs.PrimitiveRestart = (info.Flags & kDrawPrimitiveRestart) != 0; const Bool fixedRestart = m_context.IsCapabilityEnabled(CapabilityInput::PrimitiveRestartFixedIndex); - inputs.RestartIndex = fixedRestart - ? (info.IndexSize == 1 ? 0xffu : info.IndexSize == 2 ? 0xffffu : 0xffffffffu) - : info.RestartIndex; + inputs.RestartIndex = fixedRestart ? (info.IndexSize == 1 ? 0xffu + : info.IndexSize == 2 ? 0xffffu + : 0xffffffffu) + : info.RestartIndex; inputs.ClientIndices = clientIndices; inputs.ClientIndexBytes = clientIndexBytes; inputs.WantVertices = needsVertexIndices; @@ -114,8 +114,7 @@ namespace MobileGL::MG_Pipe { if (!m_plan.Elements(static_cast(attribute.Divisor), referenced)) return false; const Uint64 stride = static_cast(attribute.Stride); const Uint64 last = referenced.empty() ? 0 : referenced.back(); - if (stride != 0 && last > (std::numeric_limits::max() - elementBytes) / stride) - return false; + if (stride != 0 && last > (std::numeric_limits::max() - elementBytes) / stride) return false; const SizeT byteCount = static_cast(last * stride + elementBytes); Vector bytes(byteCount, 0); if (!referenced.empty()) { @@ -139,7 +138,8 @@ namespace MobileGL::MG_Pipe { } } } - m_vertexBuffers[location] = MakeOwnedBuffer(m_context, bytes.data(), bytes.size(), BufferTarget::Vertex); + m_vertexBuffers[location] = + MakeOwnedBuffer(m_context, bytes.data(), bytes.size(), BufferTarget::Vertex); handles[location] = MGPipeResourceTrackerInstance().Find(*m_vertexBuffers[location]); } MGPipeVertexInputEmitterInstance().EmitVertexBuffers(m_context, info.StartInstance, &handles); @@ -173,15 +173,30 @@ namespace MobileGL::MG_Pipe { static SizeT AttributeBytes(const MG_State::GLState::VertexAttribute& attribute) { if (attribute.IsBgra || attribute.Type == DataType::Int2101010Rev || - attribute.Type == DataType::Uint2101010Rev) return 4; + attribute.Type == DataType::Uint2101010Rev) + return 4; SizeT component = 0; switch (attribute.Type) { - case DataType::Int8: case DataType::Uint8: component = 1; break; - case DataType::Int16: case DataType::Uint16: case DataType::Float16: component = 2; break; - case DataType::Int32: case DataType::Uint32: case DataType::Float32: case DataType::Fixed32: - component = 4; break; - case DataType::Float64: component = 8; break; - default: return 0; + case DataType::Int8: + case DataType::Uint8: + component = 1; + break; + case DataType::Int16: + case DataType::Uint16: + case DataType::Float16: + component = 2; + break; + case DataType::Int32: + case DataType::Uint32: + case DataType::Float32: + case DataType::Fixed32: + component = 4; + break; + case DataType::Float64: + component = 8; + break; + default: + return 0; } return attribute.Size > 0 && attribute.Size <= 4 ? component * attribute.Size : 0; } diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.cpp b/MobileGL/MG_Impl/Pipe/PipeFill.cpp index 0b2f9a33..09ab3d8e 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.cpp +++ b/MobileGL/MG_Impl/Pipe/PipeFill.cpp @@ -76,7 +76,7 @@ namespace MobileGL::MG_Pipe { // list of friend functions so the header names exactly one friend. struct MGPipeFillAccess { static void CopyServerPackReference(PipeInputs& dst, const PipeInputs& actual, - const PixelStoreParameters& pack) { + const PixelStoreParameters& pack) { dst.m_pixelStore[0] = pack; dst.m_pixelStore[1] = actual.m_pixelStore[1]; } @@ -139,7 +139,8 @@ namespace MobileGL::MG_Pipe { // every draw does not pay a reference-count increment and decrement for a pointer it // already holds (P14: one of these moves per Minecraft draw, the other three rarely). case F::GetBoundVertexArray: - if (dst.m_boundVertexArray != ctx.GetBoundVertexArray()) dst.m_boundVertexArray = ctx.GetBoundVertexArray(); + if (dst.m_boundVertexArray != ctx.GetBoundVertexArray()) + dst.m_boundVertexArray = ctx.GetBoundVertexArray(); break; case F::GetBufferBindingSlot: // Every global target has a slot; Index stays null - GLContext resolves it @@ -358,11 +359,15 @@ namespace MobileGL::MG_Pipe { }; namespace { - GLContext* LiveContext() { return MG_State::pGLContext.get(); } + GLContext* LiveContext() { + return MG_State::pGLContext.get(); + } #if !MOBILEGL_BUILD_DISAGGREGATED // P13 W5: no transport, no run-ahead client. - Bool ClientRunsAhead() { return false; } + Bool ClientRunsAhead() { + return false; + } #endif #if MOBILEGL_BUILD_DISAGGREGATED // ---- P5e (ra): the fill decision (CONTRACT-P5E §3.1) ----------------------------- @@ -408,12 +413,12 @@ namespace MobileGL::MG_Pipe { if (g_forceBarrieredFill) return true; MGPWireOp op = MGPipeRecordOpForVerb(verb); if (op == MGPWireOp::ReadPixels && PackReadbackGoesToBuffer(ctx)) op = MGPWireOp::ReadPixelsToBuffer; - if (op == MGPWireOp::GetTextureImage && PackReadbackGoesToBuffer(ctx)) op = MGPWireOp::GetTextureImageToBuffer; + if (op == MGPWireOp::GetTextureImage && PackReadbackGoesToBuffer(ctx)) + op = MGPWireOp::GetTextureImageToBuffer; // No record (answered from the caps mirror): no apply can read a fill for it. if (op == kMGPipeNoRecord) return false; if (MGPipeWaitClassFor(op) != kWaitNone) return true; - if (MGPipeCallClassFor(op) == kCtxVerb && ctx != nullptr && - ctx->IsTransformFeedbackActive()) { + if (MGPipeCallClassFor(op) == kCtxVerb && ctx != nullptr && ctx->IsTransformFeedbackActive()) { return true; } return false; @@ -529,7 +534,8 @@ namespace MobileGL::MG_Pipe { // precedes MGP_INPUT_VERIFY_READ in every accessor and a stamp of 0 is never fresh, // so such a read is Fatal{UnmigratedPipeInput, "@"} before the hook // could matter - which holds only while a verify build always carries the poison. - static_assert(MOBILEGL_PIPE_POISON, "the compare-at-read hook relies on the poison for reads before the first fill"); + static_assert(MOBILEGL_PIPE_POISON, + "the compare-at-read hook relies on the poison for reads before the first fill"); struct VerifyState { Bool Parsed = false; @@ -569,8 +575,8 @@ namespace MobileGL::MG_Pipe { // filled). Cost: two Bool compares and one String compare per fill, verify builds only. void ArmVerify() { const auto& features = MG_Config::Features; - if (g_verify.Parsed && g_verify.Enabled == features.PipeVerify && g_verify.Fatal == features.PipeVerifyFatal && - g_verify.CorruptKnob == features.PipeVerifyCorrupt) { + if (g_verify.Parsed && g_verify.Enabled == features.PipeVerify && + g_verify.Fatal == features.PipeVerifyFatal && g_verify.CorruptKnob == features.PipeVerifyCorrupt) { return; } g_verify.Parsed = true; @@ -588,18 +594,20 @@ namespace MobileGL::MG_Pipe { g_verify.Corrupt = field; } // The lanes grep for this line: a verify run whose log lacks it never armed. - MGLOG_I("MGPipe: verify armed - %u fields, %u verbs, fatal=%d", static_cast(kMGPipeInputFieldCount), - static_cast(kMGPipeVerbCount), g_verify.Fatal ? 1 : 0); + MGLOG_I("MGPipe: verify armed - %u fields, %u verbs, fatal=%d", + static_cast(kMGPipeInputFieldCount), static_cast(kMGPipeVerbCount), + g_verify.Fatal ? 1 : 0); if (g_verify.Corrupt) { - MGLOG_I("MGPipe: verify corruption armed - %s", kMGPipeInputFieldNames[static_cast(*g_verify.Corrupt)]); + MGLOG_I("MGPipe: verify corruption armed - %s", + kMGPipeInputFieldNames[static_cast(*g_verify.Corrupt)]); } } void ReportDivergence(MGPipeInputField field, const char* where) { const Uint64 serial = MGPipeFillAccess::Filled(MGPipeClientInputs()).CurrentVerbSerial; MGLOG_F("MGPipe: Fatal{PipeVerifyDiffer, \"%s@%s\", verb=%llu, where=%s}", - kMGPipeInputFieldNames[static_cast(field)], MGPipeVerbName(MGPipeClientInputs().CurrentVerb()), - static_cast(serial), where); + kMGPipeInputFieldNames[static_cast(field)], + MGPipeVerbName(MGPipeClientInputs().CurrentVerb()), static_cast(serial), where); if (g_verify.Fatal) std::abort(); g_verify.Divergences.fetch_add(1, std::memory_order_relaxed); } @@ -685,7 +693,9 @@ namespace MobileGL::MG_Pipe { // PipeInputs.h. The FATAL=0 summary, written while the role's log is still open: // MobileGL::Destroy calls this right before MG_Util::Debug::Close() - see VerifyState for why // the static destructor cannot be the writer. - void MGPipeVerifyFlushSummary() { g_verify.FlushSummary(); } + void MGPipeVerifyFlushSummary() { + g_verify.FlushSummary(); + } #endif // MOBILEGL_PIPE_VERIFY // ---- push on mutation (P1 lane finding F2) ---- @@ -722,8 +732,8 @@ namespace MobileGL::MG_Pipe { // barriered verb the applier is usually already caught up, so this is a watermark test // rather than a park in the common case. QuiesceApplierBeforeFill("MGPipeNoteFrontendMutation"); - MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt( - "MGPipeNoteFrontendMutation", /*isBarrieredFill=*/true); + MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt("MGPipeNoteFrontendMutation", + /*isBarrieredFill=*/true); #endif auto* ctx = LiveContext(); if (ctx == nullptr) return; @@ -931,8 +941,8 @@ namespace MobileGL::MG_Pipe { // place. Monolith keeps initialBytes on the record; its applier reads them directly. The // apply thread (RunsAsTheServerRole()) also takes the plain route: it emits no client wire // records, so the follow-up's self-check would abort the server. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole() && initialBytes != nullptr) { + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole() && + initialBytes != nullptr) { MGPipeRouteResourceRespecify(desc, nullptr); // COUNTED, NOT ASSUMED, and this is the only thing that can gate the follow-up at // all. Dropping the walk below leaves a respecify that went out with nullptr and @@ -989,15 +999,18 @@ namespace MobileGL::MG_Pipe { return stageChunk == 0 ? kMGPipeSubDataMaxRecordSize : static_cast(stageChunk); } #else - constexpr Uint64 MGPipeContentChunkCap() { return kMGPipeSubDataMaxRecordSize; } + constexpr Uint64 MGPipeContentChunkCap() { + return kMGPipeSubDataMaxRecordSize; + } #endif void MGPipeEmitResourceSubData(BufferObject& buffer, SizeT offset, SizeT size) { const MGPipeHandle handle = ContentHandleFor(buffer, "resource_subdata"); if (MGPipeHandleIsNull(handle)) return; const Uint8* base = buffer.MappedData(); - const Bool encodable = - MGPipeForEachSubDataRecordRange(offset, size, [&](Uint64 at, Uint64 length) { + const Bool encodable = MGPipeForEachSubDataRecordRange( + offset, size, + [&](Uint64 at, Uint64 length) { MGPSubData record{}; // The pre-pass inside the walk proved every piece encodable before the first // one was emitted, so this cannot be false - but a zeroed record (null @@ -1015,7 +1028,8 @@ namespace MobileGL::MG_Pipe { // rather than re-read so the staged run and the record's own claim come from // one number. MGPipeRouteResourceSubData(record, base + at, length); - }, MGPipeContentChunkCap()); + }, + MGPipeContentChunkCap()); if (!encodable) { MGLOG_E_ONCE("MGPipe: resource_subdata range [%llu, +%llu) on buffer %u cannot be encoded - " "one record's destination box caps the offset at 2^31-1", @@ -1028,8 +1042,9 @@ namespace MobileGL::MG_Pipe { const MGPipeHandle handle = ContentHandleFor(buffer, "buffer_subdata_resident"); if (MGPipeHandleIsNull(handle)) return; const auto* base = static_cast(bytes); - const Bool encodable = - MGPipeForEachSubDataRecordRange(offset, size, [&](Uint64 at, Uint64 length) { + const Bool encodable = MGPipeForEachSubDataRecordRange( + offset, size, + [&](Uint64 at, Uint64 length) { MGPSubData record{}; // NOT a verbatim level shadow: these bytes are the application's staging // store, or the pattern FillSubData expanded locally, and neither is this @@ -1049,10 +1064,10 @@ namespace MobileGL::MG_Pipe { // PipeStats.h's CallClass::ResidentSubDataEmissions for why the count is the // only thing that can tell this arm from the in-place memcpy beside it. if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::AddCalls( - MG_Util::PipeStats::CallClass::ResidentSubDataEmissions, 1); + MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::ResidentSubDataEmissions, 1); } - }, MGPipeContentChunkCap()); + }, + MGPipeContentChunkCap()); if (!encodable) { MGLOG_E_ONCE("MGPipe: buffer_subdata_resident range [%llu, +%llu) on buffer %u cannot be encoded", static_cast(offset), static_cast(size), @@ -1078,8 +1093,8 @@ namespace MobileGL::MG_Pipe { // Only that range: staging bytes outside it would write stale shadow bytes over server // data the flush does not cover, such as GPU writes. The apply thread takes the plain // route, as in MGPipeEmitResourceRespecify. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole() && size != 0) { + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole() && + size != 0) { MGPipeEmitResourceSubData(buffer, offset, size); } #endif @@ -1217,7 +1232,9 @@ namespace MobileGL::MG_Pipe { // fill only for UNBARRIERED records); an unbarriered apply may not read it at all, and // §3.3's detector is what says so by name. Clearing the stamps as well would trade that // named abort for the poison Fatal, which names the field but not the rule it broke. - void MGPipeReleaseResidualFillPins() { MGPipeFillAccess::ReleaseObjectPins(MGPipeClientInputs()); } + void MGPipeReleaseResidualFillPins() { + MGPipeFillAccess::ReleaseObjectPins(MGPipeClientInputs()); + } // P5e (ra), CONTRACT-P5E §2.5. Declared in PipeMutation.h; see there for why it is not a // ClientSession call at the GL entry point. @@ -1279,8 +1296,7 @@ namespace MobileGL::MG_Pipe { Bool MGPipeEmitVertexElementsDestroyAndFree(Uint64 lifetimeId) { #if MOBILEGL_BUILD_DISAGGREGATED - if (MGPipeDeferDestroyAndFreeIfOnApplyThread(MGPipeKind::VertexElementsCso, - lifetimeId)) { + if (MGPipeDeferDestroyAndFreeIfOnApplyThread(MGPipeKind::VertexElementsCso, lifetimeId)) { return false; } #endif @@ -1296,8 +1312,7 @@ namespace MobileGL::MG_Pipe { // May be the null handle: no slot is minted for a VAO that no draw ever validated with // and no backend twin table ever looked up. That case still raises the notice below - // see there. - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::VertexElementsCso, lifetimeId); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::VertexElementsCso, lifetimeId); // ASKED, NOT ASSUMED. A slot is not evidence of a record: DirectGLES mints one from // BackendSlotTable::GetOrCreate at every VAO sync, whether or not bit 8 asked this @@ -1355,9 +1370,9 @@ namespace MobileGL::MG_Pipe { // once instead of four times. It is deliberately NOT kMGPipeSubsystemsMigratedAtP4a // (which is 0x1fff, every bit through P4a): the rule belongs to the families this // phase adds and to no earlier one. - inline constexpr Uint64 kMGPipeP4aFamilySubsystems = - kMGPipeSubsystemFramebuffer | kMGPipeSubsystemTextureResources | - kMGPipeSubsystemSamplers | kMGPipeSubsystemPrograms; + inline constexpr Uint64 kMGPipeP4aFamilySubsystems = kMGPipeSubsystemFramebuffer | + kMGPipeSubsystemTextureResources | + kMGPipeSubsystemSamplers | kMGPipeSubsystemPrograms; // A handle family (framebuffer, texture resources, samplers, programs) emits only when // something consumes its records; otherwise it emits nothing and the legacy path runs. An @@ -1380,8 +1395,7 @@ namespace MobileGL::MG_Pipe { if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { // P5f fm: texture/framebuffer records have their own server consumers; // Magma consumes them without claiming the still-unmigrated buffer family. - return MG_Remote::Client::CapsMirrorInstance().ServerConsumes( - subsystem & kMGPipeP4aFamilySubsystems); + return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(subsystem & kMGPipeP4aFamilySubsystems); } #endif return MGPipeGetResourceOps() != nullptr; @@ -1401,36 +1415,31 @@ namespace MobileGL::MG_Pipe { constexpr Uint64 P4aFamilyDependencyTableCoverage() { Uint64 covered = 0; - for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) covered |= row.Family; + for (const MGPipeSubsystemDependencyRow& row : kMGPipeSubsystemDependencies) + covered |= row.Family; return covered; } - static_assert((P4aFamilyDependencyTableCoverage() & kMGPipeP4aFamilySubsystems) == - kMGPipeP4aFamilySubsystems, + static_assert((P4aFamilyDependencyTableCoverage() & kMGPipeP4aFamilySubsystems) == kMGPipeP4aFamilySubsystems, "every P4a family needs a D-K2 dependency row in MG_Pipe/SubsystemDeps.def, " "even an empty one"); - static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) == - kMGPipeSubsystemTextureResources, + static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) == kMGPipeSubsystemTextureResources, "bit 9 requires bit 10"); static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemTextureResources) == (kMGPipeSubsystemResources | kMGPipeSubsystemSamplers), "bit 10 requires bit 7 and bit 11"); - static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) == - kMGPipeSubsystemTextureResources, + static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) == kMGPipeSubsystemTextureResources, "bit 11 requires bit 10"); static_assert(P4aFamilyDependencyBits(kMGPipeSubsystemPrograms) == 0, "bit 12 depends on nothing"); // No family may depend on itself: a row that did would be unfalsifiable (its own bit is // set by the time the conjunct is evaluated) and would read as a dependency nobody has. - static_assert((P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) & - kMGPipeSubsystemFramebuffer) == 0 && + static_assert((P4aFamilyDependencyBits(kMGPipeSubsystemFramebuffer) & kMGPipeSubsystemFramebuffer) == 0 && (P4aFamilyDependencyBits(kMGPipeSubsystemTextureResources) & kMGPipeSubsystemTextureResources) == 0 && - (P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) & - kMGPipeSubsystemSamplers) == 0, + (P4aFamilyDependencyBits(kMGPipeSubsystemSamplers) & kMGPipeSubsystemSamplers) == 0, "a D-K2 row must not name its own family"); // The default mask carries every dependency, so the shipped arm is unchanged by all of // this - the table only ever narrows a HAND-PICKED A/B mask. - static_assert((kMGPipeSubsystemsMigratedAtP4a & - P4aFamilyDependencyBits(kMGPipeP4aFamilySubsystems)) == + static_assert((kMGPipeSubsystemsMigratedAtP4a & P4aFamilyDependencyBits(kMGPipeP4aFamilySubsystems)) == P4aFamilyDependencyBits(kMGPipeP4aFamilySubsystems), "the P4a phase mask must satisfy every dependency it declares"); @@ -1457,8 +1466,7 @@ namespace MobileGL::MG_Pipe { if (!MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemBufferBindings, pushMask)) return false; #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { - return MG_Remote::Client::CapsMirrorInstance().ServerConsumes( - kMGPipeSubsystemBufferBindings); + return MG_Remote::Client::CapsMirrorInstance().ServerConsumes(kMGPipeSubsystemBufferBindings); } #endif // Under monolith the "consumer" is the backend that registered the resource op @@ -1469,10 +1477,8 @@ namespace MobileGL::MG_Pipe { Bool FamilyIsLive(Uint64 subsystem, Uint64 wired) { const Uint64 pushMask = MG_Config::Features.PipePush; - return (pushMask & subsystem) != 0 && (wired & subsystem) != 0 && - P4aFamilyHasItsConsumer(subsystem) && - P4aFamilyDependenciesAreSet(subsystem, pushMask) && - P5eFamilyIsLive(subsystem, pushMask); + return (pushMask & subsystem) != 0 && (wired & subsystem) != 0 && P4aFamilyHasItsConsumer(subsystem) && + P4aFamilyDependenciesAreSet(subsystem, pushMask) && P5eFamilyIsLive(subsystem, pushMask); } // ---- The family seam: a birth hook forwards to its family's emitter ---- @@ -1543,7 +1549,8 @@ namespace MobileGL::MG_Pipe { // A fresh server holds no record at all (MGPipeForgetEndedSession). void ForgetAll() { - for (Vector& table : m_kinds) table.clear(); + for (Vector& table : m_kinds) + table.clear(); m_band.clear(); } @@ -1641,22 +1648,22 @@ namespace MobileGL::MG_Pipe { void MGPipeEmitTextureResourceCreate(ITextureObject& texture) { if (!FamilyIsLive(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)) return; - ForwardWhenWired( - MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.EmitResourceCreate(texture); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), + [&](auto& emitter) { emitter.EmitResourceCreate(texture); }); } void MGPipeEmitTextureResourceRespecify(ITextureObject& texture, MGPipeTextureRespecifyScope scope, Uint32 uploadTarget, Uint32 level) { if (!FamilyIsLive(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)) return; - ForwardWhenWired( - MGPipeTextureEmitterInstance(), - [&](auto& emitter) { emitter.EmitResourceRespecify(texture, scope, uploadTarget, level); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), [&](auto& emitter) { + emitter.EmitResourceRespecify(texture, scope, uploadTarget, level); + }); } void MGPipeEmitTextureParams(ITextureObject& texture) { if (!FamilyIsLive(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)) return; - ForwardWhenWired( - MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.EmitTextureParams(texture); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), + [&](auto& emitter) { emitter.EmitTextureParams(texture); }); } void MGPipeNoteTextureLevelDirty(ITextureObject& storageOwner, Uint32 uploadTarget, Uint32 level) { @@ -1668,31 +1675,29 @@ namespace MobileGL::MG_Pipe { // the texture. The level stays dirty on the object, which no live context can reach. if (MG_Remote::Client::ClientSession::DeviceLost()) return; #endif - ForwardWhenWired( - MGPipeTextureEmitterInstance(), - [&](auto& emitter) { emitter.NoteLevelDirty(storageOwner, uploadTarget, level); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), [&](auto& emitter) { + emitter.NoteLevelDirty(storageOwner, uploadTarget, level); + }); } void MGPipeEmitRenderbufferResourceCreate(RenderbufferObject& renderbuffer) { if (!FamilyIsLive(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)) return; ForwardWhenWired( - MGPipeTextureEmitterInstance(), - [&](auto& emitter) { emitter.EmitRenderbufferCreate(renderbuffer); }); + MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.EmitRenderbufferCreate(renderbuffer); }); } void MGPipeEmitRenderbufferResourceRespecify(RenderbufferObject& renderbuffer) { if (!FamilyIsLive(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)) return; ForwardWhenWired( - MGPipeTextureEmitterInstance(), - [&](auto& emitter) { emitter.EmitRenderbufferRespecify(renderbuffer); }); + MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.EmitRenderbufferRespecify(renderbuffer); }); } void MGPipeNoteTextureBoundAs(MGPipeHandle texture, Uint32 bindBit) { // Not gated on FamilyIsLive: the mask is client state (see the declaration), and the // emitter gates the emission it causes. - ForwardWhenWired( - MGPipeTextureEmitterInstance(), - [&](auto& emitter) { emitter.NoteTextureBoundAs(texture, static_cast(bindBit)); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), [&](auto& emitter) { + emitter.NoteTextureBoundAs(texture, static_cast(bindBit)); + }); } void MGPipeNoteTextureImageBound(ITextureObject& texture) { @@ -1704,20 +1709,20 @@ namespace MobileGL::MG_Pipe { void MGPipeEmitSamplerCsoCreate(SamplerObject& sampler) { if (!FamilyIsLive(kMGPipeSubsystemSamplers, kMGPipeWiredSamplerSubsystem)) return; - ForwardWhenWired( - MGPipeSamplerEmitterInstance(), [&](auto& emitter) { emitter.EmitSamplerCso(sampler); }); + ForwardWhenWired(MGPipeSamplerEmitterInstance(), + [&](auto& emitter) { emitter.EmitSamplerCso(sampler); }); } void MGPipeEmitSamplerViewCreate(ITextureObject& texture) { if (!FamilyIsLive(kMGPipeSubsystemSamplers, kMGPipeWiredSamplerSubsystem)) return; - ForwardWhenWired( - MGPipeSamplerEmitterInstance(), [&](auto& emitter) { emitter.EmitSamplerView(texture); }); + ForwardWhenWired(MGPipeSamplerEmitterInstance(), + [&](auto& emitter) { emitter.EmitSamplerView(texture); }); } void MGPipeEmitShaderCsoCreate(ProgramObject& program) { if (!FamilyIsLive(kMGPipeSubsystemPrograms, kMGPipeWiredProgramSubsystem)) return; - ForwardWhenWired( - MGPipeProgramEmitterInstance(), [&](auto& emitter) { emitter.EmitShaderCso(program); }); + ForwardWhenWired(MGPipeProgramEmitterInstance(), + [&](auto& emitter) { emitter.EmitShaderCso(program); }); } // ---- Client-side death helpers, one per handle kind minted above ---- @@ -1759,8 +1764,7 @@ namespace MobileGL::MG_Pipe { // The parameter type is the route's, which is byte-identical to the applier's // (const MGPHandleOnly&, void return), so the fix is `&MGPipeRoute` at the five call // sites; PipeCatalogue.FrontendNeverTakesAnApplierAddress checks MG_Impl in every unit lane. - Bool EmitDeleteIfPublished(MGPipeKind kind, MGPipeHandle handle, - void (*route)(const MGPHandleOnly&)) { + Bool EmitDeleteIfPublished(MGPipeKind kind, MGPipeHandle handle, void (*route)(const MGPHandleOnly&)) { if (!MGPipeHandleIsPublished(kind, handle)) return false; route(HandleOnly(kind, handle)); MGPipeNoteHandleUnpublished(kind, handle); @@ -1785,12 +1789,10 @@ namespace MobileGL::MG_Pipe { return false; } #endif - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::SamplerViewCso, lifetimeId); - const Bool published = - EmitDeleteIfPublished(MGPipeKind::SamplerViewCso, handle, &MGPipeRouteDeleteSamplerView); - ForwardWhenWired( - MGPipeSamplerEmitterInstance(), [&](auto& emitter) { emitter.NoteRecordDestroyed(handle); }); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::SamplerViewCso, lifetimeId); + const Bool published = EmitDeleteIfPublished(MGPipeKind::SamplerViewCso, handle, &MGPipeRouteDeleteSamplerView); + ForwardWhenWired(MGPipeSamplerEmitterInstance(), + [&](auto& emitter) { emitter.NoteRecordDestroyed(handle); }); // THE NOTICE IS RAISED FOR THIS KIND TOO, and the reason it once was not is wrong: // NotifyStateObjectDestroyed takes a KIND and a lifetime id, not an object // (StateObjectDeathNotice.h - one entry point for every kind rather than one ops table @@ -1813,13 +1815,12 @@ namespace MobileGL::MG_Pipe { } #endif const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, lifetimeId); - const Bool published = - EmitDeleteIfPublished(MGPipeKind::Texture, handle, &MGPipeRouteResourceDestroy); + const Bool published = EmitDeleteIfPublished(MGPipeKind::Texture, handle, &MGPipeRouteResourceDestroy); // The emitter retires its entry while the handle still resolves (C-2): the drain list // drops the dead texture's levels, the raw pointer goes, the built-in sampler's cache // reference is given back, the latches and the sticky mask are cleared. - ForwardWhenWired( - MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.NoteTextureDied(handle); }); + ForwardWhenWired(MGPipeTextureEmitterInstance(), + [&](auto& emitter) { emitter.NoteTextureDied(handle); }); NotifyAndFree(MGPipeKind::Texture, lifetimeId, handle); // The texture's SamplerViewCso shares its lifetime id and is released here, after the // texture's own record, so a server reading the view while the texture is dropped still @@ -1838,12 +1839,10 @@ namespace MobileGL::MG_Pipe { return false; } #endif - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, lifetimeId); - const Bool published = - EmitDeleteIfPublished(MGPipeKind::Renderbuffer, handle, &MGPipeRouteResourceDestroy); - ForwardWhenWired( - MGPipeTextureEmitterInstance(), [&](auto& emitter) { emitter.NoteRenderbufferDied(handle); }); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, lifetimeId); + const Bool published = EmitDeleteIfPublished(MGPipeKind::Renderbuffer, handle, &MGPipeRouteResourceDestroy); + ForwardWhenWired(MGPipeTextureEmitterInstance(), + [&](auto& emitter) { emitter.NoteRenderbufferDied(handle); }); NotifyAndFree(MGPipeKind::Renderbuffer, lifetimeId, handle); return published; } @@ -1861,10 +1860,9 @@ namespace MobileGL::MG_Pipe { // MarkFramebufferObjectForDeletion rebinds slots holding it to framebuffer 0, and a // recycled handle cannot match its predecessor's record because Gen is in its ContentHash. // Nothing latches a framebuffer publication, so the answer is always false. - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Framebuffer, lifetimeId); - ForwardWhenWired( - MGPipeFramebufferEmitterInstance(), [&](auto& emitter) { emitter.NoteFramebufferDied(handle); }); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Framebuffer, lifetimeId); + ForwardWhenWired(MGPipeFramebufferEmitterInstance(), + [&](auto& emitter) { emitter.NoteFramebufferDied(handle); }); NotifyAndFree(MGPipeKind::Framebuffer, lifetimeId, handle); return false; } @@ -1875,10 +1873,8 @@ namespace MobileGL::MG_Pipe { return false; } #endif - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::SamplerCso, lifetimeId); - const Bool published = - EmitDeleteIfPublished(MGPipeKind::SamplerCso, handle, &MGPipeRouteDeleteSamplerState); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::SamplerCso, lifetimeId); + const Bool published = EmitDeleteIfPublished(MGPipeKind::SamplerCso, handle, &MGPipeRouteDeleteSamplerState); // NOTHING TO RETIRE IN AN EMITTER FOR THIS KIND, stated rather than implied: a sampler // CSO is content-addressed and belongs to a value, so no emitter keeps an entry under // a SamplerObject's handle - the cache's entries are keyed by value and reference @@ -1901,12 +1897,10 @@ namespace MobileGL::MG_Pipe { // destructor - and the second is a proven no-op, because MGPipeSlotAllocator::Free // refuses a slot that is not live at that generation and bumps no generation of its // own (the bump rides the next handout). - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::ShaderCso, lifetimeId); - const Bool published = - EmitDeleteIfPublished(MGPipeKind::ShaderCso, handle, &MGPipeRouteDeleteShaderState); - ForwardWhenWired( - MGPipeProgramEmitterInstance(), [&](auto& emitter) { emitter.NoteRecordDestroyed(handle); }); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::ShaderCso, lifetimeId); + const Bool published = EmitDeleteIfPublished(MGPipeKind::ShaderCso, handle, &MGPipeRouteDeleteShaderState); + ForwardWhenWired(MGPipeProgramEmitterInstance(), + [&](auto& emitter) { emitter.NoteRecordDestroyed(handle); }); NotifyAndFree(MGPipeKind::ShaderCso, lifetimeId, handle); return published; } @@ -1934,8 +1928,7 @@ namespace MobileGL::MG_Pipe { // ---- liveness ---- Bool PipeInputs::IsLive() const { #if MOBILEGL_BUILD_DISAGGREGATED - if (MG_Config::Transport != MG_Config::TransportMode::Monolith) - return MGPipeServerContextIsLive(); + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) return MGPipeServerContextIsLive(); #endif return LiveContext() != nullptr; } @@ -1966,7 +1959,8 @@ namespace MobileGL::MG_Pipe { case BufferTarget::AtomicCounter: case BufferTarget::TransformFeedback: return kMGPipeMaxBufferBindingPoints; - default: return 0; + default: + return 0; } } #endif @@ -2000,7 +1994,7 @@ namespace MobileGL::MG_Pipe { }); return true; }(); - } + } // namespace void PipeInputs::InvalidateCompileEnv() { MGP_STICKY_FORWARD_PULL(InvalidateCompileEnv); @@ -2065,7 +2059,9 @@ namespace MobileGL::MG_Pipe { MGPipeTrackerInstance().SetPendingBaseInstance(baseInstance); } - Uint32 MGPipePendingBaseInstance() { return MGPipeTrackerInstance().PendingBaseInstance(); } + Uint32 MGPipePendingBaseInstance() { + return MGPipeTrackerInstance().PendingBaseInstance(); + } void MGPipeLeaveVerb() { PipeInputs& inputs = MGPipeClientInputs(); @@ -2077,8 +2073,8 @@ namespace MobileGL::MG_Pipe { if (g_lastFillWasBarriered) { // Same layer-2 gate as the fill: the serial bump and the verb reset below are // writes into gPipeInputs (gt). - MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt( - "MGPipeLeaveVerb", /*isBarrieredFill=*/true); + MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt("MGPipeLeaveVerb", + /*isBarrieredFill=*/true); } if (!g_lastFillWasBarriered) { MGPipeTrackerInstance().ClearPendingBaseInstance(); @@ -2105,7 +2101,6 @@ namespace MobileGL::MG_Pipe { MGPipeTrackerInstance().ClearPendingBaseInstance(); } - // ================================================================================ // The emission step (P2 brief D1 step 3, D5, D6, D7) // ================================================================================ @@ -2192,8 +2187,7 @@ namespace MobileGL::MG_Pipe { // rather than kMGPipeSubsystemVertexInput to pin the two maps to each other: an emitter // row moved to another subsystem must fail too. bind_vertex_elements is the family's only // Coverage.def emitter row, so it is the emitter side of all three. - static_assert(SubsystemForEmitter(MGPipeFieldEmitter::BindVertexElements) == - kMGPipeSubsystemVertexInput, + static_assert(SubsystemForEmitter(MGPipeFieldEmitter::BindVertexElements) == kMGPipeSubsystemVertexInput, "bind_vertex_elements must name the vertex-input subsystem"); static_assert(MGPipeSubsystemForDirty(MGPipeDirty::NewVertexElements) == SubsystemForEmitter(MGPipeFieldEmitter::BindVertexElements), @@ -2207,8 +2201,7 @@ namespace MobileGL::MG_Pipe { // The two vertex views' capacity is one number on both sides of the boundary. This is // the one translation unit that sees the frontend constant and the MG_Pipe one, so it // is where they are pinned together; MGPipeTypes.h says so in place. - static_assert(kMGPipeMaxVertexAttribs == - MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS, + static_assert(kMGPipeMaxVertexAttribs == MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS, "the MGPipe vertex-attribute capacity and the frontend's have drifted"); // P5e (docs/archive/contracts/CONTRACT-P5E.md §1, ruling 10), pinned here for the same reason and in @@ -2216,8 +2209,7 @@ namespace MobileGL::MG_Pipe { // and nothing else does. 84 is what the emitter may describe and what the wire carries; // the BACKEND clamps to the device's real GL_MAX_UNIFORM_BUFFER_BINDINGS, as it does // today, because a client-side clamp would read a device capability from the wrong side. - static_assert(kMGPipeMaxBufferBindingPoints == - MG_State::GLState::BufferBindingPointCount, + static_assert(kMGPipeMaxBufferBindingPoints == MG_State::GLState::BufferBindingPointCount, "the MGPipe buffer-binding-point capacity and the frontend's have drifted"); // ---- P4a's SEVEN pairings, and EVERY ONE OF THEM COMPARES AGAINST @@ -2281,8 +2273,7 @@ namespace MobileGL::MG_Pipe { // one of them was edited. Exactly the shape NoDirtyBitOwnsTheResidualSubsystem uses. constexpr Bool NoDirtyBitOwnsTheTextureResourceSubsystem() { for (SizeT i = 0; i < kMGPipeDirtyCount; ++i) { - if (MGPipeSubsystemForDirty(static_cast(i)) == - kMGPipeSubsystemTextureResources) { + if (MGPipeSubsystemForDirty(static_cast(i)) == kMGPipeSubsystemTextureResources) { return false; } } @@ -2311,24 +2302,18 @@ namespace MobileGL::MG_Pipe { // own emit header; the sampler one covers SamplerEmit.h and ImageEmit.h. The residual // subsystem is here for set_context_values, which has no dirty bit, so the residual-fill // skip reads this mask for its eight fields. - constexpr Uint64 kMGPipeWiredSubsystems = kMGPipeSubsystemRenderState | - kMGPipeSubsystemPixelPack | - kMGPipeSubsystemPatchState | - kMGPipeSubsystemVertexAttribDefaults | - kMGPipeSubsystemResidualValues | - kMGPipeSubsystemResources | - kMGPipeSubsystemVertexInput | - kMGPipeWiredFramebufferSubsystem | - kMGPipeWiredTextureSubsystem | - kMGPipeWiredSamplerSubsystem | - kMGPipeWiredProgramSubsystem | - // P5e (sb): the indexed buffer binding - // points, by the same rule and from the same - // kind of header. ID-106 pins the other half - // of the switch: MG_Backend/Init.cpp's - // consumer mask gains bit 13 in the same - // commit, or R-8 withholds the whole family. - kMGPipeWiredBufferBindingSubsystem; + constexpr Uint64 kMGPipeWiredSubsystems = + kMGPipeSubsystemRenderState | kMGPipeSubsystemPixelPack | kMGPipeSubsystemPatchState | + kMGPipeSubsystemVertexAttribDefaults | kMGPipeSubsystemResidualValues | kMGPipeSubsystemResources | + kMGPipeSubsystemVertexInput | kMGPipeWiredFramebufferSubsystem | kMGPipeWiredTextureSubsystem | + kMGPipeWiredSamplerSubsystem | kMGPipeWiredProgramSubsystem | + // P5e (sb): the indexed buffer binding + // points, by the same rule and from the same + // kind of header. ID-106 pins the other half + // of the switch: MG_Backend/Init.cpp's + // consumer mask gains bit 13 in the same + // commit, or R-8 withholds the whole family. + kMGPipeWiredBufferBindingSubsystem; // P15: the dirty bits whose family this build, push mask and consumer emit - every // condition of the per-bit gate in MGPipeValidateForVerb except the dirty bit itself. @@ -2376,11 +2361,9 @@ namespace MobileGL::MG_Pipe { static_assert(kMGPipeWiredTextureSubsystem == 0 || kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources, "TextureEmit.h's wired constant must be 0 or the texture-resource bit"); - static_assert(kMGPipeWiredSamplerSubsystem == 0 || - kMGPipeWiredSamplerSubsystem == kMGPipeSubsystemSamplers, + static_assert(kMGPipeWiredSamplerSubsystem == 0 || kMGPipeWiredSamplerSubsystem == kMGPipeSubsystemSamplers, "SamplerEmit.h's wired constant must be 0 or the sampler bit"); - static_assert(kMGPipeWiredProgramSubsystem == 0 || - kMGPipeWiredProgramSubsystem == kMGPipeSubsystemPrograms, + static_assert(kMGPipeWiredProgramSubsystem == 0 || kMGPipeWiredProgramSubsystem == kMGPipeSubsystemPrograms, "ProgramEmit.h's wired constant must be 0 or the program bit"); static_assert(kMGPipeWiredBufferBindingSubsystem == 0 || kMGPipeWiredBufferBindingSubsystem == kMGPipeSubsystemBufferBindings, @@ -2487,8 +2470,7 @@ namespace MobileGL::MG_Pipe { for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { const auto field = static_cast(i); const Uint64 subsystem = SubsystemForEmitter(kMGPipeFieldEmittedBy[i]); - if ((subsystem & kMGPipeP4aFamilySubsystems) != 0 && - EmittedCallSuppliesTheWholeField(field)) { + if ((subsystem & kMGPipeP4aFamilySubsystems) != 0 && EmittedCallSuppliesTheWholeField(field)) { return false; } } @@ -2557,8 +2539,7 @@ namespace MobileGL::MG_Pipe { }; ResidualFillPlan g_fillPlan; - MGPipeFieldMask SuppliedFieldMask(Uint64 pushMask, Bool applierDerives, - Bool contextValuesWireLive) { + MGPipeFieldMask SuppliedFieldMask(Uint64 pushMask, Bool applierDerives, Bool contextValuesWireLive) { // THE CONSUMER SIGNAL IS READ ONLY WHERE THE WALK BELOW COULD REACH IT, and that // guard is not a micro-optimisation - it is what keeps R-8's DIAGNOSTIC COUNTERS // where they were (P5d r3 package C review, minor 2). In the conjunction below @@ -2570,17 +2551,16 @@ namespace MobileGL::MG_Pipe { // where the baseline never touched the mirror, which is the opposite direction from // the one this change is supposed to move them in. The key stays COMPLETE because // pushMask is itself in the key: a mask that gains a P4a bit re-keys on the mask. - const Bool p4aConsumer = (pushMask & kMGPipeP4aFamilySubsystems) != 0 && - P4aFamilyHasItsConsumer(kMGPipeP4aFamilySubsystems); - if (g_fillPlan.Valid && g_fillPlan.PushMask == pushMask && - g_fillPlan.ApplierDerives == applierDerives && - g_fillPlan.ContextValuesWireLive == contextValuesWireLive && - g_fillPlan.P4aConsumer == p4aConsumer + const Bool p4aConsumer = + (pushMask & kMGPipeP4aFamilySubsystems) != 0 && P4aFamilyHasItsConsumer(kMGPipeP4aFamilySubsystems); + if (g_fillPlan.Valid && g_fillPlan.PushMask == pushMask && g_fillPlan.ApplierDerives == applierDerives && + g_fillPlan.ContextValuesWireLive == contextValuesWireLive && g_fillPlan.P4aConsumer == p4aConsumer #if MOBILEGL_BUILD_DISAGGREGATED - && g_fillPlan.CapsGeneration == (MG_Config::Transport != MG_Config::TransportMode::Monolith ? - MG_Remote::Client::CapsMirrorInstance().Generation() : 0) + && g_fillPlan.CapsGeneration == (MG_Config::Transport != MG_Config::TransportMode::Monolith + ? MG_Remote::Client::CapsMirrorInstance().Generation() + : 0) #endif - ) { + ) { return g_fillPlan.Supplied; } MGPipeFieldMask built{}; @@ -2605,14 +2585,11 @@ namespace MobileGL::MG_Pipe { // NO PRODUCER without a live wire (its emission is transport-gated at the validate // point), so its eight fields keep being pulled under monolith - G1's byte-for-byte // rule - and are skipped only when the record really crosses. - const Bool supplied = subsystem != 0 && (subsystem & kMGPipeWiredSubsystems) != 0 && - (pushMask & subsystem) != 0 && - P4aFamilyHasItsConsumer(subsystem) && - P4aFamilyDependenciesAreSet(subsystem, pushMask) && - EmittedCallSuppliesTheWholeField(field) && - (applierDerives || AppliedWithoutDerivation(field)) && - (emitter != MGPipeFieldEmitter::SetContextValues || - contextValuesWireLive); + const Bool supplied = + subsystem != 0 && (subsystem & kMGPipeWiredSubsystems) != 0 && (pushMask & subsystem) != 0 && + P4aFamilyHasItsConsumer(subsystem) && P4aFamilyDependenciesAreSet(subsystem, pushMask) && + EmittedCallSuppliesTheWholeField(field) && (applierDerives || AppliedWithoutDerivation(field)) && + (emitter != MGPipeFieldEmitter::SetContextValues || contextValuesWireLive); if (supplied) built.Words[i / 64] |= (Uint64{1} << (i % 64)); } // PUBLISHED IN ONE ASSIGNMENT, after the walk - see the paragraph above the struct. @@ -2622,8 +2599,9 @@ namespace MobileGL::MG_Pipe { g_fillPlan.ContextValuesWireLive = contextValuesWireLive; g_fillPlan.P4aConsumer = p4aConsumer; #if MOBILEGL_BUILD_DISAGGREGATED - g_fillPlan.CapsGeneration = MG_Config::Transport != MG_Config::TransportMode::Monolith ? - MG_Remote::Client::CapsMirrorInstance().Generation() : 0; + g_fillPlan.CapsGeneration = MG_Config::Transport != MG_Config::TransportMode::Monolith + ? MG_Remote::Client::CapsMirrorInstance().Generation() + : 0; #endif g_fillPlan.Supplied = built; for (SizeT c = 0; c < ResidualFillPlan::kClasses; ++c) { @@ -2666,8 +2644,10 @@ namespace MobileGL::MG_Pipe { const RenderStateParameters& live = ctx.GetRenderStateParameters(); MGPPatchState patch{}; patch.Vertices = live.PatchVertices; - for (SizeT i = 0; i < 4; ++i) patch.Outer[i] = live.PatchDefaultOuterLevel[i]; - for (SizeT i = 0; i < 2; ++i) patch.Inner[i] = live.PatchDefaultInnerLevel[i]; + for (SizeT i = 0; i < 4; ++i) + patch.Outer[i] = live.PatchDefaultOuterLevel[i]; + for (SizeT i = 0; i < 2; ++i) + patch.Inner[i] = live.PatchDefaultInnerLevel[i]; MGPipeRouteSetPatchState(patch); return sizeof(MGPPatchState); } @@ -2696,11 +2676,12 @@ namespace MobileGL::MG_Pipe { static_assert(kAttribs <= 32, "MGPVertexAttribDefaults::Mask is a Uint32"); Array resolved; - for (SizeT i = 0; i < kAttribs; ++i) resolved[i] = ctx.GetCurrentVertexAttribute(static_cast(i)); + for (SizeT i = 0; i < kAttribs; ++i) + resolved[i] = ctx.GetCurrentVertexAttribute(static_cast(i)); const Uint64 contentHash = XXH64(resolved.data(), sizeof(resolved), 0); if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlot::SetVertexAttribDefaults, - contentHash)) { + contentHash)) { return 0; } @@ -2729,8 +2710,7 @@ namespace MobileGL::MG_Pipe { // it says nothing about which view holds the value the other two were // converted from. MGPipeFillAttribValue(static_cast(i), resolved[i], - ctx.GetCurrentVertexAttributeClass(static_cast(i)), - tail[header.Count]); + ctx.GetCurrentVertexAttributeClass(static_cast(i)), tail[header.Count]); header.Mask |= Uint32{1} << static_cast(i); ++header.Count; staged[i] = resolved[i]; @@ -2799,10 +2779,9 @@ namespace MobileGL::MG_Pipe { values.MaxTouchedTextureUnit = static_cast(ctx.GetMaxTouchedTextureUnit()); // The array is indexed by BufferTarget value, all 15 of them (MGPipeTypes.h); // targets with no binding points answer 0 (BufferState::GetTouchedBindPointCount). - static_assert( - std::extent_v == - static_cast(BufferTarget::BufferTargetCount), - "MGPContextValues' per-target array and BufferTargetCount have drifted"); + static_assert(std::extent_v == + static_cast(BufferTarget::BufferTargetCount), + "MGPContextValues' per-target array and BufferTargetCount have drifted"); for (Uint32 t = 0; t < static_cast(BufferTarget::BufferTargetCount); ++t) { values.TouchedBufferBindingPointCount[t] = static_cast(ctx.GetTouchedBufferBindingPointCount(static_cast(t))); @@ -2847,8 +2826,6 @@ namespace MobileGL::MG_Pipe { MGPipeFieldMaskHas(mask, MGPipeInputField::GetTransformFeedbackCapturedVertices); } - - // The residual block is the ONE emission whose gate names a subsystem constant // directly instead of going through MGPipeSubsystemForDirty, and the reason is that // it has no dirty bit: it carries what has no shutter of its own, which is what makes @@ -2858,8 +2835,7 @@ namespace MobileGL::MG_Pipe { // assumed, the same discipline SubsystemForEmitter's five static_asserts use. constexpr Bool NoDirtyBitOwnsTheResidualSubsystem() { for (SizeT i = 0; i < kMGPipeDirtyCount; ++i) { - if (MGPipeSubsystemForDirty(static_cast(i)) == - kMGPipeSubsystemResidualValues) { + if (MGPipeSubsystemForDirty(static_cast(i)) == kMGPipeSubsystemResidualValues) { return false; } } @@ -2870,8 +2846,7 @@ namespace MobileGL::MG_Pipe { "residual block's gate through MGPipeSubsystemForDirty like every other " "emission, or the two gates will disagree"); - constexpr Uint32 kAllDynamicChunks = - static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1); + constexpr Uint32 kAllDynamicChunks = static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1); // create/bind_render_state and set_dynamic_state. Returns the bytes that went on the // wire, for the payload histogram. @@ -2901,8 +2876,7 @@ namespace MobileGL::MG_Pipe { // glClearColor sends D2. An EMPTY mask still sends the 32-byte header, // because the VERSION is what Magma's dynamic tail gates on and it moved. const Uint32 chunkMask = - freshlyPrimed ? kAllDynamicChunks - : MGPipeDynamicChunksThatMoved(live, tracker.Staged()); + freshlyPrimed ? kAllDynamicChunks : MGPipeDynamicChunksThatMoved(live, tracker.Staged()); Array blob; const SizeT blobBytes = MGPipeDynamicChunkBlobBytes(chunkMask); MGPipeGatherDynamicChunks(live, chunkMask, blob.data()); @@ -3017,8 +2991,12 @@ namespace MobileGL::MG_Pipe { } } // namespace - Uint64 MGPipeVertexAttribDefaultRepairCount() { return g_attribDefaultRepairs; } - MGPVertexAttribDefaults MGPipeVertexAttribDefaultsLastHeader() { return g_attribDefaultLastHeader; } + Uint64 MGPipeVertexAttribDefaultRepairCount() { + return g_attribDefaultRepairs; + } + MGPVertexAttribDefaults MGPipeVertexAttribDefaultsLastHeader() { + return g_attribDefaultLastHeader; + } // The unit gate's door onto step 4's predicate (PipeFill.h says why it needs one). It // returns the memo's answer rather than a second copy of the expression, so a case that @@ -3026,8 +3004,7 @@ namespace MobileGL::MG_Pipe { // re-implementation of it. Bool MGPipeResidualFillSuppliesField(MGPipeInputField field, Uint64 pushMask, Bool applierDerives, Bool contextValuesWireLive) { - return MGPipeFieldMaskHas(SuppliedFieldMask(pushMask, applierDerives, contextValuesWireLive), - field); + return MGPipeFieldMaskHas(SuppliedFieldMask(pushMask, applierDerives, contextValuesWireLive), field); } void MGPipeValidateForVerbBarriered(MGPipeVerb verb) { @@ -3097,10 +3074,11 @@ namespace MobileGL::MG_Pipe { // change scalars the apply thread reads inside the record it is applying. The guard // then checks the fact and aborts with Fatal{RoleViolation, "gPipeInputs"} if the wait // is missing. See ARCHITECTURE.md §9.2.2. - if (runAhead) MG_Remote::Client::ClientSession::NoteBarrieredVerbForStats(kMGPipeVerbNames[static_cast(verb)]); + if (runAhead) + MG_Remote::Client::ClientSession::NoteBarrieredVerbForStats(kMGPipeVerbNames[static_cast(verb)]); QuiesceApplierBeforeFill("MGPipeValidateForVerb"); - MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt( - "MGPipeValidateForVerb", /*isBarrieredFill=*/barriered); + MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt("MGPipeValidateForVerb", + /*isBarrieredFill=*/barriered); } #else constexpr Bool fillOwed = true; @@ -3164,10 +3142,9 @@ namespace MobileGL::MG_Pipe { // P13 W4d: AND ON MONOLITH'S RECORD ARM, where the route is the in-process applier // (Mono_SetContextValues) and the backend reads what the record put in gPipeInputs - the // same supplier as a wire, one call away. - const Bool contextValuesWireLive = - MG_Config::Transport != MG_Config::TransportMode::Monolith - ? MG_Remote::Client::ContextValuesWireLive() - : MG_Config::RecordArmAliasesFrontend(); + const Bool contextValuesWireLive = MG_Config::Transport != MG_Config::TransportMode::Monolith + ? MG_Remote::Client::ContextValuesWireLive() + : MG_Config::RecordArmAliasesFrontend(); #else // P13 W8: a library without a transport is ALWAYS monolith's record arm, so it takes the // eight fields from the record exactly as the split build's monolith does. This branch @@ -3203,8 +3180,7 @@ namespace MobileGL::MG_Pipe { // would wait on itself) and a transport with no live session (EmitApplierResetRecord // returns false) make the direct call instead. const Bool transportActive = - MG_Config::Transport != MG_Config::TransportMode::Monolith && - !MG_Remote::Client::RunsAsTheServerRole(); + MG_Config::Transport != MG_Config::TransportMode::Monolith && !MG_Remote::Client::RunsAsTheServerRole(); Bool resetCrossed = false; if (transportActive) { resetCrossed = MG_Remote::Client::EmitApplierResetRecord(); @@ -3383,8 +3359,8 @@ namespace MobileGL::MG_Pipe { // into a named abort rather than a torn read. if (fillOwed) { QuiesceApplierBeforeFill("MGPipeValidateForVerb/residual"); - MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt( - "MGPipeValidateForVerb/residual", /*isBarrieredFill=*/true); + MG_Remote::Client::ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt("MGPipeValidateForVerb/residual", + /*isBarrieredFill=*/true); } #endif const Bool applierDerives = ApplierDerivesRenderStateFields(); diff --git a/MobileGL/MG_Impl/Pipe/PipeFill.h b/MobileGL/MG_Impl/Pipe/PipeFill.h index 21cf6cd3..091aaca9 100644 --- a/MobileGL/MG_Impl/Pipe/PipeFill.h +++ b/MobileGL/MG_Impl/Pipe/PipeFill.h @@ -126,5 +126,5 @@ namespace MobileGL::MG_Pipe { // baseinstance, carrying the argument RAW. It has to be a macro for MGP_FILL's reason: the // three call sites are compiled in the pull build too, where MGPipeSetPendingBaseInstance is // neither declared nor defined. -#define MGP_SET_BASE_INSTANCE(BaseInstance) \ +#define MGP_SET_BASE_INSTANCE(BaseInstance) \ ::MobileGL::MG_Pipe::MGPipeSetPendingBaseInstance(static_cast<::MobileGL::Uint32>(BaseInstance)) diff --git a/MobileGL/MG_Impl/Pipe/ProgramEmit.h b/MobileGL/MG_Impl/Pipe/ProgramEmit.h index 57b8acc4..ac987ed3 100644 --- a/MobileGL/MG_Impl/Pipe/ProgramEmit.h +++ b/MobileGL/MG_Impl/Pipe/ProgramEmit.h @@ -64,8 +64,8 @@ namespace MobileGL::MG_Pipe { // compile take effect only at the next link, and the same stages index GetGeneratedSpirv(). inline Uint64 MGPipeStorageOverrideSignatureEntry(const String& blockName, Int binding) { Uint64 entry = std::hash{}(blockName); - entry ^= (static_cast(static_cast(binding)) + 0x9e3779b97f4a7c15ull + - (entry << 6) + (entry >> 2)); + entry ^= + (static_cast(static_cast(binding)) + 0x9e3779b97f4a7c15ull + (entry << 6) + (entry >> 2)); return entry; } @@ -97,8 +97,7 @@ namespace MobileGL::MG_Pipe { const auto& drawProgram = ctx.GetProgramForDraw(); const auto& dispatchProgram = ctx.GetProgramForDispatch(); - const MGPipeHandle drawCso = - drawProgram ? AcquireShaderCso(*drawProgram, bytes) : kMGPipeNullHandle; + const MGPipeHandle drawCso = drawProgram ? AcquireShaderCso(*drawProgram, bytes) : kMGPipeNullHandle; // P5e (pg): AFTER the create and never before it. The draft note on // MGPProgramBindings said "emitted BEFORE create_shader_state at the same validate // point", and that ordering is wrong against the applier the same contract @@ -123,13 +122,12 @@ namespace MobileGL::MG_Pipe { // one pipeline name released the other context's LIVE composite. // GetTextureContextId() is the tree's never-reused per-context id, the same // one PipeInputs carries and the backends' per-context memos key on. - MGPipeCompositeResolverInstance().Observe(ctx.GetTextureContextId(), *pipeline, - *drawProgram, drawCso); + MGPipeCompositeResolverInstance().Observe(ctx.GetTextureContextId(), *pipeline, *drawProgram, + drawCso); } } const MGPipeHandle dispatchCso = - dispatchProgram ? (dispatchProgram == drawProgram ? drawCso - : AcquireShaderCso(*dispatchProgram, bytes)) + dispatchProgram ? (dispatchProgram == drawProgram ? drawCso : AcquireShaderCso(*dispatchProgram, bytes)) : kMGPipeNullHandle; if (dispatchProgram && dispatchProgram != drawProgram) { bytes += EmitProgramBindings(*dispatchProgram, dispatchCso); @@ -353,13 +351,14 @@ namespace MobileGL::MG_Pipe { // every c_str() taken before that would dangle. m_storageOverrideNamePtrs.clear(); m_storageOverrideNamePtrs.reserve(m_storageOverrideNames.size()); - for (const String& name : m_storageOverrideNames) m_storageOverrideNamePtrs.push_back(name.c_str()); + for (const String& name : m_storageOverrideNames) + m_storageOverrideNamePtrs.push_back(name.c_str()); - MGPipeRouteSetProgramBindings( - m_lastBindings, m_blockBindings.empty() ? nullptr : m_blockBindings.data(), - m_samplerUnits.empty() ? nullptr : m_samplerUnits.data(), - m_storageOverrides.empty() ? nullptr : m_storageOverrides.data(), - m_storageOverrideNamePtrs.empty() ? nullptr : m_storageOverrideNamePtrs.data()); + MGPipeRouteSetProgramBindings(m_lastBindings, m_blockBindings.empty() ? nullptr : m_blockBindings.data(), + m_samplerUnits.empty() ? nullptr : m_samplerUnits.data(), + m_storageOverrides.empty() ? nullptr : m_storageOverrides.data(), + m_storageOverrideNamePtrs.empty() ? nullptr + : m_storageOverrideNamePtrs.data()); ++m_bindingSets; latch.BindingsLive = true; diff --git a/MobileGL/MG_Impl/Pipe/ResourceTracker.h b/MobileGL/MG_Impl/Pipe/ResourceTracker.h index 15c16711..53136652 100644 --- a/MobileGL/MG_Impl/Pipe/ResourceTracker.h +++ b/MobileGL/MG_Impl/Pipe/ResourceTracker.h @@ -128,8 +128,7 @@ namespace MobileGL::MG_Pipe { // explicitly, and it may not reach into MG_Impl to do so. The second discriminator is the // frontend enum, named rather than open-coded, and stays here because only this file // produces it. - inline constexpr Uint8 kMGPipeResourceStorageKindBuffer = - static_cast(MobileGL::TextureStorageType::Buffer); + inline constexpr Uint8 kMGPipeResourceStorageKindBuffer = static_cast(MobileGL::TextureStorageType::Buffer); // --------------------------------------------------------------------------------- // D-A2: the payload builders. Pure, so a unit case can assert field by field. @@ -138,9 +137,8 @@ namespace MobileGL::MG_Pipe { // The descriptor for `buffer`. `storageDefined` is false for the create that the // constructor emits - storage is defined lazily by the first respecify and a backend // tolerates a resource that has none - and true for every respecify. - inline MGPResourceDesc MGPipeBuildResourceDesc(const MG_State::GLState::BufferObject& buffer, - MGPipeHandle handle, Uint16 bindMask, - Bool storageDefined) { + inline MGPResourceDesc MGPipeBuildResourceDesc(const MG_State::GLState::BufferObject& buffer, MGPipeHandle handle, + Uint16 bindMask, Bool storageDefined) { MGPResourceDesc desc{}; desc.Resource = handle; desc.Target = static_cast(kMGPipeResourceTargetBuffer); @@ -158,8 +156,7 @@ namespace MobileGL::MG_Pipe { MGLOG_E_ONCE("MGPipe: buffer %u declares a store of %llu bytes, which does not fit " "MGPResourceDesc::Width - the descriptor's extent is narrowed and every " "write past 4 GiB will be refused by the applier's range gate", - buffer.GetExternalIndex(), - static_cast(buffer.GetSize())); + buffer.GetExternalIndex(), static_cast(buffer.GetSize())); MOBILEGL_ASSERT(false, "MGPResourceDesc::Width cannot carry this buffer's size"); } desc.Width = static_cast(buffer.GetSize()); @@ -313,7 +310,8 @@ namespace MobileGL::MG_Pipe { // told about: a live buffer of the ended session's contexts dies with no destroy on the // new wire, and the inverse entries stay so the handles still resolve locally. void ForgetPublication() { - for (Entry& entry : m_bySlot) entry.Published = false; + for (Entry& entry : m_bySlot) + entry.Published = false; } // The sticky everBoundAs mask. Sticky exactly as MGPResourceDesc::ImageBindableHint's @@ -504,8 +502,8 @@ namespace MobileGL::MG_Pipe { Uint64 producedAtSeq) { auto* buffer = MGPipeResourceTrackerInstance().Resolve(res); if (buffer == nullptr) { - MGLOG_E_ONCE("MGPipe: OnBufferWriteback for a handle {%u,%u} that resolves to no buffer", - res.Slot, res.Gen); + MGLOG_E_ONCE("MGPipe: OnBufferWriteback for a handle {%u,%u} that resolves to no buffer", res.Slot, + res.Gen); return; } void* bytePtr = nullptr; @@ -532,14 +530,13 @@ namespace MobileGL::MG_Pipe { const void* resolved = nullptr; if (session && session->DataLink()) { session->DataLink()->ResolveSpan(MG_Remote::Transport::LinkSegment::Event, - {bytes.Offset, bytes.Size}, &resolved); + {bytes.Offset, bytes.Size}, &resolved); } if (resolved == nullptr) { MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Offset\"} - " "the writeback blobref's {%llu + %llu} does not resolve inside the " "session's SEG_EVENT segment", - static_cast(bytes.Offset), - static_cast(bytes.Size)); + static_cast(bytes.Offset), static_cast(bytes.Size)); std::abort(); } bytePtr = const_cast(resolved); @@ -562,8 +559,8 @@ namespace MobileGL::MG_Pipe { return; } #endif - buffer->WritebackFromBackend(DataPtr{bytePtr, static_cast(bytes.Size)}, - static_cast(offset), producedAtSeq); + buffer->WritebackFromBackend(DataPtr{bytePtr, static_cast(bytes.Size)}, static_cast(offset), + producedAtSeq); } // The monolith callback's shape (MGPipeCallbacks::OnBufferWriteback). @@ -593,8 +590,7 @@ namespace MobileGL::MG_Pipe { // Loud, like its sibling above: a backend announcing a write against a handle // this client cannot resolve is a dropped MarkGpuWritten, and a dropped // MarkGpuWritten is a stale shadow read back as if it were current. - MGLOG_E_ONCE("MGPipe: OnGpuWritten for a handle {%u,%u} that resolves to no buffer", res.Slot, - res.Gen); + MGLOG_E_ONCE("MGPipe: OnGpuWritten for a handle {%u,%u} that resolves to no buffer", res.Slot, res.Gen); return; } buffer->MarkGpuWrittenInChannelOrder(); @@ -622,8 +618,7 @@ namespace MobileGL::MG_Pipe { } entry = fn; }; - install(gMGPipeCallbacks.OnBufferWriteback, &MGPipeClientOnBufferWriteback, - "OnBufferWriteback"); + install(gMGPipeCallbacks.OnBufferWriteback, &MGPipeClientOnBufferWriteback, "OnBufferWriteback"); install(gMGPipeCallbacks.OnGpuWritten, &MGPipeClientOnGpuWritten, "OnGpuWritten"); } @@ -631,8 +626,7 @@ namespace MobileGL::MG_Pipe { // and call OnGpuWritten directly; P5f fm rejects Magma's P7 buffer consumers before // reaching here. Keep a named guard at this boundary so a future caller cannot turn // a missing callback/handle into a write through a client object. - inline void MGPipeAnnounceBufferGpuWritten( - const SharedPtr& bufferObject) { + inline void MGPipeAnnounceBufferGpuWritten(const SharedPtr& bufferObject) { if (bufferObject == nullptr) return; #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { @@ -645,8 +639,7 @@ namespace MobileGL::MG_Pipe { bufferObject->MarkGpuWritten(); return; } - const MGPipeHandle res = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, bufferObject->GetLifetimeId()); + const MGPipeHandle res = MGPipeSlots().FindByLifetimeId(MGPipeKind::Buffer, bufferObject->GetLifetimeId()); if (MGPipeHandleIsNull(res)) { MGLOG_E_ONCE("MGPipe: no handle for the GPU-write announcement of buffer %u - the " "reverse channel is installed but the mint is missing, so the mark " diff --git a/MobileGL/MG_Impl/Pipe/SamplerEmit.h b/MobileGL/MG_Impl/Pipe/SamplerEmit.h index 05aa18be..39e1add7 100644 --- a/MobileGL/MG_Impl/Pipe/SamplerEmit.h +++ b/MobileGL/MG_Impl/Pipe/SamplerEmit.h @@ -132,12 +132,12 @@ namespace MobileGL::MG_Pipe { class MGPipeSamplerCsoCache { public: struct Counters { - Uint64 Mints = 0; // create_sampler_state emissions + Uint64 Mints = 0; // create_sampler_state emissions Uint64 Acquisitions = 0; - Uint64 Hits = 0; // a probe that found a live entry and passed the memcmp - Uint64 Collisions = 0; // a hash hit the memcmp REJECTED - the reason it exists - Uint64 Evictions = 0; // LRU evictions, each one a delete_sampler_state - Uint64 Releases = 0; // reference drops + Uint64 Hits = 0; // a probe that found a live entry and passed the memcmp + Uint64 Collisions = 0; // a hash hit the memcmp REJECTED - the reason it exists + Uint64 Evictions = 0; // LRU evictions, each one a delete_sampler_state + Uint64 Releases = 0; // reference drops // Mints made with the cache already full and EVERY entry referenced. A recorded // number and not a gate (ID-17): growing past 256 is the safe direction, and this // is what makes it visible instead of silent. @@ -327,8 +327,7 @@ namespace MobileGL::MG_Pipe { NotePublishedIn(entry, MGPipeCurrentShareGroupToken()); payloadBytes += sizeof(MGPSamplerDesc) + sizeof(SamplerParameters); if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, - sizeof(SamplerParameters)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, sizeof(SamplerParameters)); } } @@ -431,8 +430,7 @@ namespace MobileGL::MG_Pipe { ++m_counters.Mints; payloadBytes += sizeof(MGPSamplerDesc) + sizeof(SamplerParameters); if (MG_Util::PipeStats::Enabled()) { - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, - sizeof(SamplerParameters)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::CsoBlobBytes, sizeof(SamplerParameters)); } return cso; } @@ -619,8 +617,7 @@ namespace MobileGL::MG_Pipe { if (unit > m_resolution.MaxImageUnit) m_resolution.MaxImageUnit = unit; continue; } - const TextureTarget target = - MGPipeSamplerUniformTextureTarget(program->GetUniformType(location)); + const TextureTarget target = MGPipeSamplerUniformTextureTarget(program->GetUniformType(location)); if (target == TextureTarget::Unknown) continue; // FIRST WRITER WINS, which is DirectGLES' arbitration read forwards: when // two sampler uniforms of different types share a unit the binding placed @@ -700,8 +697,7 @@ namespace MobileGL::MG_Pipe { Uint64 EmitSamplerViews(GLContext& ctx) { const Int maxTouched = ctx.GetMaxTouchedTextureUnit(); const Uint32 count = - maxTouched < 0 ? 0u - : Min(static_cast(maxTouched) + 1u, kMGPipeMaxTextureUnits); + maxTouched < 0 ? 0u : Min(static_cast(maxTouched) + 1u, kMGPipeMaxTextureUnits); // The join is the EMITTER's, deliberately, and it is the same GetProgramForDraw() // the verb is about to make anyway: the tracker's own shutters read @@ -731,13 +727,12 @@ namespace MobileGL::MG_Pipe { // the completeness answer depends on which one applies - a mipmap mode of None // makes a single-level texture complete that would otherwise sample black. const auto& unitSampler = textureUnit.GetSamplerObject(); - const SamplerObject* effective = - unitSampler ? unitSampler.get() : texture->GetSamplerObject().get(); + const SamplerObject* effective = unitSampler ? unitSampler.get() : texture->GetSamplerObject().get(); if (MG_State::GLState::SamplesAsIncompleteTexture(texture.get(), effective)) continue; - entry.Texture = MGPipeSlots().AcquireHinted( - MGPipeKind::Texture, texture->GetLifetimeId(), - texture->PipeHandleHint(ITextureObject::PipeHintKind::Texture)); + entry.Texture = + MGPipeSlots().AcquireHinted(MGPipeKind::Texture, texture->GetLifetimeId(), + texture->PipeHandleHint(ITextureObject::PipeHintKind::Texture)); // D-A4: a texture the sampler-view resolution names in an emitted MGPBoundView // is SAMPLER-bound from then on (sticky; the texture emitter's contract door, // since this header is included BY TextureEmit.h). One early-out per unit per @@ -773,15 +768,13 @@ namespace MobileGL::MG_Pipe { Uint64 EmitSamplerStates(GLContext& ctx) { const Int maxTouched = ctx.GetMaxTouchedTextureUnit(); const Uint32 count = - maxTouched < 0 ? 0u - : Min(static_cast(maxTouched) + 1u, kMGPipeMaxTextureUnits); + maxTouched < 0 ? 0u : Min(static_cast(maxTouched) + 1u, kMGPipeMaxTextureUnits); Uint64 bytes = 0; MGPipeSamplerCsoCache& cache = MGPipeSamplerCsoCacheInstance(); for (Uint32 unit = 0; unit < count; ++unit) { const auto& sampler = ctx.GetTextureUnitObject(static_cast(unit)).GetSamplerObject(); - m_states[unit] = - sampler ? cache.Acquire(sampler->GetAllSamplerParameters(), bytes) : kMGPipeNullHandle; + m_states[unit] = sampler ? cache.Acquire(sampler->GetAllSamplerParameters(), bytes) : kMGPipeNullHandle; } for (Uint32 unit = 0; unit < kMGPipeMaxTextureUnits; ++unit) { const MGPipeHandle held = m_stateRefs[unit]; @@ -900,8 +893,7 @@ namespace MobileGL::MG_Pipe { // because a birth is not a validate-point emission and has no payload budget to report. void EmitSamplerView(ITextureObject& texture) { Uint64 bytes = 0; - const MGPipeHandle textureHandle = - MGPipeSlots().Acquire(MGPipeKind::Texture, texture.GetLifetimeId()); + const MGPipeHandle textureHandle = MGPipeSlots().Acquire(MGPipeKind::Texture, texture.GetLifetimeId()); AcquireSamplerView(texture, textureHandle, bytes); } @@ -966,9 +958,7 @@ namespace MobileGL::MG_Pipe { const MGPSamplerViews& LastSamplerViews() const { return m_lastViews; } const Array& LastBoundViews() const { return m_views; } const MGPSamplerStates& LastSamplerStates() const { return m_lastStates; } - const Array& LastSamplerStateHandles() const { - return m_states; - } + const Array& LastSamplerStateHandles() const { return m_states; } const MGPSamplerView& LastCreatedView() const { return m_lastView; } Uint64 ViewSetCount() const { return m_viewSets; } Uint64 StateSetCount() const { return m_stateSets; } diff --git a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h index b84f6d4b..7f4709ee 100644 --- a/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h +++ b/MobileGL/MG_Impl/Pipe/SetHashSuppressor.h @@ -28,7 +28,7 @@ namespace MobileGL::MG_Pipe { // hash is the "did any covered value move" answer. The enum is // CLIENT-ONLY and is not a wire opcode, so appending before Count is safe. enum class MGPipeSuppressorSlot : Uint32 { - SetVertexBuffers = 0, // P3a - wired, and its hash includes BaseInstance + SetVertexBuffers = 0, // P3a - wired, and its hash includes BaseInstance // P4a - WIRED. The three unit sets' suppressors are not optional and were never a // later phase's: MGPipeTypes.h makes the pattern mandatory for every kVarTail set_*, // because GetTextureBindGeneration() bumps on a REDUNDANT rebind - MC 26.2 rebinds the @@ -37,9 +37,9 @@ namespace MobileGL::MG_Pipe { // the design names. What P3b/P4b owns is the ~175-line BACKEND debounce these replace // (UnitBindingsSnapshot / CaptureUnitBindings / UnitBindingsUnchanged and the two // g_*SyncList tables); P4a wires the carrier, P3b/P4b deletes the backend copy. - SetSamplerViews, // P4a - wired (backend debounce deletion: P3b/P4b) - BindSamplerStates, // P4a - wired (backend debounce deletion: P3b/P4b) - SetShaderImages, // P4a - wired (backend debounce deletion: P3b/P4b) + SetSamplerViews, // P4a - wired (backend debounce deletion: P3b/P4b) + BindSamplerStates, // P4a - wired (backend debounce deletion: P3b/P4b) + SetShaderImages, // P4a - wired (backend debounce deletion: P3b/P4b) // P5e (docs/archive/contracts/CONTRACT-P5E.md §1, ruling 11): THREE SLOTS, ONE PER CLASS, not one // for the call. set_shader_buffers is emitted per Class (Uniform / ShaderStorage / // AtomicCounter) because the record's own Class field says which binding-point array @@ -51,20 +51,20 @@ namespace MobileGL::MG_Pipe { SetShaderBuffersUniform, // P5e (sb) SetShaderBuffersShaderStorage, // P5e (sb) SetShaderBuffersAtomicCounter, // P5e (sb) - SetStreamOutputTargets, // P4b + SetStreamOutputTargets, // P4b // P5e (CONTRACT-P5E.md §1): set_program_bindings, the post-link binding record. Not // kVarTail-only - it has three tails - but the same rule applies: the emitter latches // on (Cso, backendStateVersion, blockBindingVersion) and the whole-record hash is what // says "nothing moved". - SetProgramBindings, // P5e (pg) - SetVertexAttribDefaults, // P2 - the one consumer that is wired - SetFramebufferState, // P4a - wired + SetProgramBindings, // P5e (pg) + SetVertexAttribDefaults, // P2 - the one consumer that is wired + SetFramebufferState, // P4a - wired // P5c rv (CONTRACT-P5C.md §5.3). NOT kVarTail either - the same shape as // SetFramebufferState's note: MGPContextValues is one fixed-width POD, and the // whole-record hash IS its "did any covered value move" answer (there is deliberately // no dirty mask in the payload - a suppressed record means "nothing moved", never // "field invalid"). - SetContextValues, // P5c rv - wired, split+transport only (PipeFill.cpp gates) + SetContextValues, // P5c rv - wired, split+transport only (PipeFill.cpp gates) Count, }; @@ -94,14 +94,13 @@ namespace MobileGL::MG_Pipe { void Invalidate(MGPipeSuppressorSlot slot) { m_lastEmitted[static_cast(slot)] = 0; } void InvalidateAll() { - for (SizeT i = 0; i < kMGPipeSuppressorSlotCount; ++i) m_lastEmitted[i] = 0; + for (SizeT i = 0; i < kMGPipeSuppressorSlotCount; ++i) + m_lastEmitted[i] = 0; } // 0 == "never emitted". Exposed for the unit test, which is what pins that the // reserved value really is reserved. - Uint64 LastEmitted(MGPipeSuppressorSlot slot) const { - return m_lastEmitted[static_cast(slot)]; - } + Uint64 LastEmitted(MGPipeSuppressorSlot slot) const { return m_lastEmitted[static_cast(slot)]; } private: Array m_lastEmitted{}; diff --git a/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h b/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h index a0b25209..7474f899 100644 --- a/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h +++ b/MobileGL/MG_Impl/Pipe/ShaderBufferEmit.h @@ -45,9 +45,12 @@ namespace MobileGL::MG_Pipe { // chances to pair Uniform with ShaderStorage. inline constexpr BufferTarget MGPipeBufferTargetForShaderBufferClass(Uint32 cls) { switch (cls) { - case kMGPipeShaderBufferClassShaderStorage: return BufferTarget::ShaderStorage; - case kMGPipeShaderBufferClassAtomicCounter: return BufferTarget::AtomicCounter; - default: return BufferTarget::Uniform; + case kMGPipeShaderBufferClassShaderStorage: + return BufferTarget::ShaderStorage; + case kMGPipeShaderBufferClassAtomicCounter: + return BufferTarget::AtomicCounter; + default: + return BufferTarget::Uniform; } } @@ -58,7 +61,8 @@ namespace MobileGL::MG_Pipe { return MGPipeSuppressorSlot::SetShaderBuffersShaderStorage; case kMGPipeShaderBufferClassAtomicCounter: return MGPipeSuppressorSlot::SetShaderBuffersAtomicCounter; - default: return MGPipeSuppressorSlot::SetShaderBuffersUniform; + default: + return MGPipeSuppressorSlot::SetShaderBuffersUniform; } } @@ -68,9 +72,8 @@ namespace MobileGL::MG_Pipe { // same, or the per-class A/B tallies would be reading each other's traffic - and the mask // words are mixed because they are a FIELD of the record that the entries alone do not // determine (a uniform range and a storage range with the same handle differ only there). - inline Uint64 MGPipeShaderBufferSetContentHash(const MGPBufferRange* entries, Uint32 cls, - Uint32 start, Uint32 count, - const Uint32* writableMask) { + inline Uint64 MGPipeShaderBufferSetContentHash(const MGPBufferRange* entries, Uint32 cls, Uint32 start, + Uint32 count, const Uint32* writableMask) { Uint64 hash = XXH64(entries, static_cast(count) * sizeof(MGPBufferRange), 0); hash = MGPipeMixShutter(hash, cls); hash = MGPipeMixShutter(hash, start); @@ -86,9 +89,7 @@ namespace MobileGL::MG_Pipe { using GLContext = MG_State::GLState::GLContext; // Dirty bit 15 (NEW_CONST_BUFFERS) -> the uniform binding points. - Uint64 EmitConstBuffers(GLContext& ctx) { - return EmitClass(ctx, kMGPipeShaderBufferClassUniform); - } + Uint64 EmitConstBuffers(GLContext& ctx) { return EmitClass(ctx, kMGPipeShaderBufferClassUniform); } // Dirty bit 16 (NEW_SHADER_BUFFERS) -> the two WRITABLE classes, which share a bit // because they share a shutter: a storage bind and a counter bind are both "the @@ -111,7 +112,8 @@ namespace MobileGL::MG_Pipe { } void ResetCounters() { - for (Uint32 cls = 0; cls < kMGPipeShaderBufferClassCount; ++cls) m_emissions[cls] = 0; + for (Uint32 cls = 0; cls < kMGPipeShaderBufferClassCount; ++cls) + m_emissions[cls] = 0; } // What a unit case reads. The emitter hands m_entries straight to the route, so "what @@ -119,12 +121,8 @@ namespace MobileGL::MG_Pipe { const MGPShaderBuffers& LastHeader(Uint32 cls) const { return m_last[cls < kMGPipeShaderBufferClassCount ? cls : 0]; } - const Array& LastRanges() const { - return m_entries; - } - Uint64 EmissionCount(Uint32 cls) const { - return m_emissions[cls < kMGPipeShaderBufferClassCount ? cls : 0]; - } + const Array& LastRanges() const { return m_entries; } + Uint64 EmissionCount(Uint32 cls) const { return m_emissions[cls < kMGPipeShaderBufferClassCount ? cls : 0]; } private: Uint64 EmitClass(GLContext& ctx, Uint32 cls) { @@ -133,9 +131,8 @@ namespace MobileGL::MG_Pipe { // never binds an indexed buffer of this class. const SizeT touched = ctx.GetTouchedBufferBindingPointCount(target); if (touched == 0) return 0; - const Uint32 count = touched < kMGPipeMaxBufferBindingPoints - ? static_cast(touched) - : kMGPipeMaxBufferBindingPoints; + const Uint32 count = + touched < kMGPipeMaxBufferBindingPoints ? static_cast(touched) : kMGPipeMaxBufferBindingPoints; // NO CLIENT-SIDE CLAMP TO THE DEVICE's LIMIT (ruling 10). MobileGL advertises the // GL 4.5 minimum of 84 uniform binding points while ES 3.2's is 72, and which of // them the driver can actually hold is a SERVER question: the backend clamps to @@ -191,10 +188,8 @@ namespace MobileGL::MG_Pipe { if (classIsWritable) MGPipeShaderBufferMaskSet(writableMask, i); } - const Uint64 hash = - MGPipeShaderBufferSetContentHash(m_entries.data(), cls, 0, count, writableMask); - if (!MGPipeSetHashSuppressorInstance().ShouldEmit( - MGPipeSuppressorSlotForShaderBufferClass(cls), hash)) { + const Uint64 hash = MGPipeShaderBufferSetContentHash(m_entries.data(), cls, 0, count, writableMask); + if (!MGPipeSetHashSuppressorInstance().ShouldEmit(MGPipeSuppressorSlotForShaderBufferClass(cls), hash)) { return 0; } MGPShaderBuffers& header = m_last[cls]; diff --git a/MobileGL/MG_Impl/Pipe/SlotAllocator.cpp b/MobileGL/MG_Impl/Pipe/SlotAllocator.cpp index 1488605d..0cc72f04 100644 --- a/MobileGL/MG_Impl/Pipe/SlotAllocator.cpp +++ b/MobileGL/MG_Impl/Pipe/SlotAllocator.cpp @@ -11,7 +11,7 @@ #if MOBILEGL_BUILD_DISAGGREGATED #include -#include // D1c: MGPipeServerArm() +#include // D1c: MGPipeServerArm() #include #include #include @@ -70,8 +70,7 @@ namespace MobileGL::MG_Pipe { return m_kinds[index < kKindCount ? index : 0]; } - MGPipeSlotAllocator::SlotState* MGPipeSlotAllocator::EntryOf(KindState& state, MGPipeKind kind, - Uint32 slot) { + MGPipeSlotAllocator::SlotState* MGPipeSlotAllocator::EntryOf(KindState& state, MGPipeKind kind, Uint32 slot) { if (kind == MGPipeKind::ShaderCso && MGPipeIsCompositeShaderSlot(slot)) { const SizeT index = slot - kMGPipeShaderCsoCompositeSlotBase; if (index >= state.BandSlots.size()) return nullptr; @@ -81,8 +80,8 @@ namespace MobileGL::MG_Pipe { return &state.Slots[slot]; } - const MGPipeSlotAllocator::SlotState* - MGPipeSlotAllocator::EntryOf(const KindState& state, MGPipeKind kind, Uint32 slot) { + const MGPipeSlotAllocator::SlotState* MGPipeSlotAllocator::EntryOf(const KindState& state, MGPipeKind kind, + Uint32 slot) { return EntryOf(const_cast(state), kind, slot); } @@ -106,8 +105,7 @@ namespace MobileGL::MG_Pipe { if (!reused) { slot = static_cast(state.Slots.size()); - MOBILEGL_ASSERT(SlotIsAllocatable(kind, slot), - "MGPipe slot space of kind %u is exhausted at slot %u", + MOBILEGL_ASSERT(SlotIsAllocatable(kind, slot), "MGPipe slot space of kind %u is exhausted at slot %u", static_cast(kind), slot); if (!SlotIsAllocatable(kind, slot)) return kMGPipeNullHandle; state.Slots.emplace_back(); @@ -223,8 +221,7 @@ namespace MobileGL::MG_Pipe { return MGPipeHandle{it->second, entry->Gen}; } - MGPipeHandle MGPipeSlotAllocator::FindByLifetimeIdHinted(MGPipeKind kind, Uint64 lifetimeId, - Uint64& hint) const { + MGPipeHandle MGPipeSlotAllocator::FindByLifetimeIdHinted(MGPipeKind kind, Uint64 lifetimeId, Uint64& hint) const { if (hint != 0 && lifetimeId != 0) { #if MOBILEGL_BUILD_DISAGGREGATED MGPipeRefuseAllocatorFromApplyThread("FindByLifetimeIdHinted"); @@ -317,7 +314,9 @@ namespace MobileGL::MG_Pipe { return static_cast(kMGPipeShaderCsoCompositeSlotBase + state.BandSlots.size()); } - Uint32 MGPipeSlotAllocator::LiveCount(MGPipeKind kind) const { return StateOf(kind).LiveCount; } + Uint32 MGPipeSlotAllocator::LiveCount(MGPipeKind kind) const { + return StateOf(kind).LiveCount; + } Uint32 MGPipeSlotAllocator::CompositeLiveCount() const { return StateOf(MGPipeKind::ShaderCso).BandLiveCount; diff --git a/MobileGL/MG_Impl/Pipe/TextureEmit.h b/MobileGL/MG_Impl/Pipe/TextureEmit.h index 0d9e1c09..a1588c24 100644 --- a/MobileGL/MG_Impl/Pipe/TextureEmit.h +++ b/MobileGL/MG_Impl/Pipe/TextureEmit.h @@ -52,8 +52,7 @@ namespace MobileGL::MG_Pipe { // MOBILEGL_PIPE_PUSH, not the wired constants, so this constant and the sampler one are set // together, and the framebuffer constant is never set without this one. inline constexpr Uint64 kMGPipeWiredTextureSubsystem = kMGPipeSubsystemTextureResources; - static_assert(kMGPipeWiredTextureSubsystem == 0 || - kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources, + static_assert(kMGPipeWiredTextureSubsystem == 0 || kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources, "a family's wired constant is 0 or its own bit and nothing else"); // BOTH HALVES MATTER, exactly as MGPipeResourceSubsystemEnabled()'s two do, and they are @@ -170,10 +169,9 @@ namespace MobileGL::MG_Pipe { // `viewOf`, `bufferForTexBuffer` and the buffer window are handed in rather than resolved // here, because resolving them needs the slot allocator and this function must stay pure. inline MGPResourceDesc MGPipeBuildTextureResourceDesc(const MG_State::GLState::ITextureObject& texture, - MGPipeHandle handle, Uint16 bindMask, - Bool storageDefined, MGPipeHandle viewOf, - MGPipeHandle bufferForTexBuffer, Uint64 bufOffset, - Uint64 bufSize) { + MGPipeHandle handle, Uint16 bindMask, Bool storageDefined, + MGPipeHandle viewOf, MGPipeHandle bufferForTexBuffer, + Uint64 bufOffset, Uint64 bufSize) { MGPResourceDesc desc{}; desc.Resource = handle; desc.Target = static_cast(MGPipeResourceTargetForTextureTarget(texture.GetTarget())); @@ -204,9 +202,8 @@ namespace MobileGL::MG_Pipe { // count, a buffer texture with whether a buffer is attached; both are what the // backend's ensure path already tests before it uploads anything. desc.HasDefinedContent = - (mipmap != nullptr ? mipmap->GetMipmapLevelCount() > 0 : !MGPipeHandleIsNull(bufferForTexBuffer)) - ? 1 - : 0; + (mipmap != nullptr ? mipmap->GetMipmapLevelCount() > 0 : !MGPipeHandleIsNull(bufferForTexBuffer)) ? 1 + : 0; } desc.ViewOf = viewOf; desc.BufferForTexBuffer = bufferForTexBuffer; @@ -375,8 +372,8 @@ namespace MobileGL::MG_Pipe { // texel), and NOTHING has been handed out when it returns false, so the caller keeps the // whole-level record it would have emitted anyway. template - inline Bool MGPipeForEachTextureSlab(const IntVec3& levelSize, const MGPipeLevelPitch& pitch, - Uint64 levelBytes, SizeT chunkBytes, Fn&& body) { + inline Bool MGPipeForEachTextureSlab(const IntVec3& levelSize, const MGPipeLevelPitch& pitch, Uint64 levelBytes, + SizeT chunkBytes, Fn&& body) { const Uint64 cap = static_cast(chunkBytes); const Uint64 width = static_cast(std::max(levelSize.x(), 0)); const Uint64 height = static_cast(std::max(levelSize.y(), 0)); @@ -390,12 +387,11 @@ namespace MobileGL::MG_Pipe { // tail no box describes, and it stays on the whole-level record. if (sliceStride * depth != levelBytes) return false; - const auto emit = [&](Uint64 x0, Uint64 y0, Uint64 z0, Uint64 w, Uint64 h, Uint64 d, - Uint64 runOffset, Uint64 runBytes) { + const auto emit = [&](Uint64 x0, Uint64 y0, Uint64 z0, Uint64 w, Uint64 h, Uint64 d, Uint64 runOffset, + Uint64 runBytes) { MGPipeTextureSlab slab{}; slab.Box = MGPBox{static_cast(x0), static_cast(y0), static_cast(z0), - static_cast(w), static_cast(h), - static_cast(d)}; + static_cast(w), static_cast(h), static_cast(d)}; slab.RunOffset = runOffset; slab.RunBytes = runBytes; body(slab); @@ -427,8 +423,7 @@ namespace MobileGL::MG_Pipe { for (Uint64 y = 0; y < height; ++y) { for (Uint64 x = 0; x < width; x += slabTexels) { const Uint64 w = std::min(slabTexels, width - x); - emit(x, y, z, w, 1, 1, (z * height + y) * rowStride + x * texelBytes, - w * texelBytes); + emit(x, y, z, w, 1, 1, (z * height + y) * rowStride + x * texelBytes, w * texelBytes); } } } @@ -442,8 +437,7 @@ namespace MobileGL::MG_Pipe { // MGPipeBuildSubRegion computes it. THE STRIDES STAY THE LEVEL'S: a clipped rect's rows are no // more contiguous inside the piece than inside the level, and a 0 would be read as "tightly // packed" - the whole-level spelling - which would repack the wrong bytes. - inline MGPSubRegion MGPipeBuildPieceRegion(const MGPBox& box, const MGPBox& piece, - const MGPipeLevelPitch& pitch) { + inline MGPSubRegion MGPipeBuildPieceRegion(const MGPBox& box, const MGPBox& piece, const MGPipeLevelPitch& pitch) { MGPSubRegion region{}; region.X = box.X; region.Y = box.Y; @@ -467,9 +461,8 @@ namespace MobileGL::MG_Pipe { const Int64 y1 = std::min(static_cast(a.Y) + a.H, static_cast(b.Y) + b.H); const Int64 z1 = std::min(static_cast(a.Z) + a.D, static_cast(b.Z) + b.D); if (x1 <= x0 || y1 <= y0 || z1 <= z0) return MGPBox{}; - return MGPBox{static_cast(x0), static_cast(y0), static_cast(z0), - static_cast(x1 - x0), static_cast(y1 - y0), - static_cast(z1 - z0)}; + return MGPBox{static_cast(x0), static_cast(y0), static_cast(z0), + static_cast(x1 - x0), static_cast(y1 - y0), static_cast(z1 - z0)}; } // THE REGIONS ONE PIECE CARRIES: every dirty rect clipped to the piece's box, each with its @@ -478,9 +471,8 @@ namespace MobileGL::MG_Pipe { // the far side reads as tightly packed), and the piece's bytes have to cross whether or not // any of them changed, because the server's level image is assembled from these runs and a // hole in it is a texel read nothing on that side can answer. - inline SizeT MGPipeBuildSlabRegions(const MGPBox* rects, SizeT rectCount, - const MGPipeTextureSlab& slab, const MGPipeLevelPitch& pitch, - MGPSubRegion* out, SizeT maxOut) { + inline SizeT MGPipeBuildSlabRegions(const MGPBox* rects, SizeT rectCount, const MGPipeTextureSlab& slab, + const MGPipeLevelPitch& pitch, MGPSubRegion* out, SizeT maxOut) { if (out == nullptr || maxOut == 0) return 0; SizeT count = 0; for (SizeT i = 0; i < rectCount && count < maxOut; ++i) { @@ -697,8 +689,8 @@ namespace MobileGL::MG_Pipe { // entry points (glTexParameter BASE_LEVEL) move no descriptor field; a OneLevel call // never is: a non-base level's extent moves no descriptor field, and a pending box kept // across a shrink would upload past the end of the new level. - void EmitResourceRespecify(ITextureObject& texture, MGPipeTextureRespecifyScope scope, - Uint32 uploadTarget, Uint32 level) { + void EmitResourceRespecify(ITextureObject& texture, MGPipeTextureRespecifyScope scope, Uint32 uploadTarget, + Uint32 level) { const MGPipeHandle handle = AcquireTexture(texture.GetLifetimeId(), &texture); // THE VIEW'S OWNER IS ACQUIRED FIRST, and no Entry& is held across it (m3): the // owner's slot can be higher than this table's size, so AcquireTexture would @@ -734,8 +726,7 @@ namespace MobileGL::MG_Pipe { } Entry& entry = EntryFor(m_textures, handle); const MGPResourceDesc desc = MGPipeBuildTextureResourceDesc( - texture, handle, entry.BindMask, /*storageDefined=*/true, viewOf, bufferHandle, bufOffset, - bufSize); + texture, handle, entry.BindMask, /*storageDefined=*/true, viewOf, bufferHandle, bufOffset, bufSize); const Bool unchanged = entry.HasLastDesc && std::memcmp(&entry.LastDesc, &desc, sizeof(desc)) == 0; // THE KEYS THIS CALL DROPS. `keyCount == 0` is the whole resource (a null level @@ -772,9 +763,9 @@ namespace MobileGL::MG_Pipe { // REFUSED. A create rather than a respecify, because that is what the record's // absence means and because the applier starts a record over on a create. if (!MGPipeHandleIsPublished(MGPipeKind::Texture, handle)) { - const MGPResourceDesc createDesc = MGPipeBuildTextureResourceDesc( - texture, handle, entry.BindMask, /*storageDefined=*/false, viewOf, bufferHandle, - bufOffset, bufSize); + const MGPResourceDesc createDesc = + MGPipeBuildTextureResourceDesc(texture, handle, entry.BindMask, /*storageDefined=*/false, viewOf, + bufferHandle, bufOffset, bufSize); NoteDesc(createDesc, /*isCreate=*/true); PublishCreate(MGPipeKind::Texture, handle, entry, createDesc); } @@ -795,8 +786,8 @@ namespace MobileGL::MG_Pipe { static_cast(MGPipeResourceTargetForTextureTarget(texture.GetTarget())), uploadTarget); Bool accepted = false; if (keyCount == 0) { - accepted = RespecifyOnce(texture, handle, entry, desc, nullptr, viewOf, bufferHandle, bufOffset, - bufSize); + accepted = + RespecifyOnce(texture, handle, entry, desc, nullptr, viewOf, bufferHandle, bufOffset, bufSize); } else { for (Uint32 i = 0; i < keyCount; ++i) { MGPRespecifiedLevel key{}; @@ -807,13 +798,13 @@ namespace MobileGL::MG_Pipe { MGLOG_E_ONCE("MGPipe: a per-level texture respecify had no mipmap storage; it was not emitted"); return; } - const IntVec3 exactLevelExtent = mipmap->GetMipmapTexelSize( - static_cast(uploadTarget), key.Level); + const IntVec3 exactLevelExtent = + mipmap->GetMipmapTexelSize(static_cast(uploadTarget), key.Level); key.Width = static_cast(exactLevelExtent.x()); key.Height = static_cast(exactLevelExtent.y()); key.Depth = static_cast(exactLevelExtent.z()); - accepted = RespecifyOnce(texture, handle, entry, desc, &key, viewOf, bufferHandle, bufOffset, - bufSize); + accepted = + RespecifyOnce(texture, handle, entry, desc, &key, viewOf, bufferHandle, bufOffset, bufSize); if (!accepted) break; } } @@ -833,8 +824,7 @@ namespace MobileGL::MG_Pipe { const MGPTextureParams& params) { Entry& entry = EntryFor(m_textures, handle); const MGPResourceDesc healDesc = MGPipeBuildTextureResourceDesc( - texture, handle, bindMask, /*storageDefined=*/false, kMGPipeNullHandle, - kMGPipeNullHandle, 0, 0); + texture, handle, bindMask, /*storageDefined=*/false, kMGPipeNullHandle, kMGPipeNullHandle, 0, 0); NoteDesc(healDesc, /*isCreate=*/true); PublishCreate(MGPipeKind::Texture, handle, entry, healDesc); const auto* mipmap = MG_State::GLState::AsMipmapTexture(&texture); @@ -897,8 +887,7 @@ namespace MobileGL::MG_Pipe { // at the recycle, as the belt, for a death this emitter was not told about. MGPipeSamplerCsoCache& cache = MGPipeSamplerCsoCacheInstance(); Uint64 samplerBytes = 0; - const MGPipeHandle builtinSampler = - cache.Acquire(sampler->GetAllSamplerParameters(), samplerBytes); + const MGPipeHandle builtinSampler = cache.Acquire(sampler->GetAllSamplerParameters(), samplerBytes); m_samplerCsoPayloadBytes += samplerBytes; if (entry.BuiltinSampler == builtinSampler) { // The value did not move, so the cache handed back the handle this entry @@ -947,9 +936,9 @@ namespace MobileGL::MG_Pipe { // deduped away), and the parameters land on the record that now exists. The // same repair covers the served context's teardown scope, where the records are // dropped while the objects live on. One retry, never a loop. - const MGPResourceDesc healDesc = MGPipeBuildTextureResourceDesc( - texture, handle, entry.BindMask, /*storageDefined=*/false, kMGPipeNullHandle, - kMGPipeNullHandle, 0, 0); + const MGPResourceDesc healDesc = + MGPipeBuildTextureResourceDesc(texture, handle, entry.BindMask, /*storageDefined=*/false, + kMGPipeNullHandle, kMGPipeNullHandle, 0, 0); NoteDesc(healDesc, /*isCreate=*/true); PublishCreate(MGPipeKind::Texture, handle, entry, healDesc); const auto* mipmap = MG_State::GLState::AsMipmapTexture(&texture); @@ -983,9 +972,8 @@ namespace MobileGL::MG_Pipe { void EmitRenderbufferCreate(RenderbufferObject& renderbuffer) { const MGPipeHandle handle = AcquireRenderbuffer(renderbuffer.GetLifetimeId()); Entry& entry = EntryFor(m_renderbuffers, handle); - const MGPResourceDesc desc = MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle, - entry.BindMask, - /*storageDefined=*/false); + const MGPResourceDesc desc = MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle, entry.BindMask, + /*storageDefined=*/false); NoteDesc(desc, /*isCreate=*/true); PublishCreate(MGPipeKind::Renderbuffer, handle, entry, desc); } @@ -1003,13 +991,12 @@ namespace MobileGL::MG_Pipe { void EmitRenderbufferRespecify(RenderbufferObject& renderbuffer) { const MGPipeHandle handle = AcquireRenderbuffer(renderbuffer.GetLifetimeId()); Entry& entry = EntryFor(m_renderbuffers, handle); - const MGPResourceDesc desc = MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle, - entry.BindMask, - /*storageDefined=*/true); + const MGPResourceDesc desc = MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle, entry.BindMask, + /*storageDefined=*/true); if (entry.HasLastDesc && std::memcmp(&entry.LastDesc, &desc, sizeof(desc)) == 0) return; if (!MGPipeHandleIsPublished(MGPipeKind::Renderbuffer, handle)) { - const MGPResourceDesc createDesc = MGPipeBuildRenderbufferResourceDesc( - renderbuffer, handle, entry.BindMask, /*storageDefined=*/false); + const MGPResourceDesc createDesc = + MGPipeBuildRenderbufferResourceDesc(renderbuffer, handle, entry.BindMask, /*storageDefined=*/false); NoteDesc(createDesc, /*isCreate=*/true); PublishCreate(MGPipeKind::Renderbuffer, handle, entry, createDesc); } @@ -1042,8 +1029,8 @@ namespace MobileGL::MG_Pipe { if (m_draining) return; const MGPipeHandle handle = AcquireTexture(texture.GetLifetimeId(), &texture); Entry& entry = EntryFor(m_textures, handle); - const Uint32 key = PackLevelKey(static_cast(uploadTarget), - static_cast(level)); + const Uint32 key = + PackLevelKey(static_cast(uploadTarget), static_cast(level)); for (const Uint32 present : entry.DrainKeys) { if (present == key) return; } @@ -1263,8 +1250,7 @@ namespace MobileGL::MG_Pipe { // (D-I1, c0b): MGPipeHandleIsPublished is what the death helper reads, so latching on // a call the applier refused would emit a resource_destroy for a record that does not // exist - a refused call the applier asserts on in a verify build. - void PublishCreate(MGPipeKind kind, MGPipeHandle handle, Entry& entry, - const MGPResourceDesc& desc) { + void PublishCreate(MGPipeKind kind, MGPipeHandle handle, Entry& entry, const MGPResourceDesc& desc) { Bool accepted = false; Bool dispatched = false; if constexpr (MGPipeTextureRecordsReachTheApplier()) { @@ -1303,9 +1289,9 @@ namespace MobileGL::MG_Pipe { Bool accepted = ApplyRespecify(desc, key); if constexpr (MGPipeTextureRecordsReachTheApplier()) { if (!accepted) { - const MGPResourceDesc healDesc = MGPipeBuildTextureResourceDesc( - texture, handle, entry.BindMask, /*storageDefined=*/false, viewOf, bufferHandle, - bufOffset, bufSize); + const MGPResourceDesc healDesc = + MGPipeBuildTextureResourceDesc(texture, handle, entry.BindMask, /*storageDefined=*/false, + viewOf, bufferHandle, bufOffset, bufSize); NoteDesc(healDesc, /*isCreate=*/true); PublishCreate(MGPipeKind::Texture, handle, entry, healDesc); accepted = ApplyRespecify(desc, key); @@ -1390,15 +1376,16 @@ namespace MobileGL::MG_Pipe { // and a piece clips each to its slab. mipmap->GetStorageDirtyFootprint(uploadTarget, level, m_footprint); m_rectBoxes.clear(); - const Bool wholeLevelWrite = - m_footprint.size() == 1 && m_footprint.front().lo.x() <= 0 && m_footprint.front().lo.y() <= 0 && - m_footprint.front().lo.z() <= 0 && m_footprint.front().hi.x() >= levelSize.x() && - m_footprint.front().hi.y() >= levelSize.y() && - m_footprint.front().hi.z() >= std::max(levelSize.z(), 1); + const Bool wholeLevelWrite = m_footprint.size() == 1 && m_footprint.front().lo.x() <= 0 && + m_footprint.front().lo.y() <= 0 && m_footprint.front().lo.z() <= 0 && + m_footprint.front().hi.x() >= levelSize.x() && + m_footprint.front().hi.y() >= levelSize.y() && + m_footprint.front().hi.z() >= std::max(levelSize.z(), 1); if (wholeLevelWrite) { // The box is the story. } else if (m_footprint.size() <= static_cast(kMGPipeMaxPendingUploadRegions)) { - for (const auto& box : m_footprint) m_rectBoxes.push_back(MGPipeBoxOfDirtyRegion(box)); + for (const auto& box : m_footprint) + m_rectBoxes.push_back(MGPipeBoxOfDirtyRegion(box)); } else { MGLOG_W_ONCE("MGPipe: texture {slot=%u, gen=%u} level %u has %zu disjoint dirty boxes, more than one " "record carries (%u); it goes out as the storage's rect list, which covers the writes " @@ -1406,9 +1393,10 @@ namespace MobileGL::MG_Pipe { pending.Handle.Slot, pending.Handle.Gen, static_cast(level), m_footprint.size(), kMGPipeMaxPendingUploadRegions); MG_State::GLState::MipmapDirtyRegion rects[MG_State::GLState::MipmapStorage::kMaxDirtyRects]; - const SizeT coverCount = mipmap->GetStorageDirtyRects( - uploadTarget, level, rects, MG_State::GLState::MipmapStorage::kMaxDirtyRects); - for (SizeT i = 0; i < coverCount; ++i) m_rectBoxes.push_back(MGPipeBoxOfDirtyRegion(rects[i])); + const SizeT coverCount = mipmap->GetStorageDirtyRects(uploadTarget, level, rects, + MG_State::GLState::MipmapStorage::kMaxDirtyRects); + for (SizeT i = 0; i < coverCount; ++i) + m_rectBoxes.push_back(MGPipeBoxOfDirtyRegion(rects[i])); } const SizeT rectCount = m_rectBoxes.size(); const MGPBox* rectBoxes = m_rectBoxes.data(); @@ -1468,9 +1456,8 @@ namespace MobileGL::MG_Pipe { // is: "the bytes this record declares ARE the level shadow". For a piece that // is the piece's own run of it, and the regions' SrcOffsets index into exactly // that run. - pieceAccepted = MGPipeRouteResourceSubData( - m_lastSubData, run, runBytes, - m_regions.empty() ? nullptr : m_regions.data()); + pieceAccepted = MGPipeRouteResourceSubData(m_lastSubData, run, runBytes, + m_regions.empty() ? nullptr : m_regions.data()); } else { (void)run; (void)runBytes; @@ -1496,14 +1483,13 @@ namespace MobileGL::MG_Pipe { // One slot per rect plus one for a piece no rect reaches, which is the // most MGPipeBuildSlabRegions can write. m_regions.assign(std::max(rectCount, 1), MGPSubRegion{}); - const SizeT pieceRegions = MGPipeBuildSlabRegions( - rectBoxes, rectCount, slab, pitch, m_regions.data(), m_regions.size()); + const SizeT pieceRegions = MGPipeBuildSlabRegions(rectBoxes, rectCount, slab, pitch, + m_regions.data(), m_regions.size()); m_regions.resize(pieceRegions); fillRecord(slab.Box, - static_cast(reinterpret_cast(shadow)) + - slab.RunOffset); - const Bool pieceAccepted = emitRecord( - static_cast(shadow) + slab.RunOffset, slab.RunBytes); + static_cast(reinterpret_cast(shadow)) + slab.RunOffset); + const Bool pieceAccepted = + emitRecord(static_cast(shadow) + slab.RunOffset, slab.RunBytes); accepted = accepted && pieceAccepted; }); } diff --git a/MobileGL/MG_Impl/Pipe/Tracker.h b/MobileGL/MG_Impl/Pipe/Tracker.h index cb473049..a8554f21 100644 --- a/MobileGL/MG_Impl/Pipe/Tracker.h +++ b/MobileGL/MG_Impl/Pipe/Tracker.h @@ -38,21 +38,21 @@ namespace MobileGL::MG_Pipe { // ---- value class: NEW_VERTEX_ELEMENTS is emitted from P3a and the other three from // P4a - the program family, one subsystem, three bits because the frontend moves them // as three separate events ---- - NewVertexElements, // the bound VAO's attribute configuration -> create/bind_vertex_elements - NewShader, // the current program's link version -> create/bind_shader_state, - // set_draw_program, set_dispatch_program (P4a) - NewShaderBindings, // image units, block bindings, uniform write set (P4a) - NewGlobalConstants, // the default-uniform-block image -> set_global_constants (P4a) + NewVertexElements, // the bound VAO's attribute configuration -> create/bind_vertex_elements + NewShader, // the current program's link version -> create/bind_shader_state, + // set_draw_program, set_dispatch_program (P4a) + NewShaderBindings, // image units, block bindings, uniform write set (P4a) + NewGlobalConstants, // the default-uniform-block image -> set_global_constants (P4a) // ---- object class. THE FIRST TWO ARE P3a's, not P3b/P4b's: the roadmap puts // set_vertex_buffers and set_index_buffer in the same phase as the vertex-elements // trio, and this comment said otherwise until the commit that wired them. THE NEXT // FOUR ARE P4a's. The last three are still computed and counted only, until P4b. ---- - NewVertexBuffers, // -> set_vertex_buffers (P3a) - NewIndexBuffer, // -> set_index_buffer (P3a) - NewFramebuffer, // -> set_framebuffer_state, per bound target (P4a) - NewSamplerViews, // -> set_sampler_views (P4a) - NewSamplers, // -> bind_sampler_states (P4a) - NewShaderImages, // -> set_shader_images (P4a) + NewVertexBuffers, // -> set_vertex_buffers (P3a) + NewIndexBuffer, // -> set_index_buffer (P3a) + NewFramebuffer, // -> set_framebuffer_state, per bound target (P4a) + NewSamplerViews, // -> set_sampler_views (P4a) + NewSamplers, // -> bind_sampler_states (P4a) + NewShaderImages, // -> set_shader_images (P4a) NewConstBuffers, NewShaderBuffers, NewSoTargets, @@ -90,8 +90,7 @@ namespace MobileGL::MG_Pipe { // new shutters - and it is also why the two narrowings below are stated as requirements. inline constexpr Uint32 kMGPipeDirtyEmittedAtP4a = kMGPipeDirtyEmittedAtP3a | MGPipeDirtyBit(MGPipeDirty::NewShader) | - MGPipeDirtyBit(MGPipeDirty::NewShaderBindings) | - MGPipeDirtyBit(MGPipeDirty::NewGlobalConstants) | + MGPipeDirtyBit(MGPipeDirty::NewShaderBindings) | MGPipeDirtyBit(MGPipeDirty::NewGlobalConstants) | MGPipeDirtyBit(MGPipeDirty::NewFramebuffer) | MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | MGPipeDirtyBit(MGPipeDirty::NewSamplers) | MGPipeDirtyBit(MGPipeDirty::NewShaderImages); @@ -109,24 +108,11 @@ namespace MobileGL::MG_Pipe { MGPipeDirtyBit(MGPipeDirty::NewShaderBuffers) | MGPipeDirtyBit(MGPipeDirty::NewSoTargets); inline constexpr const char* kMGPipeDirtyNames[kMGPipeDirtyCount] = { - "NEW_RENDER_STATE", - "NEW_PIPELINE_STATE", - "NEW_PIXEL_PACK", - "NEW_PATCH_STATE", - "NEW_VERTEX_ATTRIB_DEFAULTS", - "NEW_VERTEX_ELEMENTS", - "NEW_SHADER", - "NEW_SHADER_BINDINGS", - "NEW_GLOBAL_CONSTANTS", - "NEW_VERTEX_BUFFERS", - "NEW_INDEX_BUFFER", - "NEW_FRAMEBUFFER", - "NEW_SAMPLER_VIEWS", - "NEW_SAMPLERS", - "NEW_SHADER_IMAGES", - "NEW_CONST_BUFFERS", - "NEW_SHADER_BUFFERS", - "NEW_SO_TARGETS", + "NEW_RENDER_STATE", "NEW_PIPELINE_STATE", "NEW_PIXEL_PACK", "NEW_PATCH_STATE", + "NEW_VERTEX_ATTRIB_DEFAULTS", "NEW_VERTEX_ELEMENTS", "NEW_SHADER", "NEW_SHADER_BINDINGS", + "NEW_GLOBAL_CONSTANTS", "NEW_VERTEX_BUFFERS", "NEW_INDEX_BUFFER", "NEW_FRAMEBUFFER", + "NEW_SAMPLER_VIEWS", "NEW_SAMPLERS", "NEW_SHADER_IMAGES", "NEW_CONST_BUFFERS", + "NEW_SHADER_BUFFERS", "NEW_SO_TARGETS", }; // Which runtime MOBILEGL_PIPE_PUSH subsystem bit gates a dirty bit's emission. Zero for @@ -290,8 +276,7 @@ namespace MobileGL::MG_Pipe { const Uint64 vaoLifetime = vao ? vao->GetLifetimeId() : 0; const Uint32 vaoConfig = vao ? vao->GetConfigVersion() : 0; const Bool vaoChanged = !m_primed || vaoLifetime != m_lastVaoLifetime || vaoConfig != m_lastVaoConfig; - const Uint64 vaoIdentity = - vao ? MGPipeMixShutter(vaoLifetime, vaoConfig) : 0; + const Uint64 vaoIdentity = vao ? MGPipeMixShutter(vaoLifetime, vaoConfig) : 0; now[Index(MGPipeDirty::NewVertexElements)] = vaoIdentity; // Reads plain version counters, never GetProgramForDraw(): that joins a pending @@ -321,11 +306,10 @@ namespace MobileGL::MG_Pipe { Uint64 opaqueUnits = 0; if (program) { shader = MGPipeMixShutter(program->GetLifetimeId(), program->GetLinkVersion()); - bindings = MGPipeMixShutter( - MGPipeMixShutter(MGPipeMixShutter(program->GetImageUnitVersion(), - program->GetBackendStateVersion()), - program->GetBlockBindingVersion()), - program->GetUniformWriteSetVersion()); + bindings = MGPipeMixShutter(MGPipeMixShutter(MGPipeMixShutter(program->GetImageUnitVersion(), + program->GetBackendStateVersion()), + program->GetBlockBindingVersion()), + program->GetUniformWriteSetVersion()); constants = MGPipeMixShutter(program->GetLifetimeId(), program->GetUBOContentVersion()); // THE IDENTITY IS MIXED IN (P4a fable seam F-2), exactly as the pipeline arm // below mixes stageLinks into its half: the counter alone is a per-program @@ -378,12 +362,12 @@ namespace MobileGL::MG_Pipe { for (SizeT stage = 0; stage < Pipeline::kGraphicsStageCount; ++stage) { const auto& staged = pipeline->GetStageProgram(static_cast(stage)); if (!staged) continue; - stageLinks = MGPipeMixShutter( - MGPipeMixShutter(stageLinks, staged->GetLifetimeId()), staged->GetLinkVersion()); + stageLinks = MGPipeMixShutter(MGPipeMixShutter(stageLinks, staged->GetLifetimeId()), + staged->GetLinkVersion()); stageState = MGPipeMixShutter( - MGPipeMixShutter(MGPipeMixShutter(stageState, staged->GetBackendStateVersion()), - MGPipeMixShutter(staged->GetUBOContentVersion(), - staged->GetBlockBindingVersion())), + MGPipeMixShutter( + MGPipeMixShutter(stageState, staged->GetBackendStateVersion()), + MGPipeMixShutter(staged->GetUBOContentVersion(), staged->GetBlockBindingVersion())), staged->GetUniformWriteSetVersion()); stageImages = MGPipeMixShutter(stageImages, staged->GetImageUnitVersion()); stageOpaque = MGPipeMixShutter(stageOpaque, staged->GetBackendStateVersion()); @@ -458,10 +442,8 @@ namespace MobileGL::MG_Pipe { now[Index(MGPipeDirty::NewFramebuffer)] = MGPipeMixShutter( MGPipeMixShutter( ctx.GetAnyFramebufferAttachmentGeneration(), - m_framebufferBind.Observe( - ctx.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetVersion())), - m_readFramebufferBind.Observe( - ctx.GetFramebufferBindingSlot(FramebufferTarget::Read).GetVersion())); + m_framebufferBind.Observe(ctx.GetFramebufferBindingSlot(FramebufferTarget::Draw).GetVersion())), + m_readFramebufferBind.Observe(ctx.GetFramebufferBindingSlot(FramebufferTarget::Read).GetVersion())); // Bit 12 reads FOUR things (F-1): the two texture aggregates, the bind generation // and the program input computed above. The params aggregate is here because // SamplerEmit.h drops a unit's view to null when SamplesAsIncompleteTexture says so, @@ -480,11 +462,10 @@ namespace MobileGL::MG_Pipe { // the extra fires are absorbed by the emitter's set-hash suppressor. A missed fire // here means wrong filtering, since bind_sampler_states has no pulled fallback. now[Index(MGPipeDirty::NewSamplers)] = MGPipeMixShutter( - MGPipeMixShutter(textureParams, ctx.GetSamplingResolutionGeneration()), - ctx.GetTextureBindGeneration()); - now[Index(MGPipeDirty::NewShaderImages)] = MGPipeMixShutter( - MGPipeMixShutter(MGPipeMixShutter(textureContent, textureParams), programImages), - ctx.GetTextureBindGeneration()); + MGPipeMixShutter(textureParams, ctx.GetSamplingResolutionGeneration()), ctx.GetTextureBindGeneration()); + now[Index(MGPipeDirty::NewShaderImages)] = + MGPipeMixShutter(MGPipeMixShutter(MGPipeMixShutter(textureContent, textureParams), programImages), + ctx.GetTextureBindGeneration()); // Bits 15-17 read the buffer bind-point generations that glBindBufferBase/Range move // (BufferState::NoteBindPointChanged), not the buffer content aggregate: the records // carry {handle, offset, size}, a base binding's size travels as kMGPipeWholeBuffer @@ -493,14 +474,14 @@ namespace MobileGL::MG_Pipe { // indexed by the program's block bindings. Bit 16 covers storage and atomic-counter // points, which are emitted together. Bit 17 adds the transform-feedback generation // (capture points latch at Begin) and has no emitter. - now[Index(MGPipeDirty::NewConstBuffers)] = MGPipeMixShutter( - ctx.GetBufferBindPointGeneration(BufferTarget::Uniform), shader); - now[Index(MGPipeDirty::NewShaderBuffers)] = MGPipeMixShutter( - ctx.GetBufferBindPointGeneration(BufferTarget::ShaderStorage), - ctx.GetBufferBindPointGeneration(BufferTarget::AtomicCounter)); - now[Index(MGPipeDirty::NewSoTargets)] = MGPipeMixShutter( - ctx.GetBufferBindPointGeneration(BufferTarget::TransformFeedback), - ctx.GetTransformFeedbackGeneration()); + now[Index(MGPipeDirty::NewConstBuffers)] = + MGPipeMixShutter(ctx.GetBufferBindPointGeneration(BufferTarget::Uniform), shader); + now[Index(MGPipeDirty::NewShaderBuffers)] = + MGPipeMixShutter(ctx.GetBufferBindPointGeneration(BufferTarget::ShaderStorage), + ctx.GetBufferBindPointGeneration(BufferTarget::AtomicCounter)); + now[Index(MGPipeDirty::NewSoTargets)] = + MGPipeMixShutter(ctx.GetBufferBindPointGeneration(BufferTarget::TransformFeedback), + ctx.GetTransformFeedbackGeneration()); Uint32 dirty = 0; for (SizeT i = 0; i < kMGPipeDirtyCount; ++i) { @@ -520,8 +501,7 @@ namespace MobileGL::MG_Pipe { // own emitters still suppress unchanged records. if (vaoChanged) { dirty |= MGPipeDirtyBit(MGPipeDirty::NewVertexElements) | - MGPipeDirtyBit(MGPipeDirty::NewVertexBuffers) | - MGPipeDirtyBit(MGPipeDirty::NewIndexBuffer); + MGPipeDirtyBit(MGPipeDirty::NewVertexBuffers) | MGPipeDirtyBit(MGPipeDirty::NewIndexBuffer); } m_lastVaoLifetime = vaoLifetime; m_lastVaoConfig = vaoConfig; @@ -530,8 +510,7 @@ namespace MobileGL::MG_Pipe { // must still publish that new prefix, including its null samplers. const Int maxTextureUnit = ctx.GetMaxTouchedTextureUnit(); if (!m_primed || maxTextureUnit != m_lastMaxTextureUnit) { - dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | - MGPipeDirtyBit(MGPipeDirty::NewSamplers); + dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | MGPipeDirtyBit(MGPipeDirty::NewSamplers); } m_lastMaxTextureUnit = maxTextureUnit; @@ -560,8 +539,8 @@ namespace MobileGL::MG_Pipe { dirty |= MGPipeDirtyBit(MGPipeDirty::NewFramebuffer); } if (content || params) { - dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | - MGPipeDirtyBit(MGPipeDirty::NewShaderImages); + dirty |= + MGPipeDirtyBit(MGPipeDirty::NewSamplerViews) | MGPipeDirtyBit(MGPipeDirty::NewShaderImages); } if (params || clocks.SamplingResolution != m_lastSharedClocks.SamplingResolution) { dirty |= MGPipeDirtyBit(MGPipeDirty::NewSamplers); @@ -582,8 +561,10 @@ namespace MobileGL::MG_Pipe { // which is the whole reason this is a byte compare. PatchTrio patch{}; patch.PatchVertices = render.PatchVertices; - for (SizeT i = 0; i < 4; ++i) patch.Outer[i] = render.PatchDefaultOuterLevel[i]; - for (SizeT i = 0; i < 2; ++i) patch.Inner[i] = render.PatchDefaultInnerLevel[i]; + for (SizeT i = 0; i < 4; ++i) + patch.Outer[i] = render.PatchDefaultOuterLevel[i]; + for (SizeT i = 0; i < 2; ++i) + patch.Inner[i] = render.PatchDefaultInnerLevel[i]; if (!m_primed || std::memcmp(&patch, &m_patch, sizeof(patch)) != 0) { dirty |= MGPipeDirtyBit(MGPipeDirty::NewPatchState); m_patch = patch; @@ -650,15 +631,15 @@ namespace MobileGL::MG_Pipe { } Uint64 FireCount(MGPipeDirty bit) const { Uint64 total = 0; - for (SizeT i = 0; i < kMGPipeVerbClassCount; ++i) total += m_fires[Index(bit)][i]; + for (SizeT i = 0; i < kMGPipeVerbClassCount; ++i) + total += m_fires[Index(bit)][i]; return total; } - Uint64 WalkCount(MGPipeVerbClass verbClass) const { - return m_walks[static_cast(verbClass)]; - } + Uint64 WalkCount(MGPipeVerbClass verbClass) const { return m_walks[static_cast(verbClass)]; } Uint64 WalkCount() const { Uint64 total = 0; - for (SizeT i = 0; i < kMGPipeVerbClassCount; ++i) total += m_walks[i]; + for (SizeT i = 0; i < kMGPipeVerbClassCount; ++i) + total += m_walks[i]; return total; } @@ -758,16 +739,13 @@ namespace MobileGL::MG_Pipe { // each view from its own array; ValueClass is the record of which view the application // wrote directly, kept for the comparator and for readers - the applier no longer needs // it to rebuild anything. - inline void MGPipeFillAttribValue(Uint32 location, - const MG_State::GLState::CurrentVertexAttributeValue& value, + inline void MGPipeFillAttribValue(Uint32 location, const MG_State::GLState::CurrentVertexAttributeValue& value, Uint32 writtenClass, MGPAttribValue& out) { out = MGPAttribValue{}; out.Location = location; out.ValueClass = static_cast(writtenClass); - static_assert(sizeof(out.FloatView) == sizeof(value.floatValue), - "MGPAttribValue's views are four words each"); - static_assert(sizeof(out.IntView) == sizeof(value.intValue) && - sizeof(out.UintView) == sizeof(value.uintValue), + static_assert(sizeof(out.FloatView) == sizeof(value.floatValue), "MGPAttribValue's views are four words each"); + static_assert(sizeof(out.IntView) == sizeof(value.intValue) && sizeof(out.UintView) == sizeof(value.uintValue), "MGPAttribValue's views are four words each"); std::memcpy(out.FloatView, value.floatValue.data(), sizeof(out.FloatView)); std::memcpy(out.IntView, value.intValue.data(), sizeof(out.IntView)); diff --git a/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp b/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp index e8e45d1b..a163b63b 100644 --- a/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp +++ b/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.cpp @@ -19,8 +19,8 @@ namespace MobileGL::MG_Record { using MG_State::GLState::BufferObject; - using MobileGL::BufferTarget; using MG_State::GLState::ImageTextureBinding; + using MobileGL::BufferTarget; namespace { // Per-row tallies. Diagnostics, and the only thing a unit case can assert on: an @@ -37,10 +37,9 @@ namespace MobileGL::MG_Record { const auto& program = context->GetTransformFeedbackProgram(); if (program == nullptr) return; const SizeT declared = program->GetTransformFeedbackBufferCount(); - const SizeT count = - declared < MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS - ? declared - : static_cast(MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS); + const SizeT count = declared < MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS + ? declared + : static_cast(MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS); for (SizeT i = 0; i < count; ++i) { const auto& point = context->GetBufferBindingPoint(BufferTarget::TransformFeedback, static_cast(i)); diff --git a/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.h b/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.h index 6d1d6956..a8f27189 100644 --- a/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.h +++ b/MobileGL/MG_Impl/Pipe/Verb/GpuWriteSet.h @@ -95,8 +95,7 @@ namespace MobileGL::MG_Record { // The one place a row actually marks. Null and duplicate marks are absorbed here so the // walks stay readable, and the per-row counter moves only when a buffer really was // marked. - void MarkBufferForProducer(const SharedPtr& buffer, - GpuWriteProducer producer); + void MarkBufferForProducer(const SharedPtr& buffer, GpuWriteProducer producer); Uint64 ProducerMarkCount(GpuWriteProducer producer); void ResetProducerMarkCountsForTest(); diff --git a/MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp b/MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp index df39117e..bffb5cce 100644 --- a/MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp +++ b/MobileGL/MG_Impl/Pipe/Verb/VerbPort.cpp @@ -55,7 +55,8 @@ namespace MobileGL::MG_Record { // The same shape as MGPipeInputPoisonFatal (generated/PipeFilled.inc:407-413): names the // slot, live at every log level, aborts. Deliberately NOT MOBILEGL_ASSERT, which is // inert in an INFO build - and INFO is what every device lane runs. - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"}", slot); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"%s\"}", + slot); } // A verb that reads buffers but starts no shader: clear, blit, readback, present. The @@ -122,8 +123,7 @@ namespace MobileGL::MG_Record { // whole strides plus SKIP_PIXELS pixels, and only `width * bytesPerPixel` bytes of each // stride are written - the gaps belong to the application and are never touched. That // last clause is what the control checks with a sentinel. - Bool ReadbackPackStateIsTight(GLsizei width, Uint64 bytesPerPixel, - const PixelStoreParameters& pack) { + Bool ReadbackPackStateIsTight(GLsizei width, Uint64 bytesPerPixel, const PixelStoreParameters& pack) { // SKIP_IMAGES (and IMAGE_HEIGHT) are NOT consulted: glReadPixels is a 2-D read and GL // ignores the image-level pack parameters for it, exactly as the monolith conversion // path does in ReadPixels (DirectGLESReadback.cpp, honorPackImageParams=false). A non-zero @@ -162,7 +162,6 @@ namespace MobileGL::MG_Record { return g_verbSessionResolver(slot); } - // ---- P13 W4: THE MONOLITH VERB PORT ---------------------------------------------- // // Monolith's record arm (MG_Config::RecordArmAliasesFrontend) reads the verb's own state - @@ -304,9 +303,9 @@ namespace MobileGL::MG_Record { public: explicit VerbChannel(VerbSession* session) : m_session(session) {} - Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* varTail, Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, - Int32* statusOut, Uint64* replySizeOut = nullptr) { + Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const void* varTail, + Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, Int32* statusOut, + Uint64* replySizeOut = nullptr) { if (m_session != nullptr) { return m_session->EmitAndWait(op, payload, payloadBytes, varTail, varTailBytes, replyOut, replyBytes, statusOut, replySizeOut); @@ -316,8 +315,8 @@ namespace MobileGL::MG_Record { return 0; } Uint64 EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const MG_Pipe::MGPipeVerbTail* tails, Uint32 tailCount, void* replyOut, Uint64 replyBytes, - Int32* statusOut, Uint64* replySizeOut = nullptr) { + const MG_Pipe::MGPipeVerbTail* tails, Uint32 tailCount, void* replyOut, + Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut = nullptr) { if (m_session != nullptr) { return m_session->EmitAndWaitTails(op, payload, payloadBytes, tails, tailCount, replyOut, replyBytes, statusOut, replySizeOut); @@ -341,15 +340,15 @@ namespace MobileGL::MG_Record { } private: - void Port(MG_Pipe::MGPWireOp op, const void* payload, const MG_Pipe::MGPipeVerbTail* tails, Uint32 tailCount, - void* replyOut, Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) { + void Port(MG_Pipe::MGPWireOp op, const void* payload, const MG_Pipe::MGPipeVerbTail* tails, + Uint32 tailCount, void* replyOut, Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) { MonolithReplySink replies(replyOut, replyBytes); const Bool applied = ApplyOnMonolithPort(op, payload, tails, tailCount, &replies); // A sink that declined without answering is the decoder's DECLINED; an answer it // posted is the answer, whatever the return. - const Int32 status = replies.Posted() - ? replies.Status() - : (applied ? MG_Pipe::MGPipeReplySink::kStatusOk : MG_Pipe::MGPipeReplySink::kStatusDeclined); + const Int32 status = replies.Posted() ? replies.Status() + : (applied ? MG_Pipe::MGPipeReplySink::kStatusOk + : MG_Pipe::MGPipeReplySink::kStatusDeclined); if (statusOut != nullptr) *statusOut = status; if (replySizeOut != nullptr) *replySizeOut = replies.Posted() ? replies.Size() : 0; if (!applied && replyOut == nullptr) { @@ -397,15 +396,12 @@ namespace MobileGL::MG_Record { record.DrawBufferIndex = -1; record.BufferMask = static_cast(mask); record.ValueClass = 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::Clear, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::Clear, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } - // ---- f1: verbatim clear/copy/mipmap records (CONTRACT-P5B §2) ---- - void EmitF1Clear(const char* slot, MG_Pipe::MGPipeHandle fbo, GLenum buffer, - GLint drawbuffer, Uint8 valueClass, const void* value, - GLfloat depth = 0, GLint stencil = 0) { + void EmitF1Clear(const char* slot, MG_Pipe::MGPipeHandle fbo, GLenum buffer, GLint drawbuffer, Uint8 valueClass, + const void* value, GLfloat depth = 0, GLint stencil = 0) { VerbChannel session = ChannelFor(slot); BeforeReadOnlyVerb(); MG_Pipe::MGPClear record{}; @@ -430,58 +426,59 @@ namespace MobileGL::MG_Record { record.DepthValue = depth; record.StencilValue = stencil; break; - default: UnmigratedVerbFatal(slot); + default: + UnmigratedVerbFatal(slot); } - session.EmitAndWait(MG_Pipe::MGPWireOp::Clear, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::Clear, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } void EmitClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) { EmitF1Clear("ClearBufferfv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, value); } - void EmitClearNamedFramebufferfv(const SharedPtr& fbo, - GLenum buffer, GLint drawbuffer, const GLfloat* value) { - EmitF1Clear("ClearNamedFramebufferfv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), - buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, value); + void EmitClearNamedFramebufferfv(const SharedPtr& fbo, GLenum buffer, + GLint drawbuffer, const GLfloat* value) { + EmitF1Clear("ClearNamedFramebufferfv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), buffer, + drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, value); } void EmitClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) { EmitF1Clear("ClearBufferiv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassInt, value); } - void EmitClearNamedFramebufferiv(const SharedPtr& fbo, - GLenum buffer, GLint drawbuffer, const GLint* value) { - EmitF1Clear("ClearNamedFramebufferiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), - buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassInt, value); + void EmitClearNamedFramebufferiv(const SharedPtr& fbo, GLenum buffer, + GLint drawbuffer, const GLint* value) { + EmitF1Clear("ClearNamedFramebufferiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), buffer, + drawbuffer, MG_Pipe::kMGPipeClearValueClassInt, value); } void EmitClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) { EmitF1Clear("ClearBufferuiv", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassUint, value); } - void EmitClearNamedFramebufferuiv(const SharedPtr& fbo, - GLenum buffer, GLint drawbuffer, const GLuint* value) { - EmitF1Clear("ClearNamedFramebufferuiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), - buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassUint, value); + void EmitClearNamedFramebufferuiv(const SharedPtr& fbo, GLenum buffer, + GLint drawbuffer, const GLuint* value) { + EmitF1Clear("ClearNamedFramebufferuiv", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), buffer, + drawbuffer, MG_Pipe::kMGPipeClearValueClassUint, value); } void EmitClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { EmitF1Clear("ClearBufferfi", MG_Pipe::kMGPipeNullHandle, buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil); } - void EmitClearNamedFramebufferfi(const SharedPtr& fbo, - GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { - EmitF1Clear("ClearNamedFramebufferfi", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), - buffer, drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil); + void EmitClearNamedFramebufferfi(const SharedPtr& fbo, GLenum buffer, + GLint drawbuffer, GLfloat depth, GLint stencil) { + EmitF1Clear("ClearNamedFramebufferfi", MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*fbo), buffer, + drawbuffer, MG_Pipe::kMGPipeClearValueClassFloat, nullptr, depth, stencil); } const SharedPtr& F1BoundTexture(GLenum target) { auto& ctx = *MG_State::pGLContext; return ctx.GetTextureUnitObject(ctx.GetActiveTextureUnit()) - .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)).GetBoundObject(); + .GetBindingSlot(MG_Util::ConvertGLEnumToTextureTarget(target)) + .GetBoundObject(); } - void EmitF1Copy(GLenum target, GLint level, GLenum format, GLint x, GLint y, - GLsizei width, GLsizei height, GLint xoffset, GLint yoffset, Bool subImage) { + void EmitF1Copy(GLenum target, GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, + GLint xoffset, GLint yoffset, Bool subImage) { VerbChannel session = ChannelFor(subImage ? "CopyTexSubImage2D" : "CopyTexImage2D"); BeforeReadOnlyVerb(); MG_Pipe::MGPCopyFromFramebuffer record{}; @@ -489,19 +486,22 @@ namespace MobileGL::MG_Record { record.Target = static_cast(target); record.Level = level; record.InternalFormat = format; - record.X = x; record.Y = y; - record.Width = width; record.Height = height; - record.XOffset = xoffset; record.YOffset = yoffset; + record.X = x; + record.Y = y; + record.Width = width; + record.Height = height; + record.XOffset = xoffset; + record.YOffset = yoffset; record.SubImage = subImage; - session.EmitAndWait(MG_Pipe::MGPWireOp::CopyFramebufferToTexture, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::CopyFramebufferToTexture, &record, sizeof(record), nullptr, 0, + nullptr, 0, nullptr); } - void EmitCopyTexImage2D(GLenum target, GLint level, GLenum format, GLint x, GLint y, - GLsizei width, GLsizei height, GLint) { + void EmitCopyTexImage2D(GLenum target, GLint level, GLenum format, GLint x, GLint y, GLsizei width, + GLsizei height, GLint) { EmitF1Copy(target, level, format, x, y, width, height, 0, 0, false); } - void EmitCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, - GLint x, GLint y, GLsizei width, GLsizei height) { + void EmitCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, + GLsizei width, GLsizei height) { EmitF1Copy(target, level, 0, x, y, width, height, xoffset, yoffset, true); } void EmitGenerateMipmap(GLenum target) { @@ -514,20 +514,20 @@ namespace MobileGL::MG_Record { record.BaseLevel = texture->GetLevelRange().x(); const auto* mipmap = dynamic_cast(texture.get()); if (mipmap && !texture->GetUploadTargets().empty()) { - const auto plan = MG_Impl::GLImpl::ComputeMipmapGenerationRange(*mipmap, texture->GetUploadTargets()[0]); + const auto plan = + MG_Impl::GLImpl::ComputeMipmapGenerationRange(*mipmap, texture->GetUploadTargets()[0]); // LevelCount is the logical end-exclusive, not the number of // levels following BaseLevel. Preserve that existing carrier. record.LevelCount = static_cast(std::min(plan.End, mipmap->GetMipmapLevelCount())); } - session.EmitAndWait(MG_Pipe::MGPWireOp::GenerateMipmap, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::GenerateMipmap, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } // All draw entry points share the owned-input preparation and emission below. [[noreturn]] void RefuseDrawByName(const char* slot, const char* qualifier); - void EmitDrawRecord(const char* slot, MG_Pipe::MGPDrawInfo& info, - const MG_Pipe::MGPDrawRange* ranges, Uint32 numDraws, - const void* clientIndices, Uint64 clientIndexBytes, + void EmitDrawRecord(const char* slot, MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, + Uint32 numDraws, const void* clientIndices, Uint64 clientIndexBytes, const MG_Pipe::MGPDrawIndirect* indirect); // Does the bound VAO fetch any ENABLED attribute out of the application's own memory. @@ -638,9 +638,8 @@ namespace MobileGL::MG_Record { // The one emission for all nineteen. `clientIndices`/`clientIndexBytes` name a client // index array to stage (no element buffer bound); `indirect` is the kDrawIsIndirect // block. The two are exclusive by construction here and by the layout on both sides. - void EmitDrawRecord(const char* slot, MG_Pipe::MGPDrawInfo& info, - const MG_Pipe::MGPDrawRange* ranges, Uint32 numDraws, - const void* clientIndices, Uint64 clientIndexBytes, + void EmitDrawRecord(const char* slot, MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, + Uint32 numDraws, const void* clientIndices, Uint64 clientIndexBytes, const MG_Pipe::MGPDrawIndirect* indirect) { VerbChannel session = ChannelFor(slot); MG_Record::PersistentMapTracker::Instance().PushDrawConsumers(); @@ -652,8 +651,10 @@ namespace MobileGL::MG_Record { if (MG_State::pGLContext == nullptr) RefuseDrawByName(slot, "NO_CONTEXT"); ownedInputs = MakeUnique(*MG_State::pGLContext); if (!ownedInputs->Prepare(info, ranges, numDraws, clientIndices, clientIndexBytes, indirect)) { - MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, - MakeUnique("MG_Remote/Client", slot, + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Remote/Client", slot, "the client vertex/index fetch range cannot be represented by its storage")); return; } @@ -670,27 +671,26 @@ namespace MobileGL::MG_Record { info.NumDraws = numDraws; MG_Pipe::MGPipeVerbTail tails[2] = {{ranges, static_cast(numDraws) * sizeof(MG_Pipe::MGPDrawRange)}, - {nullptr, 0}}; + {nullptr, 0}}; Uint32 tailCount = 1; if (indirect != nullptr) { info.Flags |= MG_Pipe::kDrawIsIndirect; tails[1] = {indirect, sizeof(*indirect)}; tailCount = 2; } - session.EmitAndWaitTails(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, - tailCount, nullptr, 0, nullptr); + session.EmitAndWaitTails(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, tailCount, nullptr, 0, + nullptr); } // ---- the single-draw indexed family: DrawElements, DrawElementsBaseVertex, the two // DrawRangeElements*, the four DrawElementsInstanced* ------------------------------- - void EmitIndexedDraw(const char* slot, GLenum mode, GLsizei count, GLenum type, - const void* indices, GLint baseVertex, GLsizei instanceCount, - GLuint baseInstance, Bool hasRange, GLuint start, GLuint end) { + void EmitIndexedDraw(const char* slot, GLenum mode, GLsizei count, GLenum type, const void* indices, + GLint baseVertex, GLsizei instanceCount, GLuint baseInstance, Bool hasRange, GLuint start, + GLuint end) { const RemoteDrawBindings bindings = ReadDrawBindings(); const Uint8 indexSize = RemoteIndexSizeFor(type); if (indexSize == 0) RefuseDrawByName(slot, "INDEX_TYPE"); - MG_Pipe::MGPDrawInfo info = - PlanDrawInfo(mode, indexSize, instanceCount, baseInstance, 1, bindings); + MG_Pipe::MGPDrawInfo info = PlanDrawInfo(mode, indexSize, instanceCount, baseInstance, 1, bindings); if (hasRange) { info.Flags |= MG_Pipe::kDrawHasIndexRange; info.MinIndex = start; @@ -714,62 +714,57 @@ namespace MobileGL::MG_Record { } void EmitDrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) { - EmitIndexedDraw("DrawElementsBaseVertex", mode, count, type, indices, basevertex, 1, 0, - false, 0, 0); + EmitIndexedDraw("DrawElementsBaseVertex", mode, count, type, indices, basevertex, 1, 0, false, 0, 0); } void EmitDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices) { EmitIndexedDraw("DrawRangeElements", mode, count, type, indices, 0, 1, 0, true, start, end); } - void EmitDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, - GLenum type, const void* indices, GLint basevertex) { - EmitIndexedDraw("DrawRangeElementsBaseVertex", mode, count, type, indices, basevertex, 1, - 0, true, start, end); + void EmitDrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, + const void* indices, GLint basevertex) { + EmitIndexedDraw("DrawRangeElementsBaseVertex", mode, count, type, indices, basevertex, 1, 0, true, start, + end); } void EmitDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount) { - EmitIndexedDraw("DrawElementsInstanced", mode, count, type, indices, 0, instancecount, 0, + EmitIndexedDraw("DrawElementsInstanced", mode, count, type, indices, 0, instancecount, 0, false, 0, 0); + } + void EmitDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLint basevertex) { + EmitIndexedDraw("DrawElementsInstancedBaseVertex", mode, count, type, indices, basevertex, instancecount, 0, false, 0, 0); } - void EmitDrawElementsInstancedBaseVertex(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLint basevertex) { - EmitIndexedDraw("DrawElementsInstancedBaseVertex", mode, count, type, indices, basevertex, - instancecount, 0, false, 0, 0); - } - void EmitDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, - const void* indices, GLsizei instancecount, - GLuint baseinstance) { - EmitIndexedDraw("DrawElementsInstancedBaseInstance", mode, count, type, indices, 0, - instancecount, baseinstance, false, 0, 0); + void EmitDrawElementsInstancedBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, + GLsizei instancecount, GLuint baseinstance) { + EmitIndexedDraw("DrawElementsInstancedBaseInstance", mode, count, type, indices, 0, instancecount, + baseinstance, false, 0, 0); } void EmitDrawElementsInstancedBaseVertexBaseInstance(GLenum mode, GLsizei count, GLenum type, const void* indices, GLsizei instancecount, GLint basevertex, GLuint baseinstance) { - EmitIndexedDraw("DrawElementsInstancedBaseVertexBaseInstance", mode, count, type, indices, - basevertex, instancecount, baseinstance, false, 0, 0); + EmitIndexedDraw("DrawElementsInstancedBaseVertexBaseInstance", mode, count, type, indices, basevertex, + instancecount, baseinstance, false, 0, 0); } // ---- the instanced array draws ------------------------------------------------ - void EmitArraysDraw(const char* slot, GLenum mode, GLint first, GLsizei count, - GLsizei instanceCount, GLuint baseInstance) { + void EmitArraysDraw(const char* slot, GLenum mode, GLint first, GLsizei count, GLsizei instanceCount, + GLuint baseInstance) { const RemoteDrawBindings bindings = ReadDrawBindings(); MG_Pipe::MGPDrawInfo info = PlanDrawInfo(mode, 0, instanceCount, baseInstance, 1, bindings); MG_Pipe::MGPDrawRange range{}; - PlanDrawRange(bindings, 0, reinterpret_cast(static_cast(first)), - count, 0, range); + PlanDrawRange(bindings, 0, reinterpret_cast(static_cast(first)), count, 0, + range); EmitDrawRecord(slot, info, &range, 1, nullptr, 0, nullptr); } void EmitDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei instancecount) { EmitArraysDraw("DrawArraysInstanced", mode, first, count, instancecount, 0); } - void EmitDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, - GLsizei instancecount, GLuint baseinstance) { + void EmitDrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount, + GLuint baseinstance) { // The vertex-FETCH base instance already crossed in set_vertex_buffers::BaseInstance // at validate (D-H1: MGP_SET_BASE_INSTANCE runs before MGP_FILL); StartInstance is // gl_BaseInstance's value and feeds the GL call. - EmitArraysDraw("DrawArraysInstancedBaseInstance", mode, first, count, instancecount, - baseinstance); + EmitArraysDraw("DrawArraysInstancedBaseInstance", mode, first, count, instancecount, baseinstance); } // ---- the multi-draws: MGPDrawRange[drawcount] is exactly their shape ------------- @@ -791,16 +786,14 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPDrawInfo info = PlanDrawInfo(mode, 0, 1, 0, n, bindings); Vector& ranges = MultiDrawScratch(n); for (Uint32 i = 0; i < n; ++i) { - PlanDrawRange(bindings, 0, - reinterpret_cast(static_cast(first[i])), + PlanDrawRange(bindings, 0, reinterpret_cast(static_cast(first[i])), count[i], 0, ranges[i]); } EmitDrawRecord("MultiDrawArrays", info, ranges.data(), n, nullptr, 0, nullptr); } void EmitMultiIndexedDraw(const char* slot, GLenum mode, const GLsizei* count, GLenum type, - const GLvoid* const* indices, GLsizei drawcount, - const GLint* basevertex) { + const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex) { if (drawcount <= 0 || count == nullptr || indices == nullptr) return; const RemoteDrawBindings bindings = ReadDrawBindings(); const Uint8 indexSize = RemoteIndexSizeFor(type); @@ -810,15 +803,16 @@ namespace MobileGL::MG_Record { Vector& ranges = MultiDrawScratch(n); Vector ownedIndices; for (Uint32 i = 0; i < n; ++i) { - if (!PlanDrawRange(bindings, indexSize, indices[i], count[i], - basevertex != nullptr ? basevertex[i] : 0, ranges[i])) { + if (!PlanDrawRange(bindings, indexSize, indices[i], count[i], basevertex != nullptr ? basevertex[i] : 0, + ranges[i])) { RefuseDrawByName(slot, "INDEX_OFFSET"); } if (!bindings.ElementBufferBound) { const Uint64 byteCount = static_cast(ranges[i].Count) * indexSize; if (byteCount > std::numeric_limits::max() - ownedIndices.size() || (byteCount != 0 && indices[i] == nullptr)) { - MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, MakeUnique("MG_Remote/Client", slot, "invalid client index span")); return; } @@ -831,21 +825,18 @@ namespace MobileGL::MG_Record { EmitDrawRecord(slot, info, ranges.data(), n, ownedIndices.empty() ? nullptr : ownedIndices.data(), ownedIndices.size(), nullptr); } - void EmitMultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, - const GLvoid* const* indices, GLsizei drawcount) { + void EmitMultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const GLvoid* const* indices, + GLsizei drawcount) { EmitMultiIndexedDraw("MultiDrawElements", mode, count, type, indices, drawcount, nullptr); } void EmitMultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, - const GLvoid* const* indices, GLsizei drawcount, - const GLint* basevertex) { - EmitMultiIndexedDraw("MultiDrawElementsBaseVertex", mode, count, type, indices, drawcount, - basevertex); + const GLvoid* const* indices, GLsizei drawcount, const GLint* basevertex) { + EmitMultiIndexedDraw("MultiDrawElementsBaseVertex", mode, count, type, indices, drawcount, basevertex); } // ---- the indirect family: the block is the second tail, NumDraws is 0 ------------- - void EmitIndirectDraw(const char* slot, GLenum mode, GLenum type, const void* indirect, - GLsizei drawcount, GLsizei stride, GLintptr parameterOffset, - Bool hasParameterBuffer) { + void EmitIndirectDraw(const char* slot, GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, + GLsizei stride, GLintptr parameterOffset, Bool hasParameterBuffer) { const RemoteDrawBindings bindings = ReadDrawBindings(); const Uint8 indexSize = type != 0 ? RemoteIndexSizeFor(type) : 0; if (type != 0 && indexSize == 0) RefuseDrawByName(slot, "INDEX_TYPE"); @@ -856,8 +847,8 @@ namespace MobileGL::MG_Record { RefuseDrawByName(slot, "UNBOUND_PARAMETER"); } MG_Pipe::MGPDrawInfo info = PlanDrawInfo(mode, indexSize, 1, 0, 0, bindings); - const MG_Pipe::MGPDrawIndirect block = PlanDrawIndirect(bindings, indirect, drawcount, stride, - parameterOffset, hasParameterBuffer); + const MG_Pipe::MGPDrawIndirect block = + PlanDrawIndirect(bindings, indirect, drawcount, stride, parameterOffset, hasParameterBuffer); EmitDrawRecord(slot, info, nullptr, 0, nullptr, 0, &block); } void EmitDrawArraysIndirect(GLenum mode, const void* indirect) { @@ -866,30 +857,25 @@ namespace MobileGL::MG_Record { void EmitDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect) { EmitIndirectDraw("DrawElementsIndirect", mode, type, indirect, 1, 0, 0, false); } - void EmitMultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, - GLsizei stride) { + void EmitMultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) { EmitIndirectDraw("MultiDrawArraysIndirect", mode, 0, indirect, drawcount, stride, 0, false); } - void EmitMultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, - GLsizei drawcount, GLsizei stride) { - EmitIndirectDraw("MultiDrawElementsIndirect", mode, type, indirect, drawcount, stride, 0, - false); + void EmitMultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, + GLsizei stride) { + EmitIndirectDraw("MultiDrawElementsIndirect", mode, type, indirect, drawcount, stride, 0, false); } void EmitMultiDrawArraysIndirectCount(GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) { - EmitIndirectDraw("MultiDrawArraysIndirectCount", mode, 0, indirect, maxdrawcount, stride, - drawcount, true); + EmitIndirectDraw("MultiDrawArraysIndirectCount", mode, 0, indirect, maxdrawcount, stride, drawcount, true); } - void EmitMultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, - GLintptr drawcount, GLsizei maxdrawcount, - GLsizei stride) { - EmitIndirectDraw("MultiDrawElementsIndirectCount", mode, type, indirect, maxdrawcount, - stride, drawcount, true); + void EmitMultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, + GLsizei maxdrawcount, GLsizei stride) { + EmitIndirectDraw("MultiDrawElementsIndirectCount", mode, type, indirect, maxdrawcount, stride, drawcount, + true); } - void EmitBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, - GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, - GLenum filter) { + void EmitBlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { VerbChannel session = ChannelFor("BlitFramebuffer"); BeforeReadOnlyVerb(); @@ -909,15 +895,13 @@ namespace MobileGL::MG_Record { record.DstY1 = dstY1; record.Mask = static_cast(mask); record.Filter = static_cast(filter); - session.EmitAndWait(MG_Pipe::MGPWireOp::Blit, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::Blit, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } - void EmitBlitNamedFramebuffer( - const SharedPtr& read, - const SharedPtr& draw, - GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, - GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter) { + void EmitBlitNamedFramebuffer(const SharedPtr& read, + const SharedPtr& draw, GLint srcX0, + GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, + GLint dstY1, GLbitfield mask, GLenum filter) { VerbChannel session = ChannelFor("BlitNamedFramebuffer"); // P5c (hd, CONTRACT-P5C §3.3): the scoped rebind of the client's own read/draw // binding slots is DELETED. It existed only to stage values for the server's @@ -931,14 +915,17 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPBlit record{}; record.ReadFbo = MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*read); record.DrawFbo = MG_Pipe::MGPipeFramebufferEmitter::HandleFor(*draw); - record.SrcX0 = srcX0; record.SrcY0 = srcY0; - record.SrcX1 = srcX1; record.SrcY1 = srcY1; - record.DstX0 = dstX0; record.DstY0 = dstY0; - record.DstX1 = dstX1; record.DstY1 = dstY1; + record.SrcX0 = srcX0; + record.SrcY0 = srcY0; + record.SrcX1 = srcX1; + record.SrcY1 = srcY1; + record.DstX0 = dstX0; + record.DstY0 = dstY0; + record.DstX1 = dstX1; + record.DstY1 = dstY1; record.Mask = static_cast(mask); record.Filter = static_cast(filter); - session.EmitAndWait(MG_Pipe::MGPWireOp::Blit, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::Blit, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); } // Bytes per pixel of a glReadPixels format/type pair, for the tight extent. The pack @@ -947,26 +934,25 @@ namespace MobileGL::MG_Record { // application's GL_PACK_* state (ARCHITECTURE.md §13.1.3). The rule is about layout, // so depth and depth-stencil reads need no special case. Uint64 ReadbackBytesPerPixel(GLenum format, GLenum type) { - const TextureInputFormat inputFormat = - MG_Util::ConvertGLEnumToTextureInputFormat(format); + const TextureInputFormat inputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); const TexturePixelDataType dataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); const SizeT bytesPerPixel = MG_Util::GetInputBytesPerPixel(inputFormat, dataType); if (bytesPerPixel == 0) { // NOT a guess and not a zero-length reply. A format this build cannot size is a // readback whose answer would be silently truncated, which is the one failure a // picture comparison cannot see. - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::UnsizedReadback, "MGPipe: Fatal{UnsizedReadback, \"read_pixels\"} format=0x%04x type=0x%04x " - "- the client must declare MGPReadbackInfo::DstSize and cannot size this " - "pair; P5's reduced path reads RGBA/UNSIGNED_BYTE", - static_cast(format), static_cast(type)); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::UnsizedReadback, + "MGPipe: Fatal{UnsizedReadback, \"read_pixels\"} format=0x%04x type=0x%04x " + "- the client must declare MGPReadbackInfo::DstSize and cannot size this " + "pair; P5's reduced path reads RGBA/UNSIGNED_BYTE", + static_cast(format), static_cast(type)); } return static_cast(bytesPerPixel); } Uint64 TightReadbackBytes(GLsizei width, GLsizei height, GLenum format, GLenum type) { if (width <= 0 || height <= 0) return 0; - return static_cast(width) * static_cast(height) * - ReadbackBytesPerPixel(format, type); + return static_cast(width) * static_cast(height) * ReadbackBytesPerPixel(format, type); } // GL_PACK_SWAP_BYTES as the width of the byte group it reverses for `type`, 0 when there @@ -982,8 +968,7 @@ namespace MobileGL::MG_Record { return group <= 1 ? 0 : static_cast(group); } - void ApplyReadbackByteSwap(void* pixels, Uint64 bytes, GLenum type, - const PixelStoreParameters& pack) { + void ApplyReadbackByteSwap(void* pixels, Uint64 bytes, GLenum type, const PixelStoreParameters& pack) { const SizeT group = ReadbackSwapGroup(type, pack); if (group == 0 || pixels == nullptr) return; auto* data = static_cast(pixels); @@ -1029,40 +1014,45 @@ namespace MobileGL::MG_Record { if (MG_Pipe::MGPipeClientConfirmLossAfterDecline(kDeclineVerdictMs)) return false; } if (status == MG_Pipe::MGPipeReplySink::kStatusError) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"ReadPixels\"} - the readback answered ERROR; " - "the destination is left untouched rather than filled with stale bytes"); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"ReadPixels\"} - the readback answered ERROR; " + "the destination is left untouched rather than filled with stale bytes"); } if (status == MG_Pipe::MGPipeReplySink::kStatusDeclined) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReadbackDeclined, "MGPipe: Fatal{ReadbackDeclined, \"ReadPixels\"} - the server has no " - "GL.ReadPixels and DECLINED; a decline is a real answer for an acceptance " - "row (R-5) but a blocking readback has no pixels to return, so it is a " - "Fatal here rather than a buffer of stale bytes"); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReadbackDeclined, + "MGPipe: Fatal{ReadbackDeclined, \"ReadPixels\"} - the server has no " + "GL.ReadPixels and DECLINED; a decline is a real answer for an acceptance " + "row (R-5) but a blocking readback has no pixels to return, so it is a " + "Fatal here rather than a buffer of stale bytes"); } if (status != MG_Pipe::MGPipeReplySink::kStatusOk) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReplyStatusInvalid, "MGPipe: Fatal{ReplyStatusInvalid, \"ReadPixels\"} - unknown reply status %d", status); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReplyStatusInvalid, + "MGPipe: Fatal{ReplyStatusInvalid, \"ReadPixels\"} - unknown reply status %d", + status); } if (replySize != expected) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReadbackReplyShort, "MGPipe: Fatal{ReadbackReplyShort, \"ReadPixels %llu < %llu\"} - the OK " - "reply carried fewer bytes than the read's own DstSize (CONTRACT-P5 row " - "23's exact extent); the missing rows would otherwise be scattered as " - "whatever the destination held", - static_cast(replySize), - static_cast(expected)); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::ReadbackReplyShort, + "MGPipe: Fatal{ReadbackReplyShort, \"ReadPixels %llu < %llu\"} - the OK " + "reply carried fewer bytes than the read's own DstSize (CONTRACT-P5 row " + "23's exact extent); the missing rows would otherwise be scattered as " + "whatever the destination held", + static_cast(replySize), + static_cast(expected)); } return true; } #include "TextureReadbackEmit.inc" - void EmitReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, - GLenum type, void* pixels) { + void EmitReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { VerbChannel session = ChannelFor("ReadPixels"); // A bound PACK buffer makes pixels an offset. The owned reply is // packed on this thread and only the requested rows are uploaded. - const auto pbo = MG_State::pGLContext != nullptr - ? MG_State::pGLContext->GetBufferBindingSlot(::MobileGL::BufferTarget::PixelPack).GetBoundObject() - : SharedPtr{}; + const auto pbo = + MG_State::pGLContext != nullptr + ? MG_State::pGLContext->GetBufferBindingSlot(::MobileGL::BufferTarget::PixelPack).GetBoundObject() + : SharedPtr{}; BeforeReadOnlyVerb(); @@ -1088,16 +1078,16 @@ namespace MobileGL::MG_Record { // is an ordinary read_pixels record - its own box, and a DstSize that IS its tight // w*h*bpp extent (ID-49) - so every answer still fits one slot (ID-47) and the // server's PH-3 bound (its tight answer against its own LinkTerms.maxReplyBytes, - // PipeApplier.cpp OnReadPixels) holds unchanged: no reply is chunked, the READ is. The cap is the link's own, read live, exactly as - // before, so a different SEG_REPLY geometry or a stream link needs no edit here. + // PipeApplier.cpp OnReadPixels) holds unchanged: no reply is chunked, the READ is. The cap is the link's + // own, read live, exactly as before, so a different SEG_REPLY geometry or a stream link needs no edit here. ReadbackBandPlan plan; - if (!PlanReadbackBands(static_cast(width), static_cast(height), - bytesPerPixel, session.MaxReplyBytes(), plan)) { + if (!PlanReadbackBands(static_cast(width), static_cast(height), bytesPerPixel, + session.MaxReplyBytes(), plan)) { // NOT EVEN ONE PIXEL FITS (or there is no reply pool at all). That is the only // read no banding can answer, and ID-47's named refusal is kept for it - for the // one-pixel piece that cannot be sent, so the message names what was impossible. - session.RequireReadPixelsReplyFits(1, 1, static_cast(format), - static_cast(type), bytesPerPixel); + session.RequireReadPixelsReplyFits(1, 1, static_cast(format), static_cast(type), + bytesPerPixel); return; } @@ -1105,10 +1095,9 @@ namespace MobileGL::MG_Record { const auto bandInfo = [&](const ReadbackBand& band, Uint64 bandBytes) { MG_Pipe::MGPReadbackInfo info{}; info.Res = MG_Pipe::kMGPipeNullHandle; // "the bound read surface answers" - info.Box = MG_Pipe::MGPBox{ReadbackBandOrigin(x, band.FirstColumn), - ReadbackBandOrigin(y, band.FirstRow), 0, - static_cast(band.Columns), - static_cast(band.Rows), 1}; + info.Box = + MG_Pipe::MGPBox{ReadbackBandOrigin(x, band.FirstColumn), ReadbackBandOrigin(y, band.FirstRow), 0, + static_cast(band.Columns), static_cast(band.Rows), 1}; info.Format = static_cast(format); info.Type = static_cast(type); info.Target = 0; @@ -1132,21 +1121,21 @@ namespace MobileGL::MG_Record { if (!MG_Pipe::MGPipeHandleIsNull(target)) { MarkReadPixelsPackBuffer(); const Uint32 swapGroup = ReadbackSwapGroup(type, pack); - ForEachReadbackBand(static_cast(width), static_cast(height), plan, - [&](const ReadbackBand& band) { - MG_Pipe::MGPReadbackToBuffer record{}; - record.Src = bandInfo(band, TightReadbackBytes(static_cast(band.Columns), - static_cast(band.Rows), - format, type)); - record.Dst = target; - record.DstBase = pboOffset + layout.Start + band.FirstRow * layout.RowStride + - band.FirstColumn * bytesPerPixel; - record.RowStride = layout.RowStride; - record.ImageStride = 0; - record.SwapGroup = swapGroup; - session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixelsToBuffer, &record, - sizeof(record), nullptr, 0, nullptr, 0, nullptr); - }); + ForEachReadbackBand( + static_cast(width), static_cast(height), plan, [&](const ReadbackBand& band) { + MG_Pipe::MGPReadbackToBuffer record{}; + record.Src = + bandInfo(band, TightReadbackBytes(static_cast(band.Columns), + static_cast(band.Rows), format, type)); + record.Dst = target; + record.DstBase = pboOffset + layout.Start + band.FirstRow * layout.RowStride + + band.FirstColumn * bytesPerPixel; + record.RowStride = layout.RowStride; + record.ImageStride = 0; + record.SwapGroup = swapGroup; + session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixelsToBuffer, &record, sizeof(record), + nullptr, 0, nullptr, 0, nullptr); + }); return; } // P15: the validate point answered "pack buffer, fire-and-forget" and the reply @@ -1166,72 +1155,67 @@ namespace MobileGL::MG_Record { // this returns: R-11's rule one level out, nothing here outlives the call. Vector bounce; Bool pboSynced = false; - ForEachReadbackBand(static_cast(width), static_cast(height), plan, - [&](const ReadbackBand& band) { - // ONE tight-size function for production AND the control (M3 / codex 10a): the - // number the server allocates and the number the test asserts come from the - // same body, per band now. - const Uint64 bandBytes = TightReadbackBytes(static_cast(band.Columns), - static_cast(band.Rows), - format, type); - MG_Pipe::MGPReadbackInfo info = bandInfo(band, bandBytes); + ForEachReadbackBand( + static_cast(width), static_cast(height), plan, [&](const ReadbackBand& band) { + // ONE tight-size function for production AND the control (M3 / codex 10a): the + // number the server allocates and the number the test asserts come from the + // same body, per band now. + const Uint64 bandBytes = TightReadbackBytes(static_cast(band.Columns), + static_cast(band.Rows), format, type); + MG_Pipe::MGPReadbackInfo info = bandInfo(band, bandBytes); - // CHECKED BEFORE THE EMISSION, not after the answer (ID-47), through S1's - // helper, for every record. The plan makes it true by construction; it stays - // because it is the one check that names the read at the call site if the plan - // and the pool ever disagree, where the server's PH-3 refusal would only answer - // ERROR on the apply thread. - session.RequireReadPixelsReplyFits(static_cast(band.Columns), - static_cast(band.Rows), - static_cast(format), - static_cast(type), bandBytes); + // CHECKED BEFORE THE EMISSION, not after the answer (ID-47), through S1's + // helper, for every record. The plan makes it true by construction; it stays + // because it is the one check that names the read at the call site if the plan + // and the pool ever disagree, where the server's PH-3 refusal would only answer + // ERROR on the apply thread. + session.RequireReadPixelsReplyFits(static_cast(band.Columns), + static_cast(band.Rows), static_cast(format), + static_cast(type), bandBytes); - Int32 status = 0; - Uint64 replySize = 0; - if (direct) { - Uint8* at = pixels == nullptr - ? nullptr - : static_cast(pixels) + - (band.FirstRow * static_cast(width) + band.FirstColumn) * - bytesPerPixel; - session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixels, &info, sizeof(info), - nullptr, 0, at, bandBytes, &status, &replySize); - // M2 / codex 11: an OK reply that arrived short, or a DECLINE/ERROR, must not - // be handed back as pixels. The Fatal aborts before the application reads the - // buffer, so the bytes EmitAndWait already copied are never observed. + Int32 status = 0; + Uint64 replySize = 0; + if (direct) { + Uint8* at = + pixels == nullptr + ? nullptr + : static_cast(pixels) + + (band.FirstRow * static_cast(width) + band.FirstColumn) * bytesPerPixel; + session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixels, &info, sizeof(info), nullptr, 0, at, + bandBytes, &status, &replySize); + // M2 / codex 11: an OK reply that arrived short, or a DECLINE/ERROR, must not + // be handed back as pixels. The Fatal aborts before the application reads the + // buffer, so the bytes EmitAndWait already copied are never observed. + if (!RequireReadbackReplyComplete(status, replySize, bandBytes)) return; + ApplyReadbackByteSwap(at, bandBytes, type, pack); + return; + } + + if (bounce.size() < bandBytes) bounce.resize(static_cast(bandBytes)); + session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixels, &info, sizeof(info), nullptr, 0, bounce.data(), + bandBytes, &status, &replySize); + // BEFORE THE SCATTER, so a short or non-OK reply never reaches the application's + // pointer at all (the bounce is the only thing that held the partial bytes). if (!RequireReadbackReplyComplete(status, replySize, bandBytes)) return; - ApplyReadbackByteSwap(at, bandBytes, type, pack); - return; - } - - if (bounce.size() < bandBytes) bounce.resize(static_cast(bandBytes)); - session.EmitAndWait(MG_Pipe::MGPWireOp::ReadPixels, &info, sizeof(info), nullptr, - 0, bounce.data(), bandBytes, &status, &replySize); - // BEFORE THE SCATTER, so a short or non-OK reply never reaches the application's - // pointer at all (the bounce is the only thing that held the partial bytes). - if (!RequireReadbackReplyComplete(status, replySize, bandBytes)) return; - ApplyReadbackByteSwap(bounce.data(), bandBytes, type, pack); - if (pbo) { - // pixels is an offset, never a host pointer. Upload only the requested - // rows so PACK padding and untouched bytes survive; the offsets are the - // overflow-checked layout's, the same numbers the range check accepted. - if (!pboSynced) { - pbo->SyncGpuWrites(); - pboSynced = true; + ApplyReadbackByteSwap(bounce.data(), bandBytes, type, pack); + if (pbo) { + // pixels is an offset, never a host pointer. Upload only the requested + // rows so PACK padding and untouched bytes survive; the offsets are the + // overflow-checked layout's, the same numbers the range check accepted. + if (!pboSynced) { + pbo->SyncGpuWrites(); + pboSynced = true; + } + const Uint64 bandRowBytes = band.Columns * bytesPerPixel; + for (Uint64 row = 0; row < band.Rows; ++row) { + pbo->UploadSubData({bounce.data() + row * bandRowBytes, static_cast(bandRowBytes)}, + pboOffset + layout.Start + (band.FirstRow + row) * layout.RowStride + + band.FirstColumn * bytesPerPixel); + } + return; } - const Uint64 bandRowBytes = band.Columns * bytesPerPixel; - for (Uint64 row = 0; row < band.Rows; ++row) { - pbo->UploadSubData({bounce.data() + row * bandRowBytes, - static_cast(bandRowBytes)}, - pboOffset + layout.Start + - (band.FirstRow + row) * layout.RowStride + - band.FirstColumn * bytesPerPixel); - } - return; - } - ScatterReadbackBandIntoPackState(bounce.data(), pixels, width, band, bytesPerPixel, - pack); - }); + ScatterReadbackBandIntoPackState(bounce.data(), pixels, width, band, bytesPerPixel, pack); + }); } // ---- Image bind, compute, barriers, copy-image, storage-block binding ---- @@ -1242,11 +1226,9 @@ namespace MobileGL::MG_Record { // object the server has never seen. A null Res/Src/Dst/ShaderCso means none is // published yet. - MG_Pipe::MGPipeHandle PublishedTextureHandle( - const SharedPtr& texture) { + MG_Pipe::MGPipeHandle PublishedTextureHandle(const SharedPtr& texture) { if (!texture) return MG_Pipe::kMGPipeNullHandle; - return MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Texture, - texture->GetLifetimeId()); + return MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Texture, texture->GetLifetimeId()); } // glBindImageTexture. Emitted AT THE CALL, after the frontend has written the unit's @@ -1261,8 +1243,8 @@ namespace MobileGL::MG_Record { // A bind starts no shader and reads no buffer; the dispatch that later reads this image // is the verb that carries both hooks, and running them here as well would push the // same maps twice per dispatch and inflate b1's per-row counters. - void EmitBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, - GLint layer, GLenum access, GLenum format) { + void EmitBindImageTexture(GLuint unit, GLuint texture, GLint level, GLboolean layered, GLint layer, + GLenum access, GLenum format) { VerbChannel session = ChannelFor("BindImageTexture"); MG_Pipe::MGPImageBind record{}; @@ -1286,8 +1268,8 @@ namespace MobileGL::MG_Record { record.Access = static_cast(access); record.Format = static_cast(format); record.Layered = layered != GL_FALSE ? 1 : 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::BindShaderImage, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::BindShaderImage, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } // glDispatchCompute. THE HOOK ORDER IS b1's AND IS INHERITED FROM THE CLASS-C STUB @@ -1310,8 +1292,8 @@ namespace MobileGL::MG_Record { record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle; record.IndirectOffset = 0; record.IsIndirect = 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitDispatchComputeIndirect(GLintptr indirect) { @@ -1328,16 +1310,15 @@ namespace MobileGL::MG_Record { record.IndirectBuffer = MG_Pipe::kMGPipeNullHandle; if (MG_State::pGLContext != nullptr) { const auto& bound = - MG_State::pGLContext - ->GetBufferBindingSlot(::MobileGL::BufferTarget::DispatchIndirect) + MG_State::pGLContext->GetBufferBindingSlot(::MobileGL::BufferTarget::DispatchIndirect) .GetBoundObject(); if (bound) { - record.IndirectBuffer = MG_Pipe::MGPipeSlots().FindByLifetimeId( - MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId()); + record.IndirectBuffer = + MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Buffer, bound->GetLifetimeId()); } } - session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::LaunchGrid, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } // glMemoryBarrier / glMemoryBarrierByRegion. The bits cross VERBATIM: the frontend has @@ -1354,8 +1335,8 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPMemoryBarrier record{}; record.Bits = static_cast(barriers); record.ByRegion = 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr, - 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitMemoryBarrierByRegion(GLbitfield barriers) { @@ -1363,16 +1344,15 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPMemoryBarrier record{}; record.Bits = static_cast(barriers); record.ByRegion = 1; - session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr, - 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::MemoryBarrier, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } // glCopyImageSubData -> resource_copy_region (53), which P5b rules is glCopyImageSubData // ONLY (contract §6.4; the framebuffer-sourced copies are f1's row 76). - void EmitCopyImageSubData(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, - GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, - const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, - GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, + void EmitCopyImageSubData(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, + GLint srcX, GLint srcY, GLint srcZ, const MG_Backend::CopyImageEndpoint& dst, + GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth) { VerbChannel session = ChannelFor("CopyImageSubData"); @@ -1380,9 +1360,9 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPCopyRegion record{}; const auto handle = [](const MG_Backend::CopyImageEndpoint& endpoint) { return endpoint.IsRenderbuffer() - ? MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Renderbuffer, - endpoint.Renderbuffer->GetLifetimeId()) - : PublishedTextureHandle(endpoint.Texture); + ? MG_Pipe::MGPipeSlots().FindByLifetimeId(MG_Pipe::MGPipeKind::Renderbuffer, + endpoint.Renderbuffer->GetLifetimeId()) + : PublishedTextureHandle(endpoint.Texture); }; record.Src = handle(src); record.Dst = handle(dst); @@ -1400,22 +1380,24 @@ namespace MobileGL::MG_Record { record.DstLevel = static_cast(dstLevel); // SrcBox is {srcX, srcY, srcZ, w, h, d}: the source origin AND the extent, which is // one extent for both endpoints (GL spells the copy's size once). - record.SrcBox = MG_Pipe::MGPBox{srcX, srcY, srcZ, static_cast(srcWidth), + record.SrcBox = MG_Pipe::MGPBox{srcX, + srcY, + srcZ, + static_cast(srcWidth), static_cast(srcHeight), static_cast(srcDepth)}; record.DstX = dstX; record.DstY = dstY; record.DstZ = dstZ; - session.EmitAndWait(MG_Pipe::MGPWireOp::ResourceCopyRegion, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::ResourceCopyRegion, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } // glShaderStorageBlockBinding -> set_storage_block_binding (75), the ONE content-carrying // row P5b adds. The block is NAMED, not indexed, because the application's index is the // frontend interface-query enumeration's and no backend shares that index space // (GLFunctionsTable::ShaderStorageBlockBinding) - the name is the one coordinate all three agree on. - void EmitShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, - GLuint storageBlockBinding) { + void EmitShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding) { VerbChannel session = ChannelFor("ShaderStorageBlockBinding"); // The backend slot's own first line (ShaderStorageBlockBinding, DirectGLESCopy.cpp), kept // here so a null name never becomes a zero-size blob - which rule A forbids spelling at all. @@ -1444,8 +1426,8 @@ namespace MobileGL::MG_Record { // not NUL, so the two sides agree on where the name ends. const Uint64 nameBytes = static_cast(std::strlen(storageBlockName)) + 1ull; record.Name = session.StageBytes(storageBlockName, nameBytes); - session.EmitAndWait(MG_Pipe::MGPWireOp::SetStorageBlockBinding, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::SetStorageBlockBinding, &record, sizeof(record), nullptr, 0, + nullptr, 0, nullptr); } // ---- Transform-feedback spans, XFB object bind, patch parameter ---- @@ -1483,8 +1465,8 @@ namespace MobileGL::MG_Record { record.Targets[i] = {MG_Pipe::MGPipeResourceTrackerInstance().Find(*buffer), start, end > start ? end - start : 0}; } - session.EmitAndWait(MG_Pipe::MGPWireOp::BeginStreamOutput, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::BeginStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitEndTransformFeedback() { @@ -1503,8 +1485,8 @@ namespace MobileGL::MG_Record { record.CapturedVertices = context.GetTransformFeedbackCapturedVertices(); record.PrimitivesWritten = context.GetTransformFeedbackPrimitiveCounter(); record.PrimitiveMode = static_cast(context.GetTransformFeedbackPrimitiveMode()); - session.EmitAndWait(MG_Pipe::MGPWireOp::EndStreamOutput, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::EndStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitPauseTransformFeedback() { @@ -1516,8 +1498,8 @@ namespace MobileGL::MG_Record { // sink runs - not to carry anything. MG_Pipe::MGPStreamOutputControl record{}; record.Reserved = 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::PauseStreamOutput, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::PauseStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitResumeTransformFeedback() { @@ -1526,8 +1508,8 @@ namespace MobileGL::MG_Record { MG_Pipe::MGPStreamOutputControl record{}; record.Reserved = 0; - session.EmitAndWait(MG_Pipe::MGPWireOp::ResumeStreamOutput, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::ResumeStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitBindTransformFeedback(GLuint name) { @@ -1545,8 +1527,8 @@ namespace MobileGL::MG_Record { // recycling a name. Read AFTER GLContext::BindTransformFeedbackObject at the call // site, so it is the id of the object being bound and not of the previous one. record.LifetimeId = MG_State::pGLContext->GetBoundTransformFeedbackLifetimeId(); - session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitPatchParameteri(GLenum pname, GLint value) { @@ -1565,8 +1547,8 @@ namespace MobileGL::MG_Record { // duplicate: it is the applier's working-block copy, this is the driver push Espryt // does AT THE CALL (PatchParameteri, DirectGLESCopy.cpp), and both pushes happen today on the // monolith path too (CONTRACT-P5B.md §2 t2). - session.EmitAndWait(MG_Pipe::MGPWireOp::PatchParameter, &record, sizeof(record), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::PatchParameter, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); } void EmitDeleteTransformFeedback(GLuint name) { @@ -1574,12 +1556,12 @@ namespace MobileGL::MG_Record { BeforeReadOnlyVerb(); const auto& context = *MG_State::pGLContext; const MG_Pipe::MGPStreamOutputBind object{name, 0, context.GetTransformFeedbackLifetimeId(name)}; - session.EmitAndWait(MG_Pipe::MGPWireOp::DeleteStreamOutput, &object, sizeof(object), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::DeleteStreamOutput, &object, sizeof(object), nullptr, 0, nullptr, 0, + nullptr); if (context.GetBoundTransformFeedbackName() == name) { const MG_Pipe::MGPStreamOutputBind fallback{0, 0, context.GetTransformFeedbackLifetimeId(0)}; - session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &fallback, sizeof(fallback), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::BindStreamOutput, &fallback, sizeof(fallback), nullptr, 0, + nullptr, 0, nullptr); } } @@ -1602,14 +1584,19 @@ namespace MobileGL::MG_Record { static_cast(frameOrdinal)); } - - void ArmVerbControlKnobs() { ArmControlKnobs(); } + void ArmVerbControlKnobs() { + ArmControlKnobs(); + } #if MOBILEGL_PIPE_VERIFY - Bool MonolithPortApplyingForVerify() { return t_portApplying; } + Bool MonolithPortApplyingForVerify() { + return t_portApplying; + } #endif - void SetVerbSessionResolver(VerbSessionResolver resolver) { g_verbSessionResolver = resolver; } + void SetVerbSessionResolver(VerbSessionResolver resolver) { + g_verbSessionResolver = resolver; + } void AssignVerbEmitters(MG_Backend::GlobalBackendFunctionsTable& table) { table.GL.DeleteTransformFeedback = &EmitDeleteTransformFeedback; @@ -1726,7 +1713,9 @@ namespace MobileGL::MG_Record { g_monolithVerbPort = true; } - Bool MonolithVerbPortInstalled() { return g_monolithVerbPort; } + Bool MonolithVerbPortInstalled() { + return g_monolithVerbPort; + } Bool SetMonolithVerbPortRoutingForTesting(Bool viaPort) { const Bool previous = g_monolithVerbPort; @@ -1734,14 +1723,21 @@ namespace MobileGL::MG_Record { return previous; } - void SetDropClearEmissionForNegativeControl(Bool drop) { g_dropClearEmission = drop; } - Uint64 DroppedClearEmissions() { return g_droppedClearEmissions; } + void SetDropClearEmissionForNegativeControl(Bool drop) { + g_dropClearEmission = drop; + } + Uint64 DroppedClearEmissions() { + return g_droppedClearEmissions; + } - void SetDropDrawEmissionForNegativeControl(Bool drop) { g_dropDrawEmission = drop; } - Uint64 DroppedDrawEmissions() { return g_droppedDrawEmissions; } + void SetDropDrawEmissionForNegativeControl(Bool drop) { + g_dropDrawEmission = drop; + } + Uint64 DroppedDrawEmissions() { + return g_droppedDrawEmissions; + } - Bool ReadbackPackStateIsTightForTest(GLsizei width, Uint64 bytesPerPixel, - const PixelStoreParameters& pack) { + Bool ReadbackPackStateIsTightForTest(GLsizei width, Uint64 bytesPerPixel, const PixelStoreParameters& pack) { return ReadbackPackStateIsTight(width, bytesPerPixel, pack); } @@ -1783,18 +1779,14 @@ namespace MobileGL::MG_Record { // ID-49's scatter, for one band of a banded read (g5-readback). The whole-read scatter below // is this function over the single band {0, height, 0, width}, so there is one copy of // 8.4.4's arithmetic and the control drives it. - void ScatterReadbackBandIntoPackState(const void* band, void* destination, GLsizei width, - const ReadbackBand& where, Uint64 bytesPerPixel, - const PixelStoreParameters& pack) { - if (band == nullptr || destination == nullptr || width <= 0 || where.Rows == 0 || - where.Columns == 0) { + void ScatterReadbackBandIntoPackState(const void* band, void* destination, GLsizei width, const ReadbackBand& where, + Uint64 bytesPerPixel, const PixelStoreParameters& pack) { + if (band == nullptr || destination == nullptr || width <= 0 || where.Rows == 0 || where.Columns == 0) { return; } - const Uint64 rowPixels = - pack.RowLength > 0 ? static_cast(pack.RowLength) : static_cast(width); + const Uint64 rowPixels = pack.RowLength > 0 ? static_cast(pack.RowLength) : static_cast(width); const Uint64 alignment = pack.Alignment > 0 ? static_cast(pack.Alignment) : 1ull; - const Uint64 strideBytes = - ((rowPixels * bytesPerPixel + alignment - 1) / alignment) * alignment; + const Uint64 strideBytes = ((rowPixels * bytesPerPixel + alignment - 1) / alignment) * alignment; // m6: the client re-derives GL 4.6 8.4.4's pack layout, so it honours GL_PACK_ROW_LENGTH // VERBATIM - including the ill-formed 0 < ROW_LENGTH < width, where the row stride is // narrower than a written row and consecutive rows overlap. GL leaves that case to the @@ -1816,21 +1808,18 @@ namespace MobileGL::MG_Record { (static_cast(pack.SkipPixels) + where.FirstColumn) * bytesPerPixel; const auto* in = static_cast(band); for (Uint64 row = 0; row < where.Rows; ++row) { - std::memcpy(out + row * strideBytes, in + row * writtenPerRow, - static_cast(writtenPerRow)); + std::memcpy(out + row * strideBytes, in + row * writtenPerRow, static_cast(writtenPerRow)); } } // ID-49's scatter. Exported for the same reason as the refusal above: the control drives // THIS, which is what the emitter calls, rather than a second copy of 8.4.4's arithmetic. - void ScatterTightReadbackIntoPackState(const void* tight, void* destination, GLsizei width, - GLsizei height, Uint64 bytesPerPixel, - const PixelStoreParameters& pack) { + void ScatterTightReadbackIntoPackState(const void* tight, void* destination, GLsizei width, GLsizei height, + Uint64 bytesPerPixel, const PixelStoreParameters& pack) { if (tight == nullptr || destination == nullptr || width <= 0 || height <= 0) return; - ScatterReadbackBandIntoPackState( - tight, destination, width, - ReadbackBand{0, static_cast(height), 0, static_cast(width)}, - bytesPerPixel, pack); + ScatterReadbackBandIntoPackState(tight, destination, width, + ReadbackBand{0, static_cast(height), 0, static_cast(width)}, + bytesPerPixel, pack); } // ============================================================================= @@ -1840,16 +1829,19 @@ namespace MobileGL::MG_Record { Uint8 RemoteIndexSizeFor(GLenum indexType) { switch (indexType) { - 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; } } - MG_Pipe::MGPDrawInfo PlanDrawInfo(GLenum mode, Uint8 indexSize, GLsizei instanceCount, - GLuint baseInstance, Uint32 numDraws, - const RemoteDrawBindings& bindings) { + MG_Pipe::MGPDrawInfo PlanDrawInfo(GLenum mode, Uint8 indexSize, GLsizei instanceCount, GLuint baseInstance, + Uint32 numDraws, const RemoteDrawBindings& bindings) { MG_Pipe::MGPDrawInfo info{}; info.Mode = static_cast(mode); info.IndexSize = indexSize; @@ -1874,8 +1866,8 @@ namespace MobileGL::MG_Record { // The handle beside the call (rule D): the VAO's element buffer for an indexed draw, // the same handle set_index_buffer (32) carried at validate; null for arrays and for a // client index array. - info.IndexResource = (indexSize != 0 && bindings.ElementBufferBound) ? bindings.ElementBuffer - : MG_Pipe::kMGPipeNullHandle; + info.IndexResource = + (indexSize != 0 && bindings.ElementBufferBound) ? bindings.ElementBuffer : MG_Pipe::kMGPipeNullHandle; info.MinIndex = ~0u; // "unknown" (MGPipeTypes.h); the DrawRangeElements* callers fill it info.MaxIndex = ~0u; info.XfbCpuCapturedVertices = 0; @@ -1883,8 +1875,8 @@ namespace MobileGL::MG_Record { return info; } - Bool PlanDrawRange(const RemoteDrawBindings& bindings, Uint8 indexSize, const void* indicesOrFirst, - GLsizei count, GLint baseVertex, MG_Pipe::MGPDrawRange& out) { + Bool PlanDrawRange(const RemoteDrawBindings& bindings, Uint8 indexSize, const void* indicesOrFirst, GLsizei count, + GLint baseVertex, MG_Pipe::MGPDrawRange& out) { out = MG_Pipe::MGPDrawRange{}; out.Count = count > 0 ? static_cast(count) : 0u; if (indexSize == 0) { @@ -1915,8 +1907,8 @@ namespace MobileGL::MG_Record { } MG_Pipe::MGPDrawIndirect PlanDrawIndirect(const RemoteDrawBindings& bindings, const void* indirect, - GLsizei drawCount, GLsizei stride, - GLintptr parameterOffset, Bool hasParameterBuffer) { + GLsizei drawCount, GLsizei stride, GLintptr parameterOffset, + Bool hasParameterBuffer) { MG_Pipe::MGPDrawIndirect block{}; block.Buffer = bindings.DrawIndirectBuffer; block.ParameterBuffer = hasParameterBuffer ? bindings.ParameterBuffer : MG_Pipe::kMGPipeNullHandle; diff --git a/MobileGL/MG_Impl/Pipe/Verb/VerbPort.h b/MobileGL/MG_Impl/Pipe/Verb/VerbPort.h index ade7c4fd..447f96cf 100644 --- a/MobileGL/MG_Impl/Pipe/Verb/VerbPort.h +++ b/MobileGL/MG_Impl/Pipe/Verb/VerbPort.h @@ -26,9 +26,9 @@ namespace MobileGL::MG_Record { // it. Only MG_Remote implements it; a library without a transport never resolves one. class VerbSession { public: - virtual Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* varTail, Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, - Int32* statusOut, Uint64* replySizeOut) = 0; + virtual Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const void* varTail, + Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, Int32* statusOut, + Uint64* replySizeOut) = 0; virtual Uint64 EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const MG_Pipe::MGPipeVerbTail* tails, Uint32 tailCount, void* replyOut, Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) = 0; @@ -111,9 +111,8 @@ namespace MobileGL::MG_Record { // same reason as the refusal above: the control drives the function the emitter calls // rather than a second copy of GL 4.6 8.4.4's arithmetic. THE GAPS ARE NEVER WRITTEN - // they belong to the application - and that is what the control checks with a sentinel. - void ScatterTightReadbackIntoPackState(const void* tight, void* destination, GLsizei width, - GLsizei height, Uint64 bytesPerPixel, - const PixelStoreParameters& pack); + void ScatterTightReadbackIntoPackState(const void* tight, void* destination, GLsizei width, GLsizei height, + Uint64 bytesPerPixel, const PixelStoreParameters& pack); // A glReadPixels whose tight answer exceeds one reply slot is split into bands, each an // ordinary read_pixels record with its own box and tight DstSize, scattered under the @@ -136,14 +135,12 @@ namespace MobileGL::MG_Record { Bool PlanReadbackBands(Uint64 width, Uint64 height, Uint64 bytesPerPixel, Uint64 maxReplyBytes, ReadbackBandPlan& plan); template - void ForEachReadbackBand(Uint64 width, Uint64 height, const ReadbackBandPlan& plan, - Visit&& visit) { + void ForEachReadbackBand(Uint64 width, Uint64 height, const ReadbackBandPlan& plan, Visit&& visit) { if (plan.RowsPerBand == 0 || plan.ColumnsPerBand == 0) return; for (Uint64 row = 0; row < height; row += plan.RowsPerBand) { const Uint64 rows = plan.RowsPerBand < height - row ? plan.RowsPerBand : height - row; for (Uint64 column = 0; column < width; column += plan.ColumnsPerBand) { - const Uint64 columns = - plan.ColumnsPerBand < width - column ? plan.ColumnsPerBand : width - column; + const Uint64 columns = plan.ColumnsPerBand < width - column ? plan.ColumnsPerBand : width - column; visit(ReadbackBand{row, rows, column, columns}); } } @@ -153,17 +150,15 @@ namespace MobileGL::MG_Record { // where ScatterTightReadbackIntoPackState would have put those pixels of the whole // `width`-wide read. ScatterTightReadbackIntoPackState is this function over the single band // {0, height, 0, width}, so the whole-read arithmetic and the banded one cannot drift. - void ScatterReadbackBandIntoPackState(const void* band, void* destination, GLsizei width, - const ReadbackBand& where, Uint64 bytesPerPixel, - const PixelStoreParameters& pack); + void ScatterReadbackBandIntoPackState(const void* band, void* destination, GLsizei width, const ReadbackBand& where, + Uint64 bytesPerPixel, const PixelStoreParameters& pack); // ID-49. True when the destination layout IS the tight layout, which is the only condition // under which EmitReadPixels may read the reply straight into the application pointer and // skip the bounce. Exported because the FAST PATH and the SCATTER have to agree, and the // only honest way to state that is to drive both and compare - a case that tested either // alone would pass a predicate that said yes to a layout the scatter would have rearranged. - Bool ReadbackPackStateIsTightForTest(GLsizei width, Uint64 bytesPerPixel, - const PixelStoreParameters& pack); + Bool ReadbackPackStateIsTightForTest(GLsizei width, Uint64 bytesPerPixel, const PixelStoreParameters& pack); // M2 / codex 11. True exactly when the readback reply is OK and carries the read's own exact // extent (CONTRACT-P5 row 23). EmitReadPixels calls this and Fatals by name when it is false @@ -218,23 +213,22 @@ namespace MobileGL::MG_Record { // non-instanced entry point) and `baseInstance` its gl_BaseInstance value; the sink reads // "instanced" as InstanceCount != 1 || StartInstance != 0, so an instanced call with a // count of 1 and no base instance is dispatched as the plain draw it is equivalent to. - MG_Pipe::MGPDrawInfo PlanDrawInfo(GLenum mode, Uint8 indexSize, GLsizei instanceCount, - GLuint baseInstance, Uint32 numDraws, - const RemoteDrawBindings& bindings); + MG_Pipe::MGPDrawInfo PlanDrawInfo(GLenum mode, Uint8 indexSize, GLsizei instanceCount, GLuint baseInstance, + Uint32 numDraws, const RemoteDrawBindings& bindings); // One MGPDrawRange from one (first | indices, count, basevertex). Arrays: Start = first. // Indexed with an element buffer bound: Start = offset / IndexSize, and FALSE when the byte // offset is not a whole number of indices or does not fit the record's Uint32 Start - the // caller refuses by name rather than round. Indexed with no element buffer: Start = 0 and // the bytes are the client's (the emitter stages `count * IndexSize` of them). - Bool PlanDrawRange(const RemoteDrawBindings& bindings, Uint8 indexSize, const void* indicesOrFirst, - GLsizei count, GLint baseVertex, MG_Pipe::MGPDrawRange& out); + Bool PlanDrawRange(const RemoteDrawBindings& bindings, Uint8 indexSize, const void* indicesOrFirst, GLsizei count, + GLint baseVertex, MG_Pipe::MGPDrawRange& out); // The kDrawIsIndirect second tail: the bound GL_DRAW_INDIRECT_BUFFER handle, the call's // `indirect` byte offset, the stride and the draw count; for the *IndirectCount forms the // bound GL_PARAMETER_BUFFER handle and the byte offset the call spells as `drawcount`. MG_Pipe::MGPDrawIndirect PlanDrawIndirect(const RemoteDrawBindings& bindings, const void* indirect, - GLsizei drawCount, GLsizei stride, - GLintptr parameterOffset, Bool hasParameterBuffer); + GLsizei drawCount, GLsizei stride, GLintptr parameterOffset, + Bool hasParameterBuffer); } // namespace MobileGL::MG_Record diff --git a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h index f11dca10..736093c0 100644 --- a/MobileGL/MG_Impl/Pipe/VertexInputEmit.h +++ b/MobileGL/MG_Impl/Pipe/VertexInputEmit.h @@ -52,8 +52,7 @@ namespace MobileGL::MG_Pipe { // 32-slot bound is its invariant, not this one's, so a BindingIndex of 256 would wrap // to 0 and silently point every attribute at binding 0, and a negative Stride (the // frontend field is a signed int) would arrive as a ~4 GiB unsigned distance. - MOBILEGL_ASSERT(bindingIndex < 256u, - "MGPVertexAttribWire::BindingIndex is a Uint8 and cannot carry %u", + MOBILEGL_ASSERT(bindingIndex < 256u, "MGPVertexAttribWire::BindingIndex is a Uint8 and cannot carry %u", static_cast(bindingIndex)); MOBILEGL_ASSERT(attrib.Size >= 0 && attrib.Size <= 255, "MGPVertexAttribWire::Size is a Uint8 and cannot carry %d", attrib.Size); @@ -73,8 +72,8 @@ namespace MobileGL::MG_Pipe { // The ARB_vertex_attrib_binding view. Its initial Stride is 16, not 0 (GL 4.6 core table // 23.4), which is why the wire form keeps it signed and copies it verbatim. - inline MGPVertexBindingPointWire - MGPipeBuildVertexBindingPointWire(const MG_State::GLState::VertexBufferBindingPoint& point) { + inline MGPVertexBindingPointWire MGPipeBuildVertexBindingPointWire( + const MG_State::GLState::VertexBufferBindingPoint& point) { MGPVertexBindingPointWire wire{}; wire.Offset = static_cast(point.Offset); wire.Stride = static_cast(point.Stride); @@ -153,8 +152,8 @@ namespace MobileGL::MG_Pipe { Latch& latch = m_latch[slot]; Uint64 bytes = 0; - const Bool configMoved = !latch.Published || latch.ConfigVersion != configVersion || - latch.Gen != handle.Gen; + const Bool configMoved = + !latch.Published || latch.ConfigVersion != configVersion || latch.Gen != handle.Gen; if (configMoved) { BuildBlob(*vao); // P15: the version moves on every glBindVertexBuffer, but the record only names @@ -185,8 +184,10 @@ namespace MobileGL::MG_Pipe { // MGPipeVertexElementsRecord). The mirror below says what that is; where it does // not know, the next set goes out whatever it hashes to. auto& suppressor = MGPipeSetHashSuppressorInstance(); - if (memo.StoredBuffersValid) suppressor.Latch(MGPipeSuppressorSlot::SetVertexBuffers, memo.StoredBuffersHash); - else suppressor.Invalidate(MGPipeSuppressorSlot::SetVertexBuffers); + if (memo.StoredBuffersValid) + suppressor.Latch(MGPipeSuppressorSlot::SetVertexBuffers, memo.StoredBuffersHash); + else + suppressor.Invalidate(MGPipeSuppressorSlot::SetVertexBuffers); m_serverIndexKnown = memo.StoredIndexValid; m_serverIndexRes = memo.StoredIndexRes; } @@ -203,7 +204,7 @@ namespace MobileGL::MG_Pipe { // fetches at draw emission and calls this again with ownedClientBuffers. // Monolith keeps its existing backend-side client-array upload. Uint64 EmitVertexBuffers(GLContext& ctx, Uint32 baseInstance, - const Array* ownedClientBuffers = nullptr) { + const Array* ownedClientBuffers = nullptr) { const auto& vao = ctx.GetBoundVertexArray(); Uint32 count = 0; // The resolved set is a function of the VAO's configuration alone (buffers, strides, @@ -240,8 +241,8 @@ namespace MobileGL::MG_Pipe { entry.Res = lastHandle; } else { entry.Res = attrib.Buffer ? MGPipeSlots().AcquireHinted(MGPipeKind::Buffer, - attrib.Buffer->GetLifetimeId(), - attrib.Buffer->PipeHandleHint()) + attrib.Buffer->GetLifetimeId(), + attrib.Buffer->PipeHandleHint()) : kMGPipeNullHandle; if (attrib.Buffer) { lastBuffer = attrib.Buffer.get(); @@ -274,7 +275,8 @@ namespace MobileGL::MG_Pipe { memo->BuffersValid = memoizable && count <= kVaoMemoBuffers; memo->ConfigVersion = vao->GetConfigVersion(); memo->BufferCount = count; - for (SizeT i = 0; i < count && i < kVaoMemoBuffers; ++i) memo->Buffers[i] = m_entries[i]; + for (SizeT i = 0; i < count && i < kVaoMemoBuffers; ++i) + memo->Buffers[i] = m_entries[i]; } } @@ -493,7 +495,8 @@ namespace MobileGL::MG_Pipe { if (m_vaoMemo.empty() || (m_vaoMemoUsed + 1) * 2 > m_vaoMemo.size()) GrowVaoMemo(); const SizeT mask = m_vaoMemo.size() - 1; SizeT i = VaoMemoHome(lifetimeId, mask); - while (m_vaoMemo[i].LifetimeId != 0 && m_vaoMemo[i].LifetimeId != lifetimeId) i = (i + 1) & mask; + while (m_vaoMemo[i].LifetimeId != 0 && m_vaoMemo[i].LifetimeId != lifetimeId) + i = (i + 1) & mask; VaoMemo& memo = m_vaoMemo[i]; m_lastMemoIndex = i; if (memo.LifetimeId == 0) ++m_vaoMemoUsed; @@ -516,7 +519,8 @@ namespace MobileGL::MG_Pipe { for (const VaoMemo& entry : old) { if (entry.LifetimeId == 0) continue; SizeT i = VaoMemoHome(entry.LifetimeId, mask); - while (m_vaoMemo[i].LifetimeId != 0) i = (i + 1) & mask; + while (m_vaoMemo[i].LifetimeId != 0) + i = (i + 1) & mask; m_vaoMemo[i] = entry; ++m_vaoMemoUsed; } @@ -551,7 +555,6 @@ namespace MobileGL::MG_Pipe { } private: - struct Latch { // The PER-CONTEXT half: "has this emitter told THIS server about this handle's // configuration". Cleared by Reset() at every make-current. @@ -627,7 +630,7 @@ namespace MobileGL::MG_Pipe { Array m_attributes{}; Array m_bindingPoints{}; - Array + Array m_blob{}; Array m_entries{}; diff --git a/MobileGL/MG_Impl/SharedImageApi.cpp b/MobileGL/MG_Impl/SharedImageApi.cpp index 42b4f17d..dc28f8d7 100644 --- a/MobileGL/MG_Impl/SharedImageApi.cpp +++ b/MobileGL/MG_Impl/SharedImageApi.cpp @@ -70,9 +70,11 @@ MOBILEGL_EXPORT int mobilegl_shared_image_formats(uint32_t* formats, uint32_t ca if (total < capacity) formats[total] = format; ++total; }; - for (uint32_t format : kRgbFormats) put(format); + for (uint32_t format : kRgbFormats) + put(format); if (ActiveBackend(true) != nullptr && MobileGL::MG_Impl::EGLImpl::SharedImageYuvAvailable()) { - for (uint32_t format : kYuvFormats) put(format); + for (uint32_t format : kYuvFormats) + put(format); } return static_cast(total); } diff --git a/MobileGL/MG_Impl/SharedImageApi.h b/MobileGL/MG_Impl/SharedImageApi.h index 2b7447bc..08d2cda6 100644 --- a/MobileGL/MG_Impl/SharedImageApi.h +++ b/MobileGL/MG_Impl/SharedImageApi.h @@ -33,7 +33,8 @@ #include #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif /* 2: YUV images (NV12, P010), the plane-1 fields of mobilegl_shared_image, and @@ -55,73 +56,73 @@ extern "C" { #define MOBILEGL_SHARED_IMAGE_FOURCC_NV12 0x3231564Eu /* 'NV12' */ #define MOBILEGL_SHARED_IMAGE_FOURCC_P010 0x30313050u /* 'P010' */ -/* One allocated image. VERSIONED BY SIZE: the caller sets struct_size to the sizeof it was built - * against and the library writes no byte past it, so fields are only ever appended. */ -struct mobilegl_shared_image { - uint32_t struct_size; - uint32_t width; - uint32_t height; - uint32_t fourcc; - uint64_t id; /* the server's name for the image; pass to release */ - int32_t fd; /* the exported dma-buf, owned by the caller; -1 on failure */ - uint32_t stride; /* plane 0, bytes */ - uint32_t offset; /* plane 0, bytes */ - uint32_t reserved; /* zero */ - uint64_t modifier; /* DRM format modifier of the layout (DRM_FORMAT_MOD_INVALID today) */ - /* ABI 2: a YUV image's plane 1 (same fd); zero for the one-plane formats. */ - uint32_t plane1_stride; - uint32_t plane1_offset; -}; + /* One allocated image. VERSIONED BY SIZE: the caller sets struct_size to the sizeof it was built + * against and the library writes no byte past it, so fields are only ever appended. */ + struct mobilegl_shared_image { + uint32_t struct_size; + uint32_t width; + uint32_t height; + uint32_t fourcc; + uint64_t id; /* the server's name for the image; pass to release */ + int32_t fd; /* the exported dma-buf, owned by the caller; -1 on failure */ + uint32_t stride; /* plane 0, bytes */ + uint32_t offset; /* plane 0, bytes */ + uint32_t reserved; /* zero */ + uint64_t modifier; /* DRM format modifier of the layout (DRM_FORMAT_MOD_INVALID today) */ + /* ABI 2: a YUV image's plane 1 (same fd); zero for the one-plane formats. */ + uint32_t plane1_stride; + uint32_t plane1_offset; + }; -/* Where an imported two-plane buffer's planes are (ABI 2). */ -struct mobilegl_shared_image_planes { - uint32_t struct_size; - uint32_t plane_count; /* 2 */ - uint32_t offset[2]; - uint32_t pitch[2]; - uint64_t modifier; /* DRM_FORMAT_MOD_INVALID when none was given */ -}; + /* Where an imported two-plane buffer's planes are (ABI 2). */ + struct mobilegl_shared_image_planes { + uint32_t struct_size; + uint32_t plane_count; /* 2 */ + uint32_t offset[2]; + uint32_t pitch[2]; + uint64_t modifier; /* DRM_FORMAT_MOD_INVALID when none was given */ + }; -/* MOBILEGL_SHARED_IMAGE_ABI_VERSION of the library. */ -uint32_t mobilegl_shared_image_abi_version(void); + /* MOBILEGL_SHARED_IMAGE_ABI_VERSION of the library. */ + uint32_t mobilegl_shared_image_abi_version(void); -/* The fourccs images can be allocated in. Writes min(capacity, total) entries and returns the - * total (formats may be null when capacity is 0). */ -int mobilegl_shared_image_formats(uint32_t* formats, uint32_t capacity); + /* The fourccs images can be allocated in. Writes min(capacity, total) entries and returns the + * total (formats may be null when capacity is 0). */ + int mobilegl_shared_image_formats(uint32_t* formats, uint32_t capacity); -/* A new width x height image. Brings the process's MobileGL client session up if no EGL/GLX entry - * point has yet (exactly as the first eglGetDisplay would). */ -int mobilegl_shared_image_allocate(uint32_t width, uint32_t height, uint32_t fourcc, - struct mobilegl_shared_image* out); + /* A new width x height image. Brings the process's MobileGL client session up if no EGL/GLX entry + * point has yet (exactly as the first eglGetDisplay would). */ + int mobilegl_shared_image_allocate(uint32_t width, uint32_t height, uint32_t fourcc, + struct mobilegl_shared_image* out); -/* Names the image `fd` was exported from (the caller keeps `fd`), taking a reference to it. - * -EIO: not one of the server's images, or the size/format disagree. */ -int mobilegl_shared_image_import(int fd, uint32_t width, uint32_t height, uint32_t fourcc, uint64_t* out_id); + /* Names the image `fd` was exported from (the caller keeps `fd`), taking a reference to it. + * -EIO: not one of the server's images, or the size/format disagree. */ + int mobilegl_shared_image_import(int fd, uint32_t width, uint32_t height, uint32_t fourcc, uint64_t* out_id); -/* The same for a YUV fourcc whose two planes are in `fd` as `planes` says (ABI 2). A YUV buffer the - * server did not allocate is taken too, read through the server's CPU-copy fallback. */ -int mobilegl_shared_image_import_planes(int fd, uint32_t width, uint32_t height, uint32_t fourcc, - const struct mobilegl_shared_image_planes* planes, uint64_t* out_id); + /* The same for a YUV fourcc whose two planes are in `fd` as `planes` says (ABI 2). A YUV buffer the + * server did not allocate is taken too, read through the server's CPU-copy fallback. */ + int mobilegl_shared_image_import_planes(int fd, uint32_t width, uint32_t height, uint32_t fourcc, + const struct mobilegl_shared_image_planes* planes, uint64_t* out_id); -/* Drops this process's reference (an allocation's or an import's). */ -int mobilegl_shared_image_release(uint64_t id); + /* Drops this process's reference (an allocation's or an import's). */ + int mobilegl_shared_image_release(uint64_t id); -/* 1 when this process can be served: its session is up, or the server it is configured to dial - * answers (one bounded non-blocking connect, cached once true); 0 when not. Never brings the - * session up. A component that a system loader picked on MobileGL's behalf (the GBM backend) asks - * this first and declines, so the loader moves on to another implementation. Optional: a library - * without it is assumed available. */ -int mobilegl_server_available(void); + /* 1 when this process can be served: its session is up, or the server it is configured to dial + * answers (one bounded non-blocking connect, cached once true); 0 when not. Never brings the + * session up. A component that a system loader picked on MobileGL's behalf (the GBM backend) asks + * this first and declines, so the loader moves on to another implementation. Optional: a library + * without it is assumed available. */ + int mobilegl_server_available(void); -/* dlsym-friendly signatures. */ -typedef int (*PFN_mobilegl_server_available)(void); -typedef uint32_t (*PFN_mobilegl_shared_image_abi_version)(void); -typedef int (*PFN_mobilegl_shared_image_formats)(uint32_t*, uint32_t); -typedef int (*PFN_mobilegl_shared_image_allocate)(uint32_t, uint32_t, uint32_t, struct mobilegl_shared_image*); -typedef int (*PFN_mobilegl_shared_image_import)(int, uint32_t, uint32_t, uint32_t, uint64_t*); -typedef int (*PFN_mobilegl_shared_image_release)(uint64_t); -typedef int (*PFN_mobilegl_shared_image_import_planes)(int, uint32_t, uint32_t, uint32_t, - const struct mobilegl_shared_image_planes*, uint64_t*); + /* dlsym-friendly signatures. */ + typedef int (*PFN_mobilegl_server_available)(void); + typedef uint32_t (*PFN_mobilegl_shared_image_abi_version)(void); + typedef int (*PFN_mobilegl_shared_image_formats)(uint32_t*, uint32_t); + typedef int (*PFN_mobilegl_shared_image_allocate)(uint32_t, uint32_t, uint32_t, struct mobilegl_shared_image*); + typedef int (*PFN_mobilegl_shared_image_import)(int, uint32_t, uint32_t, uint32_t, uint64_t*); + typedef int (*PFN_mobilegl_shared_image_release)(uint64_t); + typedef int (*PFN_mobilegl_shared_image_import_planes)(int, uint32_t, uint32_t, uint32_t, + const struct mobilegl_shared_image_planes*, uint64_t*); #ifdef __cplusplus } diff --git a/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp index 7e96eea4..25aaa928 100644 --- a/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/WGLImpl/Exporting/Definitions.cpp @@ -28,8 +28,7 @@ extern "C" int WINAPI wglChoosePixelFormat(HDC hdc, CONST PIXELFORMATDESCRIPTOR* return WGL::ChoosePixelFormat(hdc, ppfd); } -extern "C" int WINAPI wglDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, - LPPIXELFORMATDESCRIPTOR ppfd) { +extern "C" int WINAPI wglDescribePixelFormat(HDC hdc, int iPixelFormat, UINT nBytes, LPPIXELFORMATDESCRIPTOR ppfd) { return WGL::DescribePixelFormat(hdc, iPixelFormat, nBytes, ppfd); } @@ -141,14 +140,12 @@ extern "C" BOOL WINAPI wglUseFontBitmapsW(HDC, DWORD, DWORD, DWORD) { return FALSE; } -extern "C" BOOL WINAPI wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, - LPGLYPHMETRICSFLOAT) { +extern "C" BOOL WINAPI wglUseFontOutlinesA(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT) { MGLOG_W_ONCE("wglUseFontOutlinesA is not supported"); return FALSE; } -extern "C" BOOL WINAPI wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, - LPGLYPHMETRICSFLOAT) { +extern "C" BOOL WINAPI wglUseFontOutlinesW(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT) { MGLOG_W_ONCE("wglUseFontOutlinesW is not supported"); return FALSE; } diff --git a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp index 59093cb9..11a495a3 100644 --- a/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp +++ b/MobileGL/MG_Impl/WGLImpl/WGLImpl.cpp @@ -197,8 +197,8 @@ namespace MobileGL::MG_Impl::WGLImpl { if (width == surface.Width && height == surface.Height) { return; } - if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, - static_cast(width), static_cast(height))) { + if (EGLImpl::ResizePlatformWindowSurface(surface.Display, surface.Surface, static_cast(width), + static_cast(height))) { surface.Width = width; surface.Height = height; } @@ -220,12 +220,9 @@ namespace MobileGL::MG_Impl::WGLImpl { } const EGLAttrib attribs[] = { - EGL_WIDTH, static_cast(width), - EGL_HEIGHT, static_cast(height), - EGL_NONE, + EGL_WIDTH, static_cast(width), EGL_HEIGHT, static_cast(height), EGL_NONE, }; - EGLSurface surface = - EGLImpl::CreatePlatformWindowSurface(context.Display, context.Config, hwnd, attribs); + EGLSurface surface = EGLImpl::CreatePlatformWindowSurface(context.Display, context.Config, hwnd, attribs); if (surface == EGL_NO_SURFACE) { MGLOG_E_ONCE("wgl: failed to create window surface for HWND %p (%ux%u)", hwnd, width, height); return nullptr; @@ -262,14 +259,22 @@ namespace MobileGL::MG_Impl::WGLImpl { HWND hwnd = WindowFromDC(hdc); const PixelFormatInfo& pixelFormat = PixelFormatForWindow(hwnd); const EGLint configAttribs[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, pixelFormat.AlphaBits, - EGL_DEPTH_SIZE, pixelFormat.DepthBits, - EGL_STENCIL_SIZE, pixelFormat.StencilBits, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT | EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + pixelFormat.AlphaBits, + EGL_DEPTH_SIZE, + pixelFormat.DepthBits, + EGL_STENCIL_SIZE, + pixelFormat.StencilBits, + EGL_SURFACE_TYPE, + EGL_WINDOW_BIT | EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, EGL_NONE, }; EGLConfig config = nullptr; @@ -345,15 +350,17 @@ namespace MobileGL::MG_Impl::WGLImpl { } } - if (major < 1 || (profileMask & ~(WGL_CONTEXT_CORE_PROFILE_BIT_ARB | - WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { + if (major < 1 || + (profileMask & ~(WGL_CONTEXT_CORE_PROFILE_BIT_ARB | WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB))) { SetLastError(profileMask ? ERROR_INVALID_PROFILE_ARB : ERROR_INVALID_VERSION_ARB); return nullptr; } Vector attribs = { - EGL_CONTEXT_MAJOR_VERSION, major, - EGL_CONTEXT_MINOR_VERSION, minor, + EGL_CONTEXT_MAJOR_VERSION, + major, + EGL_CONTEXT_MINOR_VERSION, + minor, }; const Bool wantsCompat = (profileMask & WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB) != 0; if (major > 3 || (major == 3 && minor >= 2) || profileMask != 0) { @@ -625,9 +632,12 @@ namespace MobileGL::MG_Impl::WGLImpl { // A legacy WGL context is a compatibility-profile context; MobileGL keys // its relaxed-semantics mode off the explicit compatibility bit. const EGLint attribs[] = { - EGL_CONTEXT_MAJOR_VERSION, 3, - EGL_CONTEXT_MINOR_VERSION, 3, - EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, EGL_NONE, }; return CreateContextFromEGLAttribs(hdc, nullptr, attribs); diff --git a/MobileGL/MG_IntegrationTest/Harness/DualBlockPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/DualBlockPeek.cpp index da999575..e7256d08 100644 --- a/MobileGL/MG_IntegrationTest/Harness/DualBlockPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/DualBlockPeek.cpp @@ -51,8 +51,12 @@ namespace MGITest { return true; } #else - bool PeekDualBlockState(DualBlockPeekState*) { return false; } - bool DualBlockDriveClientVerbBoundary() { return false; } + bool PeekDualBlockState(DualBlockPeekState*) { + return false; + } + bool DualBlockDriveClientVerbBoundary() { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.cpp b/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.cpp index e84052a0..7734b40e 100644 --- a/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.cpp @@ -79,8 +79,8 @@ namespace MGITest { } registered = true; } - return CreateWindowExW(0, kClassName, L"MobileGL Integration Test", WS_OVERLAPPEDWINDOW, - CW_USEDEFAULT, CW_USEDEFAULT, kSurfaceWidth, kSurfaceHeight, nullptr, nullptr, + return CreateWindowExW(0, kClassName, L"MobileGL Integration Test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, + CW_USEDEFAULT, kSurfaceWidth, kSurfaceHeight, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr); } #elif defined(__ANDROID__) @@ -99,8 +99,8 @@ namespace MGITest { constexpr int kMaxImages = 4; const media_status_t status = AImageReader_newWithUsage( kSurfaceWidth, kSurfaceHeight, AIMAGE_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, - kMaxImages, &g_imageReader); + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, kMaxImages, + &g_imageReader); if (status != AMEDIA_OK || g_imageReader == nullptr) return false; AImageReader_ImageListener listener = {nullptr, DrainImageReader}; @@ -241,9 +241,9 @@ namespace MGITest { EGLConfig config = nullptr; EGLint configCount = 0; if (eglChooseConfig(display, configAttribs, &config, 1, &configCount) != EGL_TRUE || configCount < 1) { - outReason = WithEglError(useWindowSurface - ? "eglChooseConfig found no window-capable RGBA8/D24 config" - : "eglChooseConfig found no pbuffer-capable RGBA8/D24 config"); + outReason = + WithEglError(useWindowSurface ? "eglChooseConfig found no window-capable RGBA8/D24 config" + : "eglChooseConfig found no pbuffer-capable RGBA8/D24 config"); return 4; } @@ -281,8 +281,8 @@ namespace MGITest { #if defined(__ANDROID__) DestroyImageReaderWindow(); #endif - outReason = WithEglError(useWindowSurface ? "eglCreateWindowSurface failed" - : "eglCreatePbufferSurface failed"); + outReason = + WithEglError(useWindowSurface ? "eglCreateWindowSurface failed" : "eglCreatePbufferSurface failed"); return 6; } // The step that brings the whole backend up (DirectVulkan creates its @@ -536,13 +536,12 @@ namespace MGITest { } ::testing::AssertionResult RegionIsMostly(const Image& image, int x0, int x1, int y0, int y1, - const char* expectedColor, double tolerance, - const std::string& when) { + const char* expectedColor, double tolerance, const std::string& when) { const RegionScan scan = ScanRegion(image, x0, x1, y0, y1, expectedColor); if (scan.total == 0) { return ::testing::AssertionFailure() - << when << ": region x[" << x0 << "," << x1 << "] y[" << y0 << "," << y1 - << "] is empty against a " << image.Width() << "x" << image.Height() << " readback"; + << when << ": region x[" << x0 << "," << x1 << "] y[" << y0 << "," << y1 << "] is empty against a " + << image.Width() << "x" << image.Height() << " readback"; } const double offendingFraction = static_cast(scan.offenders) / scan.total; if (offendingFraction <= tolerance) { @@ -599,8 +598,8 @@ namespace MGITest { // means something is different between the two attempts (a leaked // exclusive device, an environment the child did not have). m_skipReason = reason + (RemoteConnectRequested() - ? " - remote TCP bring-up failed" - : " - although an identical bring-up in a forked pre-flight child succeeded"); + ? " - remote TCP bring-up failed" + : " - although an identical bring-up in a forked pre-flight child succeeded"); return false; } diff --git a/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.h b/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.h index 93c34813..367b8eb3 100644 --- a/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.h +++ b/MobileGL/MG_IntegrationTest/Harness/HeadlessGL.h @@ -242,7 +242,6 @@ namespace MGITest { // do - they inset their regions away from primitive edges so exactness is // achievable). ::testing::AssertionResult RegionIsMostly(const Image& image, int x0, int x1, int y0, int y1, - const char* expectedColor, double tolerance, - const std::string& when); + const char* expectedColor, double tolerance, const std::string& when); } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/P4aFinalFixPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/P4aFinalFixPeek.cpp index 226ddd6d..6083b109 100644 --- a/MobileGL/MG_IntegrationTest/Harness/P4aFinalFixPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/P4aFinalFixPeek.cpp @@ -61,9 +61,15 @@ namespace MGITest { return true; } #else - bool PeekPipeTextureResourceRecord(unsigned, PipeTextureResourceRecordPeek*) { return false; } - bool PeekPipeStatsTextureRemintPulls(unsigned long long*) { return false; } - bool PeekPipeStatsTextureUploadEmissions(unsigned long long*) { return false; } + bool PeekPipeTextureResourceRecord(unsigned, PipeTextureResourceRecordPeek*) { + return false; + } + bool PeekPipeStatsTextureRemintPulls(unsigned long long*) { + return false; + } + bool PeekPipeStatsTextureUploadEmissions(unsigned long long*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/P4aSeamPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/P4aSeamPeek.cpp index 935e8686..890b9b4e 100644 --- a/MobileGL/MG_IntegrationTest/Harness/P4aSeamPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/P4aSeamPeek.cpp @@ -29,7 +29,9 @@ namespace MGITest { // on Magma no ES entry point was ever resolved and every member of g_GLESFuncs is null. // It is asked BEFORE SamplerSubsystemEnabled(), which is Espryt's own latch and must not // be resolved on a process whose backend is not Espryt. - bool EsprytIsRunning() { return MGB::g_GLESFuncs.glBindSampler != nullptr; } + bool EsprytIsRunning() { + return MGB::g_GLESFuncs.glBindSampler != nullptr; + } } // namespace bool PeekEsprytSamplerHandleArmIsLive(bool* outLive) { @@ -74,8 +76,8 @@ namespace MGITest { const MGP::MGPipeHandle cso = applier.BoundSamplerStates[unit]; out->CsoHandleSlot = static_cast(cso.Slot); out->CsoHandleGen = static_cast(cso.Gen); - out->UnitInsideWindow = unit >= applier.SamplerStateStart && - unit - applier.SamplerStateStart < applier.SamplerStateCount; + out->UnitInsideWindow = + unit >= applier.SamplerStateStart && unit - applier.SamplerStateStart < applier.SamplerStateCount; // The twin AT THE CSO HANDLE, asked of the same table Espryt asks (FindByHandle): a null // here with a live handle is the F-4 shape - a content-addressed handle looked up in a // table that only ever held identity-minted slots. @@ -87,8 +89,8 @@ namespace MGITest { // And the twin keyed on the frontend OBJECT, which is what the pre-handle program pass // used to mint and bind, so a scenario can say which of the two the driver holds. - const auto& object = MobileGL::MG_State::pGLContext->GetSamplerObject( - static_cast(glSamplerName)); + const auto& object = + MobileGL::MG_State::pGLContext->GetSamplerObject(static_cast(glSamplerName)); if (object) { // Resolved the client's way (the harness is the client): the object's identity handle, // if one was ever minted, then the twin at it. @@ -101,10 +103,18 @@ namespace MGITest { return true; } #else - bool PeekEsprytSamplerHandleArmIsLive(bool*) { return false; } - bool PeekEsprytFramebufferHandleArmIsLive(bool*) { return false; } - bool PeekPipeShaderImageWindow(PipeShaderImageWindowPeek*) { return false; } - bool PeekEsprytUnitSampler(unsigned, unsigned, EsprytUnitSamplerPeek*) { return false; } + bool PeekEsprytSamplerHandleArmIsLive(bool*) { + return false; + } + bool PeekEsprytFramebufferHandleArmIsLive(bool*) { + return false; + } + bool PeekPipeShaderImageWindow(PipeShaderImageWindowPeek*) { + return false; + } + bool PeekEsprytUnitSampler(unsigned, unsigned, EsprytUnitSamplerPeek*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp index 58c52d43..54ce9b77 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/PersistentMapPeek.cpp @@ -37,7 +37,8 @@ namespace MGITest { *pushedBytes = Stats::TotalBytes(Stats::ByteClass::PersistentMapPush); return true; #else - (void)acquisitions; (void)pushedBytes; + (void)acquisitions; + (void)pushedBytes; return false; #endif } @@ -93,8 +94,7 @@ namespace MGITest { if (outLive == nullptr) return false; MobileGL::MG_State::GLState::BufferObject* buffer = FrontendBuffer(bufferName); if (buffer == nullptr) return false; - *outLive = static_cast( - MobileGL::MG_Record::PersistentMapTracker::IsLivePersistentMap(*buffer)); + *outLive = static_cast(MobileGL::MG_Record::PersistentMapTracker::IsLivePersistentMap(*buffer)); return true; #else (void)bufferName; diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp index de8eae5d..fdfc2aaf 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.cpp @@ -38,8 +38,8 @@ namespace MGITest { // state would answer the question the scenario is asking with the input to it.) MobileGL::MG_State::GLState::ITextureObject* FrontendTexture(unsigned glTextureName) { if (!MobileGL::MG_State::pGLContext) return nullptr; - const auto& object = MobileGL::MG_State::pGLContext->GetTextureObject( - static_cast(glTextureName)); + const auto& object = + MobileGL::MG_State::pGLContext->GetTextureObject(static_cast(glTextureName)); return object ? object.get() : nullptr; } @@ -67,8 +67,7 @@ namespace MGITest { // to what an untouched texture already has). This is that decode, and nothing else in // this module may open-code it. int DepthStencilModeToGLEnum(MobileGL::Uint8 encoded) { - return encoded == MGP::kMGPipeDepthStencilModeStencil ? GL_STENCIL_INDEX - : GL_DEPTH_COMPONENT; + return encoded == MGP::kMGPipeDepthStencilModeStencil ? GL_STENCIL_INDEX : GL_DEPTH_COMPONENT; } // The GL_TEXTURE_BINDING_* query for a target, or 0 where this file has no answer. A @@ -76,26 +75,33 @@ namespace MGITest { // pname would leave the driver bound to this test's texture. int BindingQueryFor(unsigned glTarget) { switch (glTarget) { - case GL_TEXTURE_2D: return GL_TEXTURE_BINDING_2D; - default: return 0; + case GL_TEXTURE_2D: + return GL_TEXTURE_BINDING_2D; + default: + return 0; } } } // namespace - static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, - unsigned glTarget, EsprytAppliedTextureParamsPeek* out); + static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, unsigned glTarget, + EsprytAppliedTextureParamsPeek* out); static void CopyTextureParamsRecord(const MGP::MGPipeResourceRecord& record, unsigned slot, PipeTextureParamsRecordPeek& out) { out.Slot = slot; out.Gen = static_cast(record.Gen); out.ParamsSerial = static_cast(record.ParamsSerial); - for (int channel = 0; channel < 4; ++channel) out.Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]); + for (int channel = 0; channel < 4; ++channel) + out.Swizzle[channel] = SwizzleToGLEnum(record.Params.Swizzle[channel]); out.DepthStencilMode = DepthStencilModeToGLEnum(record.Params.DepthStencilMode); } #if MOBILEGL_BUILD_DISAGGREGATED - enum class ServerPeekKind { Record, Applied, View }; + enum class ServerPeekKind { + Record, + Applied, + View + }; static bool PeekOnApplyThread(unsigned glTextureName, unsigned target, ServerPeekKind kind, void* output) { auto* object = FrontendTexture(glTextureName); auto* session = MobileGL::MG_Remote::Client::ClientSession::Active(); @@ -107,11 +113,12 @@ namespace MGITest { void* Output; bool Result = false; } request{MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::Texture, object->GetLifetimeId()), - MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::SamplerViewCso, object->GetLifetimeId()), - target, kind, output}; + MGP::MGPipeSlots().FindByLifetimeId(MGP::MGPipeKind::SamplerViewCso, object->GetLifetimeId()), target, + kind, output}; if (MGP::MGPipeHandleIsNull(request.Texture)) return false; if (session->WaitForApplied(session->LastPublishedSeq(), 30000) != - MobileGL::MG_Remote::Transport::SessionWait::Reached) return false; + MobileGL::MG_Remote::Transport::SessionWait::Reached) + return false; const auto result = MobileGL::MG_Remote::Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void* opaque) -> MobileGLResult { auto& r = *static_cast(opaque); @@ -120,7 +127,8 @@ namespace MGITest { const auto& record = records[r.Texture.Slot]; if (!record.Live || record.Gen != r.Texture.Gen) return MOBILEGL_OK; if (r.Kind == ServerPeekKind::Record) { - CopyTextureParamsRecord(record, r.Texture.Slot, *static_cast(r.Output)); + CopyTextureParamsRecord(record, r.Texture.Slot, + *static_cast(r.Output)); r.Result = true; return MOBILEGL_OK; } @@ -128,14 +136,15 @@ namespace MGITest { if (!found || !*found) return MOBILEGL_OK; if (r.Kind == ServerPeekKind::Applied) r.Result = ReadAppliedTextureParams(found->get(), r.Target, - static_cast(r.Output)); + static_cast(r.Output)); else { *static_cast(r.Output) = !MGP::MGPipeHandleIsNull(r.View) && - MGB::SamplerViewImpl::FindSamplerViewForHandle(r.View) != nullptr; + MGB::SamplerViewImpl::FindSamplerViewForHandle(r.View) != nullptr; r.Result = true; } return MOBILEGL_OK; - }, &request); + }, + &request); return result == MOBILEGL_OK && request.Result; } #endif @@ -168,17 +177,16 @@ namespace MGITest { return ReadAppliedTextureParams(EsprytTwin(glTextureName), glTarget, out); } - static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, - unsigned glTarget, EsprytAppliedTextureParamsPeek* out) { + static bool ReadAppliedTextureParams(MGB::TextureImpl::BackendTextureObject* twin, unsigned glTarget, + EsprytAppliedTextureParamsPeek* out) { if (out == nullptr) return false; const int bindingQuery = BindingQueryFor(glTarget); if (bindingQuery == 0) return false; if (twin == nullptr) return false; const MobileGL::Uint backendId = twin->GetBackendTextureId(); if (backendId == 0) return false; - if (MGB::g_GLESFuncs.glGetTexParameteriv == nullptr || - MGB::g_GLESFuncs.glBindTexture == nullptr || MGB::g_GLESFuncs.glGetIntegerv == nullptr || - MGB::g_GLESFuncs.glGetError == nullptr) { + if (MGB::g_GLESFuncs.glGetTexParameteriv == nullptr || MGB::g_GLESFuncs.glBindTexture == nullptr || + MGB::g_GLESFuncs.glGetIntegerv == nullptr || MGB::g_GLESFuncs.glGetError == nullptr) { return false; } @@ -192,12 +200,11 @@ namespace MGITest { MGB::g_GLESFuncs.glBindTexture(static_cast(glTarget), backendId); out->BackendTextureId = static_cast(backendId); - static const GLenum kSwizzlePnames[4] = {GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, - GL_TEXTURE_SWIZZLE_B, GL_TEXTURE_SWIZZLE_A}; + static const GLenum kSwizzlePnames[4] = {GL_TEXTURE_SWIZZLE_R, GL_TEXTURE_SWIZZLE_G, GL_TEXTURE_SWIZZLE_B, + GL_TEXTURE_SWIZZLE_A}; for (int channel = 0; channel < 4; ++channel) { GLint value = 0; - MGB::g_GLESFuncs.glGetTexParameteriv(static_cast(glTarget), - kSwizzlePnames[channel], &value); + MGB::g_GLESFuncs.glGetTexParameteriv(static_cast(glTarget), kSwizzlePnames[channel], &value); out->Swizzle[channel] = static_cast(value); } @@ -207,18 +214,14 @@ namespace MGITest { // from this call's - Espryt drains it the same way at every one of its own sync sites // (DebugImpl::ErrorLopper), and this module's own GL errors are read from the FRONTEND // state (ScenarioTest::FirstGLError), which none of this touches. - while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) { - } + while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) {} GLint mode = 0; - MGB::g_GLESFuncs.glGetTexParameteriv(static_cast(glTarget), - GL_DEPTH_STENCIL_TEXTURE_MODE, &mode); + MGB::g_GLESFuncs.glGetTexParameteriv(static_cast(glTarget), GL_DEPTH_STENCIL_TEXTURE_MODE, &mode); out->DepthStencilModeIsReadable = MGB::g_GLESFuncs.glGetError() == GL_NO_ERROR; out->DepthStencilMode = static_cast(mode); - MGB::g_GLESFuncs.glBindTexture(static_cast(glTarget), - static_cast(previousBinding)); - while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) { - } + MGB::g_GLESFuncs.glBindTexture(static_cast(glTarget), static_cast(previousBinding)); + while (MGB::g_GLESFuncs.glGetError() != GL_NO_ERROR) {} return true; } @@ -253,12 +256,18 @@ namespace MGITest { return true; } #else - bool PeekPipeTextureParamsRecord(unsigned, PipeTextureParamsRecordPeek*) { return false; } + bool PeekPipeTextureParamsRecord(unsigned, PipeTextureParamsRecordPeek*) { + return false; + } bool PeekEsprytAppliedTextureParams(unsigned, unsigned, EsprytAppliedTextureParamsPeek*) { return false; } - bool PeekEsprytHasSamplerViewForTexture(unsigned, bool*) { return false; } - bool PeekPipeApplierRefusedNoConsumer(unsigned long long*) { return false; } + bool PeekEsprytHasSamplerViewForTexture(unsigned, bool*) { + return false; + } + bool PeekPipeApplierRefusedNoConsumer(unsigned long long*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.h b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.h index 233e0982..f441e32d 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.h +++ b/MobileGL/MG_IntegrationTest/Harness/PipeApplyPeek.h @@ -86,8 +86,7 @@ namespace MGITest { bool DepthStencilModeIsReadable; }; - bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget, - EsprytAppliedTextureParamsPeek* out); + bool PeekEsprytAppliedTextureParams(unsigned glTextureName, unsigned glTarget, EsprytAppliedTextureParamsPeek* out); // ---- and the claim that makes the two above mean anything ------------------------------ // diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeSlotPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/PipeSlotPeek.cpp index 6ec26666..89f07a88 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeSlotPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/PipeSlotPeek.cpp @@ -24,16 +24,22 @@ namespace MGITest { // it never looked at. The trailing return is the unreachable one the compiler needs. MobileGL::MG_Pipe::MGPipeKind Translate(PipeSlotKind kind) { switch (kind) { - case PipeSlotKind::Buffer: return MobileGL::MG_Pipe::MGPipeKind::Buffer; - case PipeSlotKind::VertexElementsCso: - return MobileGL::MG_Pipe::MGPipeKind::VertexElementsCso; - case PipeSlotKind::Texture: return MobileGL::MG_Pipe::MGPipeKind::Texture; - case PipeSlotKind::Renderbuffer: return MobileGL::MG_Pipe::MGPipeKind::Renderbuffer; - case PipeSlotKind::Framebuffer: return MobileGL::MG_Pipe::MGPipeKind::Framebuffer; - case PipeSlotKind::SamplerCso: return MobileGL::MG_Pipe::MGPipeKind::SamplerCso; - case PipeSlotKind::SamplerViewCso: - return MobileGL::MG_Pipe::MGPipeKind::SamplerViewCso; - case PipeSlotKind::ShaderCso: return MobileGL::MG_Pipe::MGPipeKind::ShaderCso; + case PipeSlotKind::Buffer: + return MobileGL::MG_Pipe::MGPipeKind::Buffer; + case PipeSlotKind::VertexElementsCso: + return MobileGL::MG_Pipe::MGPipeKind::VertexElementsCso; + case PipeSlotKind::Texture: + return MobileGL::MG_Pipe::MGPipeKind::Texture; + case PipeSlotKind::Renderbuffer: + return MobileGL::MG_Pipe::MGPipeKind::Renderbuffer; + case PipeSlotKind::Framebuffer: + return MobileGL::MG_Pipe::MGPipeKind::Framebuffer; + case PipeSlotKind::SamplerCso: + return MobileGL::MG_Pipe::MGPipeKind::SamplerCso; + case PipeSlotKind::SamplerViewCso: + return MobileGL::MG_Pipe::MGPipeKind::SamplerViewCso; + case PipeSlotKind::ShaderCso: + return MobileGL::MG_Pipe::MGPipeKind::ShaderCso; } return MobileGL::MG_Pipe::MGPipeKind::None; } @@ -70,11 +76,21 @@ namespace MGITest { return true; } #else - bool PeekPipeSlotLiveCount(PipeSlotKind, unsigned*) { return false; } - bool PeekPipeSlotHighWater(PipeSlotKind, unsigned*) { return false; } - bool PeekPipeCompositeSlotLiveCount(unsigned*) { return false; } - bool PeekPipeCompositeSlotHighWater(unsigned*) { return false; } - bool PeekPipeCompositeSlotBandBase(unsigned*) { return false; } + bool PeekPipeSlotLiveCount(PipeSlotKind, unsigned*) { + return false; + } + bool PeekPipeSlotHighWater(PipeSlotKind, unsigned*) { + return false; + } + bool PeekPipeCompositeSlotLiveCount(unsigned*) { + return false; + } + bool PeekPipeCompositeSlotHighWater(unsigned*) { + return false; + } + bool PeekPipeCompositeSlotBandBase(unsigned*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h b/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h index c7cd2439..25f19cbf 100644 --- a/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h +++ b/MobileGL/MG_IntegrationTest/Harness/PipeStatsWindow.h @@ -75,7 +75,9 @@ namespace MGITest::PipeStatsWindow { // The lane's private CLIENT log path, or empty when the lane configured none. In the pull // build this IS the whole log. - inline std::string LibraryLogPath() { return RoleLogPath("client"); } + inline std::string LibraryLogPath() { + return RoleLogPath("client"); + } // The server role's half, or EMPTY when this build has no separate server half. Empty rather // than "the same path again": every caller below concatenates the two, and a pull build that @@ -141,11 +143,12 @@ namespace MGITest::PipeStatsWindow { #if MOBILEGL_BUILD_DISAGGREGATED MGPipeSyncPeerLog(); #endif - return ReadFileSince(LibraryLogPath(), mark.client) - + ReadFileSince(ServerLibraryLogPath(), mark.server); + return ReadFileSince(LibraryLogPath(), mark.client) + ReadFileSince(ServerLibraryLogPath(), mark.server); } - inline std::string ReadLaneLog() { return ReadLaneLogSince(LogMark{}); } + inline std::string ReadLaneLog() { + return ReadLaneLogSince(LogMark{}); + } // THE SERVER HALF ALONE, for a claim whose whole content is WHICH ROLE said it. The // concatenation above answers "did anyone report this", which is the right question for a diff --git a/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h b/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h index 0176c5b2..faa3dc0d 100644 --- a/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h +++ b/MobileGL/MG_IntegrationTest/Harness/ScenarioFixture.h @@ -56,7 +56,11 @@ namespace MGITest { // QueryEnvQuirkOverride / IsTruthyValue): unset is Auto - device auto-detection or a // built-in default, i.e. a value only the implementation knows - a truthy value is // On, and anything else that IS set ("0", "false", "") is Off. - enum class AmbientQuirk { Auto, On, Off }; + enum class AmbientQuirk { + Auto, + On, + Off + }; inline AmbientQuirk AmbientQuirkFromEnvironment(const char* name) { const char* value = std::getenv(name); @@ -146,8 +150,7 @@ namespace MGITest { // and ImplementedVerbCount(), none of which a message edit can move. Registrations are // never deleted (gate G14); they skip, naming exactly which fact is not true. if (SplitLane::IsSplitLane()) { - if (const std::string splitSkip = SplitLane::SkipReasonForSplitOnlyAssertions(); - !splitSkip.empty()) { + if (const std::string splitSkip = SplitLane::SkipReasonForSplitOnlyAssertions(); !splitSkip.empty()) { GTEST_SKIP() << splitSkip; } // Armed. Take the wire's baseline, so the destructor can require that this case diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitLane.h b/MobileGL/MG_IntegrationTest/Harness/SplitLane.h index 35b7df60..ea80ceea 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitLane.h +++ b/MobileGL/MG_IntegrationTest/Harness/SplitLane.h @@ -56,13 +56,19 @@ namespace MGITest::SplitLane { return (value != nullptr) ? std::string(value) : std::string(); } - inline bool MarkerIsOne(const char* name) { return MarkerValue(name) == "1"; } + inline bool MarkerIsOne(const char* name) { + return MarkerValue(name) == "1"; + } // True in the DirectGLES.Split.* entries only. - inline bool IsSplitLane() { return MarkerIsOne("MGITEST_SPLIT_LANE"); } + inline bool IsSplitLane() { + return MarkerIsOne("MGITEST_SPLIT_LANE"); + } // True in exit gate E3(e)'s small-ring lane. - inline bool IsSmallRingLane() { return MarkerIsOne("MGITEST_SMALL_RING_LANE"); } + inline bool IsSmallRingLane() { + return MarkerIsOne("MGITEST_SMALL_RING_LANE"); + } // Empty when this case may assert; otherwise the reason to GTEST_SKIP() with. The reason is // spelled out rather than summarised because a skip line is the only thing anyone reads when @@ -79,6 +85,8 @@ namespace MGITest::SplitLane { } // "adopted", "emulated", or empty when the lane declared nothing. - inline std::string DeclaredPersistentMapArm() { return MarkerValue("MGITEST_PERSISTENT_MAP_ARM"); } + inline std::string DeclaredPersistentMapArm() { + return MarkerValue("MGITEST_PERSISTENT_MAP_ARM"); + } } // namespace MGITest::SplitLane diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp index f915f63f..13fa77c7 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.cpp @@ -49,7 +49,7 @@ namespace MGITest { std::this_thread::sleep_for(std::chrono::milliseconds(1)); g_holdActive.store(false, std::memory_order_release); } - } + } // namespace #endif bool ArmSplitApplyHoldForTesting() { #if defined(MGITEST_SPLIT_RUNTIME_PEEK_LIVE) @@ -111,7 +111,8 @@ namespace MGITest { } while (std::chrono::steady_clock::now() < until); return control->Progress.appliedSeq.load(std::memory_order_acquire) >= seq; #else - (void)seq; (void)timeoutMs; + (void)seq; + (void)timeoutMs; return false; #endif } @@ -134,9 +135,15 @@ namespace MGITest { state.peekAvailable = true; state.totalVerbSlots = MobileGL::MG_Remote::Client::kRemoteEmitSlotCount; switch (MobileGL::MG_Config::Transport) { - case TransportMode::Monolith: state.transportName = "monolith"; break; - case TransportMode::InProcess: state.transportName = "inproc"; break; - default: state.transportName = "non-monolith"; break; + case TransportMode::Monolith: + state.transportName = "monolith"; + break; + case TransportMode::InProcess: + state.transportName = "inproc"; + break; + default: + state.transportName = "non-monolith"; + break; } state.transportResolved = MobileGL::MG_Config::Transport != TransportMode::Monolith; state.dataArmIsRecord = MobileGL::MG_Config::DataArmIsRecord(); @@ -144,8 +151,7 @@ namespace MGITest { // Active() is c0's one deliberately non-aborting accessor: "does a session exist" has a // legitimate no. Everything below it is only reached through a live session, so nothing // here can trip one of c0's Fatal stubs. - MobileGL::MG_Remote::Client::ClientSession* session = - MobileGL::MG_Remote::Client::ClientSession::Active(); + MobileGL::MG_Remote::Client::ClientSession* session = MobileGL::MG_Remote::Client::ClientSession::Active(); state.sessionActive = session != nullptr; state.implementedVerbs = MobileGL::MG_Remote::Client::ImplementedVerbCount(); if (session != nullptr) { @@ -158,8 +164,7 @@ namespace MGITest { // MGPipeResourceOpsHaveSubDataResident reads (MG_Impl/Pipe/PipeFill.cpp), so a // green case here and a resident emission there cannot disagree about the bit. state.residentSubDataCap = - MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap( - MobileGL::MG_Pipe::kCapResidentSubData); + MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap(MobileGL::MG_Pipe::kCapResidentSubData); state.presentCredit = MobileGL::MG_Config::Ipc.PresentCredit; state.presentCreditWaits = session->PresentCreditWaits(); if (const auto* control = session->Control()) { @@ -178,8 +183,8 @@ namespace MGITest { state.cmdBytesWritten = encoder.CmdBytesWritten(); state.stageReclaimWaits = encoder.StageReclaimWaits(); state.replyPostings = session->ReplyPostings(); - state.packBufferReadbackMarks = MobileGL::MG_Record::ProducerMarkCount( - MobileGL::MG_Record::GpuWriteProducer::ReadPixelsPackBuffer); + state.packBufferReadbackMarks = + MobileGL::MG_Record::ProducerMarkCount(MobileGL::MG_Record::GpuWriteProducer::ReadPixelsPackBuffer); const auto fences = MobileGL::MG_Remote::Client::ReadFencePollCounters(); state.fenceLocalAnswers = fences.LocalAnswers; state.fenceEscalations = fences.Escalations; @@ -214,8 +219,7 @@ namespace MGITest { "G14) and skips rather than passing against the monolith path"; } if (state.implementedVerbs == 0) { - return "MG_Remote::Client::ImplementedVerbCount() is 0 of " + - std::to_string(state.totalVerbSlots) + + return "MG_Remote::Client::ImplementedVerbCount() is 0 of " + std::to_string(state.totalVerbSlots) + ": the emit table has no real emitter, so every verb this scenario issues would " "take the Fatal{UnmigratedVerb} arm or fall through. Package c1 owns it"; } diff --git a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h index 2484cdc7..b7768d11 100644 --- a/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h +++ b/MobileGL/MG_IntegrationTest/Harness/SplitRuntimePeek.h @@ -93,7 +93,6 @@ namespace MGITest { unsigned long long presentAckSerial = 0; unsigned long long presentCreditWaits = 0; - // ---- the wire producer's ledger, for exit gates E3(e) and R-10's proof obligation --- // // All four are 0 when there is no session, which is why every case that reads them has @@ -160,7 +159,6 @@ namespace MGITest { // GL command or change the production wait rule. bool WaitForSplitAppliedForTesting(unsigned long long seq, unsigned int timeoutMs = 5000); - // Empty when this process is a real split run that can be asserted about; otherwise the // reason to GTEST_SKIP() with, naming the first fact that is not true and the package that // owns it. diff --git a/MobileGL/MG_IntegrationTest/Harness/TextureEmitPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/TextureEmitPeek.cpp index 53feae02..fa933bf0 100644 --- a/MobileGL/MG_IntegrationTest/Harness/TextureEmitPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/TextureEmitPeek.cpp @@ -32,11 +32,15 @@ namespace MGITest { return true; } - const char* TextureEmitPeekSkipReason() { return ""; } + const char* TextureEmitPeekSkipReason() { + return ""; + } #else - bool PeekTextureEmitCounters(TextureEmitCountersPeek*) { return false; } + bool PeekTextureEmitCounters(TextureEmitCountersPeek*) { + return false; + } const char* TextureEmitPeekSkipReason() { #if defined(__ANDROID__) diff --git a/MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp b/MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp index 140cbaea..a616eecd 100644 --- a/MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/WireDeclinePeek.cpp @@ -32,7 +32,9 @@ namespace MGITest { return false; } #else - bool PeekWireDeclineCount(const char*, unsigned long long*) { return false; } + bool PeekWireDeclineCount(const char*, unsigned long long*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp b/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp index 55b3b15c..d0d5cdb3 100644 --- a/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp +++ b/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.cpp @@ -31,7 +31,9 @@ namespace MGITest { return true; } #else - bool PeekWireIndirectCounters(WireIndirectCounters*) { return false; } + bool PeekWireIndirectCounters(WireIndirectCounters*) { + return false; + } #endif } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.h b/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.h index 38f7191c..3efaefa3 100644 --- a/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.h +++ b/MobileGL/MG_IntegrationTest/Harness/WireIndirectPeek.h @@ -16,11 +16,11 @@ namespace MGITest { struct WireIndirectCounters { - unsigned long long nativeDraws = 0; // wire-indirect-native-draws (`wind`) - unsigned long long cpuExpansions = 0; // wire-indirect-cpu-expansions (`wixp`) - unsigned long long barriers = 0; // wire-indirect-barriers (`wibar`) - unsigned long long hostWaits = 0; // wire-host-waits (`whw`) - unsigned long long indirectWaits = 0; // wire-host-waits-indirect (`whwi`) + unsigned long long nativeDraws = 0; // wire-indirect-native-draws (`wind`) + unsigned long long cpuExpansions = 0; // wire-indirect-cpu-expansions (`wixp`) + unsigned long long barriers = 0; // wire-indirect-barriers (`wibar`) + unsigned long long hostWaits = 0; // wire-host-waits (`whw`) + unsigned long long indirectWaits = 0; // wire-host-waits-indirect (`whwi`) unsigned long long nativeDispatches = 0; // P8-SV: wire-indirect-native-dispatches (`wdsp`) unsigned long long resourceReadbacks = 0; // P13 W4a: resource-readbacks (client emissions) }; diff --git a/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h b/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h index 4b45560f..4d16a09b 100644 --- a/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h +++ b/MobileGL/MG_IntegrationTest/Harness/WireLedgerChecks.h @@ -86,31 +86,28 @@ namespace MGITest::WireLedger { // which satisfies `< cap` perfectly and is the exact shape of an emit table that // resolved the transport and then fell through to the driver. EXPECT_GT(state.maxRecordBytes, 0u) - << where << ": the largest record this session wrote is 0 bytes, so nothing crossed " - "SEG_CMD. R-10's proof obligation has no subject and the lane did not go " - "through the wire"; + << where + << ": the largest record this session wrote is 0 bytes, so nothing crossed " + "SEG_CMD. R-10's proof obligation has no subject and the lane did not go " + "through the wire"; EXPECT_LT(state.maxRecordBytes, state.maxRecordBytesCap) << where << ": R-10 - the largest record this session wrote is " << state.maxRecordBytes - << " bytes and RingProducer::MaxRecordBytes() is " << state.maxRecordBytesCap - << " (half of a " << (state.maxRecordBytesCap * 2) + << " bytes and RingProducer::MaxRecordBytes() is " << state.maxRecordBytesCap << " (half of a " + << (state.maxRecordBytesCap * 2) << " byte SEG_CMD, i.e. MOBILEGL_IPC_RING_MB). The content rows cut their blobs at " "the stage chunk budget, so nothing cuts this record: one at or above the cap is " "Fatal{RingOverrun} at the encoder, and a maximum that has climbed to it is the " "proof obligation failing. Report it to the integrator, who decides between a " "cut for that row and a bigger default ring"; - ::testing::Test::RecordProperty("max_record_bytes", - static_cast(state.maxRecordBytes)); - ::testing::Test::RecordProperty("max_record_bytes_cap", - static_cast(state.maxRecordBytesCap)); + ::testing::Test::RecordProperty("max_record_bytes", static_cast(state.maxRecordBytes)); + ::testing::Test::RecordProperty("max_record_bytes_cap", static_cast(state.maxRecordBytesCap)); } // E3(e)'s reading. The CALLER drives the workload; this only reads the result, so that the // thing being asserted about is the workload and not this header. - inline void ExpectSmallRingWrappedAtLeastOnce(const char* where, - unsigned long long bytesDriven) { + inline void ExpectSmallRingWrappedAtLeastOnce(const char* where, unsigned long long bytesDriven) { const SplitRuntimeState state = PeekSplitRuntime(); - ASSERT_TRUE(state.sessionActive) - << "the wire ledger was read in a process with no client session"; + ASSERT_TRUE(state.sessionActive) << "the wire ledger was read in a process with no client session"; const unsigned long long capacity = state.maxRecordBytesCap * 2; // MaxRecordBytes == cap/2 // THE WRAP FIRST, AND THE DENOMINATOR RIGHT BEHIND IT, both as EXPECT so that a red // carries both sentences. Order matters for what the failure SAYS: the thing this gate @@ -120,8 +117,7 @@ namespace MGITest::WireLedger { // is how the red-once line for this control was measured, and why it is written this // way round. EXPECT_GE(state.cmdWraps, 1u) - << where << ": exit gate E3(e) - " << bytesDriven << " bytes were written into a " - << capacity + << where << ": exit gate E3(e) - " << bytesDriven << " bytes were written into a " << capacity << " byte SEG_CMD and the producer's head NEVER WENT ROUND: no wrap, so this entry " "exercised exactly what the default lane exercises and the word SmallRing in its " "name asserts nothing. That is what the joint gate recorded as 'SmallRing entries " @@ -131,15 +127,15 @@ namespace MGITest::WireLedger { "proved to be load-bearing (R-16), by re-running this entry's own command with " "MOBILEGL_IPC_RING_MB=8 and nothing else changed"; EXPECT_GT(bytesDriven, capacity) - << where << ": and the reason is the denominator - the drive loop pushed only " - << bytesDriven << " bytes through a " << capacity + << where << ": and the reason is the denominator - the drive loop pushed only " << bytesDriven + << " bytes through a " << capacity << " byte SEG_CMD, which cannot reach a wrap boundary at all. The loop stops at " "kSmallRingLaneCmdByteTarget, which is sized for the 1 MiB ring this lane " "declares (MGL_ITEST_GLES_SPLIT_SMALL_RING_ENVIRONMENT); a larger ring needs a " "larger workload and is not what this lane is for"; - EXPECT_GE(state.stageReclaimWaits, 1u) - << where << ": exit gate E3(e) - producer NEVER WAITED for staging retirement; " - "lazy reclamation of already-retired bytes is not back-pressure"; + EXPECT_GE(state.stageReclaimWaits, 1u) << where + << ": exit gate E3(e) - producer NEVER WAITED for staging retirement; " + "lazy reclamation of already-retired bytes is not back-pressure"; ::testing::Test::RecordProperty("ring_wraps", static_cast(state.cmdWraps)); ::testing::Test::RecordProperty("ring_wrap_pads", static_cast(state.cmdWrapPads)); ::testing::Test::RecordProperty("ring_waits", static_cast(state.stageReclaimWaits)); @@ -147,6 +143,8 @@ namespace MGITest::WireLedger { } // Bytes the producer has written so far, or 0 outside a split process. - inline unsigned long long CmdBytesWritten() { return PeekSplitRuntime().cmdBytesWritten; } + inline unsigned long long CmdBytesWritten() { + return PeekSplitRuntime().cmdBytesWritten; + } } // namespace MGITest::WireLedger diff --git a/MobileGL/MG_IntegrationTest/Main.cpp b/MobileGL/MG_IntegrationTest/Main.cpp index 433a5041..47037089 100644 --- a/MobileGL/MG_IntegrationTest/Main.cpp +++ b/MobileGL/MG_IntegrationTest/Main.cpp @@ -37,12 +37,10 @@ namespace { constexpr const char* surfaceKind = "pbuffer"; #endif std::fprintf(stderr, " renderer: %s\n surface: %dx%d %s (headless, EGL_PLATFORM=%s)\n", - gl.RendererString().c_str(), gl.Width(), gl.Height(), - surfaceKind, + gl.RendererString().c_str(), gl.Width(), gl.Height(), surfaceKind, eglPlatform != nullptr ? eglPlatform : ""); } else if (MGITest::RequireGpu()) { - std::fprintf(stderr, - " FAILING every scenario (MOBILEGL_ITEST_REQUIRE_GPU is set): %s\n", + std::fprintf(stderr, " FAILING every scenario (MOBILEGL_ITEST_REQUIRE_GPU is set): %s\n", gl.SkipReason().c_str()); } else { std::fprintf(stderr, diff --git a/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp index 479683a3..bc5372b8 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/AdvertisedLimitsScenario.cpp @@ -87,14 +87,13 @@ namespace MGITest { GLint value = -424242; glGetIntegerv(bound.pname, &value); const unsigned int error = FirstGLError(); - EXPECT_EQ(error, GLenum(GL_NO_ERROR)) - << bound.name << " is not answerable: " << GLErrorName(error); + EXPECT_EQ(error, GLenum(GL_NO_ERROR)) << bound.name << " is not answerable: " << GLErrorName(error); if (error != GL_NO_ERROR) continue; EXPECT_GE(value, bound.minimum) - << bound.name << " = " << value << " is below the GL required minimum " - << bound.minimum << " (a negative or tiny value here is a uint32 limit that lost " - "its top bit on the way to a signed Int)"; + << bound.name << " = " << value << " is below the GL required minimum " << bound.minimum + << " (a negative or tiny value here is a uint32 limit that lost " + "its top bit on the way to a signed Int)"; EXPECT_LE(value, bound.ceiling) << bound.name << " = " << value << " exceeds the ceiling " << bound.ceiling << " this implementation is willing to promise - an application that allocates " @@ -180,8 +179,8 @@ namespace MGITest { glGetIntegerv(relation.combined, &combined); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << relation.stageName; EXPECT_LE(stage, combined) - << relation.stageName << " = " << stage << " exceeds " << relation.combinedName << " = " - << combined << "; GL 4.6 table 23.64 orders MAX_*_BUFFER_BINDINGS >= MAX_COMBINED_*_BLOCKS >= " + << relation.stageName << " = " << stage << " exceeds " << relation.combinedName << " = " << combined + << "; GL 4.6 table 23.64 orders MAX_*_BUFFER_BINDINGS >= MAX_COMBINED_*_BLOCKS >= " "every per-stage count, and a single-stage program may use its whole per-stage allowance"; } } @@ -218,13 +217,12 @@ namespace MGITest { ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << family.maxSizeName; ASSERT_GT(maxBindings, 0) << family.maxBindingsName; const GLint perBinding = maxSize / maxBindings; - EXPECT_GT(perBinding, 0) - << family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName << " (" - << maxBindings << ") is zero, and BindBufferRange rejects a zero size"; + EXPECT_GT(perBinding, 0) << family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName + << " (" << maxBindings << ") is zero, and BindBufferRange rejects a zero size"; EXPECT_EQ(perBinding % family.sizeGranularity, 0) - << family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName << " (" - << maxBindings << ") = " << perBinding << " is not a multiple of the " - << family.sizeGranularity << "-byte size granularity BindBufferRange requires for it"; + << family.maxSizeName << " (" << maxSize << ") / " << family.maxBindingsName << " (" << maxBindings + << ") = " << perBinding << " is not a multiple of the " << family.sizeGranularity + << "-byte size granularity BindBufferRange requires for it"; } } @@ -244,9 +242,9 @@ namespace MGITest { EXPECT_GT(blockSize, 0); // GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS is derived from the product of these two, // so their product has to stay representable. - EXPECT_LE(static_cast(blocks) * blockSize, - static_cast(2147483647)) - << "blocks(" << blocks << ") * blockSize(" << blockSize << ") overflows the GLint the " + EXPECT_LE(static_cast(blocks) * blockSize, static_cast(2147483647)) + << "blocks(" << blocks << ") * blockSize(" << blockSize + << ") overflows the GLint the " "derived component limits are computed in"; } @@ -281,8 +279,7 @@ namespace MGITest { {GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS", 128, 1 << 16}, {GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, "GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS", 128, 1 << 16}, {GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, "GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS", 16, 256}, - {GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, "GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS", 1024, - 1 << 20}, + {GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, "GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS", 1024, 1 << 20}, {GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, "GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS", 14, 256}, {GL_MAX_TESS_PATCH_COMPONENTS, "GL_MAX_TESS_PATCH_COMPONENTS", 120, 1 << 16}, {GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, "GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS", @@ -296,10 +293,10 @@ namespace MGITest { const unsigned int error = FirstGLError(); EXPECT_EQ(error, GLenum(GL_NO_ERROR)) << bound.name << " is not answerable: " << GLErrorName(error); if (error != GL_NO_ERROR) continue; - EXPECT_GE(value, bound.minimum) << bound.name << " = " << value << " is below the GL 4.5 minimum " - << bound.minimum; - EXPECT_LE(value, bound.ceiling) << bound.name << " = " << value << " exceeds the ceiling " - << bound.ceiling; + EXPECT_GE(value, bound.minimum) + << bound.name << " = " << value << " is below the GL 4.5 minimum " << bound.minimum; + EXPECT_LE(value, bound.ceiling) + << bound.name << " = " << value << " exceeds the ceiling " << bound.ceiling; } // ARB_cull_distance's pair. Zero is honest on a backend with no cull-distance route, @@ -380,7 +377,6 @@ namespace MGITest { EXPECT_GE(viewportDims[1], maxRenderbufferSize); } - // THE INDEXED AND PER-PROGRAM QUERIES THAT NAME FRONTEND STATE, pinned on both lanes. // // Both backends used to carry their own arms for GL_SHADER_STORAGE_BUFFER_* and diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp index ae58ce8e..df849e14 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ArbShaderObjectsScenario.cpp @@ -55,31 +55,32 @@ // GLhandleARB is GLuint everywhere but Apple, which this module does not build for. Resolved by // the linker straight into MobileGL, like every other gl* call here. -extern "C" { -GLuint glCreateShaderObjectARB(GLenum shaderType); -GLuint glCreateProgramObjectARB(void); -void glDeleteObjectARB(GLuint obj); -GLuint glGetHandleARB(GLenum pname); -void glDetachObjectARB(GLuint containerObj, GLuint attachedObj); -void glAttachObjectARB(GLuint containerObj, GLuint obj); -void glShaderSourceARB(GLuint shaderObj, GLsizei count, const char** string, const GLint* length); -void glCompileShaderARB(GLuint shaderObj); -void glLinkProgramARB(GLuint programObj); -void glUseProgramObjectARB(GLuint programObj); -void glValidateProgramARB(GLuint programObj); -void glUniform4fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); -void glGetObjectParameterivARB(GLuint obj, GLenum pname, GLint* params); -void glGetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params); -void glGetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* infoLog); -void glGetAttachedObjectsARB(GLuint containerObj, GLsizei maxCount, GLsizei* count, GLuint* obj); -GLint glGetUniformLocationARB(GLuint programObj, const char* name); -void glBindAttribLocationARB(GLuint programObj, GLuint index, const char* name); -GLint glGetAttribLocationARB(GLuint programObj, const char* name); -void glVertexAttrib4fARB(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); -void glDisableVertexAttribArrayARB(GLuint index); -void glGetVertexAttribfvARB(GLuint index, GLenum pname, GLfloat* params); -void glGetUniformfvARB(GLuint programObj, GLint location, GLfloat* params); -void glGetShaderSourceARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* source); +extern "C" +{ + GLuint glCreateShaderObjectARB(GLenum shaderType); + GLuint glCreateProgramObjectARB(void); + void glDeleteObjectARB(GLuint obj); + GLuint glGetHandleARB(GLenum pname); + void glDetachObjectARB(GLuint containerObj, GLuint attachedObj); + void glAttachObjectARB(GLuint containerObj, GLuint obj); + void glShaderSourceARB(GLuint shaderObj, GLsizei count, const char** string, const GLint* length); + void glCompileShaderARB(GLuint shaderObj); + void glLinkProgramARB(GLuint programObj); + void glUseProgramObjectARB(GLuint programObj); + void glValidateProgramARB(GLuint programObj); + void glUniform4fARB(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void glGetObjectParameterivARB(GLuint obj, GLenum pname, GLint* params); + void glGetObjectParameterfvARB(GLuint obj, GLenum pname, GLfloat* params); + void glGetInfoLogARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* infoLog); + void glGetAttachedObjectsARB(GLuint containerObj, GLsizei maxCount, GLsizei* count, GLuint* obj); + GLint glGetUniformLocationARB(GLuint programObj, const char* name); + void glBindAttribLocationARB(GLuint programObj, GLuint index, const char* name); + GLint glGetAttribLocationARB(GLuint programObj, const char* name); + void glVertexAttrib4fARB(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); + void glDisableVertexAttribArrayARB(GLuint index); + void glGetVertexAttribfvARB(GLuint index, GLenum pname, GLfloat* params); + void glGetUniformfvARB(GLuint programObj, GLint location, GLfloat* params); + void glGetShaderSourceARB(GLuint obj, GLsizei maxLength, GLsizei* length, char* source); } namespace MGITest { @@ -154,14 +155,13 @@ void main() { oColor = uColor; } const char* names[] = { "glDeleteObjectARB", "glGetHandleARB", "glDetachObjectARB", "glCreateShaderObjectARB", "glShaderSourceARB", "glCompileShaderARB", "glCreateProgramObjectARB", "glAttachObjectARB", - "glLinkProgramARB", "glUseProgramObjectARB", "glValidateProgramARB", "glUniform1fARB", - "glUniform2fARB", "glUniform3fARB", "glUniform4fARB", "glUniform1iARB", "glUniform2iARB", - "glUniform3iARB", "glUniform4iARB", "glUniform1fvARB", "glUniform2fvARB", "glUniform3fvARB", - "glUniform4fvARB", "glUniform1ivARB", "glUniform2ivARB", "glUniform3ivARB", "glUniform4ivARB", - "glUniformMatrix2fvARB", "glUniformMatrix3fvARB", "glUniformMatrix4fvARB", - "glGetObjectParameterfvARB", "glGetObjectParameterivARB", "glGetInfoLogARB", - "glGetAttachedObjectsARB", "glGetUniformLocationARB", "glGetActiveUniformARB", - "glGetUniformfvARB", "glGetUniformivARB", "glGetShaderSourceARB", + "glLinkProgramARB", "glUseProgramObjectARB", "glValidateProgramARB", "glUniform1fARB", "glUniform2fARB", + "glUniform3fARB", "glUniform4fARB", "glUniform1iARB", "glUniform2iARB", "glUniform3iARB", + "glUniform4iARB", "glUniform1fvARB", "glUniform2fvARB", "glUniform3fvARB", "glUniform4fvARB", + "glUniform1ivARB", "glUniform2ivARB", "glUniform3ivARB", "glUniform4ivARB", "glUniformMatrix2fvARB", + "glUniformMatrix3fvARB", "glUniformMatrix4fvARB", "glGetObjectParameterfvARB", + "glGetObjectParameterivARB", "glGetInfoLogARB", "glGetAttachedObjectsARB", "glGetUniformLocationARB", + "glGetActiveUniformARB", "glGetUniformfvARB", "glGetUniformivARB", "glGetShaderSourceARB", // GL_ARB_vertex_shader, advertised alongside. "glVertexAttrib1sARB", "glVertexAttrib1fARB", "glVertexAttrib1dARB", "glVertexAttrib4fARB", "glVertexAttrib4fvARB", "glVertexAttrib4NubARB", "glVertexAttrib4NubvARB", "glVertexAttrib4uivARB", @@ -226,7 +226,7 @@ void main() { oColor = uColor; } EXPECT_EQ(FirstGLError(), 0u); glDeleteObjectARB(coreShader); - glDeleteShader(arbShader); // attached to coreProgram: flagged, not yet gone + glDeleteShader(arbShader); // attached to coreProgram: flagged, not yet gone glDeleteObjectARB(arbProgram); glDeleteProgram(coreProgram); // takes arbShader with it EXPECT_EQ(glIsShader(coreShader), GL_FALSE); @@ -275,7 +275,8 @@ void main() { oColor = uColor; } glUniform4f(glGetUniformLocation(program, "uColor"), 1.0f, 0.0f, 0.0f, 1.0f); DrawInto(); const Image red = ReadPixels(kEdge, kEdge); - EXPECT_TRUE(RegionIsMostly(red, 0, kEdge - 1, 0, kEdge - 1, "red", 0.0, "relinked after detach + reattach")); + EXPECT_TRUE( + RegionIsMostly(red, 0, kEdge - 1, 0, kEdge - 1, "red", 0.0, "relinked after detach + reattach")); glUseProgram(0); glDeleteObjectARB(program); glDeleteObjectARB(vs); @@ -326,7 +327,8 @@ void main() { oColor = uColor; } glUniform4f(glGetUniformLocation(program, "uColor"), 0.0f, 0.0f, 1.0f, 1.0f); DrawInto(); const Image blue = ReadPixels(kEdge, kEdge); - EXPECT_TRUE(RegionIsMostly(blue, 0, kEdge - 1, 0, kEdge - 1, "blue", 0.0, "core uniform on an ARB program")); + EXPECT_TRUE( + RegionIsMostly(blue, 0, kEdge - 1, 0, kEdge - 1, "blue", 0.0, "core uniform on an ARB program")); glUseProgram(0); EXPECT_EQ(glGetHandleARB(MGITEST_PROGRAM_OBJECT_ARB), 0u); @@ -457,8 +459,9 @@ void main() { oColor = vColor; } std::vector freed; for (int round = 0; round < 4; ++round) { for (int i = 0; i < 8; ++i) { - const GLuint names[4] = {glCreateShaderObjectARB(GL_VERTEX_SHADER), glCreateShader(GL_FRAGMENT_SHADER), - glCreateProgramObjectARB(), glCreateProgram()}; + const GLuint names[4] = {glCreateShaderObjectARB(GL_VERTEX_SHADER), + glCreateShader(GL_FRAGMENT_SHADER), glCreateProgramObjectARB(), + glCreateProgram()}; for (GLuint name : names) { EXPECT_NE(name, 0u); EXPECT_TRUE(live.insert(name).second) << "name " << name << " handed out while live"; @@ -482,8 +485,10 @@ void main() { oColor = vColor; } } } } - for (GLuint name : live) EXPECT_TRUE(glIsShader(name) || glIsProgram(name)) << name; - for (GLuint name : live) glDeleteObjectARB(name); + for (GLuint name : live) + EXPECT_TRUE(glIsShader(name) || glIsProgram(name)) << name; + for (GLuint name : live) + glDeleteObjectARB(name); EXPECT_EQ(FirstGLError(), 0u); } @@ -505,7 +510,8 @@ void main() { oColor = vColor; } glGenProgramPipelines(1, &pipeline); glGenTransformFeedbacks(1, &xfb); ASSERT_EQ(FirstGLError(), 0u); - for (GLuint name : {buffer, texture, vao, fbo, rbo, sampler, query, pipeline, xfb}) EXPECT_NE(name, 0u); + for (GLuint name : {buffer, texture, vao, fbo, rbo, sampler, query, pipeline, xfb}) + EXPECT_NE(name, 0u); // Reserved, not yet objects. EXPECT_EQ(glIsBuffer(buffer), GL_FALSE); EXPECT_EQ(glIsTexture(texture), GL_FALSE); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/AsyncCompileScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/AsyncCompileScenario.cpp index 117b55e0..4e3a9fce 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/AsyncCompileScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/AsyncCompileScenario.cpp @@ -349,7 +349,8 @@ void main() { glDeleteProgram(program); glDeleteShader(vs); - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } @@ -386,8 +387,7 @@ void main() { ASSERT_NE(extensions, nullptr); const std::string extensionString(extensions); const bool inString = extensionString.find("GL_KHR_parallel_shader_compile") != std::string::npos; - EXPECT_EQ(inString, expected) - << "backend " << Gl().BackendName() << " GL_EXTENSIONS = " << extensionString; + EXPECT_EQ(inString, expected) << "backend " << Gl().BackendName() << " GL_EXTENSIONS = " << extensionString; // LWJGL builds GLCapabilities from the INDEXED form on a core profile, so the // two spellings disagreeing would be invisible to the check above and fatal @@ -441,8 +441,10 @@ void main() { EXPECT_EQ(ShaderCompletion(shader), GL_TRUE) << "a compile after a zero count must be synchronous"; } - for (const GLuint shader : backlog) glDeleteShader(shader); - for (const GLuint shader : serial) glDeleteShader(shader); + for (const GLuint shader : backlog) + glDeleteShader(shader); + for (const GLuint shader : serial) + glDeleteShader(shader); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } @@ -533,7 +535,8 @@ void main() { const Image image = DrawFrameWith(programs[static_cast(i)]); EXPECT_EQ(image.QuadrantSignature(), "blue,green,red,white") << "program " << i; } - for (const GLuint program : programs) glDeleteProgram(program); + for (const GLuint program : programs) + glDeleteProgram(program); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } @@ -622,7 +625,8 @@ void main() { const Image image = DrawFrameWith(program); EXPECT_EQ(image.QuadrantSignature(), "blue,green,red,white") << "program " << i; } - for (const GLuint program : programs) glDeleteProgram(program); + for (const GLuint program : programs) + glDeleteProgram(program); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/AtomicCounterScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/AtomicCounterScenario.cpp index a8d50874..0f677260 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/AtomicCounterScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/AtomicCounterScenario.cpp @@ -135,9 +135,8 @@ void main() { GLuint buffer = 0; glGenBuffers(1, &buffer); glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, buffer); - glBufferData(GL_ATOMIC_COUNTER_BUFFER, - static_cast(seed.size() * sizeof(unsigned int)), seed.data(), - GL_DYNAMIC_DRAW); + glBufferData(GL_ATOMIC_COUNTER_BUFFER, static_cast(seed.size() * sizeof(unsigned int)), + seed.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, binding, buffer); glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); m_buffers.push_back(buffer); @@ -222,7 +221,8 @@ void main() { values = ReadCounters(zero, 2); EXPECT_EQ(FirstGLError(), 0u); EXPECT_EQ(values[0], reseed[0] + kInvocations) << "the re-seeded value did not reach the shader"; - EXPECT_EQ(values[1], reseed[1] + 2 * kInvocations) << "the re-seeded value at offset 4 did not reach the shader"; + EXPECT_EQ(values[1], reseed[1] + 2 * kInvocations) + << "the re-seeded value at offset 4 did not reach the shader"; } // A CPU glBufferSubData issued AFTER a dispatch, read back with NO further GPU work in diff --git a/MobileGL/MG_IntegrationTest/Scenarios/BandedReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/BandedReadbackScenario.cpp index 39f9fc4a..e2c2e884 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/BandedReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/BandedReadbackScenario.cpp @@ -58,8 +58,7 @@ namespace MGITest { // A texel that names its own position, so a misplaced band cannot match. std::array PatternRgba8(int x, int y) { - return {static_cast(x & 0xFF), - static_cast(((x >> 8) & 0x0F) | (((y >> 8) & 0x0F) << 4)), + return {static_cast(x & 0xFF), static_cast(((x >> 8) & 0x0F) | (((y >> 8) & 0x0F) << 4)), static_cast(y & 0xFF), static_cast((x * 3 + y * 5) & 0xFF)}; } @@ -115,8 +114,10 @@ namespace MGITest { if (m_pbo != 0) glDeleteBuffers(1, &m_pbo); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo); - for (GLuint texture : m_textures) glDeleteTextures(1, &texture); - for (GLuint renderbuffer : m_renderbuffers) glDeleteRenderbuffers(1, &renderbuffer); + for (GLuint texture : m_textures) + glDeleteTextures(1, &texture); + for (GLuint renderbuffer : m_renderbuffers) + glDeleteRenderbuffers(1, &renderbuffer); m_pbo = 0; m_fbo = 0; m_textures.clear(); @@ -135,8 +136,7 @@ namespace MGITest { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // A texture holding the position pattern, attached as COLOR_ATTACHMENT0 and read. @@ -250,9 +250,8 @@ namespace MGITest { const auto expected = stripeColour(x, y); const GLfloat* got = pixels.data() + (static_cast(y) * kWidth + x) * 4; if (std::memcmp(got, expected.data(), sizeof(expected)) != 0 && bad++ == 0) { - first = "(" + std::to_string(x) + "," + std::to_string(y) + ") = " + std::to_string(got[0]) + - "," + std::to_string(got[1]) + "," + std::to_string(got[2]) + "," + - std::to_string(got[3]); + first = "(" + std::to_string(x) + "," + std::to_string(y) + ") = " + std::to_string(got[0]) + "," + + std::to_string(got[1]) + "," + std::to_string(got[2]) + "," + std::to_string(got[3]); } } } @@ -420,8 +419,7 @@ namespace MGITest { const int y = static_cast(i / kWidth); if (std::fabs(depth[i] - depthAt(y)) > 1.0f / 4096.0f && bad++ == 0) firstBad = i; } - EXPECT_EQ(bad, 0u) << bad << " depth values wrong, first at pixel " << firstBad << " = " - << depth[firstBad]; + EXPECT_EQ(bad, 0u) << bad << " depth values wrong, first at pixel " << firstBad << " = " << depth[firstBad]; } { struct D32fS8 { @@ -442,9 +440,8 @@ namespace MGITest { firstBad = i; } } - EXPECT_EQ(bad, 0u) << bad << " packed depth/stencil pairs wrong, first at pixel " << firstBad - << " = depth " << packed[firstBad].depth << " stencil " - << (packed[firstBad].stencil & 0xFFu); + EXPECT_EQ(bad, 0u) << bad << " packed depth/stencil pairs wrong, first at pixel " << firstBad << " = depth " + << packed[firstBad].depth << " stencil " << (packed[firstBad].stencil & 0xFFu); } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp index 392ebad8..58688e35 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/BufferTextureScenario.cpp @@ -94,8 +94,7 @@ void main() { GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits >= 1 && maxComputeImageUniforms >= 1; } @@ -181,8 +180,7 @@ void main() { GLuint buffer = 0; glGenBuffers(1, &buffer); glBindBuffer(GL_TEXTURE_BUFFER, buffer); - glBufferData(GL_TEXTURE_BUFFER, static_cast(texels.size()), texels.data(), - GL_DYNAMIC_DRAW); + glBufferData(GL_TEXTURE_BUFFER, static_cast(texels.size()), texels.data(), GL_DYNAMIC_DRAW); GLuint texture = 0; glGenTextures(1, &texture); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClearOrderScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClearOrderScenario.cpp index 438bdd5e..dc84275d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClearOrderScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClearOrderScenario.cpp @@ -36,318 +36,319 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -using Pixel = std::array; -constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}, black{0, 0, 0, 255}; -constexpr int kSize = 8, kTargetWidth = 16; -constexpr const char* kVertex = R"(#version 330 core + namespace { + using Pixel = std::array; + constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}, black{0, 0, 0, 255}; + constexpr int kSize = 8, kTargetWidth = 16; + constexpr const char* kVertex = R"(#version 330 core uniform float z; void main() { vec2 p[3] = vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1,3)); gl_Position = vec4(p[gl_VertexID], z, 1); })"; -constexpr const char* kSample = R"(#version 330 core + constexpr const char* kSample = R"(#version 330 core uniform sampler2D src; layout(location=0) out vec4 color; void main() { color = texelFetch(src, ivec2(gl_FragCoord.xy) % 8, 0); })"; -constexpr const char* kTint = R"(#version 330 core + constexpr const char* kTint = R"(#version 330 core uniform vec4 tint; layout(location=0) out vec4 color; void main() { color = tint; })"; -class ClearOrderScenario : public ScenarioTest { -protected: - GLuint target = 0, sample = 0, tint = 0, vao = 0; - std::vector textures, fbos; + class ClearOrderScenario : public ScenarioTest { + protected: + GLuint target = 0, sample = 0, tint = 0, vao = 0; + std::vector textures, fbos; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_TRUE); - glActiveTexture(GL_TEXTURE0); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - target = NewFramebuffer(NewColor(kTargetWidth, kSize, black), 0); - sample = Link(kSample); - tint = Link(kTint); - } + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + glActiveTexture(GL_TEXTURE0); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + target = NewFramebuffer(NewColor(kTargetWidth, kSize, black), 0); + sample = Link(kSample); + tint = Link(kTint); + } - void TearDown() override { - if (Ready()) { - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDisable(GL_DEPTH_TEST); - glDisable(GL_SCISSOR_TEST); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + void TearDown() override { + if (Ready()) { + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0); + if (vao) glDeleteVertexArrays(1, &vao); + if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); + if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); + if (sample) glDeleteProgram(sample); + if (tint) glDeleteProgram(tint); + } + ScenarioTest::TearDown(); + } + + GLuint NewColor(int width, int height, Pixel color) { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + const std::vector pixels(size_t(width) * size_t(height), color); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + return texture; + } + + GLuint NewDepth(int width, int height) { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, + nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + return texture; + } + + // `color` and/or `depth` may be 0. + GLuint NewFramebuffer(GLuint color, GLuint depth) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + fbos.push_back(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + if (color) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); + if (depth) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0); + if (!color) { + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + return fbo; + } + + static GLuint Link(const char* fragment) { + const GLuint linked = glCreateProgram(); + const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; + const std::array sources{kVertex, fragment}; + for (size_t i = 0; i < kinds.size(); ++i) { + const GLuint shader = glCreateShader(kinds[i]); + glShaderSource(shader, 1, &sources[i], nullptr); + glCompileShader(shader); + glAttachShader(linked, shader); + glDeleteShader(shader); + } + glLinkProgram(linked); + GLint ok = GL_FALSE; + glGetProgramiv(linked, GL_LINK_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE); + return linked; + } + + static void ClearColor(GLuint fbo, Pixel color) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClearColor(color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, color[3] / 255.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + + // Draws `color` over (x, y, w, h) of `fbo` at depth `z` (NDC). + void Fill(GLuint fbo, Pixel color, int x, int y, int w, int h, float z = 0.0f) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glUseProgram(tint); + glUniform1f(glGetUniformLocation(tint, "z"), z); + glUniform4f(glGetUniformLocation(tint, "tint"), color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, + color[3] / 255.0f); + glViewport(x, y, w, h); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + // Samples `texture` into the 8x8 tile `tile` of the target. + void SampleInto(GLuint texture, int tile) { + glBindFramebuffer(GL_FRAMEBUFFER, target); + glUseProgram(sample); + glUniform1f(glGetUniformLocation(sample, "z"), 0.0f); + glBindTexture(GL_TEXTURE_2D, texture); + glViewport(tile * kSize, 0, kSize, kSize); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + void ExpectPixel(GLuint fbo, int x, int y, Pixel expected, const char* why) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + Pixel actual{}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); + for (size_t channel = 0; channel < actual.size(); ++channel) + EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; + } + + // Main framebuffer (colour + depth) and a depth-only clear framebuffer sharing its depth. + struct Pair { + GLuint color, depth, main, depthOnly; + }; + Pair NewPair() { + Pair pair{}; + pair.color = NewColor(kSize, kSize, black); + pair.depth = NewDepth(kSize, kSize); + pair.main = NewFramebuffer(pair.color, pair.depth); + pair.depthOnly = NewFramebuffer(0, pair.depth); + Prime(pair.main); + Prime(pair.depthOnly); + return pair; + } + + // Uses `fbo` once - a clear and a draw - so the cases below run on images whose records have + // settled, the steady state a game frame sees (a first use republishes the attachments' records, + // and Magma then runs a pending clear at once rather than folding it into the next pass). + void Prime(GLuint fbo) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_ALWAYS); + Fill(fbo, black, 0, 0, kSize, kSize); + glDepthFunc(GL_LESS); + glDisable(GL_DEPTH_TEST); + glFinish(); + } + + // Draws one pixel of the target nothing checks: whatever pass held the case's images is then + // closed, so a clear of them is not taken inside it. + void SwitchPassAway() { Fill(target, black, kTargetWidth - 1, kSize - 1, 1, 1); } + }; + + TEST_F(ClearOrderScenario, ADepthClearThroughADepthOnlyFramebufferReachesTheNextPass) { + if (!Ready() || IsSkipped()) return; + const Pair pair = NewPair(); + glBindFramebuffer(GL_FRAMEBUFFER, pair.main); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearDepth(0.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // depth 0: nothing passes LESS + glFinish(); + SwitchPassAway(); + glBindFramebuffer(GL_FRAMEBUFFER, pair.depthOnly); glClearDepth(1.0); - if (vao) glDeleteVertexArrays(1, &vao); - if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); - if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); - if (sample) glDeleteProgram(sample); - if (tint) glDeleteProgram(tint); + glClear(GL_DEPTH_BUFFER_BIT); // through the other framebuffer + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + Fill(pair.main, green, 0, 0, kSize, kSize, 0.0f); + glDisable(GL_DEPTH_TEST); + ExpectPixel(pair.main, 4, 4, green, "the draw after the depth-only clear sees the cleared depth"); } - ScenarioTest::TearDown(); - } - GLuint NewColor(int width, int height, Pixel color) { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - const std::vector pixels(size_t(width) * size_t(height), color); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - return texture; - } - - GLuint NewDepth(int width, int height) { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - return texture; - } - - // `color` and/or `depth` may be 0. - GLuint NewFramebuffer(GLuint color, GLuint depth) { - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - fbos.push_back(fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - if (color) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0); - if (depth) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0); - if (!color) { - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); + TEST_F(ClearOrderScenario, ADepthClearThroughADepthOnlyFramebufferLandsInsideTheOpenPass) { + if (!Ready() || IsSkipped()) return; + const Pair pair = NewPair(); + glBindFramebuffer(GL_FRAMEBUFFER, pair.main); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClearDepth(1.0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glFinish(); + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + Fill(pair.main, red, 0, 0, kSize, kSize, -0.5f); // the main pass is open, depth now -0.5 -> 0.25 + glBindFramebuffer(GL_FRAMEBUFFER, pair.depthOnly); + glClearDepth(1.0); + glClear(GL_DEPTH_BUFFER_BIT); // the open pass holds this depth texture + Fill(pair.main, green, 0, 0, kSize, kSize, 0.0f); // behind the red one unless the clear landed + glDisable(GL_DEPTH_TEST); + ExpectPixel(pair.main, 4, 4, green, "the draw after the clear passes the cleared depth"); } - EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - return fbo; - } - static GLuint Link(const char* fragment) { - const GLuint linked = glCreateProgram(); - const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; - const std::array sources{kVertex, fragment}; - for (size_t i = 0; i < kinds.size(); ++i) { - const GLuint shader = glCreateShader(kinds[i]); - glShaderSource(shader, 1, &sources[i], nullptr); - glCompileShader(shader); - glAttachShader(linked, shader); - glDeleteShader(shader); + TEST_F(ClearOrderScenario, AColourClearThroughItsOwnFramebufferReachesAnotherFramebufferHoldingTheTexture) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, black); + const GLuint own = NewFramebuffer(texture, 0); + const GLuint depth = NewDepth(kSize, kSize); + const GLuint other = NewFramebuffer(texture, depth); + Prime(own); + Prime(other); + SwitchPassAway(); + ClearColor(own, red); + Fill(other, blue, 0, 0, kSize / 2, kSize); // the left half, through the other framebuffer + SampleInto(texture, 0); + ExpectPixel(target, 1, 4, blue, "the draw through the other framebuffer lands over the clear"); + ExpectPixel(target, 6, 4, red, "the clear holds where the draw did not reach"); } - glLinkProgram(linked); - GLint ok = GL_FALSE; - glGetProgramiv(linked, GL_LINK_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE); - return linked; - } - static void ClearColor(GLuint fbo, Pixel color) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glClearColor(color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, color[3] / 255.0f); - glClear(GL_COLOR_BUFFER_BIT); - } + TEST_F(ClearOrderScenario, AClearedTextureIsSampledBeforeAnyPassDrawsIntoIt) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, green); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + ClearColor(fbo, red); + SampleInto(texture, 0); + ExpectPixel(target, 4, 4, red, "the sample sees the clear"); + } - // Draws `color` over (x, y, w, h) of `fbo` at depth `z` (NDC). - void Fill(GLuint fbo, Pixel color, int x, int y, int w, int h, float z = 0.0f) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glUseProgram(tint); - glUniform1f(glGetUniformLocation(tint, "z"), z); - glUniform4f(glGetUniformLocation(tint, "tint"), color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, - color[3] / 255.0f); - glViewport(x, y, w, h); - glDrawArrays(GL_TRIANGLES, 0, 3); - } + TEST_F(ClearOrderScenario, AClearedTextureIsBlittedFrom) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, green); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + ClearColor(fbo, red); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); + glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); + ExpectPixel(target, 4, 4, red, "the blit reads the cleared texels"); + } - // Samples `texture` into the 8x8 tile `tile` of the target. - void SampleInto(GLuint texture, int tile) { - glBindFramebuffer(GL_FRAMEBUFFER, target); - glUseProgram(sample); - glUniform1f(glGetUniformLocation(sample, "z"), 0.0f); - glBindTexture(GL_TEXTURE_2D, texture); - glViewport(tile * kSize, 0, kSize, kSize); - glDrawArrays(GL_TRIANGLES, 0, 3); - } + TEST_F(ClearOrderScenario, AnUploadAfterAClearLandsOverIt) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, black); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + ClearColor(fbo, red); + const std::vector patch(4, green); + glBindTexture(GL_TEXTURE_2D, texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, patch.data()); + SampleInto(texture, 0); + ExpectPixel(target, 1, 1, green, "the upload after the clear is not overwritten by it"); + ExpectPixel(target, 6, 6, red, "the clear holds outside the upload"); + } - void ExpectPixel(GLuint fbo, int x, int y, Pixel expected, const char* why) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - Pixel actual{}; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); - for (size_t channel = 0; channel < actual.size(); ++channel) - EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; - } + TEST_F(ClearOrderScenario, AClearedTextureIsReadBack) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, green); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + ClearColor(fbo, red); + ExpectPixel(fbo, 4, 4, red, "the readback sees the clear"); + } - // Main framebuffer (colour + depth) and a depth-only clear framebuffer sharing its depth. - struct Pair { - GLuint color, depth, main, depthOnly; - }; - Pair NewPair() { - Pair pair{}; - pair.color = NewColor(kSize, kSize, black); - pair.depth = NewDepth(kSize, kSize); - pair.main = NewFramebuffer(pair.color, pair.depth); - pair.depthOnly = NewFramebuffer(0, pair.depth); - Prime(pair.main); - Prime(pair.depthOnly); - return pair; - } + TEST_F(ClearOrderScenario, ARespecifiedTextureKeepsItsNewTexelsAndNotAnEarlierClear) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, green); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + ClearColor(fbo, red); + const std::vector fresh(size_t(kSize) * kSize, blue); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, fresh.data()); + SampleInto(texture, 0); + ExpectPixel(target, 4, 4, blue, "the re-specification replaces the cleared texels"); + } - // Uses `fbo` once - a clear and a draw - so the cases below run on images whose records have - // settled, the steady state a game frame sees (a first use republishes the attachments' records, - // and Magma then runs a pending clear at once rather than folding it into the next pass). - void Prime(GLuint fbo) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_ALWAYS); - Fill(fbo, black, 0, 0, kSize, kSize); - glDepthFunc(GL_LESS); - glDisable(GL_DEPTH_TEST); - glFinish(); - } + TEST_F(ClearOrderScenario, AScissoredClearClearsOnlyItsBox) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewColor(kSize, kSize, green); + const GLuint fbo = NewFramebuffer(texture, 0); + glFinish(); + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, kSize / 2, kSize); + ClearColor(fbo, red); + glDisable(GL_SCISSOR_TEST); + SampleInto(texture, 0); + ExpectPixel(target, 1, 4, red, "inside the scissor box"); + ExpectPixel(target, 6, 4, green, "outside the scissor box"); + } - // Draws one pixel of the target nothing checks: whatever pass held the case's images is then - // closed, so a clear of them is not taken inside it. - void SwitchPassAway() { Fill(target, black, kTargetWidth - 1, kSize - 1, 1, 1); } -}; - -TEST_F(ClearOrderScenario, ADepthClearThroughADepthOnlyFramebufferReachesTheNextPass) { - if (!Ready() || IsSkipped()) return; - const Pair pair = NewPair(); - glBindFramebuffer(GL_FRAMEBUFFER, pair.main); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClearDepth(0.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // depth 0: nothing passes LESS - glFinish(); - SwitchPassAway(); - glBindFramebuffer(GL_FRAMEBUFFER, pair.depthOnly); - glClearDepth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); // through the other framebuffer - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - Fill(pair.main, green, 0, 0, kSize, kSize, 0.0f); - glDisable(GL_DEPTH_TEST); - ExpectPixel(pair.main, 4, 4, green, "the draw after the depth-only clear sees the cleared depth"); -} - -TEST_F(ClearOrderScenario, ADepthClearThroughADepthOnlyFramebufferLandsInsideTheOpenPass) { - if (!Ready() || IsSkipped()) return; - const Pair pair = NewPair(); - glBindFramebuffer(GL_FRAMEBUFFER, pair.main); - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClearDepth(1.0); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glFinish(); - glEnable(GL_DEPTH_TEST); - glDepthFunc(GL_LESS); - Fill(pair.main, red, 0, 0, kSize, kSize, -0.5f); // the main pass is open, depth now -0.5 -> 0.25 - glBindFramebuffer(GL_FRAMEBUFFER, pair.depthOnly); - glClearDepth(1.0); - glClear(GL_DEPTH_BUFFER_BIT); // the open pass holds this depth texture - Fill(pair.main, green, 0, 0, kSize, kSize, 0.0f); // behind the red one unless the clear landed - glDisable(GL_DEPTH_TEST); - ExpectPixel(pair.main, 4, 4, green, "the draw after the clear passes the cleared depth"); -} - -TEST_F(ClearOrderScenario, AColourClearThroughItsOwnFramebufferReachesAnotherFramebufferHoldingTheTexture) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, black); - const GLuint own = NewFramebuffer(texture, 0); - const GLuint depth = NewDepth(kSize, kSize); - const GLuint other = NewFramebuffer(texture, depth); - Prime(own); - Prime(other); - SwitchPassAway(); - ClearColor(own, red); - Fill(other, blue, 0, 0, kSize / 2, kSize); // the left half, through the other framebuffer - SampleInto(texture, 0); - ExpectPixel(target, 1, 4, blue, "the draw through the other framebuffer lands over the clear"); - ExpectPixel(target, 6, 4, red, "the clear holds where the draw did not reach"); -} - -TEST_F(ClearOrderScenario, AClearedTextureIsSampledBeforeAnyPassDrawsIntoIt) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, green); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - ClearColor(fbo, red); - SampleInto(texture, 0); - ExpectPixel(target, 4, 4, red, "the sample sees the clear"); -} - -TEST_F(ClearOrderScenario, AClearedTextureIsBlittedFrom) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, green); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - ClearColor(fbo, red); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, target); - glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); - ExpectPixel(target, 4, 4, red, "the blit reads the cleared texels"); -} - -TEST_F(ClearOrderScenario, AnUploadAfterAClearLandsOverIt) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, black); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - ClearColor(fbo, red); - const std::vector patch(4, green); - glBindTexture(GL_TEXTURE_2D, texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, patch.data()); - SampleInto(texture, 0); - ExpectPixel(target, 1, 1, green, "the upload after the clear is not overwritten by it"); - ExpectPixel(target, 6, 6, red, "the clear holds outside the upload"); -} - -TEST_F(ClearOrderScenario, AClearedTextureIsReadBack) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, green); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - ClearColor(fbo, red); - ExpectPixel(fbo, 4, 4, red, "the readback sees the clear"); -} - -TEST_F(ClearOrderScenario, ARespecifiedTextureKeepsItsNewTexelsAndNotAnEarlierClear) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, green); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - ClearColor(fbo, red); - const std::vector fresh(size_t(kSize) * kSize, blue); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, fresh.data()); - SampleInto(texture, 0); - ExpectPixel(target, 4, 4, blue, "the re-specification replaces the cleared texels"); -} - -TEST_F(ClearOrderScenario, AScissoredClearClearsOnlyItsBox) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewColor(kSize, kSize, green); - const GLuint fbo = NewFramebuffer(texture, 0); - glFinish(); - glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, kSize / 2, kSize); - ClearColor(fbo, red); - glDisable(GL_SCISSOR_TEST); - SampleInto(texture, 0); - ExpectPixel(target, 1, 4, red, "inside the scissor box"); - ExpectPixel(target, 6, 4, green, "outside the scissor box"); -} - -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClearTexImageUndefinedLevelZeroScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClearTexImageUndefinedLevelZeroScenario.cpp index 9cb5782b..f5fc577a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClearTexImageUndefinedLevelZeroScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClearTexImageUndefinedLevelZeroScenario.cpp @@ -94,8 +94,7 @@ namespace MGITest { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // One level and nothing else, through glTexImage2D - deliberately NOT glTexStorage2D, @@ -148,8 +147,8 @@ namespace MGITest { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kLevelExtent, kLevelExtent, 0, GL_RGBA, GL_UNSIGNED_BYTE, base.data()); const std::vector gapped(static_cast(kLevelExtent) * kLevelExtent, kInitialValue); - glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, kLevelExtent, kLevelExtent, 0, GL_RGBA, - GL_UNSIGNED_BYTE, gapped.data()); + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, kLevelExtent, kLevelExtent, 0, GL_RGBA, GL_UNSIGNED_BYTE, + gapped.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -186,8 +185,8 @@ namespace MGITest { if (!Ready()) GTEST_SKIP(); MakeTextureWithOnlyLevel(kDefinedLevel); - glClearTexSubImage(m_texture, kDefinedLevel, 0, 0, 0, kLevelExtent, kLevelExtent, 1, GL_RGBA, - GL_UNSIGNED_BYTE, &kClearValue); + glClearTexSubImage(m_texture, kDefinedLevel, 0, 0, 0, kLevelExtent, kLevelExtent, 1, GL_RGBA, GL_UNSIGNED_BYTE, + &kClearValue); EXPECT_EQ(FirstGLError(), 0u) << "glClearTexSubImage was rejected"; ExpectAllTexels("after the sub-image clear", ReadLevel(kDefinedLevel), kClearValue); @@ -265,18 +264,25 @@ void main() { color=textureLod(source,vec2(0.5),lod); })"; struct Objects { GLuint program, vao = 0, fbo = 0, output = 0; ~Objects() { - glUseProgram(0); glBindVertexArray(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); glDeleteTextures(1, &output); - glDeleteVertexArrays(1, &vao); glDeleteProgram(program); + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &output); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); } } objects{program}; - glGenVertexArrays(1, &objects.vao); glBindVertexArray(objects.vao); + glGenVertexArrays(1, &objects.vao); + glBindVertexArray(objects.vao); glActiveTexture(GL_TEXTURE0); - glGenTextures(1, &objects.output); glBindTexture(GL_TEXTURE_2D, objects.output); + glGenTextures(1, &objects.output); + glBindTexture(GL_TEXTURE_2D, objects.output); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glGenFramebuffers(1, &objects.fbo); glBindFramebuffer(GL_FRAMEBUFFER, objects.fbo); + glGenFramebuffers(1, &objects.fbo); + glBindFramebuffer(GL_FRAMEBUFFER, objects.fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, objects.output, 0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); MakeTextureWithOnlyLevel(0); @@ -284,8 +290,13 @@ void main() { color=textureLod(source,vec2(0.5),lod); })"; glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, upper.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); - glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); glDisable(GL_CULL_FACE); - glDisable(GL_STENCIL_TEST); glDisable(GL_RASTERIZER_DISCARD); glDisable(GL_FRAMEBUFFER_SRGB); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_STENCIL_TEST); + glDisable(GL_RASTERIZER_DISCARD); + glDisable(GL_FRAMEBUFFER_SRGB); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); glUseProgram(program); const GLint lod = glGetUniformLocation(program, "lod"); @@ -293,9 +304,13 @@ void main() { color=textureLod(source,vec2(0.5),lod); })"; std::vector readUpper(4 * 4); glGetTexImage(GL_TEXTURE_2D, 1, GL_RGBA, GL_UNSIGNED_BYTE, readUpper.data()); ExpectAllTexels("upper level readback", readUpper, kClearValue); - glViewport(0, 0, 4, 4); glUniform1f(lod, 0); glDrawArrays(GL_TRIANGLES, 0, 3); + glViewport(0, 0, 4, 4); + glUniform1f(lod, 0); + glDrawArrays(GL_TRIANGLES, 0, 3); ExpectAllTexels("base level readback", ReadLevel(0), kInitialValue); - glViewport(4, 0, 4, 4); glUniform1f(lod, 1); glDrawArrays(GL_TRIANGLES, 0, 3); + glViewport(4, 0, 4, 4); + glUniform1f(lod, 1); + glDrawArrays(GL_TRIANGLES, 0, 3); Texel8 baseSample{}, upperSample{}; glReadPixels(2, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &baseSample); glReadPixels(6, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &upperSample); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp index f8c7955a..319701d9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClearThenReadPixelsScenario.cpp @@ -128,11 +128,11 @@ void main() { o_color = vec4(0.1, 0.2, 0.3, 1.0); } ++nonZero; } } - EXPECT_EQ(nonZero, 0) << "glClear(0,0,0,0) followed by glReadPixels with no draw returned " << nonZero - << " of " << (width * height) << " non-zero pixels; first at (" << firstX << ", " - << firstY << ") = (" << static_cast(firstOffender.r) << ", " - << static_cast(firstOffender.g) << ", " << static_cast(firstOffender.b) - << ", " << static_cast(firstOffender.a) << ")"; + EXPECT_EQ(nonZero, 0) << "glClear(0,0,0,0) followed by glReadPixels with no draw returned " << nonZero << " of " + << (width * height) << " non-zero pixels; first at (" << firstX << ", " << firstY + << ") = (" << static_cast(firstOffender.r) << ", " + << static_cast(firstOffender.g) << ", " << static_cast(firstOffender.b) << ", " + << static_cast(firstOffender.a) << ")"; gl.EndFrame(); glDeleteProgram(program); @@ -222,9 +222,9 @@ void main() { o_color = vec4(0.1, 0.2, 0.3, 1.0); } EXPECT_EQ(FirstGLError(), 0u); const Rgba8 centre = blitted.At(width / 2, height / 2); EXPECT_NEAR(centre.r, 26, 2) << "the blit into the default framebuffer did not survive the clear that " - "preceded it; read back rgba(" << static_cast(centre.r) << ", " - << static_cast(centre.g) << ", " << static_cast(centre.b) << ", " - << static_cast(centre.a) << ")"; + "preceded it; read back rgba(" + << static_cast(centre.r) << ", " << static_cast(centre.g) << ", " + << static_cast(centre.b) << ", " << static_cast(centre.a) << ")"; EXPECT_NEAR(centre.g, 51, 2); EXPECT_NEAR(centre.b, 77, 2); @@ -292,9 +292,8 @@ void main() { o_color = vec4(0.1, 0.2, 0.3, 1.0); } << static_cast(bottom.b) << ") - blue there means the resolve landed " << "in the mirrored band"; EXPECT_LT(bottom.b, 60); - EXPECT_GT(top.b, 200) << "the top band should be blue after the resolve, got rgba(" - << static_cast(top.r) << ", " << static_cast(top.g) << ", " - << static_cast(top.b) << ")"; + EXPECT_GT(top.b, 200) << "the top band should be blue after the resolve, got rgba(" << static_cast(top.r) + << ", " << static_cast(top.g) << ", " << static_cast(top.b) << ")"; EXPECT_LT(top.r, 60); glDeleteRenderbuffers(1, &rbo); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClientVertexArrayScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClientVertexArrayScenario.cpp index f32d90db..b862c7e8 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClientVertexArrayScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClientVertexArrayScenario.cpp @@ -177,8 +177,7 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } // Two strips out of one client array: the left half and the right half, so a // MultiDrawArrays that uploaded only the FIRST sub-draw's range leaves one side blue. static const float kTwoHalves[] = { - -1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, - 0.0f, -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 0.0f, -1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, }; ClientArrayQuad quad; quad.Bind(kTwoHalves); @@ -220,7 +219,8 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } quad.Bind(positions.data()); glUseProgram(program); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices.data()); - for (size_t i = 0; i < positions.size(); i += 2) positions[i] += 1; + for (size_t i = 0; i < positions.size(); i += 2) + positions[i] += 1; glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices.data()); // No readback or Finish between the draws and the host writes. std::fill(positions.begin(), positions.end(), -100.0f); @@ -287,8 +287,7 @@ void main() { glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); ClearTo(0, 0, 1, 1); - const float positions[]{100, 100, 100, 100, 100, 100, 100, 100, - -1, -1, 1, -1, -1, 1, 1, 1}; + const float positions[]{100, 100, 100, 100, 100, 100, 100, 100, -1, -1, 1, -1, -1, 1, 1, 1}; ClientArrayQuad quad; quad.Bind(positions); GLuint ebo = 0; @@ -333,8 +332,7 @@ void main() { gl_Position = vec4(aPos + aOffset, 0.0, 1.0); } glDisable(GL_DEPTH_TEST); glDisable(GL_SCISSOR_TEST); ClearTo(0, 0, 1, 1); - const float positions[]{100, 100, 100, 100, 100, 100, 100, 100, - -1, -1, 1, -1, -1, 1, 1, 1}; + const float positions[]{100, 100, 100, 100, 100, 100, 100, 100, -1, -1, 1, -1, -1, 1, 1, 1}; const float offsets[]{100, 100, 0, 0}; ClientArrayQuad quad; quad.Bind(positions); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClipControlScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClipControlScenario.cpp index b7475696..e06354cd 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClipControlScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClipControlScenario.cpp @@ -57,10 +57,10 @@ void main() { o_color = gl_FrontFacing ? vec4(1.0, 0.0, 0.0, 1.0) : vec4(0.0, 1. // Left half of clip space counter-clockwise, right half clockwise. constexpr float kWoundHalves[] = { - -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, // - 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, // + -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, -1.0f, 1.0f, 0.0f, // + 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, // 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, -1.0f, 0.0f, // - 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, }; bool ExtensionAdvertised(const char* name) { @@ -94,14 +94,14 @@ void main() { o_color = gl_FrontFacing ? vec4(1.0, 0.0, 0.0, 1.0) : vec4(0.0, 1. // The lower half of clip space (y in [-1, 0]) as two triangles at depth z. void DrawLowerHalf(unsigned int program, float z = 0.0f) { - const float quad[] = {-1.0f, -1.0f, z, 1.0f, -1.0f, z, 1.0f, 0.0f, z, + const float quad[] = {-1.0f, -1.0f, z, 1.0f, -1.0f, z, 1.0f, 0.0f, z, -1.0f, -1.0f, z, 1.0f, 0.0f, z, -1.0f, 0.0f, z}; DrawTriangles(program, quad, 6); } // The whole of clip space at depth z. void DrawEverything(unsigned int program, float z) { - const float quad[] = {-1.0f, -1.0f, z, 1.0f, -1.0f, z, 1.0f, 1.0f, z, + const float quad[] = {-1.0f, -1.0f, z, 1.0f, -1.0f, z, 1.0f, 1.0f, z, -1.0f, -1.0f, z, 1.0f, 1.0f, z, -1.0f, 1.0f, z}; DrawTriangles(program, quad, 6); } @@ -248,8 +248,8 @@ void main() { o_color = gl_FrontFacing ? vec4(1.0, 0.0, 0.0, 1.0) : vec4(0.0, 1. const char* cwColor = frontFace == GL_CCW ? "green" : "red"; EXPECT_TRUE(RegionIsMostly(image, w / 8, w / 2 - 1 - w / 8, h / 8, h - 1 - h / 8, ccwColor, 0.0, when + ": counter-clockwise half")); - EXPECT_TRUE(RegionIsMostly(image, w / 2 + w / 8, w - 1 - w / 8, h / 8, h - 1 - h / 8, cwColor, - 0.0, when + ": clockwise half")); + EXPECT_TRUE(RegionIsMostly(image, w / 2 + w / 8, w - 1 - w / 8, h / 8, h - 1 - h / 8, cwColor, 0.0, + when + ": clockwise half")); } } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp index 6d4b33d2..94aff76b 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ClipDistanceScenario.cpp @@ -139,13 +139,9 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } glDrawArrays(GL_TRIANGLES, 0, 3); } - static bool IsGreen(const unsigned char* px) { - return px[0] < 64 && px[1] > 192; - } + static bool IsGreen(const unsigned char* px) { return px[0] < 64 && px[1] > 192; } - static bool IsRed(const unsigned char* px) { - return px[0] > 192 && px[1] < 64; - } + static bool IsRed(const unsigned char* px) { return px[0] > 192 && px[1] < 64; } void PixelAt(int x, int y, unsigned char* out) const { glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, out); @@ -275,8 +271,9 @@ void main() { fragColor = vec4(0.0, 1.0, 0.0, 1.0); } PixelAt(width / 4, height / 2, left); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(right)) << "the kept half is not painted (" << int(right[0]) << "," << int(right[1]) - << "," << int(right[2]) << ") - the draw itself did not happen, so the clipped " + EXPECT_TRUE(IsGreen(right)) << "the kept half is not painted (" << int(right[0]) << "," << int(right[1]) << "," + << int(right[2]) + << ") - the draw itself did not happen, so the clipped " "half below proves nothing"; EXPECT_TRUE(IsRed(left)) << "gl_ClipDistance[0] is negative on the left half and GL_CLIP_DISTANCE0 is " "enabled, so those fragments must be clipped away; found (" diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLayeredScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLayeredScenario.cpp index ef2d76d9..707011be 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLayeredScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLayeredScenario.cpp @@ -123,10 +123,9 @@ namespace MGITest { glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 1, 1, 1); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - while (glGetError() != GL_NO_ERROR) { - } - glCopyImageSubData(probe[0], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, probe[1], GL_TEXTURE_2D_ARRAY, 0, 0, 0, - 0, 1, 1, 1); + while (glGetError() != GL_NO_ERROR) {} + glCopyImageSubData(probe[0], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, probe[1], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, + 1, 1, 1); const bool usable = glGetError() == GL_NO_ERROR; glDeleteTextures(2, probe); return usable; @@ -152,8 +151,7 @@ namespace MGITest { for (int level = 0; level < levels; ++level) { const int levelWidth = kWidth << (levels - 1 - level); const int levelHeight = kHeight << (levels - 1 - level); - const int levelSlices = - target == GL_TEXTURE_3D ? (kSlices << (levels - 1 - level)) : kSlices; + const int levelSlices = target == GL_TEXTURE_3D ? (kSlices << (levels - 1 - level)) : kSlices; for (int slice = 0; slice < levelSlices; ++slice) { const Rgba8 color = colorForSlice(slice % kSlices); std::vector texels(static_cast(levelWidth) * levelHeight, color); @@ -194,16 +192,15 @@ namespace MGITest { // The assertion every case ends with: slices inside [dstZ, dstZ + depth) hold the // source slice they were fed, and every slice outside it still holds its own fill. - void ExpectCopied(GLuint destination, int level, int width, int height, int sliceCount, int srcZ, - int dstZ, int depth, const char* what) { + void ExpectCopied(GLuint destination, int level, int width, int height, int sliceCount, int srcZ, int dstZ, + int depth, const char* what) { for (int slice = 0; slice < sliceCount; ++slice) { const bool inRange = slice >= dstZ && slice < dstZ + depth; - const Rgba8 expected = - inRange ? SourceColor(srcZ + (slice - dstZ)) : DestinationFill(slice); + const Rgba8 expected = inRange ? SourceColor(srcZ + (slice - dstZ)) : DestinationFill(slice); const Rgba8 actual = ReadSlice(destination, level, slice, width, height); EXPECT_TRUE(actual == expected) - << what << ": destination slice " << slice << (inRange ? " (copied)" : " (untouched)") - << " is " << Describe(actual) << ", expected " << Describe(expected); + << what << ": destination slice " << slice << (inRange ? " (copied)" : " (untouched)") << " is " + << Describe(actual) << ", expected " << Describe(expected); } } @@ -285,8 +282,7 @@ namespace MGITest { kDstZ, kWidth, kHeight, kDepth); ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "glCopyImageSubData raised an error"; - ExpectCopied(destination, kLevel, kWidth, kHeight, kSlices, kSrcZ, kDstZ, kDepth, - "3d->3d at mip level 1"); + ExpectCopied(destination, kLevel, kWidth, kHeight, kSlices, kSrcZ, kDstZ, kDepth, "3d->3d at mip level 1"); } // 2d_array -> 3d. The mixed shape: the source counts its slices as layers, the destination diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLevelRangeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLevelRangeScenario.cpp index 2cc4ea79..11349848 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLevelRangeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageLevelRangeScenario.cpp @@ -85,8 +85,7 @@ namespace MGITest { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } void DeleteTextures() { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CopyImagePacked16Scenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CopyImagePacked16Scenario.cpp index 802c834e..687448c7 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CopyImagePacked16Scenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CopyImagePacked16Scenario.cpp @@ -155,10 +155,9 @@ namespace MGITest { glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 1, 1, 1); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - while (glGetError() != GL_NO_ERROR) { - } - glCopyImageSubData(probe[0], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, probe[1], GL_TEXTURE_2D_ARRAY, 0, 0, 0, - 0, 1, 1, 1); + while (glGetError() != GL_NO_ERROR) {} + glCopyImageSubData(probe[0], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, probe[1], GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, + 1, 1, 1); const bool usable = glGetError() == GL_NO_ERROR; glDeleteTextures(2, probe); return usable; @@ -182,8 +181,8 @@ namespace MGITest { kLevel1Size, kLayers, 0, format.transferFormat, format.transferType, level1.data()); const int level2Size = kLevel1Size / 2; const auto level2 = MakeWords(format.transferType, level2Size * level2Size * kLayers, 211); - glTexImage3D(GL_TEXTURE_2D_ARRAY, 2, static_cast(format.internalFormat), level2Size, - level2Size, kLayers, 0, format.transferFormat, format.transferType, level2.data()); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 2, static_cast(format.internalFormat), level2Size, level2Size, + kLayers, 0, format.transferFormat, format.transferType, level2.data()); glBindTexture(GL_TEXTURE_2D_ARRAY, 0); return texture; } @@ -210,8 +209,8 @@ namespace MGITest { return texture; } - std::vector ReadTexImage(GLenum target, GLuint texture, int level, - const PackedFormatCase& format, size_t texelCount) { + std::vector ReadTexImage(GLenum target, GLuint texture, int level, const PackedFormatCase& format, + size_t texelCount) { std::vector words(texelCount, 0); glBindTexture(target, texture); glGetTexImage(target, level, format.transferFormat, format.transferType, words.data()); @@ -228,17 +227,15 @@ namespace MGITest { const std::vector& fill, const char* what) { for (int y = 0; y < width; ++y) { for (int x = 0; x < width && static_cast(y * width + x) < got.size(); ++x) { - const bool inRegion = - x >= x0 && x < x0 + kRegion && y >= y0 && y < y0 + kRegion; + const bool inRegion = x >= x0 && x < x0 + kRegion && y >= y0 && y < y0 + kRegion; const GLushort actual = got[static_cast(y * width + x)]; const GLushort expected = - inRegion ? source[static_cast((sourceY0 + y - y0) * sourceWidth + sourceX0 + - (x - x0))] - : fill[static_cast(y * width + x)]; + inRegion + ? source[static_cast((sourceY0 + y - y0) * sourceWidth + sourceX0 + (x - x0))] + : fill[static_cast(y * width + x)]; EXPECT_EQ(actual, expected) - << what << ": texel (" << x << ", " << y << ")" - << (inRegion ? " (copied)" : " (untouched)") << " holds 0x" << std::hex << actual - << ", expected 0x" << expected; + << what << ": texel (" << x << ", " << y << ")" << (inRegion ? " (copied)" : " (untouched)") + << " holds 0x" << std::hex << actual << ", expected 0x" << expected; if (actual != expected) return; // one texel names the defect; 224 more would bury it } } @@ -302,8 +299,8 @@ namespace MGITest { ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << format.name << ": glCopyImageSubData raised an error"; - const auto got = ReadTexImage(GL_TEXTURE_2D, destination, 0, format, - static_cast(kFlatSize) * kFlatSize); + const auto got = + ReadTexImage(GL_TEXTURE_2D, destination, 0, format, static_cast(kFlatSize) * kFlatSize); ExpectRegion(got, kFlatSize, 0, 0, level1, kLevel1Size, kArrayOffset, kArrayOffset, fill, (std::string("2d_array level 1 -> 2d, ") + format.name).c_str()); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageStoreReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageStoreReadbackScenario.cpp index bb0fbb7a..8017b4c5 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CopyImageStoreReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CopyImageStoreReadbackScenario.cpp @@ -84,7 +84,9 @@ namespace MGITest { {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV, 0, false, "rgb9_e5"}, }; - int TexelBytes(const CopyFormat& format) { return format.components == 0 ? 4 : format.components * 2; } + int TexelBytes(const CopyFormat& format) { + return format.components == 0 ? 4 : format.components * 2; + } // Every word differs from its neighbours and from the other patterns, so a mis-addressed // row, a swapped endpoint or a copy that never happened cannot cancel out. SNORM never @@ -113,9 +115,9 @@ namespace MGITest { } // `image` with the w x h block at (dx, dy) replaced by the block of `source` at (sx, sy). - std::vector WithBlock(std::vector image, int width, const std::vector& source, - int sourceWidth, int sx, int sy, int dx, int dy, int w, int h, - int texelBytes) { + std::vector WithBlock(std::vector image, int width, + const std::vector& source, int sourceWidth, int sx, int sy, + int dx, int dy, int w, int h, int texelBytes) { for (int row = 0; row < h; ++row) { std::memcpy(image.data() + (static_cast(dy + row) * width + dx) * texelBytes, source.data() + (static_cast(sy + row) * sourceWidth + sx) * texelBytes, @@ -226,8 +228,8 @@ namespace MGITest { // W4b) that server code runs in this process and logs into the whole lane log. void ExpectStoreReadNamedSince(const PipeStatsWindow::LogMark& mark) { if (!m_forced || !m_espryt || !m_recordArm) return; - const std::string log = m_wire ? PipeStatsWindow::ReadServerLogSince(mark) - : PipeStatsWindow::ReadLaneLogSince(mark); + const std::string log = + m_wire ? PipeStatsWindow::ReadServerLogSince(mark) : PipeStatsWindow::ReadLaneLogSince(mark); EXPECT_NE(log.find(kStoreReadMarker), std::string::npos) << kRefuseExtentKnob << " is set but the server log carries no '" << kStoreReadMarker << "' line: the knob did not reach the server, or no read took the store route"; @@ -369,8 +371,8 @@ namespace MGITest { for (std::size_t texel = 0; texel < cleared.size() / 8; ++texel) { std::memcpy(cleared.data() + texel * 8, words, 8); } - const auto want = WithBlock(destinationTexels, m_width, cleared, m_width, kSrcX, kSrcY, kDstX, kDstY, - kCopyW, kCopyH, 8); + const auto want = + WithBlock(destinationTexels, m_width, cleared, m_width, kSrcX, kSrcY, kDstX, kDstY, kCopyW, kCopyH, 8); ExpectImage(ReadTexImage(destination, format), want, format, "the texture a renderbuffer was copied into"); ASSERT_EQ(FirstGLError(), 0u) << "glGetTexImage raised a GL error"; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CsoContentAddressingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CsoContentAddressingScenario.cpp index 51b2a457..bd586d66 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CsoContentAddressingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CsoContentAddressingScenario.cpp @@ -177,8 +177,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } m_program = CompileProgram(kVS, kFS, &error); ASSERT_NE(m_program, 0u) << error; - const float quad[12] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; + const float quad[12] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); glGenBuffers(1, &m_vbo); @@ -303,8 +302,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } "reuse them. It reported: " << window.line; EXPECT_LT(window.mints, window.binds) - << "with content addressing on the cache must be answering binds it did not mint. " - << window.line; + << "with content addressing on the cache must be answering binds it did not mint. " << window.line; } else if (m_lane == kLaneNoContentAddressing) { EXPECT_EQ(window.mints, window.binds) << "kMGPipeBehaviourNoCsoContentAddressing (bit 63 of MOBILEGL_PIPE_PUSH) must make every " @@ -319,12 +317,12 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // ... and the picture is the same in both arms and after every toggle. The quad is // opaque, so the blended and unblended draws agree by construction. - EXPECT_TRUE(RegionIsMostly(first, kInset, first.Width() - kInset, kInset, first.Height() - kInset, - "green", 0.0, "the blend-toggle frame [" + m_lane + "]")); + EXPECT_TRUE(RegionIsMostly(first, kInset, first.Width() - kInset, kInset, first.Height() - kInset, "green", + 0.0, "the blend-toggle frame [" + m_lane + "]")); const Image second = RunBlendToggleFrame(); - EXPECT_TRUE(second == first) - << "the second toggle frame does not match the first: " << second.ByteDiffCount(first) - << " bytes differ. The CSO path must not change what is drawn."; + EXPECT_TRUE(second == first) << "the second toggle frame does not match the first: " + << second.ByteDiffCount(first) + << " bytes differ. The CSO path must not change what is drawn."; } } // namespace diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CtWireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CtWireScenario.cpp index 5fd086d5..17547b37 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CtWireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CtWireScenario.cpp @@ -31,7 +31,7 @@ #include "../Harness/SplitRuntimePeek.h" #include -#include // `t6`: the spawn arm cannot observe the server process's counters +#include // `t6`: the spawn arm cannot observe the server process's counters #include #if !defined(_WIN32) @@ -50,378 +50,382 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { + namespace { - MobileGL::MG_Remote::Server::ServerVerbSink& ServerVerbs() { - return MobileGL::MG_Remote::Server::ServerSessionInstance().Applier().Verbs(); - } - - // Inproc reads its local sink. A separate-process session snapshots that - // same sink on LogFlush, after the client fences its complete command prefix. - // TCP forwards the server log; unix+shm reads the server's local role file. - struct CounterSnapshot { - bool valid = false; - MobileGL::Uint64 resets = 0, serial = 0, deaths = 0; - }; - - CounterSnapshot ServerCounters() { - if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Spawn) { - return {true, ServerVerbs().ApplierResets(), ServerVerbs().ExpectedApplierResetSerial(), - ServerVerbs().ObjectDeaths()}; - } - MGPipeSyncPeerLog(); - const std::string log = PipeStatsWindow::ReadWholeFile(PipeStatsWindow::ServerLibraryLogPath()); - const auto at = log.rfind("MGPipe server counters:"); - if (at == std::string::npos) return {}; - const PipeStatsWindow::Window window{true, log.substr(at, log.find('\n', at) - at)}; - const auto resets = PipeStatsWindow::CounterOrAbsent(window, "resets"); - const auto serial = PipeStatsWindow::CounterOrAbsent(window, "reset-serial"); - const auto deaths = PipeStatsWindow::CounterOrAbsent(window, "deaths"); - if (resets < 0 || serial < 0 || deaths < 0) return {}; - return {true, static_cast(resets), static_cast(serial), - static_cast(deaths)}; - } - - class CtWireScenario : public ScenarioTest { - protected: - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - const auto why = SplitRuntimeSkipReason(); - if (!why.empty()) GTEST_SKIP() << why; + MobileGL::MG_Remote::Server::ServerVerbSink& ServerVerbs() { + return MobileGL::MG_Remote::Server::ServerSessionInstance().Applier().Verbs(); } - // One 4x4 RGBA8 texture whose every texel is `rgba`, uploaded so the object crosses - // (no handle, no record - CONTRACT-P5C.md §5.2: an object that never crossed emits - // nothing at death, and this case needs a real death). - GLuint MakeSolidTexture(const GLubyte rgba[4]) { - GLuint texture = 0; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - GLubyte texels[4 * 4 * 4]; - for (int i = 0; i < 4 * 4; ++i) { - for (int c = 0; c < 4; ++c) texels[i * 4 + c] = rgba[c]; + // Inproc reads its local sink. A separate-process session snapshots that + // same sink on LogFlush, after the client fences its complete command prefix. + // TCP forwards the server log; unix+shm reads the server's local role file. + struct CounterSnapshot { + bool valid = false; + MobileGL::Uint64 resets = 0, serial = 0, deaths = 0; + }; + + CounterSnapshot ServerCounters() { + if (MobileGL::MG_Config::Transport != MobileGL::MG_Config::TransportMode::Spawn) { + return {true, ServerVerbs().ApplierResets(), ServerVerbs().ExpectedApplierResetSerial(), + ServerVerbs().ObjectDeaths()}; } - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, texels); - return texture; + MGPipeSyncPeerLog(); + const std::string log = PipeStatsWindow::ReadWholeFile(PipeStatsWindow::ServerLibraryLogPath()); + const auto at = log.rfind("MGPipe server counters:"); + if (at == std::string::npos) return {}; + const PipeStatsWindow::Window window{true, log.substr(at, log.find('\n', at) - at)}; + const auto resets = PipeStatsWindow::CounterOrAbsent(window, "resets"); + const auto serial = PipeStatsWindow::CounterOrAbsent(window, "reset-serial"); + const auto deaths = PipeStatsWindow::CounterOrAbsent(window, "deaths"); + if (resets < 0 || serial < 0 || deaths < 0) return {}; + return {true, static_cast(resets), static_cast(serial), + static_cast(deaths)}; } - // The texture's level-0 content, read back through a throwaway FBO, as four bytes. - // The FBO is bound AND deleted here, so it never leaks a framebuffer death into the - // tally a case is watching. - void ReadTextureLevel(GLuint texture, GLubyte out[4]) { - GLuint fbo = 0; + class CtWireScenario : public ScenarioTest { + protected: + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + const auto why = SplitRuntimeSkipReason(); + if (!why.empty()) GTEST_SKIP() << why; + } + + // One 4x4 RGBA8 texture whose every texel is `rgba`, uploaded so the object crosses + // (no handle, no record - CONTRACT-P5C.md §5.2: an object that never crossed emits + // nothing at death, and this case needs a real death). + GLuint MakeSolidTexture(const GLubyte rgba[4]) { + GLuint texture = 0; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + GLubyte texels[4 * 4 * 4]; + for (int i = 0; i < 4 * 4; ++i) { + for (int c = 0; c < 4; ++c) + texels[i * 4 + c] = rgba[c]; + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, texels); + return texture; + } + + // The texture's level-0 content, read back through a throwaway FBO, as four bytes. + // The FBO is bound AND deleted here, so it never leaks a framebuffer death into the + // tally a case is watching. + void ReadTextureLevel(GLuint texture, GLubyte out[4]) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, out); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + } + }; + + TEST_F(CtWireScenario, ApplierResetCrossesAtThePrimedEdgeInSerialOrder) { + if (!Ready()) return; + // The first verb of the process primes the tracker; the edge is the producer. The + // clear also gives the case its pixels, so a lane that emitted nothing is red twice + // (here and in the fixture's emit-ordinal rule). + glClearColor(0.2f, 0.4f, 0.6f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + const auto counters = ServerCounters(); + ASSERT_TRUE(counters.valid) << "server control-record telemetry missing"; + EXPECT_GE(counters.resets, 1u) + << "the FreshlyPrimed edge emitted no applier_reset; the server's g_applier was " + "never reset for this session"; + // The serial sequence is the session's own count (§1: asserted, never dispatched + // on): every record the sink accepted carried the serial it expected, or the counter + // and the accepted tally would disagree. + EXPECT_EQ(counters.serial, counters.resets) + << "an applier_reset record was dropped or replayed on the wire"; + + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.ApplierReset.error"; + const int expected[4] = {51, 102, 153, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "Ct.ApplierReset.pixels"; + } + + TEST_F(CtWireScenario, TextureDeathCrossesAndTheRecycledSlotAnswersTheNewObject) { + if (!Ready()) return; + // P7 wave 2 package C: THE BACKEND GUARD IS GONE, and both halves of why it was here + // are answered rather than waived. The producer used to be Espryt-side only + // (OnFrontendStateObjectDestroyed, CONTRACT-P5C.md §5.2), so a DirectVulkan run had + // no death record to watch; Magma now installs its own StateObjectDeathOps + // (DirectVulkan.cpp, CONTRACT-P7 §5.5) and emits the same `object_death`. Package L + // measured that the SPAWN arm already passed once the WireTables install condition + // went backend-agnostic and that the INPROC arm failed here with `deaths` 0 vs 0 + // (docs/archive/notes/p7/magma-two-process-first-run.md §6); the table is what closes the inproc + // half, and this case is the gate on it. It is also the RED-ONCE for that table: with + // the install site short-circuited, both cases red on the two EXPECT_GT below. + const GLubyte red[4] = {255, 0, 0, 255}; + const GLubyte green[4] = {0, 255, 0, 255}; + + // A texture lives and crosses (the upload emits its records). It is also READ BACK + // once before it dies: the read forces the server-side sync that creates the twin - + // and, one level down, resolves the Espryt slot arm that INSTALLS the death-notice + // ops, which no process has before its first twin lookup. The red picture is the + // pre-death control the recycle below is read against. + GLuint texture = MakeSolidTexture(red); + GLubyte before[4]{}; + ReadTextureLevel(texture, before); + const int redExpected[4] = {255, 0, 0, 255}; + for (int i = 0; i < 4; ++i) + ASSERT_NEAR(before[i], redExpected[i], 1) << "Ct.TextureDeath.before"; + + // The object dies unbound so the destructor - and the death record - fire at the + // delete, and the tally is read AFTER the EmitAndWait the delete blocked on. + glBindTexture(GL_TEXTURE_2D, 0); + const auto beforeCounters = ServerCounters(); + ASSERT_TRUE(beforeCounters.valid) << "server object-death telemetry missing"; + const MobileGL::Uint64 deathsBefore = beforeCounters.deaths; + glDeleteTextures(1, &texture); + // THE FENCE, AND WHY IT IS NO LONGER A CLEAR. + // + // object_death is a kCtxObject value-class record whose wait class is kWaitNone, so + // under run-ahead the client publishes it and moves on, and the server-side tally below + // only moves at APPLY time. The clear this case used to fence on is kWaitNone TOO - + // PipeCalls.def's Clear row - so once the wait column governed (P5e's run-ahead), it + // waited for nothing and the read below raced the apply thread. Nothing had hidden that + // for a while except the per-record barriers the converted rows still took; P12 took + // those away on the wire arm (set_texture_params, respecify and the texture sub-data + // stop owning an answer), the client now runs further ahead, and the race stopped being + // a rarity. MEASURED on this entry under llvmpipe: 2 red of 5 runs (`deaths` 1 vs 1, + // the record applied a moment later) and 3 green of 3 with MOBILEGL_IPC_RUN_AHEAD=0. A + // timing race, never a lost record: the notice is emitted and applied either way. + // + // WHAT IS A REAL BOUNDARY, MEASURED ON ALL THREE ARMS RATHER THAN ASSUMED. The + // harness's applied-seq fence (WaitForSplitAppliedForTesting) is the right shape for + // inproc - and it is NOT usable here: this case is registered on the spawn and tcp arms + // too, the client's progress page there is advanced by frames rather than by the + // peer's own apply, and the wait times out. MEASURED with that fence in place: this + // case red at 5.0 s on DirectGLES.Tcp.Ct. + // + // A READBACK IS THE ONE BOUNDARY THAT HOLDS ON EVERY ARM. ReadPixels is + // kReplySlot|kWaitReply, so the client blocks on its answer, and an answer is posted + // only after its record has been APPLIED - and the ring applies in order, so the death + // published before it is applied too. (On the spawn and tcp arms the counter below + // comes from the peer's log, which ServerCounters() syncs before it reads - and that + // sync is itself a round trip the death has already crossed.) + GLubyte fencePixel[4]{}; + glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, fencePixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the fence readback left a GL error behind"; + const auto afterCounters = ServerCounters(); + ASSERT_TRUE(afterCounters.valid) << "server object-death telemetry missing"; + EXPECT_GT(afterCounters.deaths, deathsBefore) + << "the texture's death produced no object_death record; the server's twin was " + "never told to let go"; + + // The slot recycles forward: a new texture (the frontend hands the same GL name back + // more often than not, which is exactly the ABA shape) must answer with ITS content, + // not the dead twin's. Red then green, read back as pixels: a stale twin is red. + texture = MakeSolidTexture(green); + GLubyte pixel[4]{}; + ReadTextureLevel(texture, pixel); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &texture); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.TextureDeath.error"; + const int expected[4] = {0, 255, 0, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) + << "Ct.TextureDeath.pixels - the recycled texture read back the dead twin's content"; + } + + // ------------------------------------------------------------------------------------ + // P7 wave 2 package C, OQ-10 (CONTRACT-P7 §5.4): the resident-sub-data capability, read + // on the CLIENT, on a real session, on both backends and all three transports. + // + // WHAT IT PINS AND WHY IT IS HERE RATHER THAN IN A BUFFER SCENARIO. `kCapResidentSubData` + // is the one thing the frontend can ask, under a transport, about whether the side that + // will APPLY a write implements the resident arm: the client has no op table of its own + // to probe (MGPipeResourceOpsHaveSubDataResident says so in as many words) and reads this + // bit instead. Until this package nothing ever ORed it into the server's mask, so the + // answer was permanently "no" - which silently pinned BOTH backends to the ordered + // in-place host memcpy and made Magma's own SubDataResident arm + // (VkBufferManager.cpp's g_vulkanWireResourceOps) dead code on the wire. A capability + // that is never published is indistinguishable from one that does not exist, and no + // pixel, GL query or error can tell the two apart - so the bit itself is the assertion. + // + // THE SERVER HALF IS ASSERTED ELSEWHERE, deliberately: RemoteClientTest's + // MagmaTransportPublishesRealBufferConsumersWithoutRunAhead pins that the bit is DERIVED + // FROM THE SERVER'S OWN RESOURCE TABLE (`ops->SubDataResident != nullptr`) and never from + // a backend enum, which is ID-39's lesson. This case pins the other half - that what the + // server published actually reached the client - and the two together are what make the + // frontend's read of it mean anything. + // + // WHAT IT DOES NOT PIN, AND THE REASON IS R-6 RATHER THAN AN OMISSION. This does not + // assert that a `buffer_subdata_resident` record (opcode 49) crossed, because under a + // transport none can yet: the resident arm is reached only from a store that is GPU + // RESIDENT, residency comes only from AdoptPersistentMap, and every persistent-map + // acquisition declines under split by R-6 (PipeApply.cpp's MGPipeApplyMapPersistent: + // "A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION, ALWAYS"; T0/T1 are + // unimplemented, refused by name at the handshake since P11 A1). So the capability is wired + // AHEAD of its consumer on purpose, and `rsd=` on the PipeStats line is the counter that + // will show the records the day P11 lands a tier that adopts. See docs/archive/notes/p7/magma-c.md. + TEST_F(CtWireScenario, TheServerPublishesTheResidentSubDataCapabilityFromItsOwnTable) { + if (!Ready()) return; + // A REAL WORKLOAD FIRST, and it is not decoration: ScenarioFixture's arming rule + // (ScenarioFixture.h:90) fails a case that runs in an armed split lane without moving + // the client encoder's record ordinal, because a case that emits nothing cannot tell + // "the transport resolved and carried this session" from "the emit table resolved and + // then put nothing on the wire". The bit below is a property OF that session, so the + // session has to have carried something before the reading means anything. + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.ResidentSubDataCap.workload"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "Ct.ResidentSubDataCap.pixels"; + + const auto runtime = PeekSplitRuntime(); + if (!runtime.peekAvailable || !runtime.sessionActive) { + GTEST_SKIP() << "no split runtime peek or no live client session: 'could not look' " + "and 'the bit was withheld' are the same false"; + } + EXPECT_TRUE(runtime.residentSubDataCap) + << "the client's caps mirror did not receive kCapResidentSubData on a " << runtime.transportName + << " session with backend " << Gl().BackendName() + << ". The server publishes it in MG_Backend/Init.cpp's InitServerRoleCommon from " + "its own wire resource table; without it every resident write falls back to " + "the in-place memcpy and opcode 49 never crosses on either backend"; + } + + TEST_F(CtWireScenario, FramebufferDeathCrossesAndTheRecycledSlotAnswersTheNewObject) { + if (!Ready()) return; + // Backend-agnostic for the texture case's reason (P7 wave 2 package C): both + // backends now install a StateObjectDeathOps table and both emit `object_death`. + // Framebuffer is the kind object_death EXISTS for: it has no other wire delete + // opcode (CONTRACT-P5C.md §5.2) - which is exactly why the missing Magma table was + // a SERVER-side twin leak and not merely a missing counter: with no consumer for + // the notice, nothing on the wire ever told the server this framebuffer had died. + // The renderbuffer goes along so the FBO has storage. + GLuint fbo = 0, renderbuffer = 0; glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, out); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) + << "Ct.FramebufferDeath.setup"; + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + const int blue[4] = {0, 0, 255, 255}; + for (int i = 0; i < 4; ++i) + ASSERT_NEAR(pixel[i], blue[i], 1) << "Ct.FramebufferDeath.first"; + + // Die unbound, framebuffer first so the attachment's own death is a separate record. glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + const auto beforeCounters = ServerCounters(); + ASSERT_TRUE(beforeCounters.valid) << "server object-death telemetry missing"; + const MobileGL::Uint64 deathsBefore = beforeCounters.deaths; + // THE CLIENT'S OWN ACCOUNTING, read beside the server's (P7 wave 2 package C, + // CONTRACT-P7 §5.5's PipeSlotPeek clause). The two numbers answer DIFFERENT halves of + // one death and neither implies the other, which is exactly why both are read here: + // * `deaths` below says the SERVER was told - the half the missing Magma table + // broke, and the only delivery a framebuffer has (no wire delete opcode at all); + // * this one says the CLIENT returned the slot. That half has been backend-neutral + // since P4a (PipeFill.cpp's NotifyAndFree frees whatever the notice does), so it + // is a CONTROL rather than the subject: if it ever regressed, a green `deaths` + // would be measuring an object that never let go of its handle. + // `false` means the allocator is out of reach (a pull build, or Android's hidden + // symbols), and the peek is then simply not asserted - "could not look" is not + // "did not leak", so nothing is concluded from it either way. + unsigned fboSlotsBefore = 0; + const bool slotsReadable = PeekPipeSlotLiveCount(PipeSlotKind::Framebuffer, &fboSlotsBefore); glDeleteFramebuffers(1, &fbo); - } - }; - - TEST_F(CtWireScenario, ApplierResetCrossesAtThePrimedEdgeInSerialOrder) { - if (!Ready()) return; - // The first verb of the process primes the tracker; the edge is the producer. The - // clear also gives the case its pixels, so a lane that emitted nothing is red twice - // (here and in the fixture's emit-ordinal rule). - glClearColor(0.2f, 0.4f, 0.6f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - const auto counters = ServerCounters(); - ASSERT_TRUE(counters.valid) << "server control-record telemetry missing"; - EXPECT_GE(counters.resets, 1u) - << "the FreshlyPrimed edge emitted no applier_reset; the server's g_applier was " - "never reset for this session"; - // The serial sequence is the session's own count (§1: asserted, never dispatched - // on): every record the sink accepted carried the serial it expected, or the counter - // and the accepted tally would disagree. - EXPECT_EQ(counters.serial, counters.resets) - << "an applier_reset record was dropped or replayed on the wire"; - - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.ApplierReset.error"; - const int expected[4] = {51, 102, 153, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "Ct.ApplierReset.pixels"; - } - - TEST_F(CtWireScenario, TextureDeathCrossesAndTheRecycledSlotAnswersTheNewObject) { - if (!Ready()) return; - // P7 wave 2 package C: THE BACKEND GUARD IS GONE, and both halves of why it was here - // are answered rather than waived. The producer used to be Espryt-side only - // (OnFrontendStateObjectDestroyed, CONTRACT-P5C.md §5.2), so a DirectVulkan run had - // no death record to watch; Magma now installs its own StateObjectDeathOps - // (DirectVulkan.cpp, CONTRACT-P7 §5.5) and emits the same `object_death`. Package L - // measured that the SPAWN arm already passed once the WireTables install condition - // went backend-agnostic and that the INPROC arm failed here with `deaths` 0 vs 0 - // (docs/archive/notes/p7/magma-two-process-first-run.md §6); the table is what closes the inproc - // half, and this case is the gate on it. It is also the RED-ONCE for that table: with - // the install site short-circuited, both cases red on the two EXPECT_GT below. - const GLubyte red[4] = {255, 0, 0, 255}; - const GLubyte green[4] = {0, 255, 0, 255}; - - // A texture lives and crosses (the upload emits its records). It is also READ BACK - // once before it dies: the read forces the server-side sync that creates the twin - - // and, one level down, resolves the Espryt slot arm that INSTALLS the death-notice - // ops, which no process has before its first twin lookup. The red picture is the - // pre-death control the recycle below is read against. - GLuint texture = MakeSolidTexture(red); - GLubyte before[4]{}; - ReadTextureLevel(texture, before); - const int redExpected[4] = {255, 0, 0, 255}; - for (int i = 0; i < 4; ++i) ASSERT_NEAR(before[i], redExpected[i], 1) << "Ct.TextureDeath.before"; - - // The object dies unbound so the destructor - and the death record - fire at the - // delete, and the tally is read AFTER the EmitAndWait the delete blocked on. - glBindTexture(GL_TEXTURE_2D, 0); - const auto beforeCounters = ServerCounters(); - ASSERT_TRUE(beforeCounters.valid) << "server object-death telemetry missing"; - const MobileGL::Uint64 deathsBefore = beforeCounters.deaths; - glDeleteTextures(1, &texture); - // THE FENCE, AND WHY IT IS NO LONGER A CLEAR. - // - // object_death is a kCtxObject value-class record whose wait class is kWaitNone, so - // under run-ahead the client publishes it and moves on, and the server-side tally below - // only moves at APPLY time. The clear this case used to fence on is kWaitNone TOO - - // PipeCalls.def's Clear row - so once the wait column governed (P5e's run-ahead), it - // waited for nothing and the read below raced the apply thread. Nothing had hidden that - // for a while except the per-record barriers the converted rows still took; P12 took - // those away on the wire arm (set_texture_params, respecify and the texture sub-data - // stop owning an answer), the client now runs further ahead, and the race stopped being - // a rarity. MEASURED on this entry under llvmpipe: 2 red of 5 runs (`deaths` 1 vs 1, - // the record applied a moment later) and 3 green of 3 with MOBILEGL_IPC_RUN_AHEAD=0. A - // timing race, never a lost record: the notice is emitted and applied either way. - // - // WHAT IS A REAL BOUNDARY, MEASURED ON ALL THREE ARMS RATHER THAN ASSUMED. The - // harness's applied-seq fence (WaitForSplitAppliedForTesting) is the right shape for - // inproc - and it is NOT usable here: this case is registered on the spawn and tcp arms - // too, the client's progress page there is advanced by frames rather than by the - // peer's own apply, and the wait times out. MEASURED with that fence in place: this - // case red at 5.0 s on DirectGLES.Tcp.Ct. - // - // A READBACK IS THE ONE BOUNDARY THAT HOLDS ON EVERY ARM. ReadPixels is - // kReplySlot|kWaitReply, so the client blocks on its answer, and an answer is posted - // only after its record has been APPLIED - and the ring applies in order, so the death - // published before it is applied too. (On the spawn and tcp arms the counter below - // comes from the peer's log, which ServerCounters() syncs before it reads - and that - // sync is itself a round trip the death has already crossed.) - GLubyte fencePixel[4]{}; - glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, fencePixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the fence readback left a GL error behind"; - const auto afterCounters = ServerCounters(); - ASSERT_TRUE(afterCounters.valid) << "server object-death telemetry missing"; - EXPECT_GT(afterCounters.deaths, deathsBefore) - << "the texture's death produced no object_death record; the server's twin was " - "never told to let go"; - - // The slot recycles forward: a new texture (the frontend hands the same GL name back - // more often than not, which is exactly the ABA shape) must answer with ITS content, - // not the dead twin's. Red then green, read back as pixels: a stale twin is red. - texture = MakeSolidTexture(green); - GLubyte pixel[4]{}; - ReadTextureLevel(texture, pixel); - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteTextures(1, &texture); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.TextureDeath.error"; - const int expected[4] = {0, 255, 0, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) - << "Ct.TextureDeath.pixels - the recycled texture read back the dead twin's content"; - } - - // ------------------------------------------------------------------------------------ - // P7 wave 2 package C, OQ-10 (CONTRACT-P7 §5.4): the resident-sub-data capability, read - // on the CLIENT, on a real session, on both backends and all three transports. - // - // WHAT IT PINS AND WHY IT IS HERE RATHER THAN IN A BUFFER SCENARIO. `kCapResidentSubData` - // is the one thing the frontend can ask, under a transport, about whether the side that - // will APPLY a write implements the resident arm: the client has no op table of its own - // to probe (MGPipeResourceOpsHaveSubDataResident says so in as many words) and reads this - // bit instead. Until this package nothing ever ORed it into the server's mask, so the - // answer was permanently "no" - which silently pinned BOTH backends to the ordered - // in-place host memcpy and made Magma's own SubDataResident arm - // (VkBufferManager.cpp's g_vulkanWireResourceOps) dead code on the wire. A capability - // that is never published is indistinguishable from one that does not exist, and no - // pixel, GL query or error can tell the two apart - so the bit itself is the assertion. - // - // THE SERVER HALF IS ASSERTED ELSEWHERE, deliberately: RemoteClientTest's - // MagmaTransportPublishesRealBufferConsumersWithoutRunAhead pins that the bit is DERIVED - // FROM THE SERVER'S OWN RESOURCE TABLE (`ops->SubDataResident != nullptr`) and never from - // a backend enum, which is ID-39's lesson. This case pins the other half - that what the - // server published actually reached the client - and the two together are what make the - // frontend's read of it mean anything. - // - // WHAT IT DOES NOT PIN, AND THE REASON IS R-6 RATHER THAN AN OMISSION. This does not - // assert that a `buffer_subdata_resident` record (opcode 49) crossed, because under a - // transport none can yet: the resident arm is reached only from a store that is GPU - // RESIDENT, residency comes only from AdoptPersistentMap, and every persistent-map - // acquisition declines under split by R-6 (PipeApply.cpp's MGPipeApplyMapPersistent: - // "A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION, ALWAYS"; T0/T1 are - // unimplemented, refused by name at the handshake since P11 A1). So the capability is wired - // AHEAD of its consumer on purpose, and `rsd=` on the PipeStats line is the counter that - // will show the records the day P11 lands a tier that adopts. See docs/archive/notes/p7/magma-c.md. - TEST_F(CtWireScenario, TheServerPublishesTheResidentSubDataCapabilityFromItsOwnTable) { - if (!Ready()) return; - // A REAL WORKLOAD FIRST, and it is not decoration: ScenarioFixture's arming rule - // (ScenarioFixture.h:90) fails a case that runs in an armed split lane without moving - // the client encoder's record ordinal, because a case that emits nothing cannot tell - // "the transport resolved and carried this session" from "the emit table resolved and - // then put nothing on the wire". The bit below is a property OF that session, so the - // session has to have carried something before the reading means anything. - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.ResidentSubDataCap.workload"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) - << "Ct.ResidentSubDataCap.pixels"; - - const auto runtime = PeekSplitRuntime(); - if (!runtime.peekAvailable || !runtime.sessionActive) { - GTEST_SKIP() << "no split runtime peek or no live client session: 'could not look' " - "and 'the bit was withheld' are the same false"; - } - EXPECT_TRUE(runtime.residentSubDataCap) - << "the client's caps mirror did not receive kCapResidentSubData on a " - << runtime.transportName << " session with backend " << Gl().BackendName() - << ". The server publishes it in MG_Backend/Init.cpp's InitServerRoleCommon from " - "its own wire resource table; without it every resident write falls back to " - "the in-place memcpy and opcode 49 never crosses on either backend"; - } - - TEST_F(CtWireScenario, FramebufferDeathCrossesAndTheRecycledSlotAnswersTheNewObject) { - if (!Ready()) return; - // Backend-agnostic for the texture case's reason (P7 wave 2 package C): both - // backends now install a StateObjectDeathOps table and both emit `object_death`. - // Framebuffer is the kind object_death EXISTS for: it has no other wire delete - // opcode (CONTRACT-P5C.md §5.2) - which is exactly why the missing Magma table was - // a SERVER-side twin leak and not merely a missing counter: with no consumer for - // the notice, nothing on the wire ever told the server this framebuffer had died. - // The renderbuffer goes along so the FBO has storage. - GLuint fbo = 0, renderbuffer = 0; - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) - << "Ct.FramebufferDeath.setup"; - glClearColor(0.0f, 0.0f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - const int blue[4] = {0, 0, 255, 255}; - for (int i = 0; i < 4; ++i) ASSERT_NEAR(pixel[i], blue[i], 1) << "Ct.FramebufferDeath.first"; - - // Die unbound, framebuffer first so the attachment's own death is a separate record. - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - const auto beforeCounters = ServerCounters(); - ASSERT_TRUE(beforeCounters.valid) << "server object-death telemetry missing"; - const MobileGL::Uint64 deathsBefore = beforeCounters.deaths; - // THE CLIENT'S OWN ACCOUNTING, read beside the server's (P7 wave 2 package C, - // CONTRACT-P7 §5.5's PipeSlotPeek clause). The two numbers answer DIFFERENT halves of - // one death and neither implies the other, which is exactly why both are read here: - // * `deaths` below says the SERVER was told - the half the missing Magma table - // broke, and the only delivery a framebuffer has (no wire delete opcode at all); - // * this one says the CLIENT returned the slot. That half has been backend-neutral - // since P4a (PipeFill.cpp's NotifyAndFree frees whatever the notice does), so it - // is a CONTROL rather than the subject: if it ever regressed, a green `deaths` - // would be measuring an object that never let go of its handle. - // `false` means the allocator is out of reach (a pull build, or Android's hidden - // symbols), and the peek is then simply not asserted - "could not look" is not - // "did not leak", so nothing is concluded from it either way. - unsigned fboSlotsBefore = 0; - const bool slotsReadable = - PeekPipeSlotLiveCount(PipeSlotKind::Framebuffer, &fboSlotsBefore); - glDeleteFramebuffers(1, &fbo); - // THE FENCE, the texture case's exactly: object_death is a kCtxObject value-class - // record with wait class kWaitNone, so under run-ahead it is published WITHOUT waiting - // for its own apply (production-safe: the in-order ring applies it before any record - // that recycles the slot), and the server-side counter this assertion reads only moves - // at apply time. A clear cannot be that boundary - its own class is kWaitNone too - so - // this waits on a READBACK, which is kWaitReply on every arm. The texture case carries - // the measurements, including the boundary that is right for inproc and wrong for - // spawn/tcp. - GLubyte fencePixel[4]{}; - glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, fencePixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the fence readback left a GL error behind"; - const auto afterCounters = ServerCounters(); - ASSERT_TRUE(afterCounters.valid) << "server object-death telemetry missing"; - EXPECT_GT(afterCounters.deaths, deathsBefore) - << "the framebuffer's death produced no object_death record - and no other " - "opcode can carry it. Backend " - << Gl().BackendName(); - if (slotsReadable) { - unsigned fboSlotsAfter = 0; - ASSERT_TRUE(PeekPipeSlotLiveCount(PipeSlotKind::Framebuffer, &fboSlotsAfter)); - EXPECT_LT(fboSlotsAfter, fboSlotsBefore) - << "the dead framebuffer kept its client slot: live count " << fboSlotsBefore - << " -> " << fboSlotsAfter << " across the delete. Backend " + // THE FENCE, the texture case's exactly: object_death is a kCtxObject value-class + // record with wait class kWaitNone, so under run-ahead it is published WITHOUT waiting + // for its own apply (production-safe: the in-order ring applies it before any record + // that recycles the slot), and the server-side counter this assertion reads only moves + // at apply time. A clear cannot be that boundary - its own class is kWaitNone too - so + // this waits on a READBACK, which is kWaitReply on every arm. The texture case carries + // the measurements, including the boundary that is right for inproc and wrong for + // spawn/tcp. + GLubyte fencePixel[4]{}; + glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, fencePixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the fence readback left a GL error behind"; + const auto afterCounters = ServerCounters(); + ASSERT_TRUE(afterCounters.valid) << "server object-death telemetry missing"; + EXPECT_GT(afterCounters.deaths, deathsBefore) + << "the framebuffer's death produced no object_death record - and no other " + "opcode can carry it. Backend " << Gl().BackendName(); - } - glDeleteRenderbuffers(1, &renderbuffer); + if (slotsReadable) { + unsigned fboSlotsAfter = 0; + ASSERT_TRUE(PeekPipeSlotLiveCount(PipeSlotKind::Framebuffer, &fboSlotsAfter)); + EXPECT_LT(fboSlotsAfter, fboSlotsBefore) + << "the dead framebuffer kept its client slot: live count " << fboSlotsBefore << " -> " + << fboSlotsAfter << " across the delete. Backend " << Gl().BackendName(); + } + glDeleteRenderbuffers(1, &renderbuffer); - // Recycle: a new FBO and a new backing store, cleared to a different colour. A stale - // twin answering for the recycled framebuffer renders the blue of the dead one. - glGenFramebuffers(1, &fbo); - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) - << "Ct.FramebufferDeath.recycle"; - glClearColor(1.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindRenderbuffer(GL_RENDERBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - glDeleteRenderbuffers(1, &renderbuffer); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.FramebufferDeath.error"; - const int yellow[4] = {255, 255, 0, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], yellow[i], 1) - << "Ct.FramebufferDeath.pixels - the recycled framebuffer read back the dead twin's content"; - } + // Recycle: a new FBO and a new backing store, cleared to a different colour. A stale + // twin answering for the recycled framebuffer renders the blue of the dead one. + glGenFramebuffers(1, &fbo); + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) + << "Ct.FramebufferDeath.recycle"; + glClearColor(1.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteRenderbuffers(1, &renderbuffer); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "Ct.FramebufferDeath.error"; + const int yellow[4] = {255, 255, 0, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], yellow[i], 1) + << "Ct.FramebufferDeath.pixels - the recycled framebuffer read back the dead twin's content"; + } #if !defined(_WIN32) - TEST_F(CtWireScenario, TheDirectApplierResetCallOnTheGLThreadIsRoleViolation) { - if (!Ready()) return; - // A verb in the PARENT, so the fixture's emit-ordinal rule has something to see: the - // death statement below runs only in the child, and a parent that emitted nothing - // is the shape that rule exists to fail. - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - // THE RED-ONCE FOR §5.1, AUTOMATED. This process has a LIVE inproc session, and the - // GL-thread direct call the applier_reset record replaced is a named Fatal there - // (CONTRACT-P5C.md §5.1 / §6 layer 2) - which is exactly the shape reverting - // PipeFill's FreshlyPrimed arm would take, so the guard is what turns that revert - // red. EXPECT_EXIT re-runs the case in a child process: the child brings up its own - // session (its SetUp is this same fixture's) and aborts at the statement. The two - // NON-Fatal arms - monolith, and a transport with no client session - are the unit - // suite's CtWireFatals.TheDirectApplierResetCallSurvivesMonolithAndAWirelessTransport. - // - // The regex is ".*" because the library's Fatal line goes to its log file, not to - // the stderr a death test matches (ServerLoopEglTest's own EXPECT_EXIT does the - // same); the NAME is asserted from the log below, which the child truncated and - // wrote before it died. - EXPECT_EXIT(MobileGL::MG_Pipe::MGPipeApplierReset(), ::testing::KilledBySignal(SIGABRT), ".*"); - // THE CLIENT'S LOG: MGPipeApplierReset is called here, on the GL thread, so the guard's - // line is written under the client role. P6 splits the lane's log per role and the - // suffix rule lives in PipeStatsWindow. - if (const std::string logPath = MGITest::PipeStatsWindow::LibraryLogPath(); !logPath.empty()) { - std::ifstream in(logPath, std::ios::binary); - std::ostringstream log; - log << in.rdbuf(); - EXPECT_NE(log.str().find("Fatal{RoleViolation, \"g_applier\"}"), std::string::npos) - << "the child aborted, but not with the layer-2 guard's own line:\n" - << log.str(); + TEST_F(CtWireScenario, TheDirectApplierResetCallOnTheGLThreadIsRoleViolation) { + if (!Ready()) return; + // A verb in the PARENT, so the fixture's emit-ordinal rule has something to see: the + // death statement below runs only in the child, and a parent that emitted nothing + // is the shape that rule exists to fail. + glClearColor(0.1f, 0.1f, 0.1f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + // THE RED-ONCE FOR §5.1, AUTOMATED. This process has a LIVE inproc session, and the + // GL-thread direct call the applier_reset record replaced is a named Fatal there + // (CONTRACT-P5C.md §5.1 / §6 layer 2) - which is exactly the shape reverting + // PipeFill's FreshlyPrimed arm would take, so the guard is what turns that revert + // red. EXPECT_EXIT re-runs the case in a child process: the child brings up its own + // session (its SetUp is this same fixture's) and aborts at the statement. The two + // NON-Fatal arms - monolith, and a transport with no client session - are the unit + // suite's CtWireFatals.TheDirectApplierResetCallSurvivesMonolithAndAWirelessTransport. + // + // The regex is ".*" because the library's Fatal line goes to its log file, not to + // the stderr a death test matches (ServerLoopEglTest's own EXPECT_EXIT does the + // same); the NAME is asserted from the log below, which the child truncated and + // wrote before it died. + EXPECT_EXIT(MobileGL::MG_Pipe::MGPipeApplierReset(), ::testing::KilledBySignal(SIGABRT), ".*"); + // THE CLIENT'S LOG: MGPipeApplierReset is called here, on the GL thread, so the guard's + // line is written under the client role. P6 splits the lane's log per role and the + // suffix rule lives in PipeStatsWindow. + if (const std::string logPath = MGITest::PipeStatsWindow::LibraryLogPath(); !logPath.empty()) { + std::ifstream in(logPath, std::ios::binary); + std::ostringstream log; + log << in.rdbuf(); + EXPECT_NE(log.str().find("Fatal{RoleViolation, \"g_applier\"}"), std::string::npos) + << "the child aborted, but not with the layer-2 guard's own line:\n" + << log.str(); + } } - } #endif -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/CubeFaceReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/CubeFaceReadbackScenario.cpp index 9a593bd1..9601e49a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/CubeFaceReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/CubeFaceReadbackScenario.cpp @@ -52,8 +52,10 @@ namespace MGITest { void TearDown() override { if (!Ready()) return; glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - for (GLuint buffer : m_buffers) glDeleteBuffers(1, &buffer); - for (GLuint texture : m_textures) glDeleteTextures(1, &texture); + for (GLuint buffer : m_buffers) + glDeleteBuffers(1, &buffer); + for (GLuint texture : m_textures) + glDeleteTextures(1, &texture); m_buffers.clear(); m_textures.clear(); DrainErrors(); @@ -78,15 +80,15 @@ namespace MGITest { GLint count = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &count); for (GLint i = 0; i < count; ++i) { - const auto* ext = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); + const auto* ext = + reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); if (ext != nullptr && std::strcmp(ext, name) == 0) return true; } return false; } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } std::vector m_buffers; @@ -103,8 +105,8 @@ namespace MGITest { glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, kEdge, kEdge); for (int face = 0; face < 6; ++face) { std::vector seed(static_cast(kFaceBytes), static_cast(10 + face)); - glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, 0, 0, kEdge, kEdge, GL_RGBA, - GL_UNSIGNED_BYTE, seed.data()); + glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, 0, 0, kEdge, kEdge, GL_RGBA, GL_UNSIGNED_BYTE, + seed.data()); } ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "seeding the six faces failed"; @@ -120,9 +122,8 @@ namespace MGITest { std::vector packed(static_cast(kFaceBytes), 0); glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, kFaceBytes, packed.data()); for (size_t i = 0; i < packed.size(); ++i) { - ASSERT_EQ(static_cast(packed[i]), 15) - << "byte " << i << ": the pack buffer holds face " << (static_cast(packed[i]) - 10) - << ", not -Z (face 5)"; + ASSERT_EQ(static_cast(packed[i]), 15) << "byte " << i << ": the pack buffer holds face " + << (static_cast(packed[i]) - 10) << ", not -Z (face 5)"; } glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DefaultFramebufferAcrossSwapScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DefaultFramebufferAcrossSwapScenario.cpp index 234eb5c9..d7d1b22c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DefaultFramebufferAcrossSwapScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DefaultFramebufferAcrossSwapScenario.cpp @@ -73,10 +73,8 @@ void main() { o_color = vColor; } // off, so the readback lands on a named colour whatever the driver's exact rounding is. void DrawFullViewportQuad(unsigned int program, float red, float green, float blue) { const float vertices[] = { - -1.0f, -1.0f, red, green, blue, 1.0f, - 1.0f, -1.0f, red, green, blue, 1.0f, - -1.0f, 1.0f, red, green, blue, 1.0f, - 1.0f, 1.0f, red, green, blue, 1.0f, + -1.0f, -1.0f, red, green, blue, 1.0f, 1.0f, -1.0f, red, green, blue, 1.0f, + -1.0f, 1.0f, red, green, blue, 1.0f, 1.0f, 1.0f, red, green, blue, 1.0f, }; GLuint vao = 0, vbo = 0; glGenVertexArrays(1, &vao); @@ -137,7 +135,8 @@ void main() { o_color = vColor; } << "the default framebuffer stopped naming the image the app rendered into once a swap " "had run; Present() acquires ahead of the app (and a kWaitPresent record is applied " "without the app waiting), so this readback must not resolve the freshly acquired " - "image " << gl.FrameIndex() << " frames in"; + "image " + << gl.FrameIndex() << " frames in"; gl.EndFrame(); glDeleteProgram(program); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp index ad06ea59..26e3ca9b 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DefaultScissorFollowsSurfaceScenario.cpp @@ -32,55 +32,55 @@ #include namespace MGITest { -namespace { + namespace { -class DefaultScissorFollowsSurfaceScenario : public ScenarioTest {}; + class DefaultScissorFollowsSurfaceScenario : public ScenarioTest {}; -TEST_F(DefaultScissorFollowsSurfaceScenario, AClearOnALargerSurfaceMadeCurrentReachesItsFarCorner) { - if (!Ready() || IsSkipped()) return; - const EGLDisplay display = eglGetCurrentDisplay(); - const EGLSurface first = eglGetCurrentSurface(EGL_DRAW); - const EGLContext context = eglGetCurrentContext(); - ASSERT_NE(display, EGL_NO_DISPLAY); - ASSERT_NE(first, EGL_NO_SURFACE); - EGLint width = 0, height = 0, configId = 0, configs = 0; - ASSERT_TRUE(eglQuerySurface(display, first, EGL_WIDTH, &width)); - ASSERT_TRUE(eglQuerySurface(display, first, EGL_HEIGHT, &height)); - ASSERT_TRUE(eglQueryContext(display, context, EGL_CONFIG_ID, &configId)); - const EGLint pick[] = {EGL_CONFIG_ID, configId, EGL_NONE}; - EGLConfig config = nullptr; - ASSERT_TRUE(eglChooseConfig(display, pick, &config, 1, &configs)); - ASSERT_EQ(configs, 1); + TEST_F(DefaultScissorFollowsSurfaceScenario, AClearOnALargerSurfaceMadeCurrentReachesItsFarCorner) { + if (!Ready() || IsSkipped()) return; + const EGLDisplay display = eglGetCurrentDisplay(); + const EGLSurface first = eglGetCurrentSurface(EGL_DRAW); + const EGLContext context = eglGetCurrentContext(); + ASSERT_NE(display, EGL_NO_DISPLAY); + ASSERT_NE(first, EGL_NO_SURFACE); + EGLint width = 0, height = 0, configId = 0, configs = 0; + ASSERT_TRUE(eglQuerySurface(display, first, EGL_WIDTH, &width)); + ASSERT_TRUE(eglQuerySurface(display, first, EGL_HEIGHT, &height)); + ASSERT_TRUE(eglQueryContext(display, context, EGL_CONFIG_ID, &configId)); + const EGLint pick[] = {EGL_CONFIG_ID, configId, EGL_NONE}; + EGLConfig config = nullptr; + ASSERT_TRUE(eglChooseConfig(display, pick, &config, 1, &configs)); + ASSERT_EQ(configs, 1); - // The first surface's size goes into the memo: a clear with the scissor test on and no box. - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDisable(GL_CULL_FACE); - glEnable(GL_SCISSOR_TEST); - glClearColor(1.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glFinish(); + // The first surface's size goes into the memo: a clear with the scissor test on and no box. + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDisable(GL_CULL_FACE); + glEnable(GL_SCISSOR_TEST); + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); - const EGLint attribs[] = {EGL_WIDTH, width * 2, EGL_HEIGHT, height * 2, EGL_NONE}; - const EGLSurface second = eglCreatePbufferSurface(display, config, attribs); - if (second == EGL_NO_SURFACE) GTEST_SKIP() << "no second pbuffer surface on this platform"; - ASSERT_TRUE(eglMakeCurrent(display, second, second, context)); - glViewport(0, 0, width * 2, height * 2); - glEnable(GL_CULL_FACE); // a capability change, so the render state is synced again for the clear - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - std::array corner{}; - glReadPixels(width * 2 - 1, height * 2 - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, corner.data()); - EXPECT_EQ(int(corner[0]), 0) << "the clear reaches the larger surface's far corner"; - EXPECT_EQ(int(corner[1]), 255) << "the clear reaches the larger surface's far corner"; + const EGLint attribs[] = {EGL_WIDTH, width * 2, EGL_HEIGHT, height * 2, EGL_NONE}; + const EGLSurface second = eglCreatePbufferSurface(display, config, attribs); + if (second == EGL_NO_SURFACE) GTEST_SKIP() << "no second pbuffer surface on this platform"; + ASSERT_TRUE(eglMakeCurrent(display, second, second, context)); + glViewport(0, 0, width * 2, height * 2); + glEnable(GL_CULL_FACE); // a capability change, so the render state is synced again for the clear + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + std::array corner{}; + glReadPixels(width * 2 - 1, height * 2 - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, corner.data()); + EXPECT_EQ(int(corner[0]), 0) << "the clear reaches the larger surface's far corner"; + EXPECT_EQ(int(corner[1]), 255) << "the clear reaches the larger surface's far corner"; - glDisable(GL_CULL_FACE); - glDisable(GL_SCISSOR_TEST); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - ASSERT_TRUE(eglMakeCurrent(display, first, first, context)); - glViewport(0, 0, width, height); - eglDestroySurface(display, second); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); -} + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + ASSERT_TRUE(eglMakeCurrent(display, first, first, context)); + glViewport(0, 0, width, height); + eglDestroySurface(display, second); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + } -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DeletedBoundBufferScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DeletedBoundBufferScenario.cpp index 0bc3a5b9..4989236a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DeletedBoundBufferScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DeletedBoundBufferScenario.cpp @@ -75,7 +75,9 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } << declaration << "\n" << "void main() {\n" " uint v = 0u;\n" - " if (u_read != 0u) { v = " << read << "; }\n" + " if (u_read != 0u) { v = " + << read + << "; }\n" " marker = 0xC0FFEEu;\n" " value = v;\n" "}\n"; @@ -150,10 +152,12 @@ void main() { GLuint LinkShaders(std::initializer_list shaders, const char* varying = nullptr) { const GLuint program = glCreateProgram(); - for (GLuint s : shaders) glAttachShader(program, s); + for (GLuint s : shaders) + glAttachShader(program, s); if (varying != nullptr) glTransformFeedbackVaryings(program, 1, &varying, GL_INTERLEAVED_ATTRIBS); glLinkProgram(program); - for (GLuint s : shaders) glDeleteShader(s); + for (GLuint s : shaders) + glDeleteShader(s); GLint ok = 0; glGetProgramiv(program, GL_LINK_STATUS, &ok); if (!ok) { @@ -199,7 +203,8 @@ void main() { bound = 0; std::vector payload(64); for (int i = 0; i < kRecycledBuffers; ++i) { - for (auto& word : payload) word = static_cast(i); + for (auto& word : payload) + word = static_cast(i); m_owned.push_back(MakeBuffer(GL_ARRAY_BUFFER, payload.data(), static_cast(payload.size() * sizeof(unsigned int)))); } @@ -238,8 +243,8 @@ void main() { } // The three shader-buffer targets share one shape. - void RunShaderBufferTarget(GLenum target, const char* declaration, const char* read, - unsigned int freshSeed, unsigned int expectedRead) { + void RunShaderBufferTarget(GLenum target, const char* declaration, const char* read, unsigned int freshSeed, + unsigned int expectedRead) { m_compute = LinkCompute(ComputeSource(declaration, read)); ASSERT_NE(m_compute, 0u) << m_log; @@ -297,15 +302,14 @@ void main() { TEST_F(DeletedBoundBufferScenario, AStorageBufferDeletedWhileBoundLeavesNoStaleBinding) { if (!Ready() || IsSkipped()) return; - RunShaderBufferTarget(GL_SHADER_STORAGE_BUFFER, - "layout(std430, binding = 0) buffer Target { uint word; };", "word", - kFreshWord, kFreshWord); + RunShaderBufferTarget(GL_SHADER_STORAGE_BUFFER, "layout(std430, binding = 0) buffer Target { uint word; };", + "word", kFreshWord, kFreshWord); } TEST_F(DeletedBoundBufferScenario, AUniformBufferDeletedWhileBoundLeavesNoStaleBinding) { if (!Ready() || IsSkipped()) return; - RunShaderBufferTarget(GL_UNIFORM_BUFFER, "layout(std140, binding = 0) uniform Target { uint word; };", - "word", kFreshWord, kFreshWord); + RunShaderBufferTarget(GL_UNIFORM_BUFFER, "layout(std140, binding = 0) uniform Target { uint word; };", "word", + kFreshWord, kFreshWord); } // The counter's pre-increment value is what the shader reads, and the buffer then holds seed + 1. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackAttachmentShapeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackAttachmentShapeScenario.cpp index d36be1b1..11275841 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackAttachmentShapeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackAttachmentShapeScenario.cpp @@ -276,12 +276,12 @@ namespace MGITest { GLint depthBits = 0; GLint stencilBits = 0; GLint componentType = GL_UNSIGNED_NORMALIZED; - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH, - GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, &depthBits); - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL, - GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, &stencilBits); - glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH, - GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &componentType); + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE, + &depthBits); + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE, + &stencilBits); + glGetFramebufferAttachmentParameteriv(GL_DRAW_FRAMEBUFFER, GL_DEPTH, GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, + &componentType); EXPECT_EQ(FirstGLError(), 0u); if (depthBits <= 0 || stencilBits <= 0) { GTEST_SKIP() << "this surface has no packed depth/stencil (depth=" << depthBits @@ -290,8 +290,8 @@ namespace MGITest { // The one sized format the reported description names. Getting here with the wrong // answer is the bug: the two candidates are not interchangeable for a blit. - const GLenum reported = (componentType == GL_FLOAT || depthBits > 24) ? GL_DEPTH32F_STENCIL8 - : GL_DEPTH24_STENCIL8; + const GLenum reported = + (componentType == GL_FLOAT || depthBits > 24) ? GL_DEPTH32F_STENCIL8 : GL_DEPTH24_STENCIL8; GLuint fbo = 0; GLuint colorRbo = 0; @@ -350,8 +350,8 @@ namespace MGITest { glDeleteRenderbuffers(1, &colorRbo); glDeleteRenderbuffers(1, &depthRbo); gl.EndFrame(); - GTEST_SKIP() << "backend " << gl.BackendName() << " on this surface transferred no colour either (green=" - << int(color[1]) + GTEST_SKIP() << "backend " << gl.BackendName() + << " on this surface transferred no colour either (green=" << int(color[1]) << "): it cannot blit out of the default framebuffer here, so the depth/stencil half proves " "nothing. The GL-error assertion above still ran, and it is the format contract"; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp index dab46c22..ff34bab8 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackMatrixScenario.cpp @@ -94,13 +94,18 @@ namespace MGITest { GLenum AttachmentPointFor(GLenum internalFormat) { switch (internalFormat) { case GL_DEPTH24_STENCIL8: - case GL_DEPTH32F_STENCIL8: return GL_DEPTH_STENCIL_ATTACHMENT; - case GL_STENCIL_INDEX8: return GL_STENCIL_ATTACHMENT; - default: return GL_DEPTH_ATTACHMENT; + case GL_DEPTH32F_STENCIL8: + return GL_DEPTH_STENCIL_ATTACHMENT; + case GL_STENCIL_INDEX8: + return GL_STENCIL_ATTACHMENT; + default: + return GL_DEPTH_ATTACHMENT; } } - bool FormatHasDepth(GLenum internalFormat) { return internalFormat != GL_STENCIL_INDEX8; } + bool FormatHasDepth(GLenum internalFormat) { + return internalFormat != GL_STENCIL_INDEX8; + } bool FormatHasStencil(GLenum internalFormat) { return internalFormat == GL_DEPTH24_STENCIL8 || internalFormat == GL_DEPTH32F_STENCIL8 || internalFormat == GL_STENCIL_INDEX8; @@ -208,8 +213,8 @@ namespace MGITest { ++bad; } } - EXPECT_EQ(bad, 0u) << what << ": " << bad << " of " << values.size() - << " depth values differ from " << expected << "; first bad value " << worst + EXPECT_EQ(bad, 0u) << what << ": " << bad << " of " << values.size() << " depth values differ from " + << expected << "; first bad value " << worst << (std::fabs(worst - kDepthPoison) < 1e-6f ? " - which is the poison value, so nothing was written at all" : ""); @@ -224,8 +229,8 @@ namespace MGITest { ++bad; } } - EXPECT_EQ(bad, 0u) << what << ": " << bad << " of " << values.size() - << " stencil values differ from " << expected << "; first bad value " << worst + EXPECT_EQ(bad, 0u) << what << ": " << bad << " of " << values.size() << " stencil values differ from " + << expected << "; first bad value " << worst << (worst == kStencilPoison ? " - which is the poison value, so nothing was written at all" : ""); @@ -287,12 +292,14 @@ namespace MGITest { server.find("ResolveWireDepthStencil: resolved by the VK_KHR_depth_stencil_resolve render pass") != std::string::npos; if (forcedBug || elidedSubject || renderPassDropped) { - EXPECT_TRUE(shaderResolved) << what << ": the server never resolved with the shader pass (probe=" - << probe << ", force-shader=" << renderPassDropped << ")"; - EXPECT_FALSE(renderPassResolved) - << what << ": the server resolved with the no-draw render pass, the arm the probe verdict " - "(or the knob) rules out (probe=" << probe + EXPECT_TRUE(shaderResolved) + << what << ": the server never resolved with the shader pass (probe=" << probe << ", force-shader=" << renderPassDropped << ")"; + EXPECT_FALSE(renderPassResolved) + << what + << ": the server resolved with the no-draw render pass, the arm the probe verdict " + "(or the knob) rules out (probe=" + << probe << ", force-shader=" << renderPassDropped << ")"; if (forcedBug) { EXPECT_NE(server.find("verdict=render-pass-resolve-broken"), std::string::npos) << what << ": the forced `bug` measurement did not evaluate to the defect verdict"; @@ -300,10 +307,11 @@ namespace MGITest { if (elidedSubject) { // The source phrase is the REAL measurement's (a canned one says "forced by"), // and ArmWireDepthResolveOrder states it once per process. - EXPECT_NE(server.find("depth/stencil resolve probe (measured on this device with its render-pass " - "resolve elided by MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=elide-subject) " - "verdict=render-pass-resolve-broken"), - std::string::npos) + EXPECT_NE( + server.find("depth/stencil resolve probe (measured on this device with its render-pass " + "resolve elided by MGITEST_MAGMA_DEPTH_RESOLVE_PROBE=elide-subject) " + "verdict=render-pass-resolve-broken"), + std::string::npos) << what << ": the real probe, its render-pass resolve elided, did not report the defect"; // DescribeWireDepthResolveFormat's reading, one line per probed format: some // format's render pass matched no texel and kept the sentinel in all 16, and its @@ -321,8 +329,9 @@ namespace MGITest { line.find("sentinel 16), shader control 16/16") != std::string::npos; } EXPECT_TRUE(sentinelKeptBesideAResolvedControl) - << what << ": no real reading shows the elided render pass's sentinel kept beside a " - "shader control that resolved"; + << what + << ": no real reading shows the elided render pass's sentinel kept beside a " + "shader control that resolved"; } return; } @@ -331,8 +340,9 @@ namespace MGITest { << what << ": the server's resolve probe did not measure this device's render pass clean"; EXPECT_TRUE(renderPassResolved) << what << ": a clean probe verdict must leave the render pass first, and it never resolved"; - EXPECT_FALSE(shaderResolved) << what << ": the shader pass resolved although the probe found the " - "render pass clean"; + EXPECT_FALSE(shaderResolved) << what + << ": the shader pass resolved although the probe found the " + "render pass clean"; } }; @@ -613,8 +623,8 @@ namespace MGITest { glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampled.fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolved.fbo); glDisable(GL_SCISSOR_TEST); - glBlitFramebuffer(0, kHeight, kWidth, 0, 0, 0, kWidth, kHeight, - GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(0, kHeight, kWidth, 0, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, + GL_NEAREST); glFinish(); EXPECT_EQ(FirstGLError(), 0u) << "a flipped multisample resolve is a legal blit"; @@ -645,8 +655,8 @@ namespace MGITest { constexpr int kSx = 5, kSy = 8, kDx = 20, kDy = 4, kW = 29, kH = 32; glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampled.fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolved.fbo); - glBlitFramebuffer(kSx, kSy + kH, kSx + kW, kSy, kDx, kDy, kDx + kW, kDy + kH, - GL_STENCIL_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(kSx, kSy + kH, kSx + kW, kSy, kDx, kDy, kDx + kW, kDy + kH, GL_STENCIL_BUFFER_BIT, + GL_NEAREST); glFinish(); EXPECT_EQ(FirstGLError(), 0u) << "a flipped, narrow multisample stencil resolve is a legal blit"; glBindFramebuffer(GL_FRAMEBUFFER, resolved.fbo); @@ -663,14 +673,19 @@ namespace MGITest { } const int got = stencil[static_cast(y) * kWidth + x]; if (got != want) { - if (bad == 0) { firstX = x; firstY = y; firstGot = got; firstWant = want; } + if (bad == 0) { + firstX = x; + firstY = y; + firstGot = got; + firstWant = want; + } ++bad; } } } EXPECT_EQ(bad, 0u) << "flipped narrow stencil resolve: " << bad << " of " << stencil.size() - << " stencil values are wrong; first at (" << firstX << ", " << firstY - << "): got " << firstGot << ", want " << firstWant; + << " stencil values are wrong; first at (" << firstX << ", " << firstY << "): got " + << firstGot << ", want " << firstWant; } // The two flipped resolves above are the only ones in this case that reach an arm: every // leg below declines on its shape first. @@ -681,8 +696,7 @@ namespace MGITest { ASSERT_EQ(FirstGLError(), 0u); glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampled.fbo); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolved.fbo); - glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth / 2, kHeight / 2, - GL_DEPTH_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth / 2, kHeight / 2, GL_DEPTH_BUFFER_BIT, GL_NEAREST); glFinish(); // the decline's error rides a later reply under a transport EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_OPERATION)) << "a scaled multisample resolve is INVALID_OPERATION, not a picture"; @@ -778,13 +792,13 @@ namespace MGITest { std::array window{}; glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, window.data()); EXPECT_EQ(window, (std::array{255, 0, 255, 255})) - << "the declined COLOR|DEPTH scale onto the window must not have written its colour at (" - << x << ", " << y << ")"; + << "the declined COLOR|DEPTH scale onto the window must not have written its colour at (" << x + << ", " << y << ")"; float depth = kDepthPoison; glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depth); EXPECT_NEAR(depth, 0.625f, 1.0f / 4096.0f) - << "the declined COLOR|DEPTH scale onto the window must have left its depth alone at (" - << x << ", " << y << ")"; + << "the declined COLOR|DEPTH scale onto the window must have left its depth alone at (" << x << ", " + << y << ")"; } EXPECT_EQ(FirstGLError(), 0u) << "the session survived the default-framebuffer decline"; } @@ -831,7 +845,7 @@ namespace MGITest { // enough for a CPU rasterizer, plus the matrix's own rectangle. const Extent extents[] = {{1, 1}, {256, 1}, {1, 256}, {kWidth, kHeight}}; - const auto makePair = [](GLenum internalFormat, int width, int height, GLuint (&fbo)[2], GLuint (&rbo)[2]) { + const auto makePair = [](GLenum internalFormat, int width, int height, GLuint(&fbo)[2], GLuint(&rbo)[2]) { glGenFramebuffers(2, fbo); glCreateRenderbuffers(2, rbo); for (int i = 0; i < 2; ++i) { @@ -1125,7 +1139,13 @@ namespace MGITest { if (!Ready()) return; DepthSource source = MakeTextureSource(GL_DEPTH_COMPONENT32F); ASSERT_TRUE(SourceIsUsable()); - struct Sample { float depth; GLbyte byte; GLshort shortValue; GLint intValue; GLushort halfBits; }; + struct Sample { + float depth; + GLbyte byte; + GLshort shortValue; + GLint intValue; + GLushort halfBits; + }; const Sample samples[] = { {0.0f, 0, 0, 0, 0x0000}, {0.5f, 64, 16384, 1073741824, 0x3800}, @@ -1144,10 +1164,11 @@ namespace MGITest { std::vector values(static_cast(kWidth) * kHeight, static_cast(-37)); glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, type, values.data()); EXPECT_EQ(FirstGLError(), 0u) << "depth=" << sample.depth << " type=" << type; - const auto bad = std::count_if(values.begin(), values.end(), [&](Value value) { return value != expected; }); + const auto bad = + std::count_if(values.begin(), values.end(), [&](Value value) { return value != expected; }); EXPECT_EQ(bad, 0) << "depth=" << sample.depth << " type=" << type - << " first=" << static_cast(values[0]) - << " expected=" << static_cast(expected); + << " first=" << static_cast(values[0]) + << " expected=" << static_cast(expected); }; read(GL_BYTE, sample.byte); read(GL_SHORT, sample.shortValue); @@ -1190,8 +1211,8 @@ namespace MGITest { GLuint copy = 0; glGenTextures(1, ©); glBindTexture(GL_TEXTURE_2D, copy); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kWidth, kHeight, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kWidth, kHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + nullptr); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 0, 0, kWidth, kHeight, 0); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackScenario.cpp index ddba3e9e..221bd041 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DepthStencilReadbackScenario.cpp @@ -205,8 +205,8 @@ namespace MGITest { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0); glGenTextures(1, &depthTex); glBindTexture(GL_TEXTURE_2D, depthTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); glViewport(0, 0, width, height); @@ -268,8 +268,8 @@ namespace MGITest { glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTex, 0); glGenTextures(1, &depthTex); glBindTexture(GL_TEXTURE_2D, depthTex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + nullptr); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); ASSERT_EQ(FirstGLError(), 0u); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DescriptorPoolGrowthScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DescriptorPoolGrowthScenario.cpp index 3dddbe5a..0e36b5da 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DescriptorPoolGrowthScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DescriptorPoolGrowthScenario.cpp @@ -231,7 +231,8 @@ void main() { o_color = texture(uTex, vec2(0.5)); } glBindTexture(GL_TEXTURE_2D, 0); BindDefaultFramebuffer(); if (m_program != 0) glDeleteProgram(m_program); - if (!m_textures.empty()) glDeleteTextures(static_cast(m_textures.size()), m_textures.data()); + if (!m_textures.empty()) + glDeleteTextures(static_cast(m_textures.size()), m_textures.data()); if (m_vbo != 0) glDeleteBuffers(1, &m_vbo); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); DestroyColorFbo(m_target); @@ -267,7 +268,8 @@ void main() { o_color = texture(uTex, vec2(0.5)); } // The burst: every pixel, one draw and one descriptor set each. void DrawBurst() { ClearTo(0.0f, 0.0f, 0.0f, 0.0f); - for (int i = 0; i < kDraws; ++i) Draw(i); + for (int i = 0; i < kDraws; ++i) + Draw(i); } static bool IsVulkan() { return Gl().BackendName() == std::string("DirectVulkan"); } @@ -364,8 +366,8 @@ void main() { o_color = texture(uTex, vec2(0.5)); } << "the burst did not allocate one descriptor set per draw: " << Describe(afterBurst); EXPECT_GT(afterQuiet.slotTrims, afterBurst.slotTrims) << quietFrames << " frames of one draw each followed a " << kDraws << "-set frame, but no frame slot was " - << "trimmed (MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES=" << trimFrames << "). After the burst: " - << Describe(afterBurst) << "; after the quiet frames: " << Describe(afterQuiet); + << "trimmed (MOBILEGL_MAGMA_DESCRIPTOR_TRIM_FRAMES=" << trimFrames + << "). After the burst: " << Describe(afterBurst) << "; after the quiet frames: " << Describe(afterQuiet); EXPECT_LE(afterQuiet.cachedSets + held / 2, afterBurst.cachedSets) << "the trim did not give the burst's sets back. After the burst: " << Describe(afterBurst) << "; after the quiet frames: " << Describe(afterQuiet); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp index 5a931749..795a69b3 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp @@ -96,8 +96,7 @@ void main() { glGenBuffers(1, &m_output); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_output); const std::vector zeroes(kOutputSlots, 0.0f); - glBufferData(GL_SHADER_STORAGE_BUFFER, kOutputSlots * sizeof(float), zeroes.data(), - GL_DYNAMIC_DRAW); + glBufferData(GL_SHADER_STORAGE_BUFFER, kOutputSlots * sizeof(float), zeroes.data(), GL_DYNAMIC_DRAW); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_output); glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); } @@ -293,9 +292,8 @@ void main() { // diagnosis rather than "the buffer is wrong". std::string DescribeOffset(const BlockLayout& layout, int offset) { const std::pair members[] = { - {layout.data0, "data0"}, {layout.data1, "data1"}, {layout.data2, "data2"}, - {layout.data3, "data3"}, {layout.data4, "data4"}, {layout.data5, "data5"}, - {layout.data6, "data6"}}; + {layout.data0, "data0"}, {layout.data1, "data1"}, {layout.data2, "data2"}, {layout.data3, "data3"}, + {layout.data4, "data4"}, {layout.data5, "data5"}, {layout.data6, "data6"}}; const char* owner = "(padding before data0)"; for (const auto& [start, name] : members) { if (offset >= start) owner = name; @@ -378,12 +376,10 @@ void main() { }; constexpr DoubleShape kDoubleShapes[] = { - {"g_0 double", 0, 1, 1, &kG0}, {"g_1 dvec2", 1, 1, 2, kG1}, - {"g_2 dvec3", 3, 1, 3, kG2}, {"g_3 dvec4", 6, 1, 4, kG3}, - {"g_4 dmat2", 10, 2, 2, kG4}, {"g_5 dmat2x3", 14, 2, 3, kG5}, - {"g_6 dmat2x4", 20, 2, 4, kG6}, {"g_7 dmat3x2", 28, 3, 2, kG7}, - {"g_8 dmat3", 34, 3, 3, kG8}, {"g_9 dmat3x4", 43, 3, 4, kG9}, - {"g_10 dmat4x2", 55, 4, 2, kG10}, {"g_11 dmat4x3", 63, 4, 3, kG11}, + {"g_0 double", 0, 1, 1, &kG0}, {"g_1 dvec2", 1, 1, 2, kG1}, {"g_2 dvec3", 3, 1, 3, kG2}, + {"g_3 dvec4", 6, 1, 4, kG3}, {"g_4 dmat2", 10, 2, 2, kG4}, {"g_5 dmat2x3", 14, 2, 3, kG5}, + {"g_6 dmat2x4", 20, 2, 4, kG6}, {"g_7 dmat3x2", 28, 3, 2, kG7}, {"g_8 dmat3", 34, 3, 3, kG8}, + {"g_9 dmat3x4", 43, 3, 4, kG9}, {"g_10 dmat4x2", 55, 4, 2, kG10}, {"g_11 dmat4x3", 63, 4, 3, kG11}, {"g_12 dmat4", 75, 4, 4, kG12}, }; @@ -477,7 +473,8 @@ void main() { // Column-major, and every entry distinct so a transposed or mis-strided write // cannot land on a value that happens to match. GLdouble matrixValue[16] = {}; - for (int i = 0; i < 16; ++i) matrixValue[i] = 100.0 + i; + for (int i = 0; i < 16; ++i) + matrixValue[i] = 100.0 + i; glUniformMatrix4dv(matrix, 1, GL_FALSE, matrixValue); const GLdouble arrayValue[3] = {71.0, 72.0, 73.0}; glUniform1dv(array0, 3, arrayValue); @@ -504,7 +501,8 @@ void main() { const GLint matrix = glGetUniformLocation(m_program, "uMatrix"); ASSERT_GE(matrix, 0); GLdouble matrixValue[16] = {}; - for (int i = 0; i < 16; ++i) matrixValue[i] = 100.0 + i; + for (int i = 0; i < 16; ++i) + matrixValue[i] = 100.0 + i; glUniformMatrix4dv(matrix, 1, GL_TRUE, matrixValue); glUseProgram(0); @@ -547,8 +545,8 @@ void main() { GLsizei length = 0; GLint size = 0; GLenum type = 0; - glGetActiveUniform(m_program, static_cast(index), sizeof(name) - 1, &length, &size, - &type, name); + glGetActiveUniform(m_program, static_cast(index), sizeof(name) - 1, &length, &size, &type, + name); if (std::string(name, static_cast(length)) != expectation.name) continue; found = true; EXPECT_EQ(type, expectation.type) << expectation.name; @@ -573,7 +571,8 @@ void main() { const GLdouble vectorValue[3] = {11.5, 12.5, 13.5}; glUniform3dv(vector, 1, vectorValue); GLdouble matrixValue[16] = {}; - for (int i = 0; i < 16; ++i) matrixValue[i] = 100.0 + i; + for (int i = 0; i < 16; ++i) + matrixValue[i] = 100.0 + i; glUniformMatrix4dv(matrix, 1, GL_FALSE, matrixValue); glUseProgram(0); @@ -753,8 +752,7 @@ void main() { for (int c = 0; c < shape.columns; ++c) { for (int r = 0; r < shape.rows; ++r) { const int component = c * shape.rows + r; - EXPECT_FLOAT_EQ(values[shape.base + component], - static_cast(shape.values[component])) + EXPECT_FLOAT_EQ(values[shape.base + component], static_cast(shape.values[component])) << pass << ": " << shape.name << " column " << c << " row " << r; } } @@ -1020,8 +1018,8 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } const char* packing = pass == 0 ? "std140" : "std430"; std::vector observed(expected.size(), 0xEE); glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[2 + pass]); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, - static_cast(observed.size()), observed.data()); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, static_cast(observed.size()), + observed.data()); int mismatches = 0; int firstMismatch = -1; for (std::size_t i = 0; i < expected.size(); ++i) { @@ -1029,12 +1027,11 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } ++mismatches; if (firstMismatch < 0) firstMismatch = static_cast(i); } - EXPECT_EQ(mismatches, 0) - << packing << " block: " << mismatches << " of " << expected.size() - << " bytes differ, first at byte " << firstMismatch << " (in " - << DescribeOffset(layout, firstMismatch < 0 ? 0 : firstMismatch) - << "); a block that was repacked around its doubles reads and writes every " - "member after the first one at the wrong offset"; + EXPECT_EQ(mismatches, 0) << packing << " block: " << mismatches << " of " << expected.size() + << " bytes differ, first at byte " << firstMismatch << " (in " + << DescribeOffset(layout, firstMismatch < 0 ? 0 : firstMismatch) + << "); a block that was repacked around its doubles reads and writes every " + "member after the first one at the wrong offset"; } glUseProgram(0); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DrawParametersScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DrawParametersScenario.cpp index 4b5cafac..4bbdb0ac 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DrawParametersScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DrawParametersScenario.cpp @@ -182,17 +182,14 @@ void main() { static DrawParams ParamsAt(const Image& image, int half) { const int x = image.Width() * (1 + 2 * half) / 4; const Rgba8 pixel = image.At(x, image.Height() / 2); - const auto decode = [](std::uint8_t channel) { - return (static_cast(channel) - 16 + 4) / 8; - }; + const auto decode = [](std::uint8_t channel) { return (static_cast(channel) - 16 + 4) / 8; }; return {decode(pixel.r), decode(pixel.g), decode(pixel.b)}; } static void ExpectParams(const Image& image, int half, const DrawParams& expected, const std::string& what) { const DrawParams actual = ParamsAt(image, half); - EXPECT_EQ(actual.baseVertex, expected.baseVertex) - << what << ": gl_BaseVertex (half " << half << ")"; + EXPECT_EQ(actual.baseVertex, expected.baseVertex) << what << ": gl_BaseVertex (half " << half << ")"; EXPECT_EQ(actual.baseInstance, expected.baseInstance) << what << ": gl_BaseInstance (half " << half << ")"; EXPECT_EQ(actual.drawId, expected.drawId) << what << ": gl_DrawID (half " << half << ")"; @@ -220,9 +217,8 @@ void main() { TEST_F(DrawParametersScenario, DrawArraysInstancedBaseInstanceReportsItsBaseInstance) { if (!Ready()) return; - const Image image = Render([&] { - glDrawArraysInstancedBaseInstance(GL_TRIANGLES, kLeftFirst, 2 * kHalfCount, 1, 5); - }); + const Image image = + Render([&] { glDrawArraysInstancedBaseInstance(GL_TRIANGLES, kLeftFirst, 2 * kHalfCount, 1, 5); }); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); ExpectParams(image, 0, {0, 5, 0}, "glDrawArraysInstancedBaseInstance(baseInstance=5)"); } @@ -245,7 +241,8 @@ void main() { TEST_F(DrawParametersScenario, DrawElementsBaseVertexReportsItsBaseVertex) { if (!Ready()) return; std::vector indices; - for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) indices.push_back(i); + for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) + indices.push_back(i); FillBuffer(m_ebo, GL_ELEMENT_ARRAY_BUFFER, indices); const Image image = Render([&] { @@ -266,8 +263,10 @@ void main() { TEST_F(DrawParametersScenario, DrawElementsAfterABaseVertexDrawReportsZeroAgain) { if (!Ready()) return; std::vector indices; - for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) indices.push_back(i); - for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) indices.push_back(i + kLeftFirst); + for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) + indices.push_back(i); + for (std::uint32_t i = 0; i < 2 * kHalfCount; ++i) + indices.push_back(i + kLeftFirst); FillBuffer(m_ebo, GL_ELEMENT_ARRAY_BUFFER, indices); const auto rebasedRun = reinterpret_cast(2 * kHalfCount * sizeof(std::uint32_t)); @@ -301,7 +300,8 @@ void main() { TEST_F(DrawParametersScenario, MultiDrawElementsIndirectCarriesEveryCommandsParameters) { if (!Ready()) return; std::vector indices; - for (std::uint32_t i = 0; i < kHalfCount; ++i) indices.push_back(i); + for (std::uint32_t i = 0; i < kHalfCount; ++i) + indices.push_back(i); FillBuffer(m_ebo, GL_ELEMENT_ARRAY_BUFFER, indices); const std::vector commands = { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DsaUpdateKeepsTheBoundUnitScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DsaUpdateKeepsTheBoundUnitScenario.cpp index 6f89bedc..0ac2a0e0 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DsaUpdateKeepsTheBoundUnitScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DsaUpdateKeepsTheBoundUnitScenario.cpp @@ -139,8 +139,8 @@ void main() { ::testing::AssertionResult WholeViewportIs(const Image& image, const char* expected, const std::string& when) { - return RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, expected, 0.0, when); + return RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, expected, + 0.0, when); } GLuint m_program = 0; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DualBlockScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DualBlockScenario.cpp index 476564f3..edddc6b3 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DualBlockScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DualBlockScenario.cpp @@ -42,52 +42,52 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { + namespace { -class DualBlockScenario : public ScenarioTest { -protected: - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - const auto why = SplitRuntimeSkipReason(); - if (!why.empty()) GTEST_SKIP() << why; - } -}; + class DualBlockScenario : public ScenarioTest { + protected: + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + const auto why = SplitRuntimeSkipReason(); + if (!why.empty()) GTEST_SKIP() << why; + } + }; -TEST_F(DualBlockScenario, TheTwoRolesHaveDistinctBlocks) { - if (!Ready()) return; - DualBlockPeekState before; - ASSERT_TRUE(PeekDualBlockState(&before)) - << "the dual-block peek is compiled out: this entry is only ever registered in a " - "disaggregated build, so a false here means the registration and the build disagree"; - ASSERT_TRUE(before.roleSplitActive) - << "dual-block control: MGPipeRoleSplitRehearsalActive() is false - MOBILEGL_IPC_ROLE_SPLIT_STATE " - "is not armed in this process (or the transport is monolith), so the two roles share " - "one PipeInputs block and this case is the knob's negative control"; - EXPECT_TRUE(before.blocksDistinct) - << "dual-block control: MGPipeClientInputs() and gPipeInputs are the SAME object with " - "the rehearsal armed - the fill side and the read side were never split"; - EXPECT_NE(before.serverIdentity, nullptr) - << "dual-block control: the server block's identity was never set (CONTRACT-P5E §3.2's " - "server-owned SetIdentity) - the backend's identity-keyed memo caches read nullptr " - "as a hit on their zero-initialised slot"; + TEST_F(DualBlockScenario, TheTwoRolesHaveDistinctBlocks) { + if (!Ready()) return; + DualBlockPeekState before; + ASSERT_TRUE(PeekDualBlockState(&before)) + << "the dual-block peek is compiled out: this entry is only ever registered in a " + "disaggregated build, so a false here means the registration and the build disagree"; + ASSERT_TRUE(before.roleSplitActive) + << "dual-block control: MGPipeRoleSplitRehearsalActive() is false - MOBILEGL_IPC_ROLE_SPLIT_STATE " + "is not armed in this process (or the transport is monolith), so the two roles share " + "one PipeInputs block and this case is the knob's negative control"; + EXPECT_TRUE(before.blocksDistinct) + << "dual-block control: MGPipeClientInputs() and gPipeInputs are the SAME object with " + "the rehearsal armed - the fill side and the read side were never split"; + EXPECT_NE(before.serverIdentity, nullptr) + << "dual-block control: the server block's identity was never set (CONTRACT-P5E §3.2's " + "server-owned SetIdentity) - the backend's identity-keyed memo caches read nullptr " + "as a hit on their zero-initialised slot"; - // One client verb boundary, driven directly so no draw reaches the server: the CLIENT - // block's serial must move and the SERVER block's must not. Under a shared block the second - // half is impossible, which is the control's other edge. The glPixelStorei dirties the - // pixel-pack state so the boundary's emission step has a record to publish - the fixture's - // destructor asserts the wire moved on every armed split case. - glPixelStorei(GL_PACK_ALIGNMENT, 4); - ASSERT_TRUE(DualBlockDriveClientVerbBoundary()); - DualBlockPeekState after; - ASSERT_TRUE(PeekDualBlockState(&after)); - EXPECT_GT(after.clientSerial, before.clientSerial) - << "dual-block control: a client verb boundary did not move the client block's serial"; - EXPECT_EQ(after.serverSerial, before.serverSerial) - << "dual-block control: a fill-side verb boundary moved the SERVER block's serial " - << before.serverSerial << " -> " << after.serverSerial - << " - the two roles are still writing one block"; -} + // One client verb boundary, driven directly so no draw reaches the server: the CLIENT + // block's serial must move and the SERVER block's must not. Under a shared block the second + // half is impossible, which is the control's other edge. The glPixelStorei dirties the + // pixel-pack state so the boundary's emission step has a record to publish - the fixture's + // destructor asserts the wire moved on every armed split case. + glPixelStorei(GL_PACK_ALIGNMENT, 4); + ASSERT_TRUE(DualBlockDriveClientVerbBoundary()); + DualBlockPeekState after; + ASSERT_TRUE(PeekDualBlockState(&after)); + EXPECT_GT(after.clientSerial, before.clientSerial) + << "dual-block control: a client verb boundary did not move the client block's serial"; + EXPECT_EQ(after.serverSerial, before.serverSerial) + << "dual-block control: a fill-side verb boundary moved the SERVER block's serial " + << before.serverSerial << " -> " << after.serverSerial + << " - the two roles are still writing one block"; + } -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DualSourceBlendScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DualSourceBlendScenario.cpp index 407ee5b4..7b39b737 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DualSourceBlendScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DualSourceBlendScenario.cpp @@ -121,11 +121,9 @@ void main() Draw(/*src0=*/1.0f, /*src1=*/1.0f); glFinish(); const Image probe = ReadPixels(kExtent, kExtent); - m_programRenders = - !probe.Empty() && static_cast(probe.At(kExtent / 2, kExtent / 2).r) > 245; - } - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { + m_programRenders = !probe.Empty() && static_cast(probe.At(kExtent / 2, kExtent / 2).r) > 245; } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } void TearDown() override { @@ -204,9 +202,9 @@ void main() const int red = static_cast(centre.r); const bool blended = red > 100 && red < 160; const bool declined = red > 245; - EXPECT_TRUE(blended || declined) - << "got " << centre << ", which is neither the dual-source blend (~128) nor the declined " - << "straight-through source (255) - the SRC1 factors were mistranslated"; + EXPECT_TRUE(blended || declined) << "got " << centre + << ", which is neither the dual-source blend (~128) nor the declined " + << "straight-through source (255) - the SRC1 factors were mistranslated"; Gl().EndFrame(); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/EmptyTextureLevelScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/EmptyTextureLevelScenario.cpp index 66edee6a..afc36521 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/EmptyTextureLevelScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/EmptyTextureLevelScenario.cpp @@ -44,8 +44,7 @@ namespace MGITest { if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo); if (m_texture != 0) glDeleteTextures(1, &m_texture); m_fbo = m_texture = 0; - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // Every target resetStateGLCore empties, in its order, on the default textures. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp index 7b15f43e..1bbc207a 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/F1WireScenario.cpp @@ -34,895 +34,916 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -constexpr const char* kWireVertexIdTriangle = R"(#version 430 core + namespace { + constexpr const char* kWireVertexIdTriangle = R"(#version 430 core void main() { vec2 positions[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0)); gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0); } )"; -GLuint BuildWireProgram(std::initializer_list> sources) { - const GLuint program = glCreateProgram(); - for (const auto& stage : sources) { - const GLuint shader = glCreateShader(stage.first); - glShaderSource(shader, 1, &stage.second, nullptr); - glCompileShader(shader); - GLint compiled = GL_FALSE; - glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); - if (!compiled) { - char log[4096]{}; - glGetShaderInfoLog(shader, sizeof(log), nullptr, log); - ADD_FAILURE() << "wire pixel shader compilation: " << log; - glDeleteShader(shader); - glDeleteProgram(program); - return 0; + GLuint BuildWireProgram(std::initializer_list> sources) { + const GLuint program = glCreateProgram(); + for (const auto& stage : sources) { + const GLuint shader = glCreateShader(stage.first); + glShaderSource(shader, 1, &stage.second, nullptr); + glCompileShader(shader); + GLint compiled = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); + if (!compiled) { + char log[4096]{}; + glGetShaderInfoLog(shader, sizeof(log), nullptr, log); + ADD_FAILURE() << "wire pixel shader compilation: " << log; + glDeleteShader(shader); + glDeleteProgram(program); + return 0; + } + glAttachShader(program, shader); + glDeleteShader(shader); + } + glLinkProgram(program); + GLint linked = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &linked); + if (!linked) { + char log[4096]{}; + glGetProgramInfoLog(program, sizeof(log), nullptr, log); + ADD_FAILURE() << "wire pixel program link: " << log; + glDeleteProgram(program); + return 0; + } + return program; } - glAttachShader(program, shader); - glDeleteShader(shader); - } - glLinkProgram(program); - GLint linked = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &linked); - if (!linked) { - char log[4096]{}; - glGetProgramInfoLog(program, sizeof(log), nullptr, log); - ADD_FAILURE() << "wire pixel program link: " << log; - glDeleteProgram(program); - return 0; - } - return program; -} -class F1WireScenario : public ScenarioTest { -protected: - GLuint fbo = 0, texture = 0; - void SetUp() override { - ScenarioTest::SetUp(); + class F1WireScenario : public ScenarioTest { + protected: + GLuint fbo = 0, texture = 0; + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + const auto why = SplitRuntimeSkipReason(); + if (!why.empty()) GTEST_SKIP() << why; + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDepthMask(GL_TRUE); + glStencilMask(~0u); + } + void TearDown() override { + if (!Ready()) return; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (fbo) glDeleteFramebuffers(1, &fbo); + if (texture) glDeleteTextures(1, &texture); + ScenarioTest::TearDown(); + } + void Attach(GLenum format, GLenum attachment = GL_COLOR_ATTACHMENT0, int levels = 1) { + glTexStorage2D(GL_TEXTURE_2D, levels, format, 8, 8); + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture, 0); + if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + } + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) + << "F1.setup.framebuffer"; + } + void PaintNonSquareBlitSource() { + // GL coordinates: red/green on the bottom, blue/yellow on the top. + // A non-square source makes accidental extent/axis interchange visible. + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 6, 4); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + constexpr GLfloat colors[4][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; + glEnable(GL_SCISSOR_TEST); + for (int quadrant = 0; quadrant < 4; ++quadrant) { + glScissor((quadrant % 2) * 3, (quadrant / 2) * 2, 3, 2); + glClearColor(colors[quadrant][0], colors[quadrant][1], colors[quadrant][2], colors[quadrant][3]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "paint non-square source FBO"; + } + std::array ReadOnePixel(int x, int y) { + std::array pixel{31, 47, 63, 79}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + return pixel; + } + }; + } // namespace + + TEST_F(F1WireScenario, TextureReadbackLargerThanAReplySlotContainsGpuWrites) { if (!Ready()) return; - const auto why = SplitRuntimeSkipReason(); - if (!why.empty()) GTEST_SKIP() << why; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glDepthMask(GL_TRUE); - glStencilMask(~0u); - } - void TearDown() override { - if (!Ready()) return; - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if (fbo) glDeleteFramebuffers(1, &fbo); - if (texture) glDeleteTextures(1, &texture); - ScenarioTest::TearDown(); - } - void Attach(GLenum format, GLenum attachment = GL_COLOR_ATTACHMENT0, int levels = 1) { - glTexStorage2D(GL_TEXTURE_2D, levels, format, 8, 8); - glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture, 0); - if (attachment == GL_DEPTH_STENCIL_ATTACHMENT) { glDrawBuffer(GL_NONE); glReadBuffer(GL_NONE); } - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) << "F1.setup.framebuffer"; - } - void PaintNonSquareBlitSource() { - // GL coordinates: red/green on the bottom, blue/yellow on the top. - // A non-square source makes accidental extent/axis interchange visible. - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 6, 4); + constexpr int width = 1024, height = 600; + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - constexpr GLfloat colors[4][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); - for (int quadrant = 0; quadrant < 4; ++quadrant) { - glScissor((quadrant % 2) * 3, (quadrant / 2) * 2, 3, 2); - glClearColor(colors[quadrant][0], colors[quadrant][1], colors[quadrant][2], colors[quadrant][3]); - glClear(GL_COLOR_BUFFER_BIT); - } + glScissor(0, height / 2, width, height / 2); + glClearColor(0, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "paint non-square source FBO"; - } - std::array ReadOnePixel(int x, int y) { - std::array pixel{31, 47, 63, 79}; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - return pixel; - } - -}; -} - -TEST_F(F1WireScenario, TextureReadbackLargerThanAReplySlotContainsGpuWrites) { - if (!Ready()) return; - constexpr int width = 1024, height = 600; - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_SCISSOR_TEST); - glScissor(0, height / 2, width, height / 2); - glClearColor(0, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_SCISSOR_TEST); - std::vector pixels(width * height * 4, 0x5a); - glGetTextureImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast(pixels.size()), pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int y = 0; y < height; ++y) { - for (int x : {0, width / 2, width - 1}) { - const size_t offset = (size_t(y) * width + x) * 4; - const std::array expected = y < height / 2 - ? std::array{255, 0, 0, 255} : std::array{0, 0, 255, 255}; - EXPECT_TRUE(std::equal(expected.begin(), expected.end(), pixels.begin() + offset)) << x << "," << y; - } - } -} - -TEST_F(F1WireScenario, TextureReadbackRejectsPackedDestinationOverflowBeforeWriting) { - if (!Ready()) return; - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glPixelStorei(GL_PACK_SKIP_PIXELS, 1); - std::array actual{1, 2, 3, 4, 5, 6, 7, 8}; - const auto sentinel = actual; - glGetTextureImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, 4, actual.data()); - EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); - EXPECT_EQ(actual, sentinel); - GLuint pbo = 0; - glGenBuffers(1, &pbo); - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); - glBufferData(GL_PIXEL_PACK_BUFFER, 4, actual.data(), GL_DYNAMIC_READ); - glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); - glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); - EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); - glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, 4, actual.data()); - EXPECT_EQ(actual, sentinel); - glPixelStorei(GL_PACK_SKIP_PIXELS, 0); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glDeleteBuffers(1, &pbo); -} - -TEST_F(F1WireScenario, TextureAndFramebufferReadsPreservePackBufferPadding) { - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - constexpr size_t size = 512, offset = 12, stride = 40; - std::vector expected(size, 0x5a), actual(size); - GLuint pbo = 0; - glGenBuffers(1, &pbo); - glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); - glBufferData(GL_PIXEL_PACK_BUFFER, size, expected.data(), GL_DYNAMIC_READ); - glPixelStorei(GL_PACK_ROW_LENGTH, 10); - glPixelStorei(GL_PACK_SKIP_ROWS, 1); - glPixelStorei(GL_PACK_SKIP_PIXELS, 1); - for (int mode = 0; mode < 2; ++mode) { - glBufferSubData(GL_PIXEL_PACK_BUFFER, 0, size, expected.data()); - if (mode == 0) glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, reinterpret_cast(offset)); - else glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, reinterpret_cast(offset)); + std::vector pixels(width * height * 4, 0x5a); + glGetTextureImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, static_cast(pixels.size()), pixels.data()); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, size, actual.data()); - auto wanted = expected; - for (size_t y = 0; y < 8; ++y) for (size_t x = 0; x < 8; ++x) { - const size_t at = offset + stride + 4 + y * stride + x * 4; - wanted[at] = 0; wanted[at + 1] = 255; wanted[at + 2] = 0; wanted[at + 3] = 255; + for (int y = 0; y < height; ++y) { + for (int x : {0, width / 2, width - 1}) { + const size_t offset = (size_t(y) * width + x) * 4; + const std::array expected = + y < height / 2 ? std::array{255, 0, 0, 255} : std::array{0, 0, 255, 255}; + EXPECT_TRUE(std::equal(expected.begin(), expected.end(), pixels.begin() + offset)) << x << "," << y; + } } - EXPECT_EQ(actual, wanted) << "read API " << mode; } - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glPixelStorei(GL_PACK_SKIP_ROWS, 0); - glPixelStorei(GL_PACK_SKIP_PIXELS, 0); - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - glDeleteBuffers(1, &pbo); -} -// P5f exit: each published frame must have a real draw and server stamp, and -// zero residual reads. Missing instrumentation is a failure rather than a false zero. -TEST_F(F1WireScenario, EachWireFrameHasZeroResidualPulls) { - if (!Ready()) return; - if (!SplitLane::MarkerIsOne("MGITEST_P5F_RSP_LANE")) - GTEST_SKIP() << "requires the dedicated per-frame stats lane"; - ASSERT_FALSE(PipeStatsWindow::LibraryLogPath().empty()); - Attach(GL_RGBA8); - const char* fragment = R"(#version 430 core + TEST_F(F1WireScenario, TextureReadbackRejectsPackedDestinationOverflowBeforeWriting) { + if (!Ready()) return; + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glPixelStorei(GL_PACK_SKIP_PIXELS, 1); + std::array actual{1, 2, 3, 4, 5, 6, 7, 8}; + const auto sentinel = actual; + glGetTextureImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, 4, actual.data()); + EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); + EXPECT_EQ(actual, sentinel); + GLuint pbo = 0; + glGenBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); + glBufferData(GL_PIXEL_PACK_BUFFER, 4, actual.data(), GL_DYNAMIC_READ); + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); + glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + EXPECT_EQ(glGetError(), GLenum(GL_INVALID_OPERATION)); + glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, 4, actual.data()); + EXPECT_EQ(actual, sentinel); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glDeleteBuffers(1, &pbo); + } + + TEST_F(F1WireScenario, TextureAndFramebufferReadsPreservePackBufferPadding) { + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + constexpr size_t size = 512, offset = 12, stride = 40; + std::vector expected(size, 0x5a), actual(size); + GLuint pbo = 0; + glGenBuffers(1, &pbo); + glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); + glBufferData(GL_PIXEL_PACK_BUFFER, size, expected.data(), GL_DYNAMIC_READ); + glPixelStorei(GL_PACK_ROW_LENGTH, 10); + glPixelStorei(GL_PACK_SKIP_ROWS, 1); + glPixelStorei(GL_PACK_SKIP_PIXELS, 1); + for (int mode = 0; mode < 2; ++mode) { + glBufferSubData(GL_PIXEL_PACK_BUFFER, 0, size, expected.data()); + if (mode == 0) + glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, reinterpret_cast(offset)); + else + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, reinterpret_cast(offset)); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, size, actual.data()); + auto wanted = expected; + for (size_t y = 0; y < 8; ++y) + for (size_t x = 0; x < 8; ++x) { + const size_t at = offset + stride + 4 + y * stride + x * 4; + wanted[at] = 0; + wanted[at + 1] = 255; + wanted[at + 2] = 0; + wanted[at + 3] = 255; + } + EXPECT_EQ(actual, wanted) << "read API " << mode; + } + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + glDeleteBuffers(1, &pbo); + } + + // P5f exit: each published frame must have a real draw and server stamp, and + // zero residual reads. Missing instrumentation is a failure rather than a false zero. + TEST_F(F1WireScenario, EachWireFrameHasZeroResidualPulls) { + if (!Ready()) return; + if (!SplitLane::MarkerIsOne("MGITEST_P5F_RSP_LANE")) + GTEST_SKIP() << "requires the dedicated per-frame stats lane"; + ASSERT_FALSE(PipeStatsWindow::LibraryLogPath().empty()); + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core uniform float value; layout(location=0) out vec4 color; void main() { color = vec4(value, 0.25, 0.75, 1.0); } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLuint vao = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glUseProgram(program); - const GLint location = glGetUniformLocation(program, "value"); - ASSERT_GE(location, 0); - glViewport(0, 0, 8, 8); - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_RASTERIZER_DISCARD); - Gl().EndFrame(); // close setup's counter window - for (int frame = 0; frame < 3; ++frame) { - const float value = float(frame + 1) / 4.0f; - glUniform1f(location, value); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + const GLint location = glGetUniformLocation(program, "value"); + ASSERT_GE(location, 0); + glViewport(0, 0, 8, 8); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + Gl().EndFrame(); // close setup's counter window + for (int frame = 0; frame < 3; ++frame) { + const float value = float(frame + 1) / 4.0f; + glUniform1f(location, value); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_NEAR(pixel[0], value * 255.0f, 1) << "frame " << frame; + EXPECT_NEAR(pixel[1], 64, 1); + EXPECT_NEAR(pixel[2], 191, 1); + EXPECT_EQ(pixel[3], 255); + Gl().EndFrame(); + // Present returns before apply under run-ahead. Inspect this completed + // frame's window, not the previous (possibly empty setup) log line. + ASSERT_TRUE(WaitForSplitAppliedForTesting(PeekSplitRuntime().emitSeq)); + const auto window = PipeStatsWindow::LastFromLaneLog(); + ASSERT_TRUE(window.found) << "P5f rsp window missing on frame " << frame; + // Magma does not publish Espryt's draw counter. The changing uniform and + // checked pixel above prove each draw executed; a missing stats field still fails. + EXPECT_GE(PipeStatsWindow::CounterOrAbsent(window, "draws"), 0) << window.line; + EXPECT_GT(PipeStatsWindow::CounterOrAbsent(window, "vbs"), 0) << window.line; + EXPECT_EQ(PipeStatsWindow::CounterOrAbsent(window, "rsp"), 0) + << "P5f residual pull on frame " << frame << ": " << window.line; + RecordProperty("p5f_frame_" + std::to_string(frame), window.line); + } + glUseProgram(0); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); + } + + TEST_F(F1WireScenario, ClearBufferfvPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearBufferfv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA8); + const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + const auto before = PeekSplitRuntime().emitSeq; + glClearBufferfv(GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfv.wire"; + GLubyte pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfv.error"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearBufferfv.pixels"; + } + + TEST_F(F1WireScenario, ClearNamedFramebufferfvPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA8); + const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + const auto before = PeekSplitRuntime().emitSeq; + glClearNamedFramebufferfv(fbo, GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfv.wire"; + GLubyte pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfv.error"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearNamedFramebufferfv.pixels"; + } + + TEST_F(F1WireScenario, ClearBufferivPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearBufferiv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA32I); + const GLint value[4] = {-37, 19, -11, 5}; + const auto before = PeekSplitRuntime().emitSeq; + glClearBufferiv(GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferiv.wire"; + GLint pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferiv.error"; + for (int i = 0; i < 4; ++i) + EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferiv.pixels"; + } + + TEST_F(F1WireScenario, ClearNamedFramebufferivPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferiv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA32I); + const GLint value[4] = {-37, 19, -11, 5}; + const auto before = PeekSplitRuntime().emitSeq; + glClearNamedFramebufferiv(fbo, GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferiv.wire"; + GLint pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferiv.error"; + for (int i = 0; i < 4; ++i) + EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferiv.pixels"; + } + + TEST_F(F1WireScenario, ClearBufferuivPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearBufferuiv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA32UI); + const GLuint value[4] = {37, 19, 11, 5}; + const auto before = PeekSplitRuntime().emitSeq; + glClearBufferuiv(GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferuiv.wire"; + GLuint pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferuiv.error"; + for (int i = 0; i < 4; ++i) + EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferuiv.pixels"; + } + + TEST_F(F1WireScenario, ClearNamedFramebufferuivPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferuiv.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA32UI); + const GLuint value[4] = {37, 19, 11, 5}; + const auto before = PeekSplitRuntime().emitSeq; + glClearNamedFramebufferuiv(fbo, GL_COLOR, 0, value); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferuiv.wire"; + GLuint pixel[4]{}; + glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferuiv.error"; + for (int i = 0; i < 4; ++i) + EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferuiv.pixels"; + } + + TEST_F(F1WireScenario, ClearBufferfiPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearBufferfi.pixels fails. + if (!Ready()) return; + Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT); + const auto before = PeekSplitRuntime().emitSeq; + glClearBufferfi(GL_DEPTH_STENCIL, 0, 0.375f, 91); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfi.wire"; + GLuint pixel = 0; + glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfi.error"; + EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearBufferfi.pixels"; + EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearBufferfi.pixels"; + } + + TEST_F(F1WireScenario, ClearNamedFramebufferfiPixels) { + // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfi.pixels fails. + if (!Ready()) return; + Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT); + const auto before = PeekSplitRuntime().emitSeq; + glClearNamedFramebufferfi(fbo, GL_DEPTH_STENCIL, 0, 0.375f, 91); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfi.wire"; + GLuint pixel = 0; + glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfi.error"; + EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearNamedFramebufferfi.pixels"; + EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearNamedFramebufferfi.pixels"; + } + + TEST_F(F1WireScenario, CopyTexImage2DPixels) { + // Red once (executed, reverted): omit the copy sink call; F1.CopyTexImage2D.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + GLuint destination = 0; + glGenTextures(1, &destination); + glBindTexture(GL_TEXTURE_2D, destination); + + const auto before = PeekSplitRuntime().emitSeq; + glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 3, 4, 4, 0); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexImage2D.wire"; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexImage2D.error"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexImage2D.pixels"; + glDeleteTextures(1, &destination); + } + + TEST_F(F1WireScenario, CopyTexSubImage2DPixels) { + // Red once (executed, reverted): omit the copy sink call; F1.CopyTexSubImage2D.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + GLuint destination = 0; + glGenTextures(1, &destination); + glBindTexture(GL_TEXTURE_2D, destination); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); + const auto before = PeekSplitRuntime().emitSeq; + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 2, 3, 2, 2); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexSubImage2D.wire"; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexSubImage2D.error"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexSubImage2D.pixels"; + glDeleteTextures(1, &destination); + } + + TEST_F(F1WireScenario, GenerateMipmapPixels) { + // Red once (executed, reverted): omit the mipmap sink call; F1.GenerateMipmap.pixels fails. + if (!Ready()) return; + Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4); + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.GenerateMipmap.wire"; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.GenerateMipmap.error"; + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateMipmap.pixels"; + } + TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) { + if (!Ready()) return; + // Only level zero exists initially. Generating the special-format chain must use the + // shape published by the client, and level two must contain the generated GPU pixels. + std::array source{}; + for (size_t i = 0; i < source.size(); i += 3) { + source[i] = 0.25f; + source[i + 1] = 0.5f; + source[i + 2] = 0.75f; + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, 8, 8, 0, GL_RGB, GL_FLOAT, source.data()); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_FLOAT, pixel); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - EXPECT_NEAR(pixel[0], value * 255.0f, 1) << "frame " << frame; - EXPECT_NEAR(pixel[1], 64, 1); - EXPECT_NEAR(pixel[2], 191, 1); - EXPECT_EQ(pixel[3], 255); - Gl().EndFrame(); - // Present returns before apply under run-ahead. Inspect this completed - // frame's window, not the previous (possibly empty setup) log line. - ASSERT_TRUE(WaitForSplitAppliedForTesting(PeekSplitRuntime().emitSeq)); - const auto window = PipeStatsWindow::LastFromLaneLog(); - ASSERT_TRUE(window.found) << "P5f rsp window missing on frame " << frame; - // Magma does not publish Espryt's draw counter. The changing uniform and - // checked pixel above prove each draw executed; a missing stats field still fails. - EXPECT_GE(PipeStatsWindow::CounterOrAbsent(window, "draws"), 0) << window.line; - EXPECT_GT(PipeStatsWindow::CounterOrAbsent(window, "vbs"), 0) << window.line; - EXPECT_EQ(PipeStatsWindow::CounterOrAbsent(window, "rsp"), 0) - << "P5f residual pull on frame " << frame << ": " << window.line; - RecordProperty("p5f_frame_" + std::to_string(frame), window.line); - } - glUseProgram(0); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} + const GLfloat expected[4] = {0.25f, 0.5f, 0.75f, 1.0f}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 0.01f); -TEST_F(F1WireScenario, ClearBufferfvPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearBufferfv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA8); - const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f}; - const auto before = PeekSplitRuntime().emitSeq; - glClearBufferfv(GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfv.wire"; - GLubyte pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfv.error"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearBufferfv.pixels"; -} - -TEST_F(F1WireScenario, ClearNamedFramebufferfvPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA8); - const GLfloat value[4] = {0.25f, 0.5f, 0.75f, 1.0f}; - const auto before = PeekSplitRuntime().emitSeq; - glClearNamedFramebufferfv(fbo, GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfv.wire"; - GLubyte pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfv.error"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.ClearNamedFramebufferfv.pixels"; -} - -TEST_F(F1WireScenario, ClearBufferivPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearBufferiv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA32I); - const GLint value[4] = {-37, 19, -11, 5}; - const auto before = PeekSplitRuntime().emitSeq; - glClearBufferiv(GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferiv.wire"; - GLint pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferiv.error"; - for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferiv.pixels"; -} - -TEST_F(F1WireScenario, ClearNamedFramebufferivPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferiv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA32I); - const GLint value[4] = {-37, 19, -11, 5}; - const auto before = PeekSplitRuntime().emitSeq; - glClearNamedFramebufferiv(fbo, GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferiv.wire"; - GLint pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferiv.error"; - for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferiv.pixels"; -} - -TEST_F(F1WireScenario, ClearBufferuivPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearBufferuiv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA32UI); - const GLuint value[4] = {37, 19, 11, 5}; - const auto before = PeekSplitRuntime().emitSeq; - glClearBufferuiv(GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferuiv.wire"; - GLuint pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferuiv.error"; - for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearBufferuiv.pixels"; -} - -TEST_F(F1WireScenario, ClearNamedFramebufferuivPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferuiv.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA32UI); - const GLuint value[4] = {37, 19, 11, 5}; - const auto before = PeekSplitRuntime().emitSeq; - glClearNamedFramebufferuiv(fbo, GL_COLOR, 0, value); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferuiv.wire"; - GLuint pixel[4]{}; - glReadPixels(2, 3, 1, 1, GL_RGBA_INTEGER, GL_UNSIGNED_INT, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferuiv.error"; - for (int i = 0; i < 4; ++i) EXPECT_EQ(pixel[i], value[i]) << "F1.ClearNamedFramebufferuiv.pixels"; -} - -TEST_F(F1WireScenario, ClearBufferfiPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearBufferfi.pixels fails. - if (!Ready()) return; - Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT); - const auto before = PeekSplitRuntime().emitSeq; - glClearBufferfi(GL_DEPTH_STENCIL, 0, 0.375f, 91); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearBufferfi.wire"; - GLuint pixel = 0; - glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearBufferfi.error"; - EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearBufferfi.pixels"; - EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearBufferfi.pixels"; -} - -TEST_F(F1WireScenario, ClearNamedFramebufferfiPixels) { - // Red once (executed, reverted): zero the clear record values; F1.ClearNamedFramebufferfi.pixels fails. - if (!Ready()) return; - Attach(GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL_ATTACHMENT); - const auto before = PeekSplitRuntime().emitSeq; - glClearNamedFramebufferfi(fbo, GL_DEPTH_STENCIL, 0, 0.375f, 91); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ClearNamedFramebufferfi.wire"; - GLuint pixel = 0; - glReadPixels(2, 3, 1, 1, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, &pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ClearNamedFramebufferfi.error"; - EXPECT_EQ(pixel & 255u, 91u) << "F1.ClearNamedFramebufferfi.pixels"; - EXPECT_NEAR(double(pixel >> 8) / 16777215.0, 0.375, 0.00001) << "F1.ClearNamedFramebufferfi.pixels"; -} - -TEST_F(F1WireScenario, CopyTexImage2DPixels) { - // Red once (executed, reverted): omit the copy sink call; F1.CopyTexImage2D.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - GLuint destination = 0; - glGenTextures(1, &destination); - glBindTexture(GL_TEXTURE_2D, destination); - - const auto before = PeekSplitRuntime().emitSeq; - glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 3, 4, 4, 0); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexImage2D.wire"; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexImage2D.error"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexImage2D.pixels"; - glDeleteTextures(1, &destination); -} - -TEST_F(F1WireScenario, CopyTexSubImage2DPixels) { - // Red once (executed, reverted): omit the copy sink call; F1.CopyTexSubImage2D.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - GLuint destination = 0; - glGenTextures(1, &destination); - glBindTexture(GL_TEXTURE_2D, destination); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4); - const auto before = PeekSplitRuntime().emitSeq; - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 2, 3, 2, 2); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyTexSubImage2D.wire"; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destination, 0); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyTexSubImage2D.error"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.CopyTexSubImage2D.pixels"; - glDeleteTextures(1, &destination); -} - -TEST_F(F1WireScenario, GenerateMipmapPixels) { - // Red once (executed, reverted): omit the mipmap sink call; F1.GenerateMipmap.pixels fails. - if (!Ready()) return; - Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4); - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - const auto before = PeekSplitRuntime().emitSeq; - glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.GenerateMipmap.wire"; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.GenerateMipmap.error"; - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1) << "F1.GenerateMipmap.pixels"; -} -TEST_F(F1WireScenario, GenerateMipmapPackedFloatPixels) { - if (!Ready()) return; - // Only level zero exists initially. Generating the special-format chain must use the - // shape published by the client, and level two must contain the generated GPU pixels. - std::array source{}; - for (size_t i = 0; i < source.size(); i += 3) { - source[i] = 0.25f; source[i + 1] = 0.5f; source[i + 2] = 0.75f; - } - glTexImage2D(GL_TEXTURE_2D, 0, GL_R11F_G11F_B10F, 8, 8, 0, GL_RGB, GL_FLOAT, source.data()); - const auto before = PeekSplitRuntime().emitSeq; - glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - GLfloat pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_FLOAT, pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - const GLfloat expected[4] = {0.25f, 0.5f, 0.75f, 1.0f}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 0.01f); - - // Change the base on the GPU after its client upload. The regenerated mip - // must read those new pixels, and offscreen shader blits must keep row order. - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - glDisable(GL_SCISSOR_TEST); - glClearColor(0.875f, 0.375f, 0.125f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glEnable(GL_SCISSOR_TEST); - glScissor(0, 4, 8, 4); - glClearColor(0.125f, 0.75f, 0.5f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_SCISSOR_TEST); - glGenerateMipmap(GL_TEXTURE_2D); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); - GLfloat rows[8]{}; - glReadPixels(1, 0, 1, 2, GL_RGBA, GL_FLOAT, rows); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - const GLfloat generated[8] = {0.875f, 0.375f, 0.125f, 1.0f, 0.125f, 0.75f, 0.5f, 1.0f}; - for (int i = 0; i < 8; ++i) EXPECT_NEAR(rows[i], generated[i], 0.01f); -} - -// P7 wave 2-B: THE ARM THE HOST CANNOT REACH ON ITS OWN. -// -// Two Iris cases (minecraft-1.21.4-fabric-iris-iterationt-in-world and its -nodsa twin) killed -// the server apply thread on a Redmi (Adreno 830v2) with -// MGPipe: Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape"} -// deterministically, under both run-ahead and lockstep, while the monolith arm passed and the -// host lane stayed green. It stayed green because lavapipe reports BLIT_SRC|BLIT_DST for every -// colour format this scenario can create: `nativeBlit` is always true here, so NEITHER shader -// arm of GenerateWireMipmap has ever executed on the gate machine. A refusal no host lane can -// reach is a refusal that ships. -// -// MGITEST_MAGMA_FORCE_SHADER_MIPMAP is what makes them reachable, and it selects which one: -// 1 - drop the native blit only. The destination level is still a colour attachment, so the -// pass renders straight into it (the arm an Adreno takes for a format without BLIT_DST). -// 2 - also treat the destination as not attachable. The pass renders into owned scratch and -// copies the mip in (the arm it takes for a texture whose image never got -// COLOR_ATTACHMENT usage - which, before this package, was the refusal itself). -// -// The assertion is the ORDINARY mip result, per half: a shader mip that renders somewhere else -// is still wrong unless the level holds what a blit would have put there, in GL's row order. -// Red once (executed, reverted): drop the scratch arm (force `viaScratch` false) and the tier-2 -// entry dies with Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."} by name. -TEST_F(F1WireScenario, GenerateMipmapWithoutNativeBlitPixels) { - if (!Ready()) return; - const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); - if (tier.empty()) - GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver reports " - "BLIT_SRC|BLIT_DST for GL_RGBA8, so the shader mip arms under test are " - "not entered and this case would assert the native blit twice over"; - Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4); - glDisable(GL_SCISSOR_TEST); - glClearColor(0.75f, 0.25f, 0.5f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - // Two different halves, not one flat colour: a pass that loses GL's row order averages to - // the same number everywhere and a single-colour assertion would call that correct. - glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, 8, 4); - glClearColor(0.25f, 0.75f, 0.5f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_SCISSOR_TEST); - const auto before = PeekSplitRuntime().emitSeq; - glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMip.wire"; - // Level one is 4x4, and each of its rows averages two source rows from ONE half - the - // boundary never falls inside a destination texel, so the two colours survive unmixed. - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - GLubyte rows[4][4]{}; - glReadPixels(1, 0, 1, 4, GL_RGBA, GL_UNSIGNED_BYTE, rows); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMip.error"; - const int bottom[4] = {64, 191, 128, 255}; - const int top[4] = {191, 64, 128, 255}; - for (int row = 0; row < 4; ++row) { - const int* expected = row < 2 ? bottom : top; - for (int channel = 0; channel < 4; ++channel) - EXPECT_NEAR(rows[row][channel], expected[channel], 2) - << "F1.ShaderMip.pixels tier=" << tier << " row=" << row << " channel=" << channel; - } -} - -TEST_F(F1WireScenario, GenerateMipmapDepthPixels) { - if (!Ready()) return; - std::array source{}; - source.fill(0.375f); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 8, 8, 0, - GL_DEPTH_COMPONENT, GL_FLOAT, source.data()); - const auto before = PeekSplitRuntime().emitSeq; - glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, 2); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - GLfloat pixel = 0; - glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - EXPECT_NEAR(pixel, 0.375f, 0.00001f); -} - -// P7 wave 2-B, CONTRACT-P7 §5.1 (A): the baked depth mip program, on the two GL depth formats -// that reach it as a depth-only Vulkan aspect (DEPTH_COMPONENT24 -> X8_D24_UNORM_PACK32, -// DEPTH_COMPONENT32F -> D32_SFLOAT). GenerateMipmapDepthPixels above passes on this host -// because lavapipe blits depth natively; the knob removes that arm, which is what an Adreno -// does by itself - VK_FORMAT_FEATURE_BLIT_DST is OPTIONAL for depth formats, so a depth chain -// through a shader is the ordinary case and not the exotic one. -// -// TWO HALVES AND A BAND THAT IS NOT ASSERTED. The generated level is read back through a real -// sampled attachment, so a flat fill would prove nothing: the source is split at its middle -// row instead. The ported filter takes a 2x2 box at ivec2(texCoord * srcTexelSize), which -// lands half a texel off the destination centre - the monolith's own arithmetic, kept -// deliberately (§3.2 retires a refusal by matching that arm, defects included). Under either -// reading of that half texel, destination rows 0..2 come only from the lower half and rows -// 4..7 only from the upper half; row 3 is the straddle and is the one row this case does not -// pin, because pinning it would assert the half-texel offset rather than the mip. -TEST_F(F1WireScenario, GenerateMipmapDepthWithoutNativeBlitPixels) { - if (!Ready()) return; - const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); - if (tier.empty()) - GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits depth " - "natively, so the baked depth mip program under test is not entered"; - for (const GLenum internalFormat : {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F}) { - GLuint depth = 0; - glGenTextures(1, &depth); - glBindTexture(GL_TEXTURE_2D, depth); - glTexStorage2D(GL_TEXTURE_2D, 5, internalFormat, 16, 16); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) - << "format 0x" << std::hex << internalFormat; - glDepthMask(GL_TRUE); + // Change the base on the GPU after its client upload. The regenerated mip + // must read those new pixels, and offscreen shader blits must keep row order. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); glDisable(GL_SCISSOR_TEST); - glClearDepth(0.75); - glClear(GL_DEPTH_BUFFER_BIT); + glClearColor(0.875f, 0.375f, 0.125f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, 16, 8); - glClearDepth(0.25); - glClear(GL_DEPTH_BUFFER_BIT); + glScissor(0, 4, 8, 4); + glClearColor(0.125f, 0.75f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + glGenerateMipmap(GL_TEXTURE_2D); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 2); + GLfloat rows[8]{}; + glReadPixels(1, 0, 1, 2, GL_RGBA, GL_FLOAT, rows); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const GLfloat generated[8] = {0.875f, 0.375f, 0.125f, 1.0f, 0.125f, 0.75f, 0.5f, 1.0f}; + for (int i = 0; i < 8; ++i) + EXPECT_NEAR(rows[i], generated[i], 0.01f); + } + + // P7 wave 2-B: THE ARM THE HOST CANNOT REACH ON ITS OWN. + // + // Two Iris cases (minecraft-1.21.4-fabric-iris-iterationt-in-world and its -nodsa twin) killed + // the server apply thread on a Redmi (Adreno 830v2) with + // MGPipe: Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape"} + // deterministically, under both run-ahead and lockstep, while the monolith arm passed and the + // host lane stayed green. It stayed green because lavapipe reports BLIT_SRC|BLIT_DST for every + // colour format this scenario can create: `nativeBlit` is always true here, so NEITHER shader + // arm of GenerateWireMipmap has ever executed on the gate machine. A refusal no host lane can + // reach is a refusal that ships. + // + // MGITEST_MAGMA_FORCE_SHADER_MIPMAP is what makes them reachable, and it selects which one: + // 1 - drop the native blit only. The destination level is still a colour attachment, so the + // pass renders straight into it (the arm an Adreno takes for a format without BLIT_DST). + // 2 - also treat the destination as not attachable. The pass renders into owned scratch and + // copies the mip in (the arm it takes for a texture whose image never got + // COLOR_ATTACHMENT usage - which, before this package, was the refusal itself). + // + // The assertion is the ORDINARY mip result, per half: a shader mip that renders somewhere else + // is still wrong unless the level holds what a blit would have put there, in GL's row order. + // Red once (executed, reverted): drop the scratch arm (force `viaScratch` false) and the tier-2 + // entry dies with Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."} by name. + TEST_F(F1WireScenario, GenerateMipmapWithoutNativeBlitPixels) { + if (!Ready()) return; + const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); + if (tier.empty()) + GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver reports " + "BLIT_SRC|BLIT_DST for GL_RGBA8, so the shader mip arms under test are " + "not entered and this case would assert the native blit twice over"; + Attach(GL_RGBA8, GL_COLOR_ATTACHMENT0, 4); + glDisable(GL_SCISSOR_TEST); + glClearColor(0.75f, 0.25f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + // Two different halves, not one flat colour: a pass that loses GL's row order averages to + // the same number everywhere and a single-colour assertion would call that correct. + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 8, 4); + glClearColor(0.25f, 0.75f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); const auto before = PeekSplitRuntime().emitSeq; glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.DepthMip.wire"; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 1); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMip.wire"; + // Level one is 4x4, and each of its rows averages two source rows from ONE half - the + // boundary never falls inside a destination texel, so the two colours survive unmixed. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - GLfloat rows[8]{}; - glReadPixels(2, 0, 1, 8, GL_DEPTH_COMPONENT, GL_FLOAT, rows); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthMip.error"; - for (int row = 0; row < 8; ++row) { - if (row == 3) continue; - const GLfloat expected = row < 3 ? 0.25f : 0.75f; - EXPECT_NEAR(rows[row], expected, 0.002f) - << "F1.DepthMip.pixels tier=" << tier << " format=0x" << std::hex << internalFormat - << std::dec << " row=" << row; + GLubyte rows[4][4]{}; + glReadPixels(1, 0, 1, 4, GL_RGBA, GL_UNSIGNED_BYTE, rows); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMip.error"; + const int bottom[4] = {64, 191, 128, 255}; + const int top[4] = {191, 64, 128, 255}; + for (int row = 0; row < 4; ++row) { + const int* expected = row < 2 ? bottom : top; + for (int channel = 0; channel < 4; ++channel) + EXPECT_NEAR(rows[row][channel], expected[channel], 2) + << "F1.ShaderMip.pixels tier=" << tier << " row=" << row << " channel=" << channel; } - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - glDeleteTextures(1, &depth); + } + + TEST_F(F1WireScenario, GenerateMipmapDepthPixels) { + if (!Ready()) return; + std::array source{}; + source.fill(0.375f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, 8, 8, 0, GL_DEPTH_COMPONENT, GL_FLOAT, source.data()); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texture, 2); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat pixel = 0; + glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &pixel); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_NEAR(pixel, 0.375f, 0.00001f); + } + + // P7 wave 2-B, CONTRACT-P7 §5.1 (A): the baked depth mip program, on the two GL depth formats + // that reach it as a depth-only Vulkan aspect (DEPTH_COMPONENT24 -> X8_D24_UNORM_PACK32, + // DEPTH_COMPONENT32F -> D32_SFLOAT). GenerateMipmapDepthPixels above passes on this host + // because lavapipe blits depth natively; the knob removes that arm, which is what an Adreno + // does by itself - VK_FORMAT_FEATURE_BLIT_DST is OPTIONAL for depth formats, so a depth chain + // through a shader is the ordinary case and not the exotic one. + // + // TWO HALVES AND A BAND THAT IS NOT ASSERTED. The generated level is read back through a real + // sampled attachment, so a flat fill would prove nothing: the source is split at its middle + // row instead. The ported filter takes a 2x2 box at ivec2(texCoord * srcTexelSize), which + // lands half a texel off the destination centre - the monolith's own arithmetic, kept + // deliberately (§3.2 retires a refusal by matching that arm, defects included). Under either + // reading of that half texel, destination rows 0..2 come only from the lower half and rows + // 4..7 only from the upper half; row 3 is the straddle and is the one row this case does not + // pin, because pinning it would assert the half-texel offset rather than the mip. + TEST_F(F1WireScenario, GenerateMipmapDepthWithoutNativeBlitPixels) { + if (!Ready()) return; + const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); + if (tier.empty()) + GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits depth " + "natively, so the baked depth mip program under test is not entered"; + for (const GLenum internalFormat : {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32F}) { + GLuint depth = 0; + glGenTextures(1, &depth); + glBindTexture(GL_TEXTURE_2D, depth); + glTexStorage2D(GL_TEXTURE_2D, 5, internalFormat, 16, 16); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)) + << "format 0x" << std::hex << internalFormat; + glDepthMask(GL_TRUE); + glDisable(GL_SCISSOR_TEST); + glClearDepth(0.75); + glClear(GL_DEPTH_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 16, 8); + glClearDepth(0.25); + glClear(GL_DEPTH_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.DepthMip.wire"; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth, 1); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat rows[8]{}; + glReadPixels(2, 0, 1, 8, GL_DEPTH_COMPONENT, GL_FLOAT, rows); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthMip.error"; + for (int row = 0; row < 8; ++row) { + if (row == 3) continue; + const GLfloat expected = row < 3 ? 0.25f : 0.75f; + EXPECT_NEAR(rows[row], expected, 0.002f) << "F1.DepthMip.pixels tier=" << tier << " format=0x" + << std::hex << internalFormat << std::dec << " row=" << row; + } + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + glDeleteTextures(1, &depth); + glBindTexture(GL_TEXTURE_2D, texture); + } + glClearDepth(1.0); + } + + // The OTHER half of `depth-stencil-mipmap@P7`, and it is a decline rather than a retirement + // (CONTRACT-P7 §3.2): a combined depth/stencil texture has no mip generation on EITHER arm - + // the monolith's shader fallback asserts depth-only, and an assert is nothing in the release + // build both arms ship as. What this case pins is that the wire arm now says so and carries + // on, rather than taking the session down: no GL error, the session still alive afterwards, + // and level zero still holding what was cleared into it. + // + // No knob: the decline is decided on the ASPECT, before the blit-support question is asked, + // so it fires on every driver. + // Red once (executed, reverted): restore the MagmaWireFatal and this case dies with + // Fatal{UnmigratedVerb, "Magma:depth-stencil-mipmap@P7"}. + // + // MAGMA ONLY, like the three other `*Declines*` cases below. Rule I and §3.2 are MG_Backend/ + // DirectVulkan's wire-arm contract, but the DirectGLES whole-binary entry discovers this case + // too and reaches its body whenever the process resolves a split transport - the primary + // integration lane runs every integration-gpu entry under MOBILEGL_TRANSPORT=inproc. Espryt + // answers this shape with GL_INVALID_OPERATION and leaves level zero and the session intact + // (measured, DirectGLES x inproc on lavapipe): a different answer, not a broken decline. + TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) { + if (!Ready()) return; + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, depth-stencil-mipmap): Espryt (" + << Gl().BackendName() + << ") answers a depth/stencil glGenerateMipmap with GL_INVALID_OPERATION and " + "leaves level 0 intact, which is not the no-error decline this case pins"; + GLuint depthStencil = 0; + glGenTextures(1, &depthStencil); + glBindTexture(GL_TEXTURE_2D, depthStencil); + glTexStorage2D(GL_TEXTURE_2D, 4, GL_DEPTH24_STENCIL8, 8, 8); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencil, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glDepthMask(GL_TRUE); + glDisable(GL_SCISSOR_TEST); + glClearDepth(0.5); + glClear(GL_DEPTH_BUFFER_BIT); + // P8-SV: the decline is a WireDeclines.def row now (MipmapDepthStencilAspect), so the server's + // MGWIRE-DECLINES dump sees it. Readable where the server is this process: the inproc arm. + // Red once (executed, reverted): the site back to a bare MGLOG_E_ONCE - the count stays 0. + unsigned long long declinesBefore = 0; + const bool counted = PeekSplitRuntime().transportName == "inproc" && + PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesBefore); + glGenerateMipmap(GL_TEXTURE_2D); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.error"; + // The session is what the retired Fatal used to take with it. A readback is a round trip + // through the server, so it answers that question and the level-zero one at once. + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencil, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + GLfloat level0 = 0; + glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &level0); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.readback"; + EXPECT_NEAR(level0, 0.5f, 0.002f) << "F1.DepthStencilMip.level0"; + if (counted) { + unsigned long long declinesAfter = 0; + ASSERT_TRUE(PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesAfter)); + EXPECT_EQ(declinesAfter - declinesBefore, 1u) + << "F1.DepthStencilMip.tally: one named decline per refused " + "glGenerateMipmap (WireDeclines.def MipmapDepthStencilAspect)"; + } + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + glDeleteTextures(1, &depthStencil); + glBindTexture(GL_TEXTURE_2D, texture); + glClearDepth(1.0); + } + + // P8-F: `Magma:mipmap-shader-format-or-shape` IS A NAMED DECLINE ON THE WIRE ARM NOW, which is + // the monolith arm's answer for a chain it cannot build (VulkanRenderer.cpp's GenerateMipmap + // declines a format without blit support and leaves the chain unwritten). It used to end the + // session. A 3D texture is a shape neither wire shader arm takes (the colour pass samples a + // sampler2D), so with the native blit forced off (MGITEST_MAGMA_FORCE_SHADER_MIPMAP) it is what + // reaches the refusal on this host - lavapipe blits a 3D RGBA8 chain natively otherwise. + // + // What this case pins: no GL error, the session still answering (two readback round trips), level + // 0 untouched, and level 1 still holding the application's own texels - the decline wrote nothing. + // Magma only, and knob-gated, like GenerateMipmapWithoutNativeBlitPixels above. + // Red once (executed, reverted): restore the MagmaWireFatal and this case dies with + // Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."}. + TEST_F(F1WireScenario, GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession) { + if (!Ready()) return; + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (P8-F, mipmap-shader-format-or-shape): the knob and " + "the shader mip arms it forces are Magma's; Espryt (" + << Gl().BackendName() << ") generates this chain itself"; + const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); + if (tier.empty()) + GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits a 3D RGBA8 " + "chain natively, so the decline under test is not entered"; + GLuint volume = 0; + glGenTextures(1, &volume); + glBindTexture(GL_TEXTURE_3D, volume); + glTexStorage3D(GL_TEXTURE_3D, 3, GL_RGBA8, 4, 4, 4); + const std::array base{200, 40, 80, 255}; + const std::array sentinel{10, 220, 30, 255}; + const auto fill = [](int side, const std::array& texel) { + std::vector pixels(static_cast(side * side * side * 4)); + for (size_t at = 0; at < pixels.size(); at += 4) + std::copy(texel.begin(), texel.end(), pixels.begin() + at); + return pixels; + }; + const auto level0 = fill(4, base); + const auto level1 = fill(2, sentinel); + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, level0.data()); + glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.setup"; + const auto before = PeekSplitRuntime().emitSeq; + glGenerateMipmap(GL_TEXTURE_3D); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMipDecline.wire"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.error"; + for (const int level : {0, 1}) { + const int side = 4 >> level; + std::vector pixels(static_cast(side * side * side * 4), 0x5a); + glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.readback level " << level; + const std::array& expected = level == 0 ? base : sentinel; + size_t wrong = 0; + for (size_t at = 0; at < pixels.size(); at += 4) + if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong; + EXPECT_EQ(wrong, 0u) << "F1.ShaderMipDecline.level" << level << " tier=" << tier + << ": a declined mip must leave every level as the application wrote it"; + } + glBindTexture(GL_TEXTURE_3D, 0); + glDeleteTextures(1, &volume); glBindTexture(GL_TEXTURE_2D, texture); } - glClearDepth(1.0); -} -// The OTHER half of `depth-stencil-mipmap@P7`, and it is a decline rather than a retirement -// (CONTRACT-P7 §3.2): a combined depth/stencil texture has no mip generation on EITHER arm - -// the monolith's shader fallback asserts depth-only, and an assert is nothing in the release -// build both arms ship as. What this case pins is that the wire arm now says so and carries -// on, rather than taking the session down: no GL error, the session still alive afterwards, -// and level zero still holding what was cleared into it. -// -// No knob: the decline is decided on the ASPECT, before the blit-support question is asked, -// so it fires on every driver. -// Red once (executed, reverted): restore the MagmaWireFatal and this case dies with -// Fatal{UnmigratedVerb, "Magma:depth-stencil-mipmap@P7"}. -// -// MAGMA ONLY, like the three other `*Declines*` cases below. Rule I and §3.2 are MG_Backend/ -// DirectVulkan's wire-arm contract, but the DirectGLES whole-binary entry discovers this case -// too and reaches its body whenever the process resolves a split transport - the primary -// integration lane runs every integration-gpu entry under MOBILEGL_TRANSPORT=inproc. Espryt -// answers this shape with GL_INVALID_OPERATION and leaves level zero and the session intact -// (measured, DirectGLES x inproc on lavapipe): a different answer, not a broken decline. -TEST_F(F1WireScenario, GenerateMipmapDepthStencilDeclinesAndKeepsTheSession) { - if (!Ready()) return; - if (Gl().BackendName() != "DirectVulkan") - GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, depth-stencil-mipmap): Espryt (" - << Gl().BackendName() - << ") answers a depth/stencil glGenerateMipmap with GL_INVALID_OPERATION and " - "leaves level 0 intact, which is not the no-error decline this case pins"; - GLuint depthStencil = 0; - glGenTextures(1, &depthStencil); - glBindTexture(GL_TEXTURE_2D, depthStencil); - glTexStorage2D(GL_TEXTURE_2D, 4, GL_DEPTH24_STENCIL8, 8, 8); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencil, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glDepthMask(GL_TRUE); - glDisable(GL_SCISSOR_TEST); - glClearDepth(0.5); - glClear(GL_DEPTH_BUFFER_BIT); - // P8-SV: the decline is a WireDeclines.def row now (MipmapDepthStencilAspect), so the server's - // MGWIRE-DECLINES dump sees it. Readable where the server is this process: the inproc arm. - // Red once (executed, reverted): the site back to a bare MGLOG_E_ONCE - the count stays 0. - unsigned long long declinesBefore = 0; - const bool counted = PeekSplitRuntime().transportName == "inproc" && - PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesBefore); - glGenerateMipmap(GL_TEXTURE_2D); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.error"; - // The session is what the retired Fatal used to take with it. A readback is a round trip - // through the server, so it answers that question and the level-zero one at once. - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencil, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - GLfloat level0 = 0; - glReadPixels(1, 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &level0); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DepthStencilMip.readback"; - EXPECT_NEAR(level0, 0.5f, 0.002f) << "F1.DepthStencilMip.level0"; - if (counted) { - unsigned long long declinesAfter = 0; - ASSERT_TRUE(PeekWireDeclineCount("MipmapDepthStencilAspect", &declinesAfter)); - EXPECT_EQ(declinesAfter - declinesBefore, 1u) << "F1.DepthStencilMip.tally: one named decline per refused " - "glGenerateMipmap (WireDeclines.def MipmapDepthStencilAspect)"; - } - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - glDeleteTextures(1, &depthStencil); - glBindTexture(GL_TEXTURE_2D, texture); - glClearDepth(1.0); -} - -// P8-F: `Magma:mipmap-shader-format-or-shape` IS A NAMED DECLINE ON THE WIRE ARM NOW, which is -// the monolith arm's answer for a chain it cannot build (VulkanRenderer.cpp's GenerateMipmap -// declines a format without blit support and leaves the chain unwritten). It used to end the -// session. A 3D texture is a shape neither wire shader arm takes (the colour pass samples a -// sampler2D), so with the native blit forced off (MGITEST_MAGMA_FORCE_SHADER_MIPMAP) it is what -// reaches the refusal on this host - lavapipe blits a 3D RGBA8 chain natively otherwise. -// -// What this case pins: no GL error, the session still answering (two readback round trips), level -// 0 untouched, and level 1 still holding the application's own texels - the decline wrote nothing. -// Magma only, and knob-gated, like GenerateMipmapWithoutNativeBlitPixels above. -// Red once (executed, reverted): restore the MagmaWireFatal and this case dies with -// Fatal{UnmigratedVerb, "Magma:mipmap-shader-format-or-shape ..."}. -TEST_F(F1WireScenario, GenerateMipmapWithoutAShaderArmShapeDeclinesAndKeepsTheSession) { - if (!Ready()) return; - if (Gl().BackendName() != "DirectVulkan") - GTEST_SKIP() << "Magma wire-arm decline (P8-F, mipmap-shader-format-or-shape): the knob and " - "the shader mip arms it forces are Magma's; Espryt (" - << Gl().BackendName() << ") generates this chain itself"; - const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); - if (tier.empty()) - GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits a 3D RGBA8 " - "chain natively, so the decline under test is not entered"; - GLuint volume = 0; - glGenTextures(1, &volume); - glBindTexture(GL_TEXTURE_3D, volume); - glTexStorage3D(GL_TEXTURE_3D, 3, GL_RGBA8, 4, 4, 4); - const std::array base{200, 40, 80, 255}; - const std::array sentinel{10, 220, 30, 255}; - const auto fill = [](int side, const std::array& texel) { - std::vector pixels(static_cast(side * side * side * 4)); - for (size_t at = 0; at < pixels.size(); at += 4) std::copy(texel.begin(), texel.end(), pixels.begin() + at); - return pixels; - }; - const auto level0 = fill(4, base); - const auto level1 = fill(2, sentinel); - glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, 4, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, level0.data()); - glTexSubImage3D(GL_TEXTURE_3D, 1, 0, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.setup"; - const auto before = PeekSplitRuntime().emitSeq; - glGenerateMipmap(GL_TEXTURE_3D); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.ShaderMipDecline.wire"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.error"; - for (const int level : {0, 1}) { - const int side = 4 >> level; - std::vector pixels(static_cast(side * side * side * 4), 0x5a); - glGetTexImage(GL_TEXTURE_3D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.ShaderMipDecline.readback level " << level; - const std::array& expected = level == 0 ? base : sentinel; - size_t wrong = 0; - for (size_t at = 0; at < pixels.size(); at += 4) - if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong; - EXPECT_EQ(wrong, 0u) << "F1.ShaderMipDecline.level" << level << " tier=" << tier - << ": a declined mip must leave every level as the application wrote it"; - } - glBindTexture(GL_TEXTURE_3D, 0); - glDeleteTextures(1, &volume); - glBindTexture(GL_TEXTURE_2D, texture); -} - -TEST_F(F1WireScenario, GenerateMipmapHonorsMutableBaseAndMaxWithoutChangingOtherLevels) { - if (!Ready()) return; - const std::array colors[5] = { - {255, 0, 0, 255}, {0, 255, 255, 255}, {0, 0, 255, 255}, - {255, 255, 0, 255}, {255, 0, 255, 255}}; - for (int level = 0; level < 5; ++level) { - const int side = 16 >> level; - std::vector pixels(static_cast(side * side * 4)); - for (size_t at = 0; at < pixels.size(); at += 4) - std::copy(colors[level].begin(), colors[level].end(), pixels.begin() + at); - glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - // The actual base differs from its uploaded shadow. No readback separates - // this GPU write from generation, and level zero contains a different color. - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glGenerateMipmap(GL_TEXTURE_2D); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int level = 0; level < 5; ++level) { - const int side = 16 >> level; - GLint width = 0, height = 0; - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height); - EXPECT_EQ(width, side) << "level " << level; - EXPECT_EQ(height, side) << "level " << level; - std::vector pixels(static_cast(side * side * 4), 17); - glGetTexImage(GL_TEXTURE_2D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - const std::array expected = level == 1 || level == 2 - ? std::array{0, 255, 0, 255} : colors[level]; - size_t wrong = 0; - for (size_t at = 0; at < pixels.size(); at += 4) - if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong; - EXPECT_EQ(wrong, 0u) << "level " << level << " must retain or generate its own pixels"; - } - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); -} - -TEST_F(F1WireScenario, GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow) { - if (!Ready()) return; - GLuint root = 0, view = 0; - glGenTextures(1, &root); - glBindTexture(GL_TEXTURE_2D_ARRAY, root); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 5, GL_RGBA8, 16, 16, 4); - const std::array colors[5] = { - {255, 0, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}, - {255, 0, 255, 255}, {0, 255, 255, 255}}; - for (int level = 0; level < 5; ++level) { - const int side = 16 >> level; - std::vector pixels(static_cast(side * side * 4 * 4)); - for (size_t at = 0; at < pixels.size(); at += 4) - std::copy(colors[level].begin(), colors[level].end(), pixels.begin() + at); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, 4, - GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - } - glGenTextures(1, &view); - // Logical levels 0..2 alias owner levels 1..3; only owner layers 1..2 belong - // to this view. Its nonzero BASE_LEVEL makes owner level 2 the source. - glTextureView(view, GL_TEXTURE_2D_ARRAY, root, GL_RGBA8, 1, 3, 1, 2); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glTextureParameteri(view, GL_TEXTURE_BASE_LEVEL, 1); - glTextureParameteri(view, GL_TEXTURE_MAX_LEVEL, 2); - for (const int layer : {1, 2}) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 2, layer); + TEST_F(F1WireScenario, GenerateMipmapHonorsMutableBaseAndMaxWithoutChangingOtherLevels) { + if (!Ready()) return; + const std::array colors[5] = { + {255, 0, 0, 255}, {0, 255, 255, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}, {255, 0, 255, 255}}; + for (int level = 0; level < 5; ++level) { + const int side = 16 >> level; + std::vector pixels(static_cast(side * side * 4)); + for (size_t at = 0; at < pixels.size(); at += 4) + std::copy(colors[level].begin(), colors[level].end(), pixels.begin() + at); + glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, side, side, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 1); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + // The actual base differs from its uploaded shadow. No readback separates + // this GPU write from generation, and level zero contains a different color. glClearColor(0, 1, 0, 1); glClear(GL_COLOR_BUFFER_BIT); - } - glGenerateTextureMipmap(view); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glBindTexture(GL_TEXTURE_2D_ARRAY, root); - for (int level = 0; level < 5; ++level) { - const int side = 16 >> level; - GLint width = 0, height = 0, layers = 0; - glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_WIDTH, &width); - glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_HEIGHT, &height); - glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_DEPTH, &layers); - EXPECT_EQ(width, side) << "owner mip " << level; - EXPECT_EQ(height, side) << "owner mip " << level; - ASSERT_EQ(layers, 4) << "generation through a two-layer view must not resize its owner's level"; - const size_t layerBytes = static_cast(side * side * 4); - std::vector pixels(layerBytes * 4, 17); - glGetTexImage(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - for (int layer = 0; layer < 4; ++layer) { - const bool generated = (level == 2 || level == 3) && (layer == 1 || layer == 2); - const std::array expected = generated - ? std::array{0, 255, 0, 255} : colors[level]; + glGenerateMipmap(GL_TEXTURE_2D); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int level = 0; level < 5; ++level) { + const int side = 16 >> level; + GLint width = 0, height = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &height); + EXPECT_EQ(width, side) << "level " << level; + EXPECT_EQ(height, side) << "level " << level; + std::vector pixels(static_cast(side * side * 4), 17); + glGetTexImage(GL_TEXTURE_2D, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + const std::array expected = + level == 1 || level == 2 ? std::array{0, 255, 0, 255} : colors[level]; size_t wrong = 0; - for (size_t at = 0; at < layerBytes; at += 4) - if (!std::equal(expected.begin(), expected.end(), pixels.begin() + layerBytes * layer + at)) ++wrong; - EXPECT_EQ(wrong, 0u) << "owner mip " << level << ", layer " << layer; + for (size_t at = 0; at < pixels.size(); at += 4) + if (!std::equal(expected.begin(), expected.end(), pixels.begin() + at)) ++wrong; + EXPECT_EQ(wrong, 0u) << "level " << level << " must retain or generate its own pixels"; } + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); } - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - glDeleteTextures(1, &view); - glDeleteTextures(1, &root); -} -TEST_F(F1WireScenario, VertexIdSamplerAndScalarUniformPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - const char* fragment = R"(#version 430 core + TEST_F(F1WireScenario, GenerateMipmapThroughViewKeepsOwnerLevelsAndLayersOutsideItsWindow) { + if (!Ready()) return; + GLuint root = 0, view = 0; + glGenTextures(1, &root); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 5, GL_RGBA8, 16, 16, 4); + const std::array colors[5] = { + {255, 0, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}, {255, 0, 255, 255}, {0, 255, 255, 255}}; + for (int level = 0; level < 5; ++level) { + const int side = 16 >> level; + std::vector pixels(static_cast(side * side * 4 * 4)); + for (size_t at = 0; at < pixels.size(); at += 4) + std::copy(colors[level].begin(), colors[level].end(), pixels.begin() + at); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, 4, GL_RGBA, GL_UNSIGNED_BYTE, + pixels.data()); + } + glGenTextures(1, &view); + // Logical levels 0..2 alias owner levels 1..3; only owner layers 1..2 belong + // to this view. Its nonzero BASE_LEVEL makes owner level 2 the source. + glTextureView(view, GL_TEXTURE_2D_ARRAY, root, GL_RGBA8, 1, 3, 1, 2); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glTextureParameteri(view, GL_TEXTURE_BASE_LEVEL, 1); + glTextureParameteri(view, GL_TEXTURE_MAX_LEVEL, 2); + for (const int layer : {1, 2}) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 2, layer); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + glGenerateTextureMipmap(view); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + for (int level = 0; level < 5; ++level) { + const int side = 16 >> level; + GLint width = 0, height = 0, layers = 0; + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_WIDTH, &width); + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_HEIGHT, &height); + glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_DEPTH, &layers); + EXPECT_EQ(width, side) << "owner mip " << level; + EXPECT_EQ(height, side) << "owner mip " << level; + ASSERT_EQ(layers, 4) << "generation through a two-layer view must not resize its owner's level"; + const size_t layerBytes = static_cast(side * side * 4); + std::vector pixels(layerBytes * 4, 17); + glGetTexImage(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + for (int layer = 0; layer < 4; ++layer) { + const bool generated = (level == 2 || level == 3) && (layer == 1 || layer == 2); + const std::array expected = + generated ? std::array{0, 255, 0, 255} : colors[level]; + size_t wrong = 0; + for (size_t at = 0; at < layerBytes; at += 4) + if (!std::equal(expected.begin(), expected.end(), pixels.begin() + layerBytes * layer + at)) + ++wrong; + EXPECT_EQ(wrong, 0u) << "owner mip " << level << ", layer " << layer; + } + } + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glDeleteTextures(1, &view); + glDeleteTextures(1, &root); + } + + TEST_F(F1WireScenario, VertexIdSamplerAndScalarUniformPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core uniform sampler2D sourceTexture; uniform float scale; layout(location=0) out vec4 color; void main() { color = vec4(texture(sourceTexture, vec2(0.5)).rgb * scale, 1.0); } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLuint vao = 0, source = 0, sampler = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); // No attributes, VBO, UBO or SSBO participates in either draw. - glGenTextures(1, &source); - glActiveTexture(GL_TEXTURE3); - glBindTexture(GL_TEXTURE_2D, source); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); - const std::array texel{64, 128, 192, 255}; - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texel.data()); - glGenSamplers(1, &sampler); - glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glBindSampler(3, sampler); - glUseProgram(program); - const GLint sourceLocation = glGetUniformLocation(program, "sourceTexture"); - const GLint scaleLocation = glGetUniformLocation(program, "scale"); - ASSERT_GE(sourceLocation, 0); - ASSERT_GE(scaleLocation, 0); - glUniform1i(sourceLocation, 3); - glViewport(0, 0, 8, 8); - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_RASTERIZER_DISCARD); - glDisable(GL_FRAMEBUFFER_SRGB); - for (const GLfloat scale : {0.5f, 0.25f}) { - glUniform1f(scaleLocation, scale); // Post-link changes must come from GlobalConstants. - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int channel = 0; channel < 3; ++channel) - EXPECT_NEAR(pixel[channel], texel[channel] * scale, 1) << "scalar uniform " << scale; - EXPECT_EQ(pixel[3], 255); + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0, source = 0, sampler = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); // No attributes, VBO, UBO or SSBO participates in either draw. + glGenTextures(1, &source); + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_2D, source); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1); + const std::array texel{64, 128, 192, 255}; + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, texel.data()); + glGenSamplers(1, &sampler); + glSamplerParameteri(sampler, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindSampler(3, sampler); + glUseProgram(program); + const GLint sourceLocation = glGetUniformLocation(program, "sourceTexture"); + const GLint scaleLocation = glGetUniformLocation(program, "scale"); + ASSERT_GE(sourceLocation, 0); + ASSERT_GE(scaleLocation, 0); + glUniform1i(sourceLocation, 3); + glViewport(0, 0, 8, 8); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + glDisable(GL_FRAMEBUFFER_SRGB); + for (const GLfloat scale : {0.5f, 0.25f}) { + glUniform1f(scaleLocation, scale); // Post-link changes must come from GlobalConstants. + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int channel = 0; channel < 3; ++channel) + EXPECT_NEAR(pixel[channel], texel[channel] * scale, 1) << "scalar uniform " << scale; + EXPECT_EQ(pixel[3], 255); + } + glUseProgram(0); + glBindVertexArray(0); + glBindSampler(3, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glDeleteSamplers(1, &sampler); + glDeleteTextures(1, &source); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); } - glUseProgram(0); - glBindVertexArray(0); - glBindSampler(3, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glActiveTexture(GL_TEXTURE0); - glDeleteSamplers(1, &sampler); - glDeleteTextures(1, &source); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} -TEST_F(F1WireScenario, ComputeImageStoreFramebufferPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - const char* compute = R"(#version 430 core + TEST_F(F1WireScenario, ComputeImageStoreFramebufferPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + const char* compute = R"(#version 430 core layout(local_size_x=1, local_size_y=1, local_size_z=1) in; layout(rgba8, binding=2) writeonly uniform image2D destination; void main() { @@ -930,313 +951,314 @@ void main() { imageStore(destination, p, vec4(vec2(p + ivec2(1)) / 8.0, 0.5, 1.0)); } )"; - const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); - ASSERT_NE(program, 0u); - glBindImageTexture(2, texture, 0, GL_FALSE, 7, GL_WRITE_ONLY, GL_RGBA8); // 2D ignores the layer. - glUseProgram(program); - glDispatchCompute(8, 8, 1); - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); - // Read through an FBO so the assertion does not depend on the class-C GetTexImage path. - std::array pixels{}; - glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int y = 0; y < 8; ++y) { - for (int x = 0; x < 8; ++x) { - const size_t at = static_cast(y * 8 + x) * 4; - EXPECT_NEAR(pixels[at], (x + 1) * 255.0 / 8.0, 1) << x << ", " << y; - EXPECT_NEAR(pixels[at + 1], (y + 1) * 255.0 / 8.0, 1) << x << ", " << y; - EXPECT_NEAR(pixels[at + 2], 128, 1); - EXPECT_EQ(pixels[at + 3], 255); + const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); + ASSERT_NE(program, 0u); + glBindImageTexture(2, texture, 0, GL_FALSE, 7, GL_WRITE_ONLY, GL_RGBA8); // 2D ignores the layer. + glUseProgram(program); + glDispatchCompute(8, 8, 1); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); + // Read through an FBO so the assertion does not depend on the class-C GetTexImage path. + std::array pixels{}; + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int y = 0; y < 8; ++y) { + for (int x = 0; x < 8; ++x) { + const size_t at = static_cast(y * 8 + x) * 4; + EXPECT_NEAR(pixels[at], (x + 1) * 255.0 / 8.0, 1) << x << ", " << y; + EXPECT_NEAR(pixels[at + 1], (y + 1) * 255.0 / 8.0, 1) << x << ", " << y; + EXPECT_NEAR(pixels[at + 2], 128, 1); + EXPECT_EQ(pixels[at + 3], 255); + } } + glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glUseProgram(0); + glDeleteProgram(program); } - glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); - glUseProgram(0); - glDeleteProgram(program); -} -TEST_F(F1WireScenario, ComputeImageStoreThroughViewPreservesOtherRootLayer) { - if (!Ready()) return; - GLuint root = 0, view = 0; - glGenTextures(1, &root); - glBindTexture(GL_TEXTURE_2D_ARRAY, root); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 8, 8, 2); - std::array red{}; - for (size_t i = 0; i < red.size(); i += 4) { - red[i] = 255; - red[i + 3] = 255; - } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, - GL_RGBA, GL_UNSIGNED_BYTE, red.data()); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - // Materialize the root's Vulkan image before any storage-image binding. The - // subsequent view-only bind must upgrade the root and preserve its other layer. - std::array initial{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, initial.data()); - ASSERT_EQ(initial, (std::array{255, 0, 0, 255})); - glGenTextures(1, &view); - glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 1, 1); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - const char* compute = R"(#version 430 core + TEST_F(F1WireScenario, ComputeImageStoreThroughViewPreservesOtherRootLayer) { + if (!Ready()) return; + GLuint root = 0, view = 0; + glGenTextures(1, &root); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 8, 8, 2); + std::array red{}; + for (size_t i = 0; i < red.size(); i += 4) { + red[i] = 255; + red[i + 3] = 255; + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + // Materialize the root's Vulkan image before any storage-image binding. The + // subsequent view-only bind must upgrade the root and preserve its other layer. + std::array initial{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, initial.data()); + ASSERT_EQ(initial, (std::array{255, 0, 0, 255})); + glGenTextures(1, &view); + glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 1, 1); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const char* compute = R"(#version 430 core layout(local_size_x=1, local_size_y=1, local_size_z=1) in; layout(rgba8, binding=2) writeonly uniform image2D destination; void main() { imageStore(destination, ivec2(gl_GlobalInvocationID.xy), vec4(0.0, 1.0, 0.0, 1.0)); } )"; - const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); - ASSERT_NE(program, 0u); - glBindImageTexture(2, view, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); - glUseProgram(program); - glDispatchCompute(8, 8, 1); - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); - for (const int layer : {0, 1}) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, layer); - std::array pixels{}; - glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - const std::array expected = layer == 1 - ? std::array{0, 255, 0, 255} : std::array{255, 0, 0, 255}; - for (size_t at = 0; at < pixels.size(); at += 4) { - const std::array actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]}; - EXPECT_EQ(actual, expected) << "root layer " << layer << ", texel " << at / 4; + const GLuint program = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); + ASSERT_NE(program, 0u); + glBindImageTexture(2, view, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glUseProgram(program); + glDispatchCompute(8, 8, 1); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); + for (const int layer : {0, 1}) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, layer); + std::array pixels{}; + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const std::array expected = + layer == 1 ? std::array{0, 255, 0, 255} : std::array{255, 0, 0, 255}; + for (size_t at = 0; at < pixels.size(); at += 4) { + const std::array actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]}; + EXPECT_EQ(actual, expected) << "root layer " << layer << ", texel " << at / 4; + } } + glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); + glUseProgram(0); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glDeleteProgram(program); + glDeleteTextures(1, &view); + glDeleteTextures(1, &root); } - glBindImageTexture(2, 0, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8); - glUseProgram(0); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - glDeleteProgram(program); - glDeleteTextures(1, &view); - glDeleteTextures(1, &root); -} -// Historical case name retained for the registered test catalogue. The old P7 -// refusal is retired: this case now requires the VBO draw's actual green pixels. -TEST_F(F1WireScenario, EnabledVertexBufferDrawKeepsNamedP7Fatal) { - if (!Ready()) return; - Attach(GL_RGBA8); - const char* vertex = R"(#version 430 core + // Historical case name retained for the registered test catalogue. The old P7 + // refusal is retired: this case now requires the VBO draw's actual green pixels. + TEST_F(F1WireScenario, EnabledVertexBufferDrawKeepsNamedP7Fatal) { + if (!Ready()) return; + Attach(GL_RGBA8); + const char* vertex = R"(#version 430 core layout(location=0) in vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); } )"; - const char* fragment = R"(#version 430 core + const char* fragment = R"(#version 430 core layout(location=0) out vec4 color; void main() { color = vec4(0.0, 1.0, 0.0, 1.0); } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLuint vao = 0, vbo = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - const GLfloat positions[] = {-1, -1, 3, -1, -1, 3}; - glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - glEnableVertexAttribArray(0); - glUseProgram(program); - glViewport(0, 0, 8, 8); - glDisable(GL_DEPTH_TEST); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixels{}; - glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (size_t i = 0; i < pixels.size(); i += 4) { - EXPECT_EQ(pixels[i], 0) << "VBO draw red component at pixel " << i / 4; - EXPECT_EQ(pixels[i + 1], 255) << "VBO draw green component at pixel " << i / 4; - EXPECT_EQ(pixels[i + 2], 0); - EXPECT_EQ(pixels[i + 3], 255); + const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0, vbo = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + const GLfloat positions[] = {-1, -1, 3, -1, -1, 3}; + glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(0); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_DEPTH_TEST); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixels{}; + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (size_t i = 0; i < pixels.size(); i += 4) { + EXPECT_EQ(pixels[i], 0) << "VBO draw red component at pixel " << i / 4; + EXPECT_EQ(pixels[i + 1], 255) << "VBO draw green component at pixel " << i / 4; + EXPECT_EQ(pixels[i + 2], 0); + EXPECT_EQ(pixels[i + 3], 255); + } + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &vbo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); } - glUseProgram(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &vbo); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} -TEST_F(F1WireScenario, UniformBufferRangeAndRebindPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - const char* fragment = R"(#version 430 core + TEST_F(F1WireScenario, UniformBufferRangeAndRebindPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core layout(std140, binding=3) uniform Colour { vec4 value; }; layout(location=0) out vec4 color; void main() { color = value; } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - const GLuint block = glGetUniformBlockIndex(program, "Colour"); - ASSERT_NE(block, GLuint(GL_INVALID_INDEX)); - glUniformBlockBinding(program, block, 3); - GLint alignment = 0; - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); - ASSERT_GT(alignment, 0); - const size_t stride = ((sizeof(GLfloat) * 4 + alignment - 1) / alignment) * alignment; - const std::array red{1, 0, 0, 1}, green{0, 1, 0, 1}, blue{0, 0, 1, 1}, yellow{1, 1, 0, 1}; - std::vector bytes(stride * 3); - std::memcpy(bytes.data(), red.data(), sizeof(red)); - std::memcpy(bytes.data() + stride, green.data(), sizeof(green)); - std::memcpy(bytes.data() + 2 * stride, blue.data(), sizeof(blue)); - GLuint buffers[2]{}, vao = 0; - glGenBuffers(2, buffers); - glBindBuffer(GL_UNIFORM_BUFFER, buffers[0]); - glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_DYNAMIC_DRAW); - std::memcpy(bytes.data() + stride, red.data(), sizeof(red)); - glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]); - glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_DYNAMIC_DRAW); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glUseProgram(program); - glViewport(0, 0, 8, 8); - glDisable(GL_DEPTH_TEST); - const auto drawAndExpect = [&](const std::array& expected, const char* when) { - glClearColor(0, 0, 0, 1); + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + const GLuint block = glGetUniformBlockIndex(program, "Colour"); + ASSERT_NE(block, GLuint(GL_INVALID_INDEX)); + glUniformBlockBinding(program, block, 3); + GLint alignment = 0; + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); + ASSERT_GT(alignment, 0); + const size_t stride = ((sizeof(GLfloat) * 4 + alignment - 1) / alignment) * alignment; + const std::array red{1, 0, 0, 1}, green{0, 1, 0, 1}, blue{0, 0, 1, 1}, yellow{1, 1, 0, 1}; + std::vector bytes(stride * 3); + std::memcpy(bytes.data(), red.data(), sizeof(red)); + std::memcpy(bytes.data() + stride, green.data(), sizeof(green)); + std::memcpy(bytes.data() + 2 * stride, blue.data(), sizeof(blue)); + GLuint buffers[2]{}, vao = 0; + glGenBuffers(2, buffers); + glBindBuffer(GL_UNIFORM_BUFFER, buffers[0]); + glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_DYNAMIC_DRAW); + std::memcpy(bytes.data() + stride, red.data(), sizeof(red)); + glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]); + glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_DYNAMIC_DRAW); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_DEPTH_TEST); + const auto drawAndExpect = [&](const std::array& expected, const char* when) { + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << when; + for (size_t i = 0; i < pixel.size(); ++i) + EXPECT_EQ(pixel[i], GLubyte(expected[i] * 255)) << when << " channel " << i; + }; + glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[0], GLintptr(stride), sizeof(green)); + drawAndExpect(green, "nonzero UBO range offset"); + glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[0], GLintptr(2 * stride), sizeof(blue)); + drawAndExpect(blue, "same UBO, changed range"); + glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[1], GLintptr(stride), sizeof(red)); + drawAndExpect(red, "changed UBO handle at the same binding and offset"); + glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]); + glBufferSubData(GL_UNIFORM_BUFFER, GLintptr(stride), sizeof(yellow), yellow.data()); + drawAndExpect(yellow, "subdata changes an already bound UBO"); + glBindBufferRange(GL_UNIFORM_BUFFER, 5, buffers[0], 0, sizeof(red)); + glUniformBlockBinding(program, block, 5); + drawAndExpect(red, "program block binding changes without relinking"); + glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); + glBindBufferBase(GL_UNIFORM_BUFFER, 5, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glUseProgram(0); + glBindVertexArray(0); + glDeleteBuffers(2, buffers); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); + } + + TEST_F(F1WireScenario, ShortUniformBufferRangePadsMissingBytes) { + if (!Ready()) return; + // Magma's compatibility policy for short application UBOs, not a claim about + // undefined short-range reads on arbitrary native GL drivers. Bytes outside + // the bound range are deliberately nonzero, so widening a descriptor fails. + if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma short-UBO compatibility policy"; + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core +layout(std140, binding=3) uniform ShortColour { vec4 value; vec4 tail; }; +layout(location=0) out vec4 color; +void main() { color = value + tail; } +)"; + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLint alignment = 0; + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); + ASSERT_GT(alignment, 0); + const size_t offset = size_t(alignment); + std::vector bytes(offset + 8 * sizeof(GLfloat), 0); + const GLfloat values[] = {0, 1, 0, 1, 0, 1, 1, 1}; + std::memcpy(bytes.data() + offset, values, sizeof(values)); + GLuint ubo = 0, vao = 0; + glGenBuffers(1, &ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_STATIC_DRAW); + glBindBufferRange(GL_UNIFORM_BUFFER, 3, ubo, GLintptr(offset), 5 * sizeof(GLfloat)); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_DEPTH_TEST); + glClearColor(1, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); std::array pixel{}; glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << when; - for (size_t i = 0; i < pixel.size(); ++i) EXPECT_EQ(pixel[i], GLubyte(expected[i] * 255)) << when << " channel " << i; - }; - glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[0], GLintptr(stride), sizeof(green)); - drawAndExpect(green, "nonzero UBO range offset"); - glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[0], GLintptr(2 * stride), sizeof(blue)); - drawAndExpect(blue, "same UBO, changed range"); - glBindBufferRange(GL_UNIFORM_BUFFER, 3, buffers[1], GLintptr(stride), sizeof(red)); - drawAndExpect(red, "changed UBO handle at the same binding and offset"); - glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]); - glBufferSubData(GL_UNIFORM_BUFFER, GLintptr(stride), sizeof(yellow), yellow.data()); - drawAndExpect(yellow, "subdata changes an already bound UBO"); - glBindBufferRange(GL_UNIFORM_BUFFER, 5, buffers[0], 0, sizeof(red)); - glUniformBlockBinding(program, block, 5); - drawAndExpect(red, "program block binding changes without relinking"); - glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); - glBindBufferBase(GL_UNIFORM_BUFFER, 5, 0); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glUseProgram(0); - glBindVertexArray(0); - glDeleteBuffers(2, buffers); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) + << "range-external poison leaked into short UBO tail"; + glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glUseProgram(0); + glBindVertexArray(0); + glDeleteBuffers(1, &ubo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); + } -TEST_F(F1WireScenario, ShortUniformBufferRangePadsMissingBytes) { - if (!Ready()) return; - // Magma's compatibility policy for short application UBOs, not a claim about - // undefined short-range reads on arbitrary native GL drivers. Bytes outside - // the bound range are deliberately nonzero, so widening a descriptor fails. - if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma short-UBO compatibility policy"; - Attach(GL_RGBA8); - const char* fragment = R"(#version 430 core + TEST_F(F1WireScenario, SubWordUniformBufferRangePadsMissingBytes) { + if (!Ready()) return; + // The sibling of ShortUniformBufferRangePadsMissingBytes whose bound range does NOT + // end on a four-byte boundary. That window is what `uniform-buffer-byte-tail` + // used to refuse (a named Fatal that took the whole session with it), and it is the + // live boundary the Redmi Minecraft run reported (docs/archive/notes/p5f/magma-inproc-fix.md #5). + // + // Only the SIZE can land off a word here. glBindBufferRange constrains the offset to + // GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT and records GL_INVALID_VALUE otherwise + // (MG_Impl/GLImpl/Buffer/GL_Buffer.cpp ValidateBufferRangeOffsetAndSize), so an odd + // offset never reaches the backend through the public API - but GL 4.6 core 6.1.1 + // puts no such rule on size, which is why 19 is a legal bind and 19 % 4 is not 0. + if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma short-UBO compatibility policy"; + Attach(GL_RGBA8); + const char* fragment = R"(#version 430 core layout(std140, binding=3) uniform ShortColour { vec4 value; vec4 tail; }; layout(location=0) out vec4 color; void main() { color = value + tail; } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLint alignment = 0; - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); - ASSERT_GT(alignment, 0); - const size_t offset = size_t(alignment); - std::vector bytes(offset + 8 * sizeof(GLfloat), 0); - const GLfloat values[] = {0, 1, 0, 1, 0, 1, 1, 1}; - std::memcpy(bytes.data() + offset, values, sizeof(values)); - GLuint ubo = 0, vao = 0; - glGenBuffers(1, &ubo); - glBindBuffer(GL_UNIFORM_BUFFER, ubo); - glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_STATIC_DRAW); - glBindBufferRange(GL_UNIFORM_BUFFER, 3, ubo, GLintptr(offset), 5 * sizeof(GLfloat)); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glUseProgram(program); - glViewport(0, 0, 8, 8); - glDisable(GL_DEPTH_TEST); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << "range-external poison leaked into short UBO tail"; - glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glUseProgram(0); - glBindVertexArray(0); - glDeleteBuffers(1, &ubo); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLint alignment = 0; + glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); + ASSERT_GT(alignment, 0); + const size_t offset = size_t(alignment); + const GLsizeiptr boundSize = 19; // 16 bytes of `value` plus three bytes of tail.x + std::vector bytes(offset + 8 * sizeof(GLfloat), 0); + const GLfloat values[] = {0, 1, 0, 1, 0, 1, 1, 1}; + std::memcpy(bytes.data() + offset, values, sizeof(values)); + // The byte immediately past the bound range, and the reason this case is sharper than + // its four-byte sibling: rounding the read OUT to a word pulls byte 19 into the staging + // slice, and the block must still end at byte 19. If it leaks, tail.x reads 0.5f and + // the red channel comes back 127 instead of 0. + bytes[offset + 19] = 0x3F; + GLuint ubo = 0, vao = 0; + glGenBuffers(1, &ubo); + glBindBuffer(GL_UNIFORM_BUFFER, ubo); + glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_STATIC_DRAW); + glBindBufferRange(GL_UNIFORM_BUFFER, 3, ubo, GLintptr(offset), boundSize); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "a 19-byte uniform range is a legal bind"; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_DEPTH_TEST); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) + << "the sub-word uniform range did not pad to the reflected block exactly"; + glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); + glBindBuffer(GL_UNIFORM_BUFFER, 0); + glUseProgram(0); + glBindVertexArray(0); + glDeleteBuffers(1, &ubo); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); + } -TEST_F(F1WireScenario, SubWordUniformBufferRangePadsMissingBytes) { - if (!Ready()) return; - // The sibling of ShortUniformBufferRangePadsMissingBytes whose bound range does NOT - // end on a four-byte boundary. That window is what `uniform-buffer-byte-tail` - // used to refuse (a named Fatal that took the whole session with it), and it is the - // live boundary the Redmi Minecraft run reported (docs/archive/notes/p5f/magma-inproc-fix.md #5). - // - // Only the SIZE can land off a word here. glBindBufferRange constrains the offset to - // GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT and records GL_INVALID_VALUE otherwise - // (MG_Impl/GLImpl/Buffer/GL_Buffer.cpp ValidateBufferRangeOffsetAndSize), so an odd - // offset never reaches the backend through the public API - but GL 4.6 core 6.1.1 - // puts no such rule on size, which is why 19 is a legal bind and 19 % 4 is not 0. - if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma short-UBO compatibility policy"; - Attach(GL_RGBA8); - const char* fragment = R"(#version 430 core -layout(std140, binding=3) uniform ShortColour { vec4 value; vec4 tail; }; -layout(location=0) out vec4 color; -void main() { color = value + tail; } -)"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLint alignment = 0; - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &alignment); - ASSERT_GT(alignment, 0); - const size_t offset = size_t(alignment); - const GLsizeiptr boundSize = 19; // 16 bytes of `value` plus three bytes of tail.x - std::vector bytes(offset + 8 * sizeof(GLfloat), 0); - const GLfloat values[] = {0, 1, 0, 1, 0, 1, 1, 1}; - std::memcpy(bytes.data() + offset, values, sizeof(values)); - // The byte immediately past the bound range, and the reason this case is sharper than - // its four-byte sibling: rounding the read OUT to a word pulls byte 19 into the staging - // slice, and the block must still end at byte 19. If it leaks, tail.x reads 0.5f and - // the red channel comes back 127 instead of 0. - bytes[offset + 19] = 0x3F; - GLuint ubo = 0, vao = 0; - glGenBuffers(1, &ubo); - glBindBuffer(GL_UNIFORM_BUFFER, ubo); - glBufferData(GL_UNIFORM_BUFFER, GLsizeiptr(bytes.size()), bytes.data(), GL_STATIC_DRAW); - glBindBufferRange(GL_UNIFORM_BUFFER, 3, ubo, GLintptr(offset), boundSize); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "a 19-byte uniform range is a legal bind"; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glUseProgram(program); - glViewport(0, 0, 8, 8); - glDisable(GL_DEPTH_TEST); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) - << "the sub-word uniform range did not pad to the reflected block exactly"; - glBindBufferBase(GL_UNIFORM_BUFFER, 3, 0); - glBindBuffer(GL_UNIFORM_BUFFER, 0); - glUseProgram(0); - glBindVertexArray(0); - glDeleteBuffers(1, &ubo); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} - -TEST_F(F1WireScenario, ComputeWrittenVertexAndIndexBuffersDrawAndReadBack) { - if (!Ready()) return; - Attach(GL_RGBA8); - const char* compute = R"(#version 430 core + TEST_F(F1WireScenario, ComputeWrittenVertexAndIndexBuffersDrawAndReadBack) { + if (!Ready()) return; + Attach(GL_RGBA8); + const char* compute = R"(#version 430 core layout(local_size_x=1) in; layout(std430, binding=0) buffer Vertices { vec4 positions[]; }; layout(std430, binding=1) buffer Indices { uint indices[]; }; @@ -1247,1045 +1269,1041 @@ void main() { indices[0] = 0u; indices[1] = 1u; indices[2] = 2u; } )"; - const char* vertex = R"(#version 430 core + const char* vertex = R"(#version 430 core layout(location=0) in vec4 position; void main() { gl_Position = position; } )"; - const char* fragment = R"(#version 430 core + const char* fragment = R"(#version 430 core layout(location=0) out vec4 color; void main() { color = vec4(0, 1, 0, 1); } )"; - const GLuint cs = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); - const GLuint graphics = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(cs, 0u); - ASSERT_NE(graphics, 0u); - GLuint buffers[2]{}, vao = 0; - glGenBuffers(2, buffers); - const GLfloat poisonPositions[12]{}; // A degenerate triangle if stale CPU bytes reach the draw. - const GLuint poisonIndices[3]{}; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[0]); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(poisonPositions), poisonPositions, GL_DYNAMIC_COPY); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[0]); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[1]); - glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(poisonIndices), poisonIndices, GL_DYNAMIC_COPY); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffers[1]); - glUseProgram(cs); - glDispatchCompute(1, 1, 1); - glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); - glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr); - glEnableVertexAttribArray(0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); - glUseProgram(graphics); - glViewport(0, 0, 8, 8); - glDisable(GL_DEPTH_TEST); - const auto drawAndExpect = [&](const char* when) { - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr); - std::array pixel{}; - glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << when; - EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << when; - }; - drawAndExpect("GPU-written vertex/index bytes before any CPU readback"); - std::array actualPositions{}; - std::array actualIndices{}; - glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(actualPositions), actualPositions.data()); - glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(actualIndices), actualIndices.data()); - EXPECT_EQ(actualPositions, (std::array{-1, -1, 0, 1, 3, -1, 0, 1, -1, 3, 0, 1})); - EXPECT_EQ(actualIndices, (std::array{0, 1, 2})); - drawAndExpect("readback must not replace the canonical GPU-written buffers with old CPU shadows"); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); - glUseProgram(0); - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(2, buffers); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(cs); - glDeleteProgram(graphics); -} + const GLuint cs = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); + const GLuint graphics = BuildWireProgram({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(cs, 0u); + ASSERT_NE(graphics, 0u); + GLuint buffers[2]{}, vao = 0; + glGenBuffers(2, buffers); + const GLfloat poisonPositions[12]{}; // A degenerate triangle if stale CPU bytes reach the draw. + const GLuint poisonIndices[3]{}; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[0]); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(poisonPositions), poisonPositions, GL_DYNAMIC_COPY); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffers[0]); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[1]); + glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(poisonIndices), poisonIndices, GL_DYNAMIC_COPY); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, buffers[1]); + glUseProgram(cs); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_ELEMENT_ARRAY_BARRIER_BIT | + GL_BUFFER_UPDATE_BARRIER_BIT); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, buffers[0]); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), nullptr); + glEnableVertexAttribArray(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[1]); + glUseProgram(graphics); + glViewport(0, 0, 8, 8); + glDisable(GL_DEPTH_TEST); + const auto drawAndExpect = [&](const char* when) { + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, nullptr); + std::array pixel{}; + glReadPixels(4, 4, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << when; + EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << when; + }; + drawAndExpect("GPU-written vertex/index bytes before any CPU readback"); + std::array actualPositions{}; + std::array actualIndices{}; + glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(actualPositions), actualPositions.data()); + glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, sizeof(actualIndices), actualIndices.data()); + EXPECT_EQ(actualPositions, (std::array{-1, -1, 0, 1, 3, -1, 0, 1, -1, 3, 0, 1})); + EXPECT_EQ(actualIndices, (std::array{0, 1, 2})); + drawAndExpect("readback must not replace the canonical GPU-written buffers with old CPU shadows"); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, 0); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + glUseProgram(0); + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(2, buffers); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(cs); + glDeleteProgram(graphics); + } -TEST_F(F1WireScenario, UnalignedAtomicCounterRangeDeclinesAndTheSessionLives) { - if (!Ready()) return; - // The one shape in the P7 unaligned-range cluster an application can actually reach. - // GL 4.6 core 6.1.1 gives GL_ATOMIC_COUNTER_BUFFER no queryable offset alignment, so - // glBindBufferRange only enforces offset % 4 on it, while glslang lowers the counter - // block onto a storage buffer whose descriptor offset must be a multiple of - // GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT (minStorageBufferOffsetAlignment: 16 on - // lavapipe, 64 on Adreno). Offset 4 is therefore a legal bind the backend cannot - // express. It used to take the session with it as a named Fatal; it is now a decline, - // and what this case pins is exactly that - the dispatch is lost, nothing else is. - if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma descriptor-alignment decline"; - GLint storageAlignment = 0; - glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &storageAlignment); - if (storageAlignment <= 4) GTEST_SKIP() << "this device can express a 4-byte-aligned counter range"; - const char* compute = R"(#version 430 core + TEST_F(F1WireScenario, UnalignedAtomicCounterRangeDeclinesAndTheSessionLives) { + if (!Ready()) return; + // The one shape in the P7 unaligned-range cluster an application can actually reach. + // GL 4.6 core 6.1.1 gives GL_ATOMIC_COUNTER_BUFFER no queryable offset alignment, so + // glBindBufferRange only enforces offset % 4 on it, while glslang lowers the counter + // block onto a storage buffer whose descriptor offset must be a multiple of + // GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT (minStorageBufferOffsetAlignment: 16 on + // lavapipe, 64 on Adreno). Offset 4 is therefore a legal bind the backend cannot + // express. It used to take the session with it as a named Fatal; it is now a decline, + // and what this case pins is exactly that - the dispatch is lost, nothing else is. + if (Gl().BackendName() != "DirectVulkan") GTEST_SKIP() << "Magma descriptor-alignment decline"; + GLint storageAlignment = 0; + glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &storageAlignment); + if (storageAlignment <= 4) GTEST_SKIP() << "this device can express a 4-byte-aligned counter range"; + const char* compute = R"(#version 430 core layout(local_size_x=1) in; layout(binding=0, offset=0) uniform atomic_uint counter; void main() { atomicCounterIncrement(counter); } )"; - const GLuint cs = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); - ASSERT_NE(cs, 0u); - GLuint counters = 0; - glGenBuffers(1, &counters); - const std::array seed{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u}; - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, counters); - glBufferData(GL_ATOMIC_COUNTER_BUFFER, GLsizeiptr(seed.size() * sizeof(GLuint)), seed.data(), GL_DYNAMIC_COPY); - // Offset 4: a multiple of 4 and so a legal GL bind, but not a multiple of the storage - // alignment this device reports. The bind itself must NOT raise a GL error. - glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, counters, GLintptr(4), GLsizeiptr(sizeof(GLuint))); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "offset 4 is a legal GL_ATOMIC_COUNTER_BUFFER range"; - glUseProgram(cs); - glDispatchCompute(1, 1, 1); - glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); - // The session is still here - this is the whole point - and it still answers GL. - std::array after{}; - glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, GLsizeiptr(after.size() * sizeof(GLuint)), after.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the declined dispatch must not poison the context"; - // The dispatch was declined, so the counter is untouched; the bytes outside the bound - // range must be untouched either way. Both halves of that are worth pinning: a future - // copy-back implementation turns the first EXPECT into seed[1] + 1 and nothing else. - EXPECT_EQ(after[1], seed[1]) << "a declined counter dispatch must not have run"; - EXPECT_EQ(after[0], seed[0]); - EXPECT_EQ(after[2], seed[2]); - EXPECT_EQ(after[3], seed[3]); - // And the context is still usable for ordinary work afterwards. - GLuint probe = 0; - glGenBuffers(1, &probe); - glBindBuffer(GL_ARRAY_BUFFER, probe); - glBufferData(GL_ARRAY_BUFFER, 16, seed.data(), GL_STATIC_DRAW); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the session survived the decline"; - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &probe); - glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, 0); - glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); - glUseProgram(0); - glDeleteBuffers(1, &counters); - glDeleteProgram(cs); -} + const GLuint cs = BuildWireProgram({{GL_COMPUTE_SHADER, compute}}); + ASSERT_NE(cs, 0u); + GLuint counters = 0; + glGenBuffers(1, &counters); + const std::array seed{0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u}; + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, counters); + glBufferData(GL_ATOMIC_COUNTER_BUFFER, GLsizeiptr(seed.size() * sizeof(GLuint)), seed.data(), GL_DYNAMIC_COPY); + // Offset 4: a multiple of 4 and so a legal GL bind, but not a multiple of the storage + // alignment this device reports. The bind itself must NOT raise a GL error. + glBindBufferRange(GL_ATOMIC_COUNTER_BUFFER, 0, counters, GLintptr(4), GLsizeiptr(sizeof(GLuint))); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "offset 4 is a legal GL_ATOMIC_COUNTER_BUFFER range"; + glUseProgram(cs); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_ATOMIC_COUNTER_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); + // The session is still here - this is the whole point - and it still answers GL. + std::array after{}; + glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, GLsizeiptr(after.size() * sizeof(GLuint)), after.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the declined dispatch must not poison the context"; + // The dispatch was declined, so the counter is untouched; the bytes outside the bound + // range must be untouched either way. Both halves of that are worth pinning: a future + // copy-back implementation turns the first EXPECT into seed[1] + 1 and nothing else. + EXPECT_EQ(after[1], seed[1]) << "a declined counter dispatch must not have run"; + EXPECT_EQ(after[0], seed[0]); + EXPECT_EQ(after[2], seed[2]); + EXPECT_EQ(after[3], seed[3]); + // And the context is still usable for ordinary work afterwards. + GLuint probe = 0; + glGenBuffers(1, &probe); + glBindBuffer(GL_ARRAY_BUFFER, probe); + glBufferData(GL_ARRAY_BUFFER, 16, seed.data(), GL_STATIC_DRAW); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the session survived the decline"; + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &probe); + glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, 0); + glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0); + glUseProgram(0); + glDeleteBuffers(1, &counters); + glDeleteProgram(cs); + } -TEST_F(F1WireScenario, SrgbDrawTracksFramebufferConversion) { - if (!Ready()) return; - Attach(GL_SRGB8_ALPHA8); - const char* fragment = R"(#version 430 core + TEST_F(F1WireScenario, SrgbDrawTracksFramebufferConversion) { + if (!Ready()) return; + Attach(GL_SRGB8_ALPHA8); + const char* fragment = R"(#version 430 core layout(location=0) out vec4 color; void main() { color = vec4(0.5, 0.5, 0.5, 1.0); } )"; - const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, - {GL_FRAGMENT_SHADER, fragment}}); - ASSERT_NE(program, 0u); - GLuint vao = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glUseProgram(program); - glViewport(0, 0, 8, 8); - glDisable(GL_BLEND); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_CULL_FACE); - glDisable(GL_RASTERIZER_DISCARD); - for (const bool conversion : {false, true}) { - if (conversion) glEnable(GL_FRAMEBUFFER_SRGB); - else glDisable(GL_FRAMEBUFFER_SRGB); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int channel = 0; channel < 3; ++channel) - EXPECT_NEAR(pixel[channel], conversion ? 188 : 128, 1) << "FRAMEBUFFER_SRGB=" << conversion; - EXPECT_EQ(pixel[3], 255); - } - glDisable(GL_FRAMEBUFFER_SRGB); - glUseProgram(0); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - glDeleteProgram(program); -} - -TEST_F(F1WireScenario, Texture1DFramebufferClearPixels) { - if (!Ready()) return; - GLuint line = 0; - glGenTextures(1, &line); - glBindTexture(GL_TEXTURE_1D, line); - glTexStorage1D(GL_TEXTURE_1D, 1, GL_RGBA8, 8); - glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, line, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - std::array pixels{}; - glReadPixels(0, 0, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - const int expected[] = {64, 128, 191, 255}; - for (int x = 0; x < 8; ++x) - for (int channel = 0; channel < 4; ++channel) - EXPECT_NEAR(pixels[x * 4 + channel], expected[channel], 1) << "1D texel " << x; - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); - glBindTexture(GL_TEXTURE_1D, 0); - glDeleteTextures(1, &line); -} - -TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - // The client shadow is red. The GPU then changes every texel to green, so - // applying a later one-pixel upload as a whole shadow loses observable data. - std::array red{}; - for (size_t i = 0; i < red.size(); i += 4) { - red[i] = 255; - red[i + 3] = 255; - } - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - const std::array blue{0, 0, 255, 255}; - glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue.data()); - - std::array pixels{}; - glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - for (int y = 0; y < 8; ++y) { - for (int x = 0; x < 8; ++x) { - const std::array expected = x == 1 && y == 2 - ? blue : std::array{0, 255, 0, 255}; - const size_t at = static_cast(y * 8 + x) * 4; - const std::array actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]}; - EXPECT_EQ(actual, expected) << "GPU clear survived outside upload at " << x << ", " << y; + const GLuint program = + BuildWireProgram({{GL_VERTEX_SHADER, kWireVertexIdTriangle}, {GL_FRAGMENT_SHADER, fragment}}); + ASSERT_NE(program, 0u); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glUseProgram(program); + glViewport(0, 0, 8, 8); + glDisable(GL_BLEND); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + for (const bool conversion : {false, true}) { + if (conversion) + glEnable(GL_FRAMEBUFFER_SRGB); + else + glDisable(GL_FRAMEBUFFER_SRGB); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(3, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int channel = 0; channel < 3; ++channel) + EXPECT_NEAR(pixel[channel], conversion ? 188 : 128, 1) << "FRAMEBUFFER_SRGB=" << conversion; + EXPECT_EQ(pixel[3], 255); } + glDisable(GL_FRAMEBUFFER_SRGB); + glUseProgram(0); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + glDeleteProgram(program); } -} -TEST_F(F1WireScenario, TextureViewClearTargetsItsRootMipAndLayer) { - if (!Ready()) return; - GLuint root = 0, view = 0; - glGenTextures(1, &root); - glBindTexture(GL_TEXTURE_2D_ARRAY, root); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); - std::array red{}; - for (size_t i = 0; i < red.size(); i += 4) { - red[i] = 255; - red[i + 3] = 255; - } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, - GL_RGBA, GL_UNSIGNED_BYTE, red.data()); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2, - GL_RGBA, GL_UNSIGNED_BYTE, red.data()); - std::array blue{}; - for (size_t i = 0; i < blue.size(); i += 4) { - blue[i + 2] = 255; - blue[i + 3] = 255; - } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1, - GL_RGBA, GL_UNSIGNED_BYTE, blue.data()); - glGenTextures(1, &view); - glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 1, 1, 1, 1); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "view construction"; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, view, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - - std::array pixel{}; - glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << "view reads its cleared window"; - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 1); - glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << "view clear reaches root mip 1, layer 1"; - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 0); - glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{0, 0, 255, 255})) << "the neighboring root layer is preserved"; - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 1); - glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "the neighboring root mip is preserved"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); - glDeleteTextures(1, &view); - glDeleteTextures(1, &root); -} - -TEST_F(F1WireScenario, NamedBlitPreservesBindingsAndRestoresNextVerbsPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - GLuint fbos[3]{}, textures[3]{}; - glGenFramebuffers(3, fbos); - glGenTextures(3, textures); - for (int i = 0; i < 3; ++i) { - glBindFramebuffer(GL_FRAMEBUFFER, fbos[i]); - glBindTexture(GL_TEXTURE_2D, textures[i]); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 8); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0); + TEST_F(F1WireScenario, Texture1DFramebufferClearPixels) { + if (!Ready()) return; + GLuint line = 0; + glGenTextures(1, &line); + glBindTexture(GL_TEXTURE_1D, line); + glTexStorage1D(GL_TEXTURE_1D, 1, GL_RGBA8, 8); + glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, line, 0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glClearColor(0, i == 1 ? 1 : 0, 1, 1); + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - } - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[2]); - const auto before = PeekSplitRuntime().emitSeq; - glBlitNamedFramebuffer(fbo, fbos[0], 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_GT(PeekSplitRuntime().emitSeq, before); - GLint read = 0, draw = 0; - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); - EXPECT_EQ(read, GLint(fbos[1])); - EXPECT_EQ(draw, GLint(fbos[2])); - - // No intervening bind: this must clear the restored draw FBO, not the DSA destination. - glClearColor(1, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[2]); - std::array pixel{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{255, 0, 255, 255})) << "named blit restored draw before clear"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]); - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "unbound named blit copied source"; - - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]); - glBlitFramebuffer(0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[2]); - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{0, 255, 255, 255})) << "ordinary blit follows restored bindings"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(3, fbos); - glDeleteTextures(3, textures); -} - -TEST_F(F1WireScenario, NamedBlitDefaultEndpointPixels) { - if (!Ready()) return; - Attach(GL_RGBA8); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glClearColor(0, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glBlitNamedFramebuffer(fbo, 0, 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); - GLint read = 0, draw = 0; - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); - EXPECT_EQ(read, GLint(fbo)); - EXPECT_EQ(draw, GLint(fbo)); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - std::array pixel{}; - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "default draw endpoint"; - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBlitNamedFramebuffer(0, fbo, 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); - EXPECT_EQ(read, GLint(fbo)); - EXPECT_EQ(draw, GLint(fbo)); - glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "default read endpoint"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); -} - -TEST_F(F1WireScenario, ColorBlitToDefaultIgnoresViewportAndPreservesOrientation) { - if (!Ready()) return; - ASSERT_GE(Gl().Width(), 16); - ASSERT_GE(Gl().Height(), 16); - ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); - const int width = Gl().Width(), height = Gl().Height(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // The destination really is the default framebuffer. - glClearColor(1, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(1, 2, 3, 5); // glBlitFramebuffer must ignore this unrelated viewport. - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBlitFramebuffer(0, 0, 6, 4, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - EXPECT_EQ(ReadOnePixel(width / 4, height / 4), (std::array{255, 0, 0, 255})) << "bottom left"; - EXPECT_EQ(ReadOnePixel(3 * width / 4, height / 4), (std::array{0, 255, 0, 255})) << "bottom right"; - EXPECT_EQ(ReadOnePixel(width / 4, 3 * height / 4), (std::array{0, 0, 255, 255})) << "top left"; - EXPECT_EQ(ReadOnePixel(3 * width / 4, 3 * height / 4), (std::array{255, 255, 0, 255})) << "top right"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - GLint viewport[4]{}; - glGetIntegerv(GL_VIEWPORT, viewport); - EXPECT_EQ((std::array{viewport[0], viewport[1], viewport[2], viewport[3]}), - (std::array{1, 2, 3, 5})) << "blit must not mutate the application's viewport"; - Gl().EndFrame(); -} - -TEST_F(F1WireScenario, ColorBlitToDefaultHonorsScissorAndReversedRect) { - if (!Ready()) return; - ASSERT_GE(Gl().Width(), 16); - ASSERT_GE(Gl().Height(), 16); - ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); - const int width = Gl().Width(), height = Gl().Height(); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glClearColor(1, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glViewport(2, 1, 5, 3); - // Copy into the centre half with BOTH destination axes reversed. Scissor - // keeps only its left half, so a plain unclipped vkCmdBlitImage is incorrect. - glScissor(width / 4, height / 4, width / 4, height / 2); - glEnable(GL_SCISSOR_TEST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBlitFramebuffer(0, 0, 6, 4, 3 * width / 4, 3 * height / 4, - width / 4, height / 4, GL_COLOR_BUFFER_BIT, GL_NEAREST); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - EXPECT_EQ(ReadOnePixel(3 * width / 8, 3 * height / 8), (std::array{255, 255, 0, 255})) - << "reversed bottom-left destination reads the source top-right yellow quadrant"; - EXPECT_EQ(ReadOnePixel(3 * width / 8, 5 * height / 8), (std::array{0, 255, 0, 255})) - << "reversed top-left destination reads the source bottom-right green quadrant"; - const std::array untouched{255, 0, 255, 255}; - EXPECT_EQ(ReadOnePixel(5 * width / 8, 3 * height / 8), untouched) << "inside destination, outside scissor"; - EXPECT_EQ(ReadOnePixel(5 * width / 8, 5 * height / 8), untouched) << "upper destination outside scissor"; - EXPECT_EQ(ReadOnePixel(width / 8, height / 2), untouched) << "outside destination rectangle"; - EXPECT_EQ(ReadOnePixel(3 * width / 8, height / 8), untouched) << "below destination and scissor"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - glDisable(GL_SCISSOR_TEST); - Gl().EndFrame(); -} - -// P7 wave 2-B, CONTRACT-P7 §3.2: `default-color-blit-shape@P7` RETIRES. -// -// One Fatal covered three unrelated situations. The shader pass it guarded exists for ROTATION -// and can only SAMPLE its source - sampler2D, a float result, one of two 2D view types - so a -// 3D slice or an integer attachment made it return false and the refusal fired EVEN ON A SURFACE -// WITH NO ROTATION, where the ordinary vkCmdBlitImage arm below it would have done the blit -// correctly and does handle those shapes. Identity now falls through to that arm; a rotated -// surface moves the region into an owned 2D float scratch and blits that; anything outside the -// four rotations declines by name. -// -// THE SOURCE IS A 3D TEXTURE SLICE, which is ordinary defined GL (a layer of a 3D texture is a -// legal colour attachment) and is exactly the shape the shader pass cannot sample. The same case -// runs twice on each arm: plain, taking the identity fall-through, and under -// MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH, taking the rotated arm's scratch path on an identity -// surface - where it must produce THE SAME PIXELS. That equality is what makes the scratch arm -// testable at all: every surface this lane can create is identity, and so is the Redmi pbuffer -// the cluster's evidence comes from. -// -// Red once (executed, reverted): restore MagmaWireFatal("default-color-blit-shape@P7") and both -// entries die by that name. -TEST_F(F1WireScenario, ColorBlitToDefaultFromANonSampleableSourceDegrades) { - if (!Ready()) return; - ASSERT_GE(Gl().Width(), 16); - ASSERT_GE(Gl().Height(), 16); - const int width = Gl().Width(), height = Gl().Height(); - GLuint volume = 0, volumeFbo = 0; - glGenTextures(1, &volume); - glBindTexture(GL_TEXTURE_3D, volume); - glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 6, 4, 2); - glGenFramebuffers(1, &volumeFbo); - glBindFramebuffer(GL_FRAMEBUFFER, volumeFbo); - // Slice 1, not slice 0: a scratch copy that forgot the z origin would read the other slice - // and every quadrant assertion below would still be about SOME real texels. - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 1); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - constexpr GLfloat colors[4][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; - glEnable(GL_SCISSOR_TEST); - for (int quadrant = 0; quadrant < 4; ++quadrant) { - glScissor((quadrant % 2) * 3, (quadrant / 2) * 2, 3, 2); - glClearColor(colors[quadrant][0], colors[quadrant][1], colors[quadrant][2], colors[quadrant][3]); - glClear(GL_COLOR_BUFFER_BIT); - } - glDisable(GL_SCISSOR_TEST); - // The OTHER slice gets one flat colour that appears in no quadrant. - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 0); - glClearColor(0, 1, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 1); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DefaultBlitShape.setup"; - - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glClearColor(1, 0, 1, 1); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, volumeFbo); - const auto before = PeekSplitRuntime().emitSeq; - glBlitFramebuffer(0, 0, 6, 4, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.DefaultBlitShape.wire"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - const std::string arm = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH"); - EXPECT_EQ(ReadOnePixel(width / 4, height / 4), (std::array{255, 0, 0, 255})) - << "F1.DefaultBlitShape bottom left (scratch arm=" << arm << ")"; - EXPECT_EQ(ReadOnePixel(3 * width / 4, height / 4), (std::array{0, 255, 0, 255})) - << "F1.DefaultBlitShape bottom right (scratch arm=" << arm << ")"; - EXPECT_EQ(ReadOnePixel(width / 4, 3 * height / 4), (std::array{0, 0, 255, 255})) - << "F1.DefaultBlitShape top left (scratch arm=" << arm << ")"; - EXPECT_EQ(ReadOnePixel(3 * width / 4, 3 * height / 4), (std::array{255, 255, 0, 255})) - << "F1.DefaultBlitShape top right (scratch arm=" << arm << ")"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DefaultBlitShape.error"; - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &volumeFbo); - glBindTexture(GL_TEXTURE_3D, 0); - glDeleteTextures(1, &volume); - glBindTexture(GL_TEXTURE_2D, texture); - Gl().EndFrame(); -} - -namespace { -// An 8x4 RGBA8 colour renderbuffer at `samples`, bottom half red and top half green in GL row -// order. TWO HALVES AND NOT ONE COLOUR, for the reason the shader-mip case gives: a pass that -// loses GL's row order averages a flat fill back to the same number everywhere and a -// single-colour assertion calls that correct. Returns false when this driver cannot host the -// attachment, which is a skip and not a failure. -bool MakeMultisampleHalvesFbo(int samples, GLuint& fbo, GLuint& renderbuffer) { - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glGenRenderbuffers(1, &renderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, 8, 4); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) return false; - while (glGetError() != GLenum(GL_NO_ERROR)) {} - glDisable(GL_SCISSOR_TEST); - glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, 8, 2); - glClearColor(1, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glScissor(0, 2, 8, 2); - glClearColor(0, 1, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_SCISSOR_TEST); - return true; -} - -// A single-sample RGBA8 destination of the given size, cleared to a colour that appears in no -// assertion below - so "the blit did nothing" and "the blit landed the wrong band" read -// differently from each other. -void MakeSingleSampleTarget(int width, int height, const GLfloat (&clear)[4], GLuint& fbo, GLuint& tex) { - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); - glClearColor(clear[0], clear[1], clear[2], clear[3]); - glClear(GL_COLOR_BUFFER_BIT); -} -} // namespace - -// P7 wave 2-B2, CONTRACT-P7 §3.2: `multisample-blit-shape@P7` RETIRES. -// -// vkCmdResolveImage carries ONE extent and one offset per side. It can neither scale nor flip, -// so the wire arm refused every multisample colour blit whose destination rectangle was a -// different size from its source - which glBlitFramebuffer names, and which the MONOLITH arm -// already reaches by the route this package gives the wire arm (VulkanRenderer.cpp's -// `regionWasTransformed` branch: resolve into a scratch at raw offsets, then blit the scratch). -// The scratch was already here for the flip; the two size equalities were the only thing -// keeping the scale out. -// -// THE SCALE AND THE FLIP IN ONE CASE, over the same source, because they fail differently: a -// blit that dropped the scale lands a 8x4 band in a 4x2 attachment (and the driver refuses the -// command), while one that dropped the flip returns the right two colours in the wrong order. -// The second blit's expectation is the first's, rows swapped, so neither can pass the other's -// assertion. -// -// Red once (executed, reverted): restore the two `width != |dx1-dx0|` clauses in -// WireFramebuffer.cpp's colour resolve arm and both blits die as -// Fatal{UnmigratedVerb, "Magma:multisample-resolve-region"}. -TEST_F(F1WireScenario, MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch) { - if (!Ready()) return; - GLuint msFbo = 0, msRenderbuffer = 0; - if (!MakeMultisampleHalvesFbo(4, msFbo, msRenderbuffer)) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; - } - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.setup"; - - // Half the source in each axis: every destination texel is the resolve of a 2x2 source box - // that lies wholly inside ONE half, so the boundary never falls inside a destination texel - // and the two colours cannot mix. - constexpr GLfloat kBlue[4]{0, 0, 1, 1}; - GLuint dstFbo = 0, dstTexture = 0; - MakeSingleSampleTarget(4, 2, kBlue, dstFbo, dstTexture); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.target"; - - constexpr std::array kRed{255, 0, 0, 255}; - constexpr std::array kGreen{0, 255, 0, 255}; - glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); - glDisable(GL_SCISSOR_TEST); - const auto before = PeekSplitRuntime().emitSeq; - glBlitFramebuffer(0, 0, 8, 4, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsBlitShape.wire"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.scaled.error"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo); - for (int x = 0; x < 4; ++x) { - EXPECT_EQ(ReadOnePixel(x, 0), kRed) << "F1.MsBlitShape scaled bottom row, x=" << x; - EXPECT_EQ(ReadOnePixel(x, 1), kGreen) << "F1.MsBlitShape scaled top row, x=" << x; - } - - // The same source, the same destination size, with the destination rectangle inverted on - // Y. The scratch holds the resolved band at its own origin either way; only the second leg - // sees the inversion, which is what vkCmdBlitImage's reversed offsets express. - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); - glClearColor(kBlue[0], kBlue[1], kBlue[2], kBlue[3]); - glClear(GL_COLOR_BUFFER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); - glBlitFramebuffer(0, 0, 8, 4, 0, 2, 4, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.flipped.error"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo); - for (int x = 0; x < 4; ++x) { - EXPECT_EQ(ReadOnePixel(x, 0), kGreen) << "F1.MsBlitShape flipped bottom row, x=" << x; - EXPECT_EQ(ReadOnePixel(x, 1), kRed) << "F1.MsBlitShape flipped top row, x=" << x; - } - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &dstFbo); - glDeleteTextures(1, &dstTexture); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - glBindTexture(GL_TEXTURE_2D, texture); - Gl().EndFrame(); -} - -// The surviving half of `multisample-blit-shape@P7`, and it is a DECLINE (rule I (a)). -// -// A MULTISAMPLE DESTINATION is the other side of the same guard and has no arm at all: -// vkCmdCopyImage is the only command that writes a multisampled image, it takes one extent, and -// GL 4.6 core 18.3.1 makes a blit whose rectangles differ in size INVALID_OPERATION when either -// framebuffer is multisampled - so there is no picture to get right, only a shape that used to -// end the session. What this case pins is that it no longer does, and that the destination is -// exactly as the clear left it. -// -// THE DESTINATION IS READ THROUGH A RESOLVE, because glReadPixels on a multisampled framebuffer -// is INVALID_OPERATION: the 1:1 resolve into a single-sample attachment is the retired arm -// above, so this case also proves the decline did not disturb the arm next to it. -// -// Red once (executed, reverted): restore MagmaWireFatal("multisample-blit-shape@P7") and this -// case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-shape@P7"}. -TEST_F(F1WireScenario, MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession) { - if (!Ready()) return; - // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). Espryt on the split - // arm leaves the destination untouched but no GL_INVALID_OPERATION reaches the client, which - // GL 4.6 core 18.3.1 asks for here - an Espryt debt of the same class as - // DepthStencilReadbackMatrixScenario's scaled-resolve gate, not this decline's to pin. - if (Gl().BackendName() != "DirectVulkan") - GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-shape): Espryt (" - << Gl().BackendName() - << ") writes nothing for a scaled blit onto a multisample destination but " - "records no GL_INVALID_OPERATION - a P3b/P4b debt, not this decline"; - GLuint msFbo = 0, msRenderbuffer = 0; - glGenFramebuffers(1, &msFbo); - glBindFramebuffer(GL_FRAMEBUFFER, msFbo); - glGenRenderbuffers(1, &msRenderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, msRenderbuffer); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 4, 2); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRenderbuffer); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; - } - while (glGetError() != GLenum(GL_NO_ERROR)) {} - glDisable(GL_SCISSOR_TEST); - glClearColor(1, 0, 1, 1); // magenta: the colour the declined blit must leave behind - glClear(GL_COLOR_BUFFER_BIT); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.setup"; - - // A single-sample 8x4 source, flat yellow, so a blit that ran at all is visible. - constexpr GLfloat kYellow[4]{1, 1, 0, 1}; - GLuint srcFbo = 0, srcTexture = 0; - MakeSingleSampleTarget(8, 4, kYellow, srcFbo, srcTexture); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.source"; - - glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msFbo); - const auto before = PeekSplitRuntime().emitSeq; - glBlitFramebuffer(0, 0, 8, 4, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsBlitDecline.wire"; - // THE glFinish IS LOAD-BEARING, and it is what the first run of this case measured: the - // decline records its error where the blit is PERFORMED - the server's apply thread - and - // that error reaches this client's queue with a later reply, not with the blit call. Without - // the round trip the check below reads GL_NO_ERROR and the INVALID_OPERATION surfaces at - // whatever the next synchronising call happens to be, which is how it first showed up: - // attached to the resolve twenty lines down, a call that is not wrong about anything. - glFinish(); - EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_OPERATION)) - << "F1.MsBlitDecline: the decline must record the error GL names for the shape"; - - // Resolve the multisample destination 1:1 into a single-sample attachment and read THAT: - // the round trip is also the proof that the session is still answering. - constexpr GLfloat kBlue[4]{0, 0, 1, 1}; - GLuint readFbo = 0, readTexture = 0; - MakeSingleSampleTarget(4, 2, kBlue, readFbo, readTexture); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFbo); - glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.resolve"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); - constexpr std::array kMagenta{255, 0, 255, 255}; - for (int y = 0; y < 2; ++y) - for (int x = 0; x < 4; ++x) - EXPECT_EQ(ReadOnePixel(x, y), kMagenta) - << "F1.MsBlitDecline: the declined blit wrote (" << x << "," << y << ")"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline: the session survived"; - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &readFbo); - glDeleteTextures(1, &readTexture); - glDeleteFramebuffers(1, &srcFbo); - glDeleteTextures(1, &srcTexture); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - glBindTexture(GL_TEXTURE_2D, texture); - Gl().EndFrame(); -} - -// P7 wave 2-B2, CONTRACT-P7 §3.2: the surviving half of `multisample-blit-aspect@P7`. -// -// The half WITH an arm is ResolveWireDepthStencil's: a multisample depth or stencil source now -// reaches a single-sample destination on every device - through VK_KHR_depth_stencil_resolve -// where it exists, through WireMultisampleResolve.cpp's baked pass where it does not - and the -// two MsResolve entries on each arm are its reading. This is the half with none. -// -// ONE SAMPLE INTO FOUR, with rectangles that MATCH so the shape decline next door is not what -// fires. Vulkan has no command for it: vkCmdResolveImage only ever writes a single-sample -// destination, vkCmdBlitImage refuses a multisampled side outright, and vkCmdCopyImage needs -// the two counts to be equal. GL 4.6 core 18.3.1 calls this sample replication and the wire arm -// declines it by name rather than ending the session over it. -// -// Red once (executed, reverted): restore MagmaWireFatal("multisample-blit-aspect@P7") and this -// case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-aspect@P7"}. -TEST_F(F1WireScenario, MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession) { - if (!Ready()) return; - // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). The decline itself - // is a recorded Magma debt (CONTRACT-P7 §12: a same-rectangle 1 -> N blit is legal sample - // replication in GL 4.6 core 18.3.1); Espryt on the split arm neither replicates nor - // records an error, which is neither answer this case can pin. - if (Gl().BackendName() != "DirectVulkan") - GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-aspect): Espryt (" - << Gl().BackendName() - << ") writes nothing for a single-sample to multisample blit and records no " - "GL error - a P3b/P4b debt, not this decline"; - GLuint msFbo = 0, msRenderbuffer = 0; - glGenFramebuffers(1, &msFbo); - glBindFramebuffer(GL_FRAMEBUFFER, msFbo); - glGenRenderbuffers(1, &msRenderbuffer); - glBindRenderbuffer(GL_RENDERBUFFER, msRenderbuffer); - glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 4, 2); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRenderbuffer); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; - } - while (glGetError() != GLenum(GL_NO_ERROR)) {} - glDisable(GL_SCISSOR_TEST); - glClearColor(1, 0, 1, 1); // magenta: the colour the declined blit must leave behind - glClear(GL_COLOR_BUFFER_BIT); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.setup"; - - constexpr GLfloat kYellow[4]{1, 1, 0, 1}; - GLuint srcFbo = 0, srcTexture = 0; - MakeSingleSampleTarget(4, 2, kYellow, srcFbo, srcTexture); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.source"; - - glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msFbo); - const auto before = PeekSplitRuntime().emitSeq; - glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsAspectDecline.wire"; - glFinish(); // the decline's error rides a later reply - see the shape case next door - EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_OPERATION)) - << "F1.MsAspectDecline: the decline must record the error GL names for the shape"; - - constexpr GLfloat kBlue[4]{0, 0, 1, 1}; - GLuint readFbo = 0, readTexture = 0; - MakeSingleSampleTarget(4, 2, kBlue, readFbo, readTexture); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFbo); - glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.resolve"; - glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); - constexpr std::array kMagenta{255, 0, 255, 255}; - for (int y = 0; y < 2; ++y) - for (int x = 0; x < 4; ++x) - EXPECT_EQ(ReadOnePixel(x, y), kMagenta) - << "F1.MsAspectDecline: the declined blit wrote (" << x << "," << y << ")"; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline: the session survived"; - - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glDeleteFramebuffers(1, &readFbo); - glDeleteTextures(1, &readTexture); - glDeleteFramebuffers(1, &srcFbo); - glDeleteTextures(1, &srcTexture); - glDeleteFramebuffers(1, &msFbo); - glDeleteRenderbuffers(1, &msRenderbuffer); - glBindTexture(GL_TEXTURE_2D, texture); - Gl().EndFrame(); -} - -namespace { -// One 8x8x2 RGBA8 array texture with two levels, filled so that EVERY subresource this pair -// of cases touches is distinguishable from every other one. A flat fill would let a copy that -// went to the wrong level, the wrong layer or nowhere at all read as correct. -// level 0 layer 0: four quadrants (GL row order: red/green on the bottom, blue/yellow up) -// level 0 layer 1: magenta -// level 1 layer 0: cyan level 1 layer 1: white -constexpr std::array kInPlaceRed{255, 0, 0, 255}; -constexpr std::array kInPlaceGreen{0, 255, 0, 255}; -constexpr std::array kInPlaceBlue{0, 0, 255, 255}; -constexpr std::array kInPlaceYellow{255, 255, 0, 255}; -constexpr std::array kInPlaceMagenta{255, 0, 255, 255}; -constexpr std::array kInPlaceCyan{0, 255, 255, 255}; -constexpr std::array kInPlaceWhite{255, 255, 255, 255}; - -std::array InPlaceQuadrant(int x, int y) { - if (y < 4) return x < 4 ? kInPlaceRed : kInPlaceGreen; - return x < 4 ? kInPlaceBlue : kInPlaceYellow; -} - -GLuint MakeInPlaceCopyTexture() { - GLuint name = 0; - glGenTextures(1, &name); - glBindTexture(GL_TEXTURE_2D_ARRAY, name); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); - std::vector level0(8 * 8 * 2 * 4); - for (int layer = 0; layer < 2; ++layer) - for (int y = 0; y < 8; ++y) - for (int x = 0; x < 8; ++x) { - const auto colour = layer == 0 ? InPlaceQuadrant(x, y) : kInPlaceMagenta; - const size_t at = (static_cast(layer) * 64 + y * 8 + x) * 4; - std::copy(colour.begin(), colour.end(), level0.begin() + at); - } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, GL_RGBA, GL_UNSIGNED_BYTE, level0.data()); - std::vector level1(4 * 4 * 2 * 4); - for (int layer = 0; layer < 2; ++layer) - for (int at = 0; at < 16; ++at) { - const auto colour = layer == 0 ? kInPlaceCyan : kInPlaceWhite; - std::copy(colour.begin(), colour.end(), - level1.begin() + (static_cast(layer) * 16 + at) * 4); - } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data()); - return name; -} - -// (level, side) -> the whole level, every layer, as glGetTexImage hands it back. -std::vector ReadInPlaceLevel(GLint level, int side) { - std::vector pixels(static_cast(side) * side * 2 * 4, 17); - glGetTexImage(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - return pixels; -} - -::testing::AssertionResult InPlaceTexelIs(const std::vector& pixels, int side, int layer, - int x, int y, const std::array& expected) { - const size_t at = ((static_cast(layer) * side * side) + static_cast(y) * side + x) * 4; - if (at + 4 > pixels.size()) return ::testing::AssertionFailure() << "texel out of range"; - for (int channel = 0; channel < 4; ++channel) - if (pixels[at + channel] != expected[channel]) - return ::testing::AssertionFailure() - << "layer " << layer << " (" << x << "," << y << ") channel " << channel - << " is " << int(pixels[at + channel]) << ", expected " << int(expected[channel]); - return ::testing::AssertionSuccess(); -} -} // namespace - -// P7 wave 2-B, CONTRACT-P7 §3.2: `copy-image-in-place@P7` RETIRES. -// -// glCopyImageSubData between two subresources of ONE texture is ordinary, defined GL (4.6 core -// 18.3.2 even spells out that a texture and a view of it are two objects over one image and may -// be copied between). The wire arm took the session down for it, because the TRANSFER_SRC / -// TRANSFER_DST pair below it cannot describe one image: both endpoints share one tracked -// VkImageLayout and the second transition undoes the first. GENERAL can describe it. -// -// The monolith arm still DECLINES this shape outright, so this case is split-only in the -// strongest sense - there is no monolith reading of it to compare against, and that asymmetry -// is deliberate rather than an oversight (§3.2 asks the wire arm to perform the copy). -// -// Red once (executed, reverted): restore MagmaWireFatal("copy-image-in-place@P7") and this case -// dies by that name. -TEST_F(F1WireScenario, CopyImageInPlaceAcrossLevelsAndLayers) { - if (!Ready()) return; - const GLuint array = MakeInPlaceCopyTexture(); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.setup"; - - // LEVEL 0 -> LEVEL 1, same layer: the destination level is a different subresource of the - // same image, so one GENERAL transition has to cover both of them. - const auto before = PeekSplitRuntime().emitSeq; - glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, - array, GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1); - ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyInPlace.wire"; - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.error"; - { - const auto level1 = ReadInPlaceLevel(1, 4); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.readback"; - for (int y = 0; y < 4; ++y) - for (int x = 0; x < 4; ++x) { - // The source rectangle is level 0's bottom-left quadrant, which is all red. - EXPECT_TRUE(InPlaceTexelIs(level1, 4, 0, x, y, kInPlaceRed)) - << "F1.CopyInPlace.level1.layer0"; - EXPECT_TRUE(InPlaceTexelIs(level1, 4, 1, x, y, kInPlaceWhite)) - << "F1.CopyInPlace.level1.layer1 must not be touched"; - } - } - - // LAYER 0 -> LAYER 1 at level 0: the same level, disjoint slices. Defined GL, and the - // overlap decline must NOT catch it. - glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, - array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 8, 1); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.error"; - { - const auto level0 = ReadInPlaceLevel(0, 8); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.readback"; - for (int y = 0; y < 8; ++y) - for (int x = 0; x < 8; ++x) { - EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y))) - << "F1.CopyInPlace.level0.layer0 is the source and must be unchanged"; - EXPECT_TRUE(InPlaceTexelIs(level0, 8, 1, x, y, InPlaceQuadrant(x, y))) - << "F1.CopyInPlace.level0.layer1 must carry the source's quadrants"; - } - } - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - glDeleteTextures(1, &array); - glBindTexture(GL_TEXTURE_2D, texture); -} - -// The one in-place shape that stays a DECLINE (§3.2): same image, same level, same layer, -// overlapping rectangles. GL 4.6 core 18.3.2 leaves that undefined, and GENERAL makes the -// command legal to RECORD rather than meaningful to execute - vkCmdCopyImage orders nothing -// between its own reads and writes inside one region. The case pins that nothing is recorded: -// no GL error, and the level still holds exactly what it held. -TEST_F(F1WireScenario, CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone) { - if (!Ready()) return; - // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). GL leaves this copy - // undefined and Espryt performs it; only the Magma wire arm promises to record nothing. - if (Gl().BackendName() != "DirectVulkan") - GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, copy-image-in-place): GL 4.6 core " - "18.3.2 leaves an overlapping same-subresource copy undefined and Espryt (" - << Gl().BackendName() << ") performs it"; - const GLuint array = MakeInPlaceCopyTexture(); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.setup"; - // (0,0)-(4,4) onto (2,2): four of the sixteen texels are in both rectangles. - glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, - array, GL_TEXTURE_2D_ARRAY, 0, 2, 2, 0, 4, 4, 1); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.error"; - const auto level0 = ReadInPlaceLevel(0, 8); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.readback"; - for (int y = 0; y < 8; ++y) + std::array pixels{}; + glReadPixels(0, 0, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + const int expected[] = {64, 128, 191, 255}; for (int x = 0; x < 8; ++x) - EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y))) - << "F1.CopyInPlaceOverlap.level0 must be untouched by a declined copy"; - glBindTexture(GL_TEXTURE_2D_ARRAY, 0); - glDeleteTextures(1, &array); - glBindTexture(GL_TEXTURE_2D, texture); -} + for (int channel = 0; channel < 4; ++channel) + EXPECT_NEAR(pixels[x * 4 + channel], expected[channel], 1) << "1D texel " << x; + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glBindTexture(GL_TEXTURE_1D, 0); + glDeleteTextures(1, &line); + } -// ---- P7 wave 2 package B3, EXIT GATE 3: a streamed glBufferSubData -> draw pair is ordered -- -// -// THE SHAPE OF THE OpenRA DEVICE DIVERGENCE, reduced to four quads. The application writes a -// batch of vertices over the SAME range of ONE dynamic buffer and immediately draws it, then -// writes the next batch over the same range and draws that, and so on - OpenRA does this ~24 -// times per frame into GL buffer 1, and the picture that reached the Redmi was missing exactly -// the quads of one of those batches with the terrain under them intact. -// -// The server may answer such a write in two shapes: an ordered vkCmdCopyBuffer, or an immediate -// host memcpy when it believes the GPU is finished with the bytes. Believing that wrongly is -// silent and produces no log line and no GL error - the draw still executes, it just reads -// somebody else's vertices. So the case cannot assert "no error"; it has to assert the pixels. -// -// Registered TWICE per arm. The plain entry is a regression gate on every driver. The -// `StaleSerial.` entry (split and spawn only - the tcp arm's server never sees an entry's -// environment) carries MGITEST_MAGMA_FORCE_STALE_BUFFER_SERIAL=1, which forces the serial half -// of the busy predicate to answer "idle" - the state the unsound completed-serial floor put the -// wire arm into on the device. With the serial half forced to lie, only the submission-fence -// half can still order the copy, so the entry pins that DEFENCE term. The guarantee is the -// floor (VulkanRenderer::OnSubmitsCompletedUpTo), which the knob bypasses; its lane is the -// OpenRA split retrace's "MGWIRE-FLOOR unsound-serial-complete" red condition. -TEST_F(F1WireScenario, StreamedBufferSubDataBeforeEachDrawIsOrdered) { - if (!Ready()) return; - constexpr int kSize = 8, kBatches = 4, kColumn = kSize / kBatches; - Attach(GL_RGBA8); - const GLuint program = BuildWireProgram({ - {GL_VERTEX_SHADER, R"(#version 430 core + TEST_F(F1WireScenario, PartialTextureUploadPreservesGpuClearPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + // The client shadow is red. The GPU then changes every texel to green, so + // applying a later one-pixel upload as a whole shadow loses observable data. + std::array red{}; + for (size_t i = 0; i < red.size(); i += 4) { + red[i] = 255; + red[i + 3] = 255; + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + const std::array blue{0, 0, 255, 255}; + glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue.data()); + + std::array pixels{}; + glReadPixels(0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + for (int y = 0; y < 8; ++y) { + for (int x = 0; x < 8; ++x) { + const std::array expected = + x == 1 && y == 2 ? blue : std::array{0, 255, 0, 255}; + const size_t at = static_cast(y * 8 + x) * 4; + const std::array actual{pixels[at], pixels[at + 1], pixels[at + 2], pixels[at + 3]}; + EXPECT_EQ(actual, expected) << "GPU clear survived outside upload at " << x << ", " << y; + } + } + } + + TEST_F(F1WireScenario, TextureViewClearTargetsItsRootMipAndLayer) { + if (!Ready()) return; + GLuint root = 0, view = 0; + glGenTextures(1, &root); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); + std::array red{}; + for (size_t i = 0; i < red.size(); i += 4) { + red[i] = 255; + red[i + 3] = 255; + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + std::array blue{}; + for (size_t i = 0; i < blue.size(); i += 4) { + blue[i + 2] = 255; + blue[i + 3] = 255; + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1, GL_RGBA, GL_UNSIGNED_BYTE, blue.data()); + glGenTextures(1, &view); + glTextureView(view, GL_TEXTURE_2D, root, GL_RGBA8, 1, 1, 1, 1); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "view construction"; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, view, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + std::array pixel{}; + glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << "view reads its cleared window"; + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 1); + glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{0, 255, 0, 255})) << "view clear reaches root mip 1, layer 1"; + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 1, 0); + glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{0, 0, 255, 255})) << "the neighboring root layer is preserved"; + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, 0, 1); + glReadPixels(1, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "the neighboring root mip is preserved"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, 0, 0); + glDeleteTextures(1, &view); + glDeleteTextures(1, &root); + } + + TEST_F(F1WireScenario, NamedBlitPreservesBindingsAndRestoresNextVerbsPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + GLuint fbos[3]{}, textures[3]{}; + glGenFramebuffers(3, fbos); + glGenTextures(3, textures); + for (int i = 0; i < 3; ++i) { + glBindFramebuffer(GL_FRAMEBUFFER, fbos[i]); + glBindTexture(GL_TEXTURE_2D, textures[i]); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 8, 8); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glClearColor(0, i == 1 ? 1 : 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[2]); + const auto before = PeekSplitRuntime().emitSeq; + glBlitNamedFramebuffer(fbo, fbos[0], 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_GT(PeekSplitRuntime().emitSeq, before); + GLint read = 0, draw = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); + EXPECT_EQ(read, GLint(fbos[1])); + EXPECT_EQ(draw, GLint(fbos[2])); + + // No intervening bind: this must clear the restored draw FBO, not the DSA destination. + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[2]); + std::array pixel{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{255, 0, 255, 255})) << "named blit restored draw before clear"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]); + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "unbound named blit copied source"; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]); + glBlitFramebuffer(0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[2]); + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{0, 255, 255, 255})) << "ordinary blit follows restored bindings"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(3, fbos); + glDeleteTextures(3, textures); + } + + TEST_F(F1WireScenario, NamedBlitDefaultEndpointPixels) { + if (!Ready()) return; + Attach(GL_RGBA8); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glBlitNamedFramebuffer(fbo, 0, 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); + GLint read = 0, draw = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); + EXPECT_EQ(read, GLint(fbo)); + EXPECT_EQ(draw, GLint(fbo)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + std::array pixel{}; + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "default draw endpoint"; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBlitNamedFramebuffer(0, fbo, 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &read); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &draw); + EXPECT_EQ(read, GLint(fbo)); + EXPECT_EQ(draw, GLint(fbo)); + glReadPixels(2, 3, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel, (std::array{255, 0, 0, 255})) << "default read endpoint"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + } + + TEST_F(F1WireScenario, ColorBlitToDefaultIgnoresViewportAndPreservesOrientation) { + if (!Ready()) return; + ASSERT_GE(Gl().Width(), 16); + ASSERT_GE(Gl().Height(), 16); + ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); + const int width = Gl().Width(), height = Gl().Height(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); // The destination really is the default framebuffer. + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(1, 2, 3, 5); // glBlitFramebuffer must ignore this unrelated viewport. + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, 6, 4, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + EXPECT_EQ(ReadOnePixel(width / 4, height / 4), (std::array{255, 0, 0, 255})) << "bottom left"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, height / 4), (std::array{0, 255, 0, 255})) << "bottom right"; + EXPECT_EQ(ReadOnePixel(width / 4, 3 * height / 4), (std::array{0, 0, 255, 255})) << "top left"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, 3 * height / 4), (std::array{255, 255, 0, 255})) + << "top right"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + GLint viewport[4]{}; + glGetIntegerv(GL_VIEWPORT, viewport); + EXPECT_EQ((std::array{viewport[0], viewport[1], viewport[2], viewport[3]}), + (std::array{1, 2, 3, 5})) + << "blit must not mutate the application's viewport"; + Gl().EndFrame(); + } + + TEST_F(F1WireScenario, ColorBlitToDefaultHonorsScissorAndReversedRect) { + if (!Ready()) return; + ASSERT_GE(Gl().Width(), 16); + ASSERT_GE(Gl().Height(), 16); + ASSERT_NO_FATAL_FAILURE(PaintNonSquareBlitSource()); + const int width = Gl().Width(), height = Gl().Height(); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(2, 1, 5, 3); + // Copy into the centre half with BOTH destination axes reversed. Scissor + // keeps only its left half, so a plain unclipped vkCmdBlitImage is incorrect. + glScissor(width / 4, height / 4, width / 4, height / 2); + glEnable(GL_SCISSOR_TEST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, 6, 4, 3 * width / 4, 3 * height / 4, width / 4, height / 4, GL_COLOR_BUFFER_BIT, + GL_NEAREST); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + EXPECT_EQ(ReadOnePixel(3 * width / 8, 3 * height / 8), (std::array{255, 255, 0, 255})) + << "reversed bottom-left destination reads the source top-right yellow quadrant"; + EXPECT_EQ(ReadOnePixel(3 * width / 8, 5 * height / 8), (std::array{0, 255, 0, 255})) + << "reversed top-left destination reads the source bottom-right green quadrant"; + const std::array untouched{255, 0, 255, 255}; + EXPECT_EQ(ReadOnePixel(5 * width / 8, 3 * height / 8), untouched) << "inside destination, outside scissor"; + EXPECT_EQ(ReadOnePixel(5 * width / 8, 5 * height / 8), untouched) << "upper destination outside scissor"; + EXPECT_EQ(ReadOnePixel(width / 8, height / 2), untouched) << "outside destination rectangle"; + EXPECT_EQ(ReadOnePixel(3 * width / 8, height / 8), untouched) << "below destination and scissor"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glDisable(GL_SCISSOR_TEST); + Gl().EndFrame(); + } + + // P7 wave 2-B, CONTRACT-P7 §3.2: `default-color-blit-shape@P7` RETIRES. + // + // One Fatal covered three unrelated situations. The shader pass it guarded exists for ROTATION + // and can only SAMPLE its source - sampler2D, a float result, one of two 2D view types - so a + // 3D slice or an integer attachment made it return false and the refusal fired EVEN ON A SURFACE + // WITH NO ROTATION, where the ordinary vkCmdBlitImage arm below it would have done the blit + // correctly and does handle those shapes. Identity now falls through to that arm; a rotated + // surface moves the region into an owned 2D float scratch and blits that; anything outside the + // four rotations declines by name. + // + // THE SOURCE IS A 3D TEXTURE SLICE, which is ordinary defined GL (a layer of a 3D texture is a + // legal colour attachment) and is exactly the shape the shader pass cannot sample. The same case + // runs twice on each arm: plain, taking the identity fall-through, and under + // MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH, taking the rotated arm's scratch path on an identity + // surface - where it must produce THE SAME PIXELS. That equality is what makes the scratch arm + // testable at all: every surface this lane can create is identity, and so is the Redmi pbuffer + // the cluster's evidence comes from. + // + // Red once (executed, reverted): restore MagmaWireFatal("default-color-blit-shape@P7") and both + // entries die by that name. + TEST_F(F1WireScenario, ColorBlitToDefaultFromANonSampleableSourceDegrades) { + if (!Ready()) return; + ASSERT_GE(Gl().Width(), 16); + ASSERT_GE(Gl().Height(), 16); + const int width = Gl().Width(), height = Gl().Height(); + GLuint volume = 0, volumeFbo = 0; + glGenTextures(1, &volume); + glBindTexture(GL_TEXTURE_3D, volume); + glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, 6, 4, 2); + glGenFramebuffers(1, &volumeFbo); + glBindFramebuffer(GL_FRAMEBUFFER, volumeFbo); + // Slice 1, not slice 0: a scratch copy that forgot the z origin would read the other slice + // and every quadrant assertion below would still be about SOME real texels. + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 1); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + constexpr GLfloat colors[4][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; + glEnable(GL_SCISSOR_TEST); + for (int quadrant = 0; quadrant < 4; ++quadrant) { + glScissor((quadrant % 2) * 3, (quadrant / 2) * 2, 3, 2); + glClearColor(colors[quadrant][0], colors[quadrant][1], colors[quadrant][2], colors[quadrant][3]); + glClear(GL_COLOR_BUFFER_BIT); + } + glDisable(GL_SCISSOR_TEST); + // The OTHER slice gets one flat colour that appears in no quadrant. + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 0); + glClearColor(0, 1, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, volume, 0, 1); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DefaultBlitShape.setup"; + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glClearColor(1, 0, 1, 1); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, volumeFbo); + const auto before = PeekSplitRuntime().emitSeq; + glBlitFramebuffer(0, 0, 6, 4, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.DefaultBlitShape.wire"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + const std::string arm = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_DEFAULT_BLIT_SCRATCH"); + EXPECT_EQ(ReadOnePixel(width / 4, height / 4), (std::array{255, 0, 0, 255})) + << "F1.DefaultBlitShape bottom left (scratch arm=" << arm << ")"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, height / 4), (std::array{0, 255, 0, 255})) + << "F1.DefaultBlitShape bottom right (scratch arm=" << arm << ")"; + EXPECT_EQ(ReadOnePixel(width / 4, 3 * height / 4), (std::array{0, 0, 255, 255})) + << "F1.DefaultBlitShape top left (scratch arm=" << arm << ")"; + EXPECT_EQ(ReadOnePixel(3 * width / 4, 3 * height / 4), (std::array{255, 255, 0, 255})) + << "F1.DefaultBlitShape top right (scratch arm=" << arm << ")"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.DefaultBlitShape.error"; + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &volumeFbo); + glBindTexture(GL_TEXTURE_3D, 0); + glDeleteTextures(1, &volume); + glBindTexture(GL_TEXTURE_2D, texture); + Gl().EndFrame(); + } + + namespace { + // An 8x4 RGBA8 colour renderbuffer at `samples`, bottom half red and top half green in GL row + // order. TWO HALVES AND NOT ONE COLOUR, for the reason the shader-mip case gives: a pass that + // loses GL's row order averages a flat fill back to the same number everywhere and a + // single-colour assertion calls that correct. Returns false when this driver cannot host the + // attachment, which is a skip and not a failure. + bool MakeMultisampleHalvesFbo(int samples, GLuint& fbo, GLuint& renderbuffer) { + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glGenRenderbuffers(1, &renderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, GL_RGBA8, 8, 4); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) return false; + while (glGetError() != GLenum(GL_NO_ERROR)) {} + glDisable(GL_SCISSOR_TEST); + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, 8, 2); + glClearColor(1, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(0, 2, 8, 2); + glClearColor(0, 1, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + return true; + } + + // A single-sample RGBA8 destination of the given size, cleared to a colour that appears in no + // assertion below - so "the blit did nothing" and "the blit landed the wrong band" read + // differently from each other. + void MakeSingleSampleTarget(int width, int height, const GLfloat (&clear)[4], GLuint& fbo, GLuint& tex) { + glGenTextures(1, &tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); + glClearColor(clear[0], clear[1], clear[2], clear[3]); + glClear(GL_COLOR_BUFFER_BIT); + } + } // namespace + + // P7 wave 2-B2, CONTRACT-P7 §3.2: `multisample-blit-shape@P7` RETIRES. + // + // vkCmdResolveImage carries ONE extent and one offset per side. It can neither scale nor flip, + // so the wire arm refused every multisample colour blit whose destination rectangle was a + // different size from its source - which glBlitFramebuffer names, and which the MONOLITH arm + // already reaches by the route this package gives the wire arm (VulkanRenderer.cpp's + // `regionWasTransformed` branch: resolve into a scratch at raw offsets, then blit the scratch). + // The scratch was already here for the flip; the two size equalities were the only thing + // keeping the scale out. + // + // THE SCALE AND THE FLIP IN ONE CASE, over the same source, because they fail differently: a + // blit that dropped the scale lands a 8x4 band in a 4x2 attachment (and the driver refuses the + // command), while one that dropped the flip returns the right two colours in the wrong order. + // The second blit's expectation is the first's, rows swapped, so neither can pass the other's + // assertion. + // + // Red once (executed, reverted): restore the two `width != |dx1-dx0|` clauses in + // WireFramebuffer.cpp's colour resolve arm and both blits die as + // Fatal{UnmigratedVerb, "Magma:multisample-resolve-region"}. + TEST_F(F1WireScenario, MultisampleColorBlitScalesAndFlipsThroughTheResolveScratch) { + if (!Ready()) return; + GLuint msFbo = 0, msRenderbuffer = 0; + if (!MakeMultisampleHalvesFbo(4, msFbo, msRenderbuffer)) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; + } + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.setup"; + + // Half the source in each axis: every destination texel is the resolve of a 2x2 source box + // that lies wholly inside ONE half, so the boundary never falls inside a destination texel + // and the two colours cannot mix. + constexpr GLfloat kBlue[4]{0, 0, 1, 1}; + GLuint dstFbo = 0, dstTexture = 0; + MakeSingleSampleTarget(4, 2, kBlue, dstFbo, dstTexture); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.target"; + + constexpr std::array kRed{255, 0, 0, 255}; + constexpr std::array kGreen{0, 255, 0, 255}; + glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); + glDisable(GL_SCISSOR_TEST); + const auto before = PeekSplitRuntime().emitSeq; + glBlitFramebuffer(0, 0, 8, 4, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsBlitShape.wire"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.scaled.error"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo); + for (int x = 0; x < 4; ++x) { + EXPECT_EQ(ReadOnePixel(x, 0), kRed) << "F1.MsBlitShape scaled bottom row, x=" << x; + EXPECT_EQ(ReadOnePixel(x, 1), kGreen) << "F1.MsBlitShape scaled top row, x=" << x; + } + + // The same source, the same destination size, with the destination rectangle inverted on + // Y. The scratch holds the resolved band at its own origin either way; only the second leg + // sees the inversion, which is what vkCmdBlitImage's reversed offsets express. + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo); + glClearColor(kBlue[0], kBlue[1], kBlue[2], kBlue[3]); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); + glBlitFramebuffer(0, 0, 8, 4, 0, 2, 4, 0, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitShape.flipped.error"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, dstFbo); + for (int x = 0; x < 4; ++x) { + EXPECT_EQ(ReadOnePixel(x, 0), kGreen) << "F1.MsBlitShape flipped bottom row, x=" << x; + EXPECT_EQ(ReadOnePixel(x, 1), kRed) << "F1.MsBlitShape flipped top row, x=" << x; + } + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &dstFbo); + glDeleteTextures(1, &dstTexture); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + glBindTexture(GL_TEXTURE_2D, texture); + Gl().EndFrame(); + } + + // The surviving half of `multisample-blit-shape@P7`, and it is a DECLINE (rule I (a)). + // + // A MULTISAMPLE DESTINATION is the other side of the same guard and has no arm at all: + // vkCmdCopyImage is the only command that writes a multisampled image, it takes one extent, and + // GL 4.6 core 18.3.1 makes a blit whose rectangles differ in size INVALID_OPERATION when either + // framebuffer is multisampled - so there is no picture to get right, only a shape that used to + // end the session. What this case pins is that it no longer does, and that the destination is + // exactly as the clear left it. + // + // THE DESTINATION IS READ THROUGH A RESOLVE, because glReadPixels on a multisampled framebuffer + // is INVALID_OPERATION: the 1:1 resolve into a single-sample attachment is the retired arm + // above, so this case also proves the decline did not disturb the arm next to it. + // + // Red once (executed, reverted): restore MagmaWireFatal("multisample-blit-shape@P7") and this + // case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-shape@P7"}. + TEST_F(F1WireScenario, MultisampleBlitOntoAMultisampleDestinationDeclinesAndKeepsTheSession) { + if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). Espryt on the split + // arm leaves the destination untouched but no GL_INVALID_OPERATION reaches the client, which + // GL 4.6 core 18.3.1 asks for here - an Espryt debt of the same class as + // DepthStencilReadbackMatrixScenario's scaled-resolve gate, not this decline's to pin. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-shape): Espryt (" + << Gl().BackendName() + << ") writes nothing for a scaled blit onto a multisample destination but " + "records no GL_INVALID_OPERATION - a P3b/P4b debt, not this decline"; + GLuint msFbo = 0, msRenderbuffer = 0; + glGenFramebuffers(1, &msFbo); + glBindFramebuffer(GL_FRAMEBUFFER, msFbo); + glGenRenderbuffers(1, &msRenderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, msRenderbuffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 4, 2); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRenderbuffer); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; + } + while (glGetError() != GLenum(GL_NO_ERROR)) {} + glDisable(GL_SCISSOR_TEST); + glClearColor(1, 0, 1, 1); // magenta: the colour the declined blit must leave behind + glClear(GL_COLOR_BUFFER_BIT); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.setup"; + + // A single-sample 8x4 source, flat yellow, so a blit that ran at all is visible. + constexpr GLfloat kYellow[4]{1, 1, 0, 1}; + GLuint srcFbo = 0, srcTexture = 0; + MakeSingleSampleTarget(8, 4, kYellow, srcFbo, srcTexture); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.source"; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msFbo); + const auto before = PeekSplitRuntime().emitSeq; + glBlitFramebuffer(0, 0, 8, 4, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsBlitDecline.wire"; + // THE glFinish IS LOAD-BEARING, and it is what the first run of this case measured: the + // decline records its error where the blit is PERFORMED - the server's apply thread - and + // that error reaches this client's queue with a later reply, not with the blit call. Without + // the round trip the check below reads GL_NO_ERROR and the INVALID_OPERATION surfaces at + // whatever the next synchronising call happens to be, which is how it first showed up: + // attached to the resolve twenty lines down, a call that is not wrong about anything. + glFinish(); + EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_OPERATION)) + << "F1.MsBlitDecline: the decline must record the error GL names for the shape"; + + // Resolve the multisample destination 1:1 into a single-sample attachment and read THAT: + // the round trip is also the proof that the session is still answering. + constexpr GLfloat kBlue[4]{0, 0, 1, 1}; + GLuint readFbo = 0, readTexture = 0; + MakeSingleSampleTarget(4, 2, kBlue, readFbo, readTexture); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFbo); + glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline.resolve"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + constexpr std::array kMagenta{255, 0, 255, 255}; + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 4; ++x) + EXPECT_EQ(ReadOnePixel(x, y), kMagenta) + << "F1.MsBlitDecline: the declined blit wrote (" << x << "," << y << ")"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsBlitDecline: the session survived"; + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &readFbo); + glDeleteTextures(1, &readTexture); + glDeleteFramebuffers(1, &srcFbo); + glDeleteTextures(1, &srcTexture); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + glBindTexture(GL_TEXTURE_2D, texture); + Gl().EndFrame(); + } + + // P7 wave 2-B2, CONTRACT-P7 §3.2: the surviving half of `multisample-blit-aspect@P7`. + // + // The half WITH an arm is ResolveWireDepthStencil's: a multisample depth or stencil source now + // reaches a single-sample destination on every device - through VK_KHR_depth_stencil_resolve + // where it exists, through WireMultisampleResolve.cpp's baked pass where it does not - and the + // two MsResolve entries on each arm are its reading. This is the half with none. + // + // ONE SAMPLE INTO FOUR, with rectangles that MATCH so the shape decline next door is not what + // fires. Vulkan has no command for it: vkCmdResolveImage only ever writes a single-sample + // destination, vkCmdBlitImage refuses a multisampled side outright, and vkCmdCopyImage needs + // the two counts to be equal. GL 4.6 core 18.3.1 calls this sample replication and the wire arm + // declines it by name rather than ending the session over it. + // + // Red once (executed, reverted): restore MagmaWireFatal("multisample-blit-aspect@P7") and this + // case dies as Fatal{UnmigratedVerb, "Magma:multisample-blit-aspect@P7"}. + TEST_F(F1WireScenario, MultisampleBlitFromASingleSampleSourceDeclinesAndKeepsTheSession) { + if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). The decline itself + // is a recorded Magma debt (CONTRACT-P7 §12: a same-rectangle 1 -> N blit is legal sample + // replication in GL 4.6 core 18.3.1); Espryt on the split arm neither replicates nor + // records an error, which is neither answer this case can pin. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, multisample-blit-aspect): Espryt (" + << Gl().BackendName() + << ") writes nothing for a single-sample to multisample blit and records no " + "GL error - a P3b/P4b debt, not this decline"; + GLuint msFbo = 0, msRenderbuffer = 0; + glGenFramebuffers(1, &msFbo); + glBindFramebuffer(GL_FRAMEBUFFER, msFbo); + glGenRenderbuffers(1, &msRenderbuffer); + glBindRenderbuffer(GL_RENDERBUFFER, msRenderbuffer); + glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, 4, 2); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, msRenderbuffer); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GLenum(GL_FRAMEBUFFER_COMPLETE)) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + GTEST_SKIP() << "this driver cannot host a 4x multisample RGBA8 renderbuffer"; + } + while (glGetError() != GLenum(GL_NO_ERROR)) {} + glDisable(GL_SCISSOR_TEST); + glClearColor(1, 0, 1, 1); // magenta: the colour the declined blit must leave behind + glClear(GL_COLOR_BUFFER_BIT); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.setup"; + + constexpr GLfloat kYellow[4]{1, 1, 0, 1}; + GLuint srcFbo = 0, srcTexture = 0; + MakeSingleSampleTarget(4, 2, kYellow, srcFbo, srcTexture); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.source"; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, msFbo); + const auto before = PeekSplitRuntime().emitSeq; + glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_GT(PeekSplitRuntime().emitSeq, before) << "F1.MsAspectDecline.wire"; + glFinish(); // the decline's error rides a later reply - see the shape case next door + EXPECT_EQ(FirstGLError(), GLenum(GL_INVALID_OPERATION)) + << "F1.MsAspectDecline: the decline must record the error GL names for the shape"; + + constexpr GLfloat kBlue[4]{0, 0, 1, 1}; + GLuint readFbo = 0, readTexture = 0; + MakeSingleSampleTarget(4, 2, kBlue, readFbo, readTexture); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + glBindFramebuffer(GL_READ_FRAMEBUFFER, msFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, readFbo); + glBlitFramebuffer(0, 0, 4, 2, 0, 0, 4, 2, GL_COLOR_BUFFER_BIT, GL_NEAREST); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline.resolve"; + glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo); + constexpr std::array kMagenta{255, 0, 255, 255}; + for (int y = 0; y < 2; ++y) + for (int x = 0; x < 4; ++x) + EXPECT_EQ(ReadOnePixel(x, y), kMagenta) + << "F1.MsAspectDecline: the declined blit wrote (" << x << "," << y << ")"; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.MsAspectDecline: the session survived"; + + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glDeleteFramebuffers(1, &readFbo); + glDeleteTextures(1, &readTexture); + glDeleteFramebuffers(1, &srcFbo); + glDeleteTextures(1, &srcTexture); + glDeleteFramebuffers(1, &msFbo); + glDeleteRenderbuffers(1, &msRenderbuffer); + glBindTexture(GL_TEXTURE_2D, texture); + Gl().EndFrame(); + } + + namespace { + // One 8x8x2 RGBA8 array texture with two levels, filled so that EVERY subresource this pair + // of cases touches is distinguishable from every other one. A flat fill would let a copy that + // went to the wrong level, the wrong layer or nowhere at all read as correct. + // level 0 layer 0: four quadrants (GL row order: red/green on the bottom, blue/yellow up) + // level 0 layer 1: magenta + // level 1 layer 0: cyan level 1 layer 1: white + constexpr std::array kInPlaceRed{255, 0, 0, 255}; + constexpr std::array kInPlaceGreen{0, 255, 0, 255}; + constexpr std::array kInPlaceBlue{0, 0, 255, 255}; + constexpr std::array kInPlaceYellow{255, 255, 0, 255}; + constexpr std::array kInPlaceMagenta{255, 0, 255, 255}; + constexpr std::array kInPlaceCyan{0, 255, 255, 255}; + constexpr std::array kInPlaceWhite{255, 255, 255, 255}; + + std::array InPlaceQuadrant(int x, int y) { + if (y < 4) return x < 4 ? kInPlaceRed : kInPlaceGreen; + return x < 4 ? kInPlaceBlue : kInPlaceYellow; + } + + GLuint MakeInPlaceCopyTexture() { + GLuint name = 0; + glGenTextures(1, &name); + glBindTexture(GL_TEXTURE_2D_ARRAY, name); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); + std::vector level0(8 * 8 * 2 * 4); + for (int layer = 0; layer < 2; ++layer) + for (int y = 0; y < 8; ++y) + for (int x = 0; x < 8; ++x) { + const auto colour = layer == 0 ? InPlaceQuadrant(x, y) : kInPlaceMagenta; + const size_t at = (static_cast(layer) * 64 + y * 8 + x) * 4; + std::copy(colour.begin(), colour.end(), level0.begin() + at); + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 8, 8, 2, GL_RGBA, GL_UNSIGNED_BYTE, level0.data()); + std::vector level1(4 * 4 * 2 * 4); + for (int layer = 0; layer < 2; ++layer) + for (int at = 0; at < 16; ++at) { + const auto colour = layer == 0 ? kInPlaceCyan : kInPlaceWhite; + std::copy(colour.begin(), colour.end(), + level1.begin() + (static_cast(layer) * 16 + at) * 4); + } + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 2, GL_RGBA, GL_UNSIGNED_BYTE, level1.data()); + return name; + } + + // (level, side) -> the whole level, every layer, as glGetTexImage hands it back. + std::vector ReadInPlaceLevel(GLint level, int side) { + std::vector pixels(static_cast(side) * side * 2 * 4, 17); + glGetTexImage(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + return pixels; + } + + ::testing::AssertionResult InPlaceTexelIs(const std::vector& pixels, int side, int layer, int x, int y, + const std::array& expected) { + const size_t at = ((static_cast(layer) * side * side) + static_cast(y) * side + x) * 4; + if (at + 4 > pixels.size()) return ::testing::AssertionFailure() << "texel out of range"; + for (int channel = 0; channel < 4; ++channel) + if (pixels[at + channel] != expected[channel]) + return ::testing::AssertionFailure() + << "layer " << layer << " (" << x << "," << y << ") channel " << channel << " is " + << int(pixels[at + channel]) << ", expected " << int(expected[channel]); + return ::testing::AssertionSuccess(); + } + } // namespace + + // P7 wave 2-B, CONTRACT-P7 §3.2: `copy-image-in-place@P7` RETIRES. + // + // glCopyImageSubData between two subresources of ONE texture is ordinary, defined GL (4.6 core + // 18.3.2 even spells out that a texture and a view of it are two objects over one image and may + // be copied between). The wire arm took the session down for it, because the TRANSFER_SRC / + // TRANSFER_DST pair below it cannot describe one image: both endpoints share one tracked + // VkImageLayout and the second transition undoes the first. GENERAL can describe it. + // + // The monolith arm still DECLINES this shape outright, so this case is split-only in the + // strongest sense - there is no monolith reading of it to compare against, and that asymmetry + // is deliberate rather than an oversight (§3.2 asks the wire arm to perform the copy). + // + // Red once (executed, reverted): restore MagmaWireFatal("copy-image-in-place@P7") and this case + // dies by that name. + TEST_F(F1WireScenario, CopyImageInPlaceAcrossLevelsAndLayers) { + if (!Ready()) return; + const GLuint array = MakeInPlaceCopyTexture(); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.setup"; + + // LEVEL 0 -> LEVEL 1, same layer: the destination level is a different subresource of the + // same image, so one GENERAL transition has to cover both of them. + const auto before = PeekSplitRuntime().emitSeq; + glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, array, GL_TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 1); + ASSERT_GT(PeekSplitRuntime().emitSeq, before) << "F1.CopyInPlace.wire"; + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.error"; + { + const auto level1 = ReadInPlaceLevel(1, 4); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.levelCopy.readback"; + for (int y = 0; y < 4; ++y) + for (int x = 0; x < 4; ++x) { + // The source rectangle is level 0's bottom-left quadrant, which is all red. + EXPECT_TRUE(InPlaceTexelIs(level1, 4, 0, x, y, kInPlaceRed)) << "F1.CopyInPlace.level1.layer0"; + EXPECT_TRUE(InPlaceTexelIs(level1, 4, 1, x, y, kInPlaceWhite)) + << "F1.CopyInPlace.level1.layer1 must not be touched"; + } + } + + // LAYER 0 -> LAYER 1 at level 0: the same level, disjoint slices. Defined GL, and the + // overlap decline must NOT catch it. + glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, 8, 8, 1); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.error"; + { + const auto level0 = ReadInPlaceLevel(0, 8); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlace.layerCopy.readback"; + for (int y = 0; y < 8; ++y) + for (int x = 0; x < 8; ++x) { + EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y))) + << "F1.CopyInPlace.level0.layer0 is the source and must be unchanged"; + EXPECT_TRUE(InPlaceTexelIs(level0, 8, 1, x, y, InPlaceQuadrant(x, y))) + << "F1.CopyInPlace.level0.layer1 must carry the source's quadrants"; + } + } + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glDeleteTextures(1, &array); + glBindTexture(GL_TEXTURE_2D, texture); + } + + // The one in-place shape that stays a DECLINE (§3.2): same image, same level, same layer, + // overlapping rectangles. GL 4.6 core 18.3.2 leaves that undefined, and GENERAL makes the + // command legal to RECORD rather than meaningful to execute - vkCmdCopyImage orders nothing + // between its own reads and writes inside one region. The case pins that nothing is recorded: + // no GL error, and the level still holds exactly what it held. + TEST_F(F1WireScenario, CopyImageInPlaceOverlapDeclinesAndLeavesTheLevelAlone) { + if (!Ready()) return; + // Magma only (see GenerateMipmapDepthStencilDeclinesAndKeepsTheSession). GL leaves this copy + // undefined and Espryt performs it; only the Magma wire arm promises to record nothing. + if (Gl().BackendName() != "DirectVulkan") + GTEST_SKIP() << "Magma wire-arm decline (CONTRACT-P7 3.2, copy-image-in-place): GL 4.6 core " + "18.3.2 leaves an overlapping same-subresource copy undefined and Espryt (" + << Gl().BackendName() << ") performs it"; + const GLuint array = MakeInPlaceCopyTexture(); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.setup"; + // (0,0)-(4,4) onto (2,2): four of the sixteen texels are in both rectangles. + glCopyImageSubData(array, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, array, GL_TEXTURE_2D_ARRAY, 0, 2, 2, 0, 4, 4, 1); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.error"; + const auto level0 = ReadInPlaceLevel(0, 8); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.CopyInPlaceOverlap.readback"; + for (int y = 0; y < 8; ++y) + for (int x = 0; x < 8; ++x) + EXPECT_TRUE(InPlaceTexelIs(level0, 8, 0, x, y, InPlaceQuadrant(x, y))) + << "F1.CopyInPlaceOverlap.level0 must be untouched by a declined copy"; + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + glDeleteTextures(1, &array); + glBindTexture(GL_TEXTURE_2D, texture); + } + + // ---- P7 wave 2 package B3, EXIT GATE 3: a streamed glBufferSubData -> draw pair is ordered -- + // + // THE SHAPE OF THE OpenRA DEVICE DIVERGENCE, reduced to four quads. The application writes a + // batch of vertices over the SAME range of ONE dynamic buffer and immediately draws it, then + // writes the next batch over the same range and draws that, and so on - OpenRA does this ~24 + // times per frame into GL buffer 1, and the picture that reached the Redmi was missing exactly + // the quads of one of those batches with the terrain under them intact. + // + // The server may answer such a write in two shapes: an ordered vkCmdCopyBuffer, or an immediate + // host memcpy when it believes the GPU is finished with the bytes. Believing that wrongly is + // silent and produces no log line and no GL error - the draw still executes, it just reads + // somebody else's vertices. So the case cannot assert "no error"; it has to assert the pixels. + // + // Registered TWICE per arm. The plain entry is a regression gate on every driver. The + // `StaleSerial.` entry (split and spawn only - the tcp arm's server never sees an entry's + // environment) carries MGITEST_MAGMA_FORCE_STALE_BUFFER_SERIAL=1, which forces the serial half + // of the busy predicate to answer "idle" - the state the unsound completed-serial floor put the + // wire arm into on the device. With the serial half forced to lie, only the submission-fence + // half can still order the copy, so the entry pins that DEFENCE term. The guarantee is the + // floor (VulkanRenderer::OnSubmitsCompletedUpTo), which the knob bypasses; its lane is the + // OpenRA split retrace's "MGWIRE-FLOOR unsound-serial-complete" red condition. + TEST_F(F1WireScenario, StreamedBufferSubDataBeforeEachDrawIsOrdered) { + if (!Ready()) return; + constexpr int kSize = 8, kBatches = 4, kColumn = kSize / kBatches; + Attach(GL_RGBA8); + const GLuint program = BuildWireProgram({{GL_VERTEX_SHADER, R"(#version 430 core layout(location = 0) in vec2 aPos; layout(location = 1) in vec4 aColor; out vec4 vColor; void main() { gl_Position = vec4(aPos, 0.0, 1.0); vColor = aColor; } )"}, - {GL_FRAGMENT_SHADER, R"(#version 430 core + {GL_FRAGMENT_SHADER, R"(#version 430 core in vec4 vColor; out vec4 fragColor; void main() { fragColor = vColor; } )"}}); - ASSERT_NE(program, 0u); + ASSERT_NE(program, 0u); - // One vertical bar per batch, each in its own two-pixel column and its own colour, so a - // draw that read another batch's bytes lands in the wrong column AND the wrong colour. - constexpr GLfloat kColors[kBatches][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; - const auto batchVertices = [&](int batch) { - const GLfloat x0 = -1.0f + static_cast(batch) * 0.5f; - const GLfloat x1 = x0 + 0.5f; - const GLfloat* c = kColors[batch]; - return std::array{ - x0, -1, c[0], c[1], c[2], c[3], x1, -1, c[0], c[1], c[2], c[3], x1, 1, c[0], c[1], c[2], c[3], - x0, -1, c[0], c[1], c[2], c[3], x1, 1, c[0], c[1], c[2], c[3], x0, 1, c[0], c[1], c[2], c[3]}; - }; + // One vertical bar per batch, each in its own two-pixel column and its own colour, so a + // draw that read another batch's bytes lands in the wrong column AND the wrong colour. + constexpr GLfloat kColors[kBatches][4] = {{1, 0, 0, 1}, {0, 1, 0, 1}, {0, 0, 1, 1}, {1, 1, 0, 1}}; + const auto batchVertices = [&](int batch) { + const GLfloat x0 = -1.0f + static_cast(batch) * 0.5f; + const GLfloat x1 = x0 + 0.5f; + const GLfloat* c = kColors[batch]; + return std::array{x0, -1, c[0], c[1], c[2], c[3], x1, -1, c[0], c[1], c[2], c[3], + x1, 1, c[0], c[1], c[2], c[3], x0, -1, c[0], c[1], c[2], c[3], + x1, 1, c[0], c[1], c[2], c[3], x0, 1, c[0], c[1], c[2], c[3]}; + }; - GLuint vao = 0, vbo = 0; - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - glGenBuffers(1, &vbo); - glBindBuffer(GL_ARRAY_BUFFER, vbo); - // GL_DYNAMIC_DRAW and no orphaning: OpenRA's buffer 1 exactly. - glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 36, nullptr, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), reinterpret_cast(0)); - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), - reinterpret_cast(2 * sizeof(GLfloat))); - glUseProgram(program); - glViewport(0, 0, kSize, kSize); - glDisable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); + GLuint vao = 0, vbo = 0; + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + // GL_DYNAMIC_DRAW and no orphaning: OpenRA's buffer 1 exactly. + glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 36, nullptr, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), reinterpret_cast(0)); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), + reinterpret_cast(2 * sizeof(GLfloat))); + glUseProgram(program); + glViewport(0, 0, kSize, kSize); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); - for (int batch = 0; batch < kBatches; ++batch) { - const auto vertices = batchVertices(batch); - glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(sizeof(vertices)), vertices.data()); - glDrawArrays(GL_TRIANGLES, 0, 6); - } - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.StreamedSubData.draw"; - - std::vector pixels(static_cast(kSize) * kSize * 4, 0); - glReadPixels(0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.StreamedSubData.readback"; - - for (int batch = 0; batch < kBatches; ++batch) { - const GLubyte* want = nullptr; - const GLubyte expected[4] = {static_cast(kColors[batch][0] * 255.0f), - static_cast(kColors[batch][1] * 255.0f), - static_cast(kColors[batch][2] * 255.0f), - static_cast(kColors[batch][3] * 255.0f)}; - want = expected; - for (int dx = 0; dx < kColumn; ++dx) { - const int x = batch * kColumn + dx; - const GLubyte* got = pixels.data() + (static_cast(kSize / 2) * kSize + x) * 4; - EXPECT_EQ(got[0], want[0]) << "F1.StreamedSubData: batch " << batch << " column " << x - << " lost its own vertices (r)"; - EXPECT_EQ(got[1], want[1]) << "F1.StreamedSubData: batch " << batch << " column " << x - << " lost its own vertices (g)"; - EXPECT_EQ(got[2], want[2]) << "F1.StreamedSubData: batch " << batch << " column " << x - << " lost its own vertices (b)"; + for (int batch = 0; batch < kBatches; ++batch) { + const auto vertices = batchVertices(batch); + glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(sizeof(vertices)), vertices.data()); + glDrawArrays(GL_TRIANGLES, 0, 6); } - } + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.StreamedSubData.draw"; - glBindBuffer(GL_ARRAY_BUFFER, 0); - glDeleteBuffers(1, &vbo); - glBindVertexArray(0); - glDeleteVertexArrays(1, &vao); - glUseProgram(0); - glDeleteProgram(program); -} + std::vector pixels(static_cast(kSize) * kSize * 4, 0); + glReadPixels(0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "F1.StreamedSubData.readback"; + + for (int batch = 0; batch < kBatches; ++batch) { + const GLubyte* want = nullptr; + const GLubyte expected[4] = { + static_cast(kColors[batch][0] * 255.0f), static_cast(kColors[batch][1] * 255.0f), + static_cast(kColors[batch][2] * 255.0f), static_cast(kColors[batch][3] * 255.0f)}; + want = expected; + for (int dx = 0; dx < kColumn; ++dx) { + const int x = batch * kColumn + dx; + const GLubyte* got = pixels.data() + (static_cast(kSize / 2) * kSize + x) * 4; + EXPECT_EQ(got[0], want[0]) + << "F1.StreamedSubData: batch " << batch << " column " << x << " lost its own vertices (r)"; + EXPECT_EQ(got[1], want[1]) + << "F1.StreamedSubData: batch " << batch << " column " << x << " lost its own vertices (g)"; + EXPECT_EQ(got[2], want[2]) + << "F1.StreamedSubData: batch " << batch << " column " << x << " lost its own vertices (b)"; + } + } + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glDeleteBuffers(1, &vbo); + glBindVertexArray(0); + glDeleteVertexArrays(1, &vao); + glUseProgram(0); + glDeleteProgram(program); + } } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/FencePollScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/FencePollScenario.cpp index 7d640f8d..cc66f1e7 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/FencePollScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/FencePollScenario.cpp @@ -73,8 +73,8 @@ namespace MGITest { ++polls; } EXPECT_TRUE(status == GL_ALREADY_SIGNALED || status == GL_CONDITION_SATISFIED) - << "glClientWaitSync(flags 0x" << std::hex << flags << std::dec << ", 0) still " - << status << " after " << polls << " polls"; + << "glClientWaitSync(flags 0x" << std::hex << flags << std::dec << ", 0) still " << status + << " after " << polls << " polls"; glDeleteSync(sync); } QueueWork(); @@ -140,8 +140,7 @@ namespace MGITest { ASSERT_NE(sync, nullptr); const auto start = Clock::now(); GLenum status = GL_TIMEOUT_EXPIRED; - while ((status = glClientWaitSync(sync, 0, 0)) == GL_TIMEOUT_EXPIRED && - Clock::now() - start < kLoopBound) { + while ((status = glClientWaitSync(sync, 0, 0)) == GL_TIMEOUT_EXPIRED && Clock::now() - start < kLoopBound) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); } const SplitRuntimeState after = PeekSplitRuntime(); @@ -188,7 +187,8 @@ namespace MGITest { } const double ms = std::chrono::duration(Clock::now() - start).count(); const SplitRuntimeState after = PeekSplitRuntime(); - for (GLsync sync : inFlight) glDeleteSync(sync); + for (GLsync sync : inFlight) + glDeleteSync(sync); const char* escalate = std::getenv("MOBILEGL_IPC_POLL_ESCALATE"); std::printf("P10BENCH transport=%s poll-escalate=%s lag=%zu frames=%d ms=%.1f ms/frame=%.3f " "reply-records=%llu fence-round-trips=%llu fence-local=%llu fence-reports=%llu\n", diff --git a/MobileGL/MG_IntegrationTest/Scenarios/FormatlessImageBakeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/FormatlessImageBakeScenario.cpp index 82bc49b1..44de0507 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/FormatlessImageBakeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/FormatlessImageBakeScenario.cpp @@ -71,8 +71,8 @@ void main() ScenarioTest::SetUp(); if (!Ready()) return; if (!BackendHostsCompute()) { - GTEST_SKIP() << "no compute stage on " << Gl().BackendName() << " (" - << Gl().RendererString() << ")"; + GTEST_SKIP() << "no compute stage on " << Gl().BackendName() << " (" << Gl().RendererString() + << ")"; } } @@ -84,8 +84,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static GLuint BuildCompute(const char* source, std::string& log) { @@ -174,9 +173,9 @@ void main() for (int i = 0; i < kExtent * kExtent; ++i) { if (readback[i] != expected[i]) ++offenders; } - EXPECT_EQ(offenders, 0) << "the dispatch wrote " << offenders << " of " - << (kExtent * kExtent) << " texels wrongly; texel 0 was " - << readback[0] << ", expected " << expected[0] + EXPECT_EQ(offenders, 0) << "the dispatch wrote " << offenders << " of " << (kExtent * kExtent) + << " texels wrongly; texel 0 was " << readback[0] << ", expected " + << expected[0] << ". A whole stage lost to the ESSL emitter looks exactly like this."; glUseProgram(0); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/FragCoordOriginScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/FragCoordOriginScenario.cpp index 9ae737c4..04d0e07e 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/FragCoordOriginScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/FragCoordOriginScenario.cpp @@ -120,8 +120,8 @@ void main() { o_color = vec4(floor(gl_FragCoord.y) / 255.0, 0.0, 0.0, 1.0); } // Full height first: this one passed even before the fix (a flip alone maps the row set // onto itself), so it is the control that the shader and the readback agree at all. - EXPECT_TRUE(RowsAreTheirOwnIndex(RowsPaintedWithTheirOwnWindowY(program, width, fullHeight), - "full-height viewport")); + EXPECT_TRUE( + RowsAreTheirOwnIndex(RowsPaintedWithTheirOwnWindowY(program, width, fullHeight), "full-height viewport")); // Half height at GL y=0: the case the CTS failures were made of. A backend that reports // the stored row here answers `height - y` for every row - off the bottom of the range, diff --git a/MobileGL/MG_IntegrationTest/Scenarios/FragmentOutputArrayIndexScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/FragmentOutputArrayIndexScenario.cpp index 743e9308..d7def66d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/FragmentOutputArrayIndexScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/FragmentOutputArrayIndexScenario.cpp @@ -100,13 +100,11 @@ void main() { glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, - m_color[i], 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, m_color[i], 0); } const GLenum drawBuffers[2] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1}; glDrawBuffers(2, drawBuffers); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), - static_cast(GL_FRAMEBUFFER_COMPLETE)); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); const float quad[] = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; glGenVertexArrays(1, &m_vao); @@ -165,8 +163,7 @@ void main() { std::string error; const GLuint program = CompileProgram(kVS, kLoopIndexedFS, &error); - ASSERT_NE(program, 0u) << "a loop-indexed fragment output array must compile and link: " - << error; + ASSERT_NE(program, 0u) << "a loop-indexed fragment output array must compile and link: " << error; ClearToSentinel(); glUseProgram(program); @@ -175,8 +172,7 @@ void main() { const std::vector first = ReadAttachment(0); EXPECT_NEAR(first[0], 0.0f, 0.02f) << "attachment 0 red"; EXPECT_NEAR(first[1], 0.1f, 0.02f) << "attachment 0 green"; - EXPECT_NEAR(first[2], 0.2f, 0.02f) - << "attachment 0 blue - a sentinel 1.0 here means the draw never ran"; + EXPECT_NEAR(first[2], 0.2f, 0.02f) << "attachment 0 blue - a sentinel 1.0 here means the draw never ran"; EXPECT_NEAR(first[3], 0.3f, 0.02f) << "attachment 0 alpha"; const std::vector second = ReadAttachment(1); @@ -198,8 +194,7 @@ void main() { std::string error; const GLuint program = CompileProgram(kVS, kUniformIndexedFS, &error); - ASSERT_NE(program, 0u) << "a uniform-indexed fragment output array must compile and link: " - << error; + ASSERT_NE(program, 0u) << "a uniform-indexed fragment output array must compile and link: " << error; const GLint targetLocation = glGetUniformLocation(program, "uTarget"); ASSERT_GE(targetLocation, 0); glUseProgram(program); @@ -211,11 +206,9 @@ void main() { const std::vector first = ReadAttachment(0); const std::vector second = ReadAttachment(1); - EXPECT_NEAR(first[0], target == 0 ? 0.5f : 0.25f, 0.02f) - << "attachment 0 red with uTarget=" << target; + EXPECT_NEAR(first[0], target == 0 ? 0.5f : 0.25f, 0.02f) << "attachment 0 red with uTarget=" << target; EXPECT_NEAR(first[1], 0.25f, 0.02f) << "attachment 0 green with uTarget=" << target; - EXPECT_NEAR(second[0], target == 1 ? 1.0f : 0.75f, 0.02f) - << "attachment 1 red with uTarget=" << target; + EXPECT_NEAR(second[0], target == 1 ? 1.0f : 0.75f, 0.02f) << "attachment 1 red with uTarget=" << target; EXPECT_NEAR(second[1], 0.75f, 0.02f) << "attachment 1 green with uTarget=" << target; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp index b11ec637..f63314cf 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/GenerateMipmapServerScenario.cpp @@ -125,7 +125,9 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } constexpr Format kRgb16F = {GL_RGB16F, false, "RGB16F"}; constexpr Format kRgb32F = {GL_RGB32F, false, "RGB32F"}; - int ToByte(float value) { return static_cast(std::lround(value * 255.0f)); } + int ToByte(float value) { + return static_cast(std::lround(value * 255.0f)); + } class GenerateMipmapServerScenario : public ScenarioTest { protected: @@ -206,7 +208,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } if (format.depth) { texel[0] = low ? depthBottom : depthTop; } else { - for (int c = 0; c < 3; ++c) texel[c] = low ? bottom[c] : top[c]; + for (int c = 0; c < 3; ++c) + texel[c] = low ? bottom[c] : top[c]; } } } @@ -274,8 +277,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } const int want[3] = {ToByte(colour[0]), ToByte(colour[1]), ToByte(colour[2])}; const int have[3] = {got.r, got.g, got.b}; for (int c = 0; c < 3; ++c) { - EXPECT_NEAR(have[c], want[c], 2) << what << " channel " << c << " at (" << x << "," - << y << ") got " << got; + EXPECT_NEAR(have[c], want[c], 2) + << what << " channel " << c << " at (" << x << "," << y << ") got " << got; if (std::abs(have[c] - want[c]) > 2) return; } } @@ -303,8 +306,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } // whole lane log is where its line lands. void ExpectCpuArmNamed() { if (!m_forcedCpu || !m_recordArm || !m_espryt) return; - const std::string log = m_wire ? PipeStatsWindow::ReadServerLogSince(m_mark) - : PipeStatsWindow::ReadLaneLogSince(m_mark); + const std::string log = + m_wire ? PipeStatsWindow::ReadServerLogSince(m_mark) : PipeStatsWindow::ReadLaneLogSince(m_mark); EXPECT_NE(log.find(kCpuMipmapMarker), std::string::npos) << kForceCpuMipmap << " is set but the server log after glGenerateMipmap carries no '" << kCpuMipmapMarker << "' line: the knob did not reach the server, or the three-channel " @@ -352,8 +355,7 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } if (MonolithDefect(true, !format.depth)) GTEST_SKIP() << (m_espryt ? kM2 : kM3); NewTexture(); DefineLevel(format, 0, kEdge, Flat(format, kEdge, kLevelZero, kDepthLevelZero)); - DefineLevel(format, 1, kEdge / 2, - Halves(format, kEdge / 2, kBottom, kTop, kDepthBottom, kDepthTop)); + DefineLevel(format, 1, kEdge / 2, Halves(format, kEdge / 2, kBottom, kTop, kDepthBottom, kDepthTop)); DefineLevel(format, 2, kEdge / 4, Flat(format, kEdge / 4, kLevelTwo, kDepthLevelTwo)); DefineLevel(format, 3, kEdge / 8, Flat(format, kEdge / 8, kLevelThree, kDepthLevelThree)); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1); @@ -362,8 +364,10 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } Generate(); EXPECT_EQ(LevelWidth(4), 0) << format.name << ": level 4 is past MAX_LEVEL and must stay undefined"; const std::string name(format.name); - ExpectFlat(SampleLevel(0, 3), format, kLevelZero, kDepthLevelZero, name + " level 0 (below BASE_LEVEL)"); - ExpectHalves(SampleLevel(1, 3), format, kBottom, kTop, kDepthBottom, kDepthTop, name + " level 1 (base)"); + ExpectFlat(SampleLevel(0, 3), format, kLevelZero, kDepthLevelZero, + name + " level 0 (below BASE_LEVEL)"); + ExpectHalves(SampleLevel(1, 3), format, kBottom, kTop, kDepthBottom, kDepthTop, + name + " level 1 (base)"); ExpectHalves(SampleLevel(2, 3), format, kBottom, kTop, kDepthBottom, kDepthTop, name + " level 2 (generated from level 1)"); ExpectFlat(SampleLevel(3, 3), format, kLevelThree, kDepthLevelThree, @@ -424,7 +428,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } const std::string log = PipeStatsWindow::ReadServerLogSince(m_mark); EXPECT_NE(log.find(kStoreDeclinedMarker), std::string::npos) << "the base level was drawn and its readback refused, but the server log after glGenerateMipmap " - "has no '" << kStoreDeclinedMarker << "' line"; + "has no '" + << kStoreDeclinedMarker << "' line"; EXPECT_EQ(log.find(kStoreFilteredMarker), std::string::npos) << "the CPU arm filtered the staged store's copy of a level the driver has since drawn over"; const int last = 4; @@ -433,7 +438,8 @@ void main() { o_color = gl_FragCoord.y < u_split ? u_bottom : u_top; } for (int level = 1; level < last; ++level) { const Image image = SampleLevel(level, last); const Rgba8 got = image.At(0, 0); - EXPECT_FALSE(std::abs(got.r - ToByte(kSentinel[0])) <= 2 && std::abs(got.g - ToByte(kSentinel[1])) <= 2 && + EXPECT_FALSE(std::abs(got.r - ToByte(kSentinel[0])) <= 2 && + std::abs(got.g - ToByte(kSentinel[1])) <= 2 && std::abs(got.b - ToByte(kSentinel[2])) <= 2) << format.name << " level " << level << " is the uploaded sentinel " << got << ": the chain was filtered from the stale staged store"; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/GeometryDrawModeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/GeometryDrawModeScenario.cpp index 174fe25c..c6043267 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/GeometryDrawModeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/GeometryDrawModeScenario.cpp @@ -90,8 +90,8 @@ void main() glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); if (!BackendHostsGeometry()) { - GTEST_SKIP() << "no geometry stage on " << Gl().BackendName() << " (" - << Gl().RendererString() << "); there is no input primitive to validate"; + GTEST_SKIP() << "no geometry stage on " << Gl().BackendName() << " (" << Gl().RendererString() + << "); there is no input primitive to validate"; } } @@ -118,15 +118,13 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } GLuint BuildProgram(const char* geometrySource, const char* capturedVarying = nullptr) { - const std::vector> stages = { - {GL_VERTEX_SHADER, kVertexSource}, - {GL_GEOMETRY_SHADER, geometrySource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}; + const std::vector> stages = {{GL_VERTEX_SHADER, kVertexSource}, + {GL_GEOMETRY_SHADER, geometrySource}, + {GL_FRAGMENT_SHADER, kFragmentSource}}; std::vector shaders; bool ok = true; @@ -144,19 +142,22 @@ void main() } } if (!ok) { - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); return 0; } const GLuint program = glCreateProgram(); - for (const GLuint shader : shaders) glAttachShader(program, shader); + for (const GLuint shader : shaders) + glAttachShader(program, shader); if (capturedVarying != nullptr) { glTransformFeedbackVaryings(program, 1, &capturedVarying, GL_INTERLEAVED_ATTRIBS); } glLinkProgram(program); GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); if (!linked) { m_buildLog = InfoLog(program, false); glDeleteProgram(program); @@ -199,9 +200,8 @@ void main() glUseProgram(program); DrainErrors(); - for (const GLenum mode : - {static_cast(GL_LINES), static_cast(GL_LINE_STRIP), - static_cast(GL_TRIANGLES), static_cast(GL_TRIANGLE_STRIP)}) { + for (const GLenum mode : {static_cast(GL_LINES), static_cast(GL_LINE_STRIP), + static_cast(GL_TRIANGLES), static_cast(GL_TRIANGLE_STRIP)}) { glDrawArrays(mode, 0, 3); EXPECT_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)) << "mode " << mode << " does not decompose into the geometry shader's points input"; @@ -311,9 +311,8 @@ void main() // Deliberately NOT BuildProgram: the executable under test has no geometry stage. const GLuint program = glCreateProgram(); m_programs.push_back(program); - for (const auto& [stage, source] : - std::vector>{{GL_VERTEX_SHADER, kVertexSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}) { + for (const auto& [stage, source] : std::vector>{ + {GL_VERTEX_SHADER, kVertexSource}, {GL_FRAGMENT_SHADER, kFragmentSource}}) { const GLuint shader = glCreateShader(stage); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); @@ -372,9 +371,8 @@ void main() const GLuint program = glCreateProgram(); m_programs.push_back(program); - for (const auto& [stage, source] : - std::vector>{{GL_VERTEX_SHADER, kVertexSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}) { + for (const auto& [stage, source] : std::vector>{ + {GL_VERTEX_SHADER, kVertexSource}, {GL_FRAGMENT_SHADER, kFragmentSource}}) { const GLuint shader = glCreateShader(stage); glShaderSource(shader, 1, &source, nullptr); glCompileShader(shader); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp index 8489ceff..fa6ca9c9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/Glsl420DeclarationScenario.cpp @@ -195,7 +195,8 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } glUseProgram(0); if (!m_textures.empty()) glDeleteTextures(static_cast(m_textures.size()), m_textures.data()); if (!m_buffers.empty()) glDeleteBuffers(static_cast(m_buffers.size()), m_buffers.data()); - for (GLuint p : m_programs) glDeleteProgram(p); + for (GLuint p : m_programs) + glDeleteProgram(p); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); m_textures.clear(); m_buffers.clear(); @@ -391,7 +392,8 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } return; } EXPECT_EQ(centre.r, 0) << "the array of arrays was not resolved, but the draw still painted " - "a mismatch instead of being declined: " << BadElements(centre.r); + "a mismatch instead of being declined: " + << BadElements(centre.r); } // Instance k of a uniform block array sits on buffer binding point N+k - again both as diff --git a/MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp index ccef9682..9f24ad52 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/GpuWrittenDrawInputScenario.cpp @@ -184,7 +184,9 @@ void main() { std::uint32_t count, instanceCount, first, baseInstance; }; - bool IsGreen(const Rgba8& p) { return p.g > 128 && p.r < 128; } + bool IsGreen(const Rgba8& p) { + return p.g > 128 && p.r < 128; + } class GpuWrittenDrawInputScenario : public ScenarioTest { protected: @@ -221,7 +223,8 @@ void main() { *buffer = 0; } if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); - for (GLuint program : m_programs) glDeleteProgram(program); + for (GLuint program : m_programs) + glDeleteProgram(program); m_programs.clear(); BindDefaultFramebuffer(); DestroyColorFbo(m_target); @@ -229,8 +232,7 @@ void main() { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // THE ARMS THIS FILE SKIPS, BY NAME: the monolith arm of a backend whose reason is @@ -242,8 +244,8 @@ void main() { const std::string backend = Gl().BackendName(); const char* why = backend == "DirectGLES" ? espryt : backend == "DirectVulkan" ? magma : nullptr; if (why == nullptr) return false; - m_skipReason = "monolith " + backend + ": " + why + - " (dev's fix, ID-P8-3) - this case gates the split arms"; + m_skipReason = + "monolith " + backend + ": " + why + " (dev's fix, ID-P8-3) - this case gates the split arms"; return true; } static constexpr const char* kUnsyncedCount = @@ -356,8 +358,7 @@ void main() { } // A vertex + fragment program whose `varyings` are captured INTERLEAVED. - GLuint XfbProgram(const char* vertex, const char* fragment, const char* const* varyings, - GLsizei count) { + GLuint XfbProgram(const char* vertex, const char* fragment, const char* const* varyings, GLsizei count) { const GLuint vs = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vs, 1, &vertex, nullptr); glCompileShader(vs); @@ -435,9 +436,8 @@ void main() { const GLuint program = Program(kFlatVertex, kFlatFragment); ASSERT_NE(program, 0u); BuildRestartScene(/*orphaned=*/false); - const Image image = Render(program, [] { - glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); - }); + const Image image = + Render(program, [] { glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glDrawElements over compute-written restart indices"); } @@ -450,9 +450,8 @@ void main() { const GLuint program = Program(kFlatVertex, kFlatFragment); ASSERT_NE(program, 0u); BuildRestartScene(/*orphaned=*/true); - const Image image = Render(program, [] { - glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); - }); + const Image image = + Render(program, [] { glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glDrawElements over compute-written restart indices in an orphaned store"); } @@ -470,9 +469,8 @@ void main() { BuildRestartScene(/*orphaned=*/false); const GLsizei counts[2] = {3, 4}; const void* offsets[2] = {nullptr, reinterpret_cast(3 * sizeof(GLuint))}; - const Image image = Render(program, [&] { - glMultiDrawElements(GL_TRIANGLE_STRIP, counts, GL_UNSIGNED_INT, offsets, 2); - }); + const Image image = + Render(program, [&] { glMultiDrawElements(GL_TRIANGLE_STRIP, counts, GL_UNSIGNED_INT, offsets, 2); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glMultiDrawElements over compute-written restart indices"); } @@ -510,13 +508,11 @@ void main() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_commands); glBindBuffer(GL_PARAMETER_BUFFER, m_parameters); - const Image image = Render(program, [] { - glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 2, 0); - }); + const Image image = + Render(program, [] { glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 2, 0); }); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(Half(image, 0))) - << "command 0 did not draw (" << Half(image, 0) - << "): the count was read from a copy the compute shader never reached"; + EXPECT_TRUE(IsGreen(Half(image, 0))) << "command 0 did not draw (" << Half(image, 0) + << "): the count was read from a copy the compute shader never reached"; EXPECT_TRUE(IsGreen(Half(image, 1))) << "command 1 did not draw (" << Half(image, 1) << ")"; } @@ -545,13 +541,10 @@ void main() { glBindVertexArray(m_vao); glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_commands); glBindBuffer(GL_PARAMETER_BUFFER, m_parameters); - const Image image = Render(program, [] { - glMultiDrawArraysIndirectCount(GL_TRIANGLES, nullptr, 0, 2, 0); - }); + const Image image = Render(program, [] { glMultiDrawArraysIndirectCount(GL_TRIANGLES, nullptr, 0, 2, 0); }); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(Half(image, 0))) - << "command 0 did not draw (" << Half(image, 0) - << "): the count was read from a copy the compute shader never reached"; + EXPECT_TRUE(IsGreen(Half(image, 0))) << "command 0 did not draw (" << Half(image, 0) + << "): the count was read from a copy the compute shader never reached"; EXPECT_TRUE(IsGreen(Half(image, 1))) << "command 1 did not draw (" << Half(image, 1) << ")"; } @@ -580,9 +573,7 @@ void main() { command[3] = 9; } glBindVertexArray(m_vao); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_commands); - const Image image = Render(program, [] { - glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); - }); + const Image image = Render(program, [] { glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); EXPECT_TRUE(IsGreen(Half(image, 1))) << "the right half is " << Half(image, 1) @@ -614,9 +605,7 @@ void main() { command[0] = 6u; command[1] = 1u; command[2] = 0u; command[3] = 3u glBindVertexArray(m_vao); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_commands); - const Image image = Render(program, [] { - glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); - }); + const Image image = Render(program, [] { glDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); EXPECT_TRUE(IsGreen(Half(image, 0))) << "the compute-written command did not draw (" << Half(image, 0) @@ -640,9 +629,8 @@ void main() { command[0] = 6u; command[1] = 1u; command[2] = 0u; command[3] = 3u Store(m_ebo, GL_ELEMENT_ARRAY_BUFFER, kRestartIndexCount * sizeof(GLuint), kRestartPoison.data()); CaptureIntoElementBuffer(capture, kRestartIndexCount); ArmRestart(); - const Image image = Render(program, [] { - glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); - }); + const Image image = + Render(program, [] { glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glDrawElements over restart indices a transform feedback captured"); } @@ -661,9 +649,8 @@ void main() { command[0] = 6u; command[1] = 1u; command[2] = 0u; command[3] = 3u Store(m_ebo, GL_ELEMENT_ARRAY_BUFFER, sizeof(kScatterStaged), kScatterStaged); CaptureIntoElementBuffer(capture, 4); ArmRestart(); - const Image image = Render(program, [] { - glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); - }); + const Image image = + Render(program, [] { glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glDrawElements over restart indices a scattered capture composed"); } @@ -689,9 +676,8 @@ void main() { command[0] = 6u; command[1] = 1u; command[2] = 0u; command[3] = 3u glUseProgram(0); glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); ArmRestart(); - const Image image = Render(program, [] { - glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); - }); + const Image image = + Render(program, [] { glDrawElements(GL_TRIANGLE_STRIP, kRestartIndexCount, GL_UNSIGNED_INT, nullptr); }); EXPECT_EQ(FirstGLError(), 0u); ExpectSplitStrip(image, "glDrawElements over restart indices image stores wrote"); } @@ -740,13 +726,11 @@ void main() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo); glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_commands); glBindBuffer(GL_PARAMETER_BUFFER, m_parameters); - const Image image = Render(program, [] { - glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 2, 0); - }); + const Image image = + Render(program, [] { glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 2, 0); }); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(Half(image, 0))) - << "command 0 did not draw (" << Half(image, 0) - << "): the count was read from a copy the atomic counter never reached"; + EXPECT_TRUE(IsGreen(Half(image, 0))) << "command 0 did not draw (" << Half(image, 0) + << "): the count was read from a copy the atomic counter never reached"; EXPECT_TRUE(IsGreen(Half(image, 1))) << "command 1 did not draw (" << Half(image, 1) << ")"; } @@ -774,7 +758,8 @@ void main() { layout(local_size_x = 1) in; layout(std430, binding = 0) buffer Positions { uint word[]; }; void main() { - const uint hi[8] = uint[8]()") + highWords + R"(); + const uint hi[8] = uint[8]()") + highWords + + R"(); for (int i = 0; i < 8; ++i) { word[2 * i] = 0u; word[2 * i + 1] = hi[i]; } } )"; @@ -800,7 +785,6 @@ void main() { "second GPU write"; } - // P13 W4a (ID-P8-14a): AN INDIRECT DRAW WITH NO CLIENT-MEMORY ARRAY READS NOTHING BACK. The // monolith indirect executors used to pull a GPU-written command buffer back into its shadow // once PER COMMAND, PER VIEWPORT PASS - before asking whether any attribute came from client diff --git a/MobileGL/MG_IntegrationTest/Scenarios/GuiBatchScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/GuiBatchScenario.cpp index e742e927..6018d22d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/GuiBatchScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/GuiBatchScenario.cpp @@ -213,13 +213,13 @@ void main() { // Which parts of the mod's real frame this replay reproduces. Each test flips exactly // one on top of the baseline so a failure names the element that killed the quad. struct Fidelity { - bool shortIndices = false; // MC's AutoStorageIndexBuffer is USHORT at small counts - bool baseVertex = false; // ...and the second builder draws at a base vertex - bool twoBuilders = false; // two render types share one output buffer - bool blendAndDepth = false; // TRANSLUCENT_TRANSPARENCY + LEQUAL_DEPTH_TEST - bool regrow = false; // MutableBuffer.doExpand replaces the GL name + bool shortIndices = false; // MC's AutoStorageIndexBuffer is USHORT at small counts + bool baseVertex = false; // ...and the second builder draws at a base vertex + bool twoBuilders = false; // two render types share one output buffer + bool blendAndDepth = false; // TRANSLUCENT_TRANSPARENCY + LEQUAL_DEPTH_TEST + bool regrow = false; // MutableBuffer.doExpand replaces the GL name bool rewriteMapEachFrame = false; - bool skipRelayout = false; // bindDrawBuffers() only re-lays-out when resized + bool skipRelayout = false; // bindDrawBuffers() only re-lays-out when resized bool leaveMeshesUnbound = false; // a batch with no server meshes never binds binding 5 }; @@ -469,8 +469,7 @@ void main() { if (saved[i].start == 0 && saved[i].size == 0) { glBindBufferBase(target, (GLuint)i, (GLuint)saved[i].buffer); } else { - glBindBufferRange(target, (GLuint)i, (GLuint)saved[i].buffer, saved[i].start, - saved[i].size); + glBindBufferRange(target, (GLuint)i, (GLuint)saved[i].buffer, saved[i].start, saved[i].size); } } } @@ -582,8 +581,8 @@ void main() { } void ExpectQuads(const Image& image, int builders, const char* when) { - EXPECT_TRUE(RegionIsMostly(image, 6, 26, 19, 45, "blue", 0.0, - std::string("left accelerated quad, ") + when)); + EXPECT_TRUE( + RegionIsMostly(image, 6, 26, 19, 45, "blue", 0.0, std::string("left accelerated quad, ") + when)); if (builders > 1) { EXPECT_TRUE(RegionIsMostly(image, 38, 58, 19, 45, "blue", 0.0, std::string("right accelerated quad, ") + when)); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/HandleRecycleScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/HandleRecycleScenario.cpp index d6844fbd..9ae11d89 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/HandleRecycleScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/HandleRecycleScenario.cpp @@ -167,11 +167,13 @@ namespace MGITest { // shape as CsoContentAddressingScenario's FAIL() on an unknown MGITEST_CSO_LANE. bool ArmNameIsRecognised() { const char* name = std::getenv(kArmMarker); - return name == nullptr || std::strcmp(name, "handles") == 0 || - std::strcmp(name, "legacy") == 0 || std::strcmp(name, "aba") == 0; + return name == nullptr || std::strcmp(name, "handles") == 0 || std::strcmp(name, "legacy") == 0 || + std::strcmp(name, "aba") == 0; } - bool RunningInAHandleRecycleLane() { return std::getenv(kArmMarker) != nullptr; } + bool RunningInAHandleRecycleLane() { + return std::getenv(kArmMarker) != nullptr; + } // Does THIS LANE run with a live client slot allocator behind it - i.e. did it pin a // non-zero MOBILEGL_PIPE_PUSH? @@ -252,9 +254,12 @@ namespace MGITest { const char* ArmName(Arm arm) { switch (arm) { - case Arm::Handles: return "Handles"; - case Arm::AbaControl: return "AbaControl"; - default: return "Legacy"; + case Arm::Handles: + return "Handles"; + case Arm::AbaControl: + return "AbaControl"; + default: + return "Legacy"; } } @@ -333,9 +338,8 @@ namespace MGITest { // input), the probe finds it and every one of the six flips to expecting the corruption. bool ObjectAbaControlIsWiredHere() { const std::string& backend = HeadlessGL::Get().BackendName(); - const bool knob = backend == "DirectVulkan" - ? BuildMarkerIsSet("MGITEST_HANDLE_ABA_OBJECTS_DirectVulkan") - : BuildMarkerIsSet("MGITEST_HANDLE_ABA_OBJECTS_DirectGLES"); + const bool knob = backend == "DirectVulkan" ? BuildMarkerIsSet("MGITEST_HANDLE_ABA_OBJECTS_DirectVulkan") + : BuildMarkerIsSet("MGITEST_HANDLE_ABA_OBJECTS_DirectGLES"); // Both halves, because either alone is a lie: a knob consumer with no object re-key // has nothing to defeat, and an object re-key with no knob consumer cannot be defeated. return knob && ThisBackendsObjectRekeyHasLanded(); @@ -399,8 +403,8 @@ void main() { oColor = texture(uTex, vUv); } constexpr int kInset = 2; void ExpectWholeViewportIs(const Image& image, const char* expected, const std::string& when) { - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, - expected, 0.0, when)); + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, expected, + 0.0, when)); } // The one thing the whole file turns on: did the pixels come from the REPLACEMENT @@ -411,25 +415,24 @@ void main() { oColor = texture(uTex, vUv); } // passes. The arm's expectation is only half the evidence, and a reader of the CI log // should not have to infer the other half from the exit status - least of all for a // control whose whole claim is "the corruption is still reproducible here". - const bool sawStale = static_cast(RegionIsMostly( - image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, stale, 0.0, when)); - const bool sawFresh = static_cast(RegionIsMostly( - image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, fresh, 0.0, when)); + const bool sawStale = static_cast(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, + image.Height() - kInset, stale, 0.0, when)); + const bool sawFresh = static_cast(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, + image.Height() - kInset, fresh, 0.0, when)); const bool expectsStale = arm == Arm::AbaControl && armExpectsCorruption; - std::cout << "[ HandleRecycle ] arm=" << ArmName(arm) << " expected=" - << (expectsStale ? "STALE" : "FRESH") << " observed=" - << (sawStale ? "STALE" : (sawFresh ? "FRESH" : "NEITHER")) << " (stale=" << stale + std::cout << "[ HandleRecycle ] arm=" << ArmName(arm) << " expected=" << (expectsStale ? "STALE" : "FRESH") + << " observed=" << (sawStale ? "STALE" : (sawFresh ? "FRESH" : "NEITHER")) << " (stale=" << stale << ", fresh=" << fresh << ") - " << when << std::endl; if (expectsStale) { // The corruption IS the assertion. If this ever goes green-by-being-correct the // reproducer has stopped reproducing and the other two arms prove nothing. - ExpectWholeViewportIs(image, stale, when + " [AbaControl expects the STALE object's pixels: " - "the identity half of every key is deliberately " - "defeated]"); + ExpectWholeViewportIs(image, stale, + when + " [AbaControl expects the STALE object's pixels: " + "the identity half of every key is deliberately " + "defeated]"); return; } - ExpectWholeViewportIs(image, fresh, - when + " [" + ArmName(arm) + " expects the replacement's pixels]"); + ExpectWholeViewportIs(image, fresh, when + " [" + ArmName(arm) + " expects the replacement's pixels]"); } class HandleRecycleScenario : public ScenarioTest { @@ -492,29 +495,30 @@ void main() { oColor = texture(uTex, vUv); } "arm is the one that is meaningful here, and it runs."; } switch (m_arm) { - case Arm::Handles: - if (!ThisBackendsRekeyHasLanded()) { - GTEST_SKIP() << "the Handles arm needs the backend's {slot, gen} re-key, and this " - "build does not have it: the build's capability probe found no " - "slot table and no Track H subsystem constant under " - "MobileGL/MG_Backend/" - << Gl().BackendName() - << " (P2 package C for DirectGLES, package D for DirectVulkan). The " - "arm is registered and visible, and arms itself when that " - "package lands in a push build."; - } - return; - case Arm::AbaControl: - if (!BuildMarkerIsSet("MGITEST_HANDLE_ABA_IMPLEMENTED")) { - GTEST_SKIP() << "the AbaControl arm needs MOBILEGL_PIPE_HANDLE_ABA_CONTROL to have a " - "consumer, and this build has none: MG_Config parses the knob " - "(ConfigLoader.cpp) but no source under MobileGL/MG_Backend/ reads " - "Features.PipeHandleAbaControl, so the two guards the knob is " - "supposed to defeat are still in force and the ABA cannot be " - "reproduced. P2 package D owns that consumer."; - } - return; - default: return; + case Arm::Handles: + if (!ThisBackendsRekeyHasLanded()) { + GTEST_SKIP() << "the Handles arm needs the backend's {slot, gen} re-key, and this " + "build does not have it: the build's capability probe found no " + "slot table and no Track H subsystem constant under " + "MobileGL/MG_Backend/" + << Gl().BackendName() + << " (P2 package C for DirectGLES, package D for DirectVulkan). The " + "arm is registered and visible, and arms itself when that " + "package lands in a push build."; + } + return; + case Arm::AbaControl: + if (!BuildMarkerIsSet("MGITEST_HANDLE_ABA_IMPLEMENTED")) { + GTEST_SKIP() << "the AbaControl arm needs MOBILEGL_PIPE_HANDLE_ABA_CONTROL to have a " + "consumer, and this build has none: MG_Config parses the knob " + "(ConfigLoader.cpp) but no source under MobileGL/MG_Backend/ reads " + "Features.PipeHandleAbaControl, so the two guards the knob is " + "supposed to defeat are still in force and the ABA cannot be " + "reproduced. P2 package D owns that consumer."; + } + return; + default: + return; } } @@ -565,8 +569,8 @@ void main() { oColor = texture(uTex, vUv); } bool ObjectAbaExpectation(const char* kindName) { const bool wired = ObjectAbaControlIsWiredHere(); if (m_arm == Arm::AbaControl) { - std::cout << "[ HandleRecycle ] aba_control kind=" << kindName - << " backend=" << Gl().BackendName() << " wired=" << (wired ? "1" : "0") + std::cout << "[ HandleRecycle ] aba_control kind=" << kindName << " backend=" << Gl().BackendName() + << " wired=" << (wired ? "1" : "0") << (wired ? " (the knob defeats this kind's identity: the corruption IS " "the assertion)" : " (no Features.PipeHandleAbaControl consumer over this " @@ -585,9 +589,8 @@ void main() { oColor = texture(uTex, vUv); } GLuint buffer = 0; glGenBuffers(1, &buffer); glBindBuffer(GL_ARRAY_BUFFER, buffer); - glBufferData(GL_ARRAY_BUFFER, - static_cast(vertices.size() * sizeof(Vertex)), vertices.data(), - GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, static_cast(vertices.size() * sizeof(Vertex)), + vertices.data(), GL_STATIC_DRAW); return buffer; } @@ -611,7 +614,7 @@ void main() { oColor = texture(uTex, vUv); } // inheritance be seen: with one buffer, a stale set and a stale everything look the // same in the pixels. GLuint MakePositionBuffer() { - const float positions[12] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, + const float positions[12] = {-1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f}; GLuint buffer = 0; glGenBuffers(1, &buffer); @@ -728,8 +731,8 @@ void main() { oColor = texture(uTex, vUv); } // which is what C-1 is about, still moves every one of them. using ChurnRound = std::function& observe)>; void AssertChurnReturnsEverySlot(PipeSlotKind kind, SlotSpace space, const char* kindName, - const char* owner, unsigned maxInFlight, - const ChurnRound& round, unsigned warmUpRounds = 2u) { + const char* owner, unsigned maxInFlight, const ChurnRound& round, + unsigned warmUpRounds = 2u) { // THE LANE'S OWN PIN, not the arm (F-m4). The Legacy and AbaControl lanes run // MOBILEGL_PIPE_PUSH=0 and really have no allocator to leak from; the Handles // lanes and DirectVulkan.HandleRecycle.AbaControlHandles. all pin the shipping @@ -777,7 +780,8 @@ void main() { oColor = texture(uTex, vUv); } // needs and the second proves the steady state has been reached. ASSERT_GE(warmUpRounds, 2u) << "the warm-up has to reach a steady state"; round(/*checkPixels=*/true, observe); - for (unsigned i = 1; i < warmUpRounds; ++i) round(/*checkPixels=*/false, observe); + for (unsigned i = 1; i < warmUpRounds; ++i) + round(/*checkPixels=*/false, observe); peakLive = 0; unsigned liveBefore = 0; @@ -786,18 +790,18 @@ void main() { oColor = texture(uTex, vUv); } ASSERT_TRUE(ReadSpaceHighWater(kind, space, &highWaterBefore)); constexpr int kChurn = 48; - for (int i = 0; i < kChurn; ++i) round(/*checkPixels=*/false, observe); + for (int i = 0; i < kChurn; ++i) + round(/*checkPixels=*/false, observe); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the churn left a GL error behind"; unsigned liveAfter = 0; unsigned highWaterAfter = 0; ASSERT_TRUE(ReadSpaceLiveCount(kind, space, &liveAfter)); ASSERT_TRUE(ReadSpaceHighWater(kind, space, &highWaterAfter)); - std::cout << "[ HandleRecycle ] backend=" << Gl().BackendName() << " " << kindName - << SpaceSuffix(space) << " live " << liveBefore << " -> " << liveAfter - << " (peak " << peakLive << "), high water " << highWaterBefore << " -> " - << highWaterAfter << " (floor " << highWaterFloor << ") over " << kChurn - << " create/draw/destroy rounds" << std::endl; + std::cout << "[ HandleRecycle ] backend=" << Gl().BackendName() << " " << kindName << SpaceSuffix(space) + << " live " << liveBefore << " -> " << liveAfter << " (peak " << peakLive << "), high water " + << highWaterBefore << " -> " << highWaterAfter << " (floor " << highWaterFloor << ") over " + << kChurn << " create/draw/destroy rounds" << std::endl; // NOTHING WAS EVER MINTED, which is not "did not leak" and must not be reported as // one. On the P4a contract tree the client emits nothing for any of these kinds - @@ -807,9 +811,8 @@ void main() { oColor = texture(uTex, vUv); } // to the other way of not being able to look, and it arms itself the moment the // owning package's emitter lands. if (highWaterAfter == highWaterFloor && peakLive == 0 && liveAfter == 0) { - GTEST_SKIP() << "subsystem not implemented on this tree: the client minted no " - << kindName << SpaceSuffix(space) - << " slot at all over " << (kChurn + 2) + GTEST_SKIP() << "subsystem not implemented on this tree: the client minted no " << kindName + << SpaceSuffix(space) << " slot at all over " << (kChurn + 2) << " create/draw/destroy rounds, so there is nothing here that " "could leak and a green would assert nothing. P4a package " << owner @@ -1003,8 +1006,7 @@ void main() { oColor = texture(uTex, vUv); } // window gets a process of its own, where every VAO carries the same layout and the // buffer identity is again the only thing that differs. // ------------------------------------------------------------------------------------ - TEST_F(HandleRecycleScenario, - AVertexArrayAtARecycledAddressDoesNotInheritItsPredecessorsVertexBufferSet) { + TEST_F(HandleRecycleScenario, AVertexArrayAtARecycledAddressDoesNotInheritItsPredecessorsVertexBufferSet) { if (!Ready()) return; SkipUnlessTheArmIsAssertableHere(); if (IsSkipped()) return; @@ -1070,7 +1072,6 @@ void main() { oColor = texture(uTex, vUv); } glDeleteBuffers(3, splitCleanupBuffers); } - // ------------------------------------------------------------------------------------ // 1b. The BUFFER (P3a). Today a buffer's backend twin is reached from the frontend // object: Espryt keys GLESBufferResource off the BufferObject's SharedPtr and @@ -1141,8 +1142,7 @@ void main() { oColor = texture(uTex, vUv); } // only thing that differs and the only thing the pixels can show. const GLuint greenBuffer = MakeQuadBuffer(0.0f, 1.0f, 0.0f); ConfigureQuadVao(vao, greenBuffer); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) - << "building the replacement buffer left a GL error behind"; + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "building the replacement buffer left a GL error behind"; glBindVertexArray(vao); glDrawArrays(GL_TRIANGLES, 0, kVertexCount); @@ -1235,8 +1235,8 @@ void main() { oColor = texture(uTex, vUv); } RecordProperty("recycled_texture_name", static_cast(greenTexture)); const Image image = DrawTexturedQuadAndRead(vao, greenTexture); - ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("texture"), image, - "green", "red", "the draw after the texture was recycled"); + ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("texture"), image, "green", "red", + "the draw after the texture was recycled"); glBindTexture(GL_TEXTURE_2D, 0); GLuint cleanupTexture = greenTexture; @@ -1328,8 +1328,7 @@ void main() { oColor = texture(uTex, vUv); } glBindTexture(GL_TEXTURE_2D, 0); return texels; }; - const auto offendersAgainst = [](const std::vector& texels, int r, int g, - int b) { + const auto offendersAgainst = [](const std::vector& texels, int r, int g, int b) { int offenders = 0; for (std::size_t i = 0; i < texels.size(); i += 4) { if (texels[i] != r || texels[i + 1] != g || texels[i + 2] != b) ++offenders; @@ -1345,10 +1344,9 @@ void main() { oColor = texture(uTex, vUv); } const bool sawStale = deadIsNotRed != 0 && offendersAgainst(deadTexels, 0, 255, 0) == 0; std::cout << "[ HandleRecycle ] arm=" << ArmName(m_arm) << " framebuffer observed=" << (sawStale ? "STALE (the clear reached the DEAD framebuffer's attachment)" - : (replacementIsNotGreen == 0 - ? "FRESH (the clear reached the replacement's own " - "attachment)" - : "NEITHER")) + : (replacementIsNotGreen == 0 ? "FRESH (the clear reached the replacement's own " + "attachment)" + : "NEITHER")) << std::endl; if (ObjectAbaExpectation("framebuffer") && m_arm == Arm::AbaControl) { @@ -1360,27 +1358,23 @@ void main() { oColor = texture(uTex, vUv); } // claims. sawStale is the predicate the case already computes and prints - the // dead attachment is now the REPLACEMENT'S green, i.e. the replacement's clear // landed there - so the assertion and the printed line say the same thing. - EXPECT_TRUE(sawStale) - << "[AbaControl expects the STALE framebuffer] the DEAD framebuffer's " - "attachment did not come back as the replacement's green, so the " - "replacement's clear did not land on it and the ABA was not reproduced - " - "the control has stopped controlling anything. Texels that are neither the " - "warm-up red nor the replacement green are a third answer and are not a " - "reproduction either (" - << deadIsNotRed << " of " << (deadTexels.size() / 4) - << " dead texels are not red)."; + EXPECT_TRUE(sawStale) << "[AbaControl expects the STALE framebuffer] the DEAD framebuffer's " + "attachment did not come back as the replacement's green, so the " + "replacement's clear did not land on it and the ABA was not reproduced - " + "the control has stopped controlling anything. Texels that are neither the " + "warm-up red nor the replacement green are a third answer and are not a " + "reproduction either (" + << deadIsNotRed << " of " << (deadTexels.size() / 4) + << " dead texels are not red)."; } else { EXPECT_EQ(replacementIsNotGreen, 0) << "the replacement framebuffer's own attachment is not the colour it was " "cleared to, so the clear reached a framebuffer this one only shares an " "address with (first texel rgba=" - << static_cast(replacementTexels[0]) << "," - << static_cast(replacementTexels[1]) << "," - << static_cast(replacementTexels[2]) << "," - << static_cast(replacementTexels[3]) << ")"; - EXPECT_EQ(deadIsNotRed, 0) - << "the DEAD framebuffer's attachment changed colour, so the replacement's " - "clear reached the framebuffer it only shares a name with"; + << static_cast(replacementTexels[0]) << "," << static_cast(replacementTexels[1]) << "," + << static_cast(replacementTexels[2]) << "," << static_cast(replacementTexels[3]) << ")"; + EXPECT_EQ(deadIsNotRed, 0) << "the DEAD framebuffer's attachment changed colour, so the replacement's " + "clear reached the framebuffer it only shares a name with"; } glBindFramebuffer(GL_FRAMEBUFFER, 0); @@ -1479,25 +1473,21 @@ void main() { oColor = texture(uTex, vUv); } unsigned liveBefore = 0; unsigned highWaterBefore = 0; - ASSERT_TRUE(MGITest::PeekPipeSlotLiveCount(MGITest::PipeSlotKind::VertexElementsCso, - &liveBefore)); - ASSERT_TRUE(MGITest::PeekPipeSlotHighWater(MGITest::PipeSlotKind::VertexElementsCso, - &highWaterBefore)); + ASSERT_TRUE(MGITest::PeekPipeSlotLiveCount(MGITest::PipeSlotKind::VertexElementsCso, &liveBefore)); + ASSERT_TRUE(MGITest::PeekPipeSlotHighWater(MGITest::PipeSlotKind::VertexElementsCso, &highWaterBefore)); constexpr int kChurn = 48; - for (int i = 0; i < kChurn; ++i) round("a churn draw", /*checkPixels=*/false); + for (int i = 0; i < kChurn; ++i) + round("a churn draw", /*checkPixels=*/false); ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the churn left a GL error behind"; unsigned liveAfter = 0; unsigned highWaterAfter = 0; - ASSERT_TRUE(MGITest::PeekPipeSlotLiveCount(MGITest::PipeSlotKind::VertexElementsCso, - &liveAfter)); - ASSERT_TRUE(MGITest::PeekPipeSlotHighWater(MGITest::PipeSlotKind::VertexElementsCso, - &highWaterAfter)); - std::cout << "[ HandleRecycle ] backend=" << Gl().BackendName() << " VertexElementsCso live " - << liveBefore << " -> " << liveAfter << " (peak " << peakLive << "), high water " - << highWaterBefore << " -> " << highWaterAfter << " over " << kChurn - << " create/draw/destroy rounds" << std::endl; + ASSERT_TRUE(MGITest::PeekPipeSlotLiveCount(MGITest::PipeSlotKind::VertexElementsCso, &liveAfter)); + ASSERT_TRUE(MGITest::PeekPipeSlotHighWater(MGITest::PipeSlotKind::VertexElementsCso, &highWaterAfter)); + std::cout << "[ HandleRecycle ] backend=" << Gl().BackendName() << " VertexElementsCso live " << liveBefore + << " -> " << liveAfter << " (peak " << peakLive << "), high water " << highWaterBefore << " -> " + << highWaterAfter << " over " << kChurn << " create/draw/destroy rounds" << std::endl; EXPECT_EQ(liveAfter, liveBefore) << kChurn << " vertex arrays were created, drawn with and destroyed and " << (liveAfter - liveBefore) @@ -1569,8 +1559,7 @@ void main() { oColor = texture(uTex, vUv); } glBindRenderbuffer(GL_RENDERBUFFER, deadRenderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - deadRenderbuffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, deadRenderbuffer); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); for (int frame = 0; frame < kWarmupFrames; ++frame) { glBindFramebuffer(GL_FRAMEBUFFER, fbo); @@ -1594,17 +1583,15 @@ void main() { oColor = texture(uTex, vUv); } if (liveRenderbuffer != deadRenderbuffer) { glDeleteRenderbuffers(1, &liveRenderbuffer); glDeleteFramebuffers(1, &fbo); - GTEST_SKIP() << "inconclusive, not proven: glGenRenderbuffers returned " - << liveRenderbuffer << " rather than the deleted " << deadRenderbuffer - << ", so no ABA was constructed"; + GTEST_SKIP() << "inconclusive, not proven: glGenRenderbuffers returned " << liveRenderbuffer + << " rather than the deleted " << deadRenderbuffer << ", so no ABA was constructed"; } RecordProperty("recycled_renderbuffer_name", static_cast(liveRenderbuffer)); glBindRenderbuffer(GL_RENDERBUFFER, liveRenderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 8, 8); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - liveRenderbuffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, liveRenderbuffer); const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); glViewport(0, 0, 8, 8); ClearTo(0.0f, 1.0f, 0.0f, 1.0f); @@ -1613,28 +1600,26 @@ void main() { oColor = texture(uTex, vUv); } Gl().EndFrame(); const bool complete = status == GLenum(GL_FRAMEBUFFER_COMPLETE); - const bool allGreen = complete && static_cast(RegionIsMostly(image, 0, 8, 0, 8, - "green", 0.0, - "the replacement " - "renderbuffer")); - std::cout << "[ HandleRecycle ] arm=" << ArmName(m_arm) - << " renderbuffer observed=" << (allGreen ? "FRESH (8x8 all green)" - : "STALE (the replacement did not " - "get its own 8x8 storage)") + const bool allGreen = complete && static_cast(RegionIsMostly(image, 0, 8, 0, 8, "green", 0.0, + "the replacement " + "renderbuffer")); + std::cout << "[ HandleRecycle ] arm=" << ArmName(m_arm) << " renderbuffer observed=" + << (allGreen ? "FRESH (8x8 all green)" + : "STALE (the replacement did not " + "get its own 8x8 storage)") << " fbo_status=0x" << std::hex << status << std::dec << std::endl; if (ObjectAbaExpectation("renderbuffer") && m_arm == Arm::AbaControl) { - EXPECT_FALSE(allGreen) - << "[AbaControl expects the STALE renderbuffer] the replacement got its own 8x8 " - "storage with the identity half of the twin key deliberately defeated, so " - "the ABA was not reproduced and this control is controlling nothing."; + EXPECT_FALSE(allGreen) << "[AbaControl expects the STALE renderbuffer] the replacement got its own 8x8 " + "storage with the identity half of the twin key deliberately defeated, so " + "the ABA was not reproduced and this control is controlling nothing."; } else { EXPECT_EQ(status, GLenum(GL_FRAMEBUFFER_COMPLETE)) << "the framebuffer went incomplete after the recycled renderbuffer was given " "8x8 storage, which is what a driver object inherited from the dead 4x4 " "renderbuffer looks like"; - EXPECT_TRUE(RegionIsMostly(image, 0, 8, 0, 8, "green", 0.0, - "the replacement renderbuffer's own 8x8 storage")) + EXPECT_TRUE( + RegionIsMostly(image, 0, 8, 0, 8, "green", 0.0, "the replacement renderbuffer's own 8x8 storage")) << "the replacement renderbuffer did not read back as its own storage, so the " "clear reached a renderbuffer it only shares a name with"; } @@ -1730,15 +1715,13 @@ void main() { oColor = texture(uTex, vec2(4.0, 4.0)); } glDeleteBuffers(1, const_cast(&buffer)); glDeleteProgram(borderProgram); GTEST_SKIP() << "inconclusive, not proven: glGenSamplers returned " << greenSampler - << " rather than the deleted " << redSampler - << ", so no ABA was constructed"; + << " rather than the deleted " << redSampler << ", so no ABA was constructed"; } RecordProperty("recycled_sampler_name", static_cast(greenSampler)); const Image image = drawThroughSampler(greenSampler); - ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("sampler object"), - image, "green", "red", - "the draw after the sampler object was recycled"); + ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("sampler object"), image, "green", + "red", "the draw after the sampler object was recycled"); glBindSampler(0, 0); glBindTexture(GL_TEXTURE_2D, 0); @@ -1831,8 +1814,7 @@ void main() { oColor = texture(uTex, vec2(4.0, 4.0)); } RecordProperty("recycled_sampler_view_texture_name", static_cast(greenTexture)); const Image image = drawSampled(greenTexture); - ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("sampler view"), - image, "green", "red", + ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("sampler view"), image, "green", "red", "the draw after the sampler view's texture was recycled under a live " "sampler object"); @@ -1911,14 +1893,13 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } glDeleteVertexArrays(1, &vao); glDeleteBuffers(1, const_cast(&buffer)); GTEST_SKIP() << "inconclusive, not proven: glCreateProgram returned " << greenProgram - << " rather than the deleted " << redProgram - << ", so no ABA was constructed"; + << " rather than the deleted " << redProgram << ", so no ABA was constructed"; } RecordProperty("recycled_program_name", static_cast(greenProgram)); const Image image = drawWith(greenProgram); - ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("program"), image, - "green", "red", "the draw after the program was recycled"); + ExpectPixelsFor(m_arm, /*armExpectsCorruption=*/ObjectAbaExpectation("program"), image, "green", "red", + "the draw after the program was recycled"); glUseProgram(0); glDeleteProgram(greenProgram); @@ -1945,8 +1926,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } AssertChurnReturnsEverySlot( PipeSlotKind::Texture, SlotSpace::Ordinary, "Texture", "B (clientfb)", - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { const GLuint texture = MakeSolidTexture(0, 255, 0); const Image image = DrawTexturedQuadAndRead(vao, texture); if (checkPixels) { @@ -1984,8 +1964,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // that forgot one of its three frees leaks only that kind and nothing else moves. AssertChurnReturnsEverySlot( PipeSlotKind::SamplerViewCso, SlotSpace::Ordinary, "SamplerViewCso", "C (clientsp)", - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { const GLuint texture = MakeSolidTexture(0, 255, 0); const Image image = DrawTexturedQuadAndRead(vao, texture); if (checkPixels) { @@ -2014,15 +1993,13 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } AssertChurnReturnsEverySlot( PipeSlotKind::Renderbuffer, SlotSpace::Ordinary, "Renderbuffer", "B (clientfb)", - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { GLuint renderbuffer = 0; glGenRenderbuffers(1, &renderbuffer); glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - renderbuffer); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); glViewport(0, 0, 4, 4); ClearTo(0.0f, 1.0f, 0.0f, 1.0f); if (checkPixels) { @@ -2061,13 +2038,11 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // whole of its lifetime coverage rather than a supplement to a wire assertion. AssertChurnReturnsEverySlot( PipeSlotKind::Framebuffer, SlotSpace::Ordinary, "Framebuffer", "B (clientfb)", - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { GLuint fbo = 0; glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - attachment, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, attachment, 0); glViewport(0, 0, 4, 4); ClearTo(0.0f, 1.0f, 0.0f, 1.0f); if (checkPixels) { @@ -2120,8 +2095,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } glSamplerParameteri(sampler, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glSamplerParameteri(sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, - -static_cast(round % 16)); + glSamplerParameterf(sampler, GL_TEXTURE_MIN_LOD, -static_cast(round % 16)); ++round; BindDefaultFramebuffer(); @@ -2170,8 +2144,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } int round = 0; AssertChurnReturnsEverySlot( PipeSlotKind::ShaderCso, SlotSpace::Ordinary, "ShaderCso", "C (clientsp)", - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { const std::string fs = "#version 330 core\nout vec4 oColor;\nvoid main() { " "oColor = vec4(0.0, 1.0, 0.0, 1.0) + vec4(" + std::to_string(round) + ".0 * 0.0); }\n"; @@ -2246,8 +2219,7 @@ void main() { SlotSpace::CompositeBand, "ShaderCso (pipeline composites)", "C (clientsp)", // ONE in flight, because in the BAND a round holds exactly one slot: the // composite. (Against the ordinary space the answer would have been three.) - /*maxInFlight=*/1u, - [&](bool checkPixels, const std::function& observe) { + /*maxInFlight=*/1u, [&](bool checkPixels, const std::function& observe) { // A DIFFERENT FRAGMENT STAGE PER ROUND: the composite is keyed on // ProgramPipelineObject::ComputeDrawProgramSignature(), the per-stage // {lifetimeId, GetLinkVersion()} array, so a round that rebuilt an identical @@ -2255,7 +2227,8 @@ void main() { // and the case would assert nothing about the release paths. const std::string fsSource = "#version 410 core\nin vec3 vColor;\nout vec4 oColor;\nvoid main() { oColor " - "= vec4(vColor, 1.0) + vec4(" + std::to_string(round) + ".0 * 0.0); }\n"; + "= vec4(vColor, 1.0) + vec4(" + + std::to_string(round) + ".0 * 0.0); }\n"; ++round; const char* vsSource = kSeparableVS; const char* fsSourcePtr = fsSource.c_str(); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ImageBindableRemintScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ImageBindableRemintScenario.cpp index 78dc4954..0bd59a93 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ImageBindableRemintScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ImageBindableRemintScenario.cpp @@ -518,8 +518,7 @@ void main() { o_color = texelFetch(u_tex, ivec2(gl_FragCoord.xy) % 4, 0); } glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8_SNORM, kEdge, kEdge, 0, GL_RGBA, GL_BYTE, s.data()); Nearest(GL_TEXTURE_2D, t, 0); SyncBySampling(t); - ExpectImage(ImageCopy(t, 0, GL_RGBA8_SNORM, "rgba8_snorm", true), kEdge, Solid(kEdge, 255, 128, 0, 255), - 3); + ExpectImage(ImageCopy(t, 0, GL_RGBA8_SNORM, "rgba8_snorm", true), kEdge, Solid(kEdge, 255, 128, 0, 255), 3); } // Control: RGBA16_SNORM is desktop-only as an image format, so this re-mint widens too. @@ -538,8 +537,8 @@ void main() { o_color = texelFetch(u_tex, ivec2(gl_FragCoord.xy) % 4, 0); } glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kEdge, kEdge, GL_RGBA, GL_SHORT, s.data()); Nearest(GL_TEXTURE_2D, t, 0); SyncBySampling(t); - ExpectImage(ImageCopy(t, 0, GL_RGBA16_SNORM, "rgba16_snorm", true), kEdge, - Solid(kEdge, 255, 128, 0, 255), 3); + ExpectImage(ImageCopy(t, 0, GL_RGBA16_SNORM, "rgba16_snorm", true), kEdge, Solid(kEdge, 255, 128, 0, 255), + 3); } // An array texture is read back one layer at a time: the GPU-cleared layer and the diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ImageFormatQualifierScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ImageFormatQualifierScenario.cpp index 2b076bf6..713deb06 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ImageFormatQualifierScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ImageFormatQualifierScenario.cpp @@ -78,8 +78,10 @@ void main() void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint t : m_textures) glDeleteTextures(1, &t); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint t : m_textures) + glDeleteTextures(1, &t); m_programs.clear(); m_textures.clear(); GLint maxImageUnits = 0; @@ -87,8 +89,7 @@ void main() for (GLint unit = 0; unit < maxImageUnits; ++unit) { glBindImageTexture(static_cast(unit), 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } bool ImagesAreUsable() const { @@ -96,8 +97,7 @@ void main() glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits > kImageUnit && maxComputeImageUniforms >= 1; } @@ -147,8 +147,7 @@ void main() glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kExtent, kExtent, internalFormat == GL_RGBA32UI ? GL_RGBA_INTEGER : GL_RED_INTEGER, GL_UNSIGNED_INT, zeros.data()); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return texture; } @@ -157,8 +156,7 @@ void main() // a reader declared the same way and could cancel itself out. GLuint ReadRedTexel(GLuint texture, GLenum internalFormat, int x) { const bool rgba = internalFormat == GL_RGBA32UI; - std::vector texels(static_cast(kExtent) * kExtent * (rgba ? 4u : 1u), - 0xFFFFFFFFu); + std::vector texels(static_cast(kExtent) * kExtent * (rgba ? 4u : 1u), 0xFFFFFFFFu); glBindTexture(GL_TEXTURE_2D, texture); glGetTexImage(GL_TEXTURE_2D, 0, rgba ? GL_RGBA_INTEGER : GL_RED_INTEGER, GL_UNSIGNED_INT, texels.data()); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ImageLoadStoreSsoScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ImageLoadStoreSsoScenario.cpp index 7828a875..7bf6ee2f 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ImageLoadStoreSsoScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ImageLoadStoreSsoScenario.cpp @@ -91,8 +91,10 @@ void main() if (!Ready()) return; glBindProgramPipeline(0); glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint p : m_pipelines) glDeleteProgramPipelines(1, &p); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint p : m_pipelines) + glDeleteProgramPipelines(1, &p); m_programs.clear(); m_pipelines.clear(); } @@ -139,8 +141,7 @@ void main() if (Gl().BackendName() == "DirectVulkan") return true; GLint maxFragmentImageUniforms = 0; glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} // One per element of the four-element array either fragment program declares. return maxFragmentImageUniforms >= 4; } @@ -150,8 +151,7 @@ void main() bool ImagesAreUsable() const { GLint maxImageUnits = 0; glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits >= 8; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ImageSizeAfterRespecScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ImageSizeAfterRespecScenario.cpp index 77ca8922..038f9630 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ImageSizeAfterRespecScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ImageSizeAfterRespecScenario.cpp @@ -88,8 +88,7 @@ void main() if (m_image != 0) glDeleteTextures(1, &m_image); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); m_program = m_fbo = m_color = m_image = m_vao = 0; - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // imageSize() needs a fragment-stage image uniform; a driver that serves none should @@ -99,8 +98,7 @@ void main() GLint maxFragmentImageUniforms = 0; glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits >= 1 && maxFragmentImageUniforms >= 1; } @@ -211,10 +209,9 @@ void main() unsigned char centre[4] = {0, 0, 0, 0}; DrawAndReadCentre(32, 32, centre); - EXPECT_EQ(static_cast(centre[0]), 0) << "the FIRST draw already disagrees about imageSize(): got (" - << static_cast(centre[0]) << ", " - << static_cast(centre[1]) << ", " - << static_cast(centre[2]) << ")"; + EXPECT_EQ(static_cast(centre[0]), 0) + << "the FIRST draw already disagrees about imageSize(): got (" << static_cast(centre[0]) << ", " + << static_cast(centre[1]) << ", " << static_cast(centre[2]) << ")"; EXPECT_EQ(static_cast(centre[1]), 255); // The re-spec. The image unit is deliberately NOT re-bound: GL 4.6 core 8.26 says the @@ -226,8 +223,8 @@ void main() DrawAndReadCentre(16, 16, centre); EXPECT_EQ(static_cast(centre[0]), 0) << "after the re-spec the draw still sees the OLD image size; centre pixel was (" - << static_cast(centre[0]) << ", " << static_cast(centre[1]) << ", " - << static_cast(centre[2]) << ")"; + << static_cast(centre[0]) << ", " << static_cast(centre[1]) << ", " << static_cast(centre[2]) + << ")"; EXPECT_EQ(static_cast(centre[1]), 255); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ImageTargetKindScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ImageTargetKindScenario.cpp index abe912ee..50c71820 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ImageTargetKindScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ImageTargetKindScenario.cpp @@ -81,20 +81,19 @@ namespace MGITest { }; constexpr TargetKind kKind1D{"1D", GL_TEXTURE_1D, "uimage1D", "0", false, false}; - constexpr TargetKind kKind1DArray{"1DArray", GL_TEXTURE_1D_ARRAY, "uimage1DArray", "ivec2(0, 0)", false, - false}; + constexpr TargetKind kKind1DArray{"1DArray", GL_TEXTURE_1D_ARRAY, "uimage1DArray", "ivec2(0, 0)", false, false}; constexpr TargetKind kKind2D{"2D", GL_TEXTURE_2D, "uimage2D", "ivec2(0, 0)", false, false}; constexpr TargetKind kKind2DArray{"2DArray", GL_TEXTURE_2D_ARRAY, "uimage2DArray", "ivec3(0, 0, 0)", false, false}; constexpr TargetKind kKind3D{"3D", GL_TEXTURE_3D, "uimage3D", "ivec3(0, 0, 0)", false, false}; constexpr TargetKind kKindBuffer{"Buffer", GL_TEXTURE_BUFFER, "uimageBuffer", "0", false, true}; constexpr TargetKind kKindCube{"Cube", GL_TEXTURE_CUBE_MAP, "uimageCube", "ivec3(0, 0, 0)", false, false}; - constexpr TargetKind kKindCubeArray{"CubeArray", GL_TEXTURE_CUBE_MAP_ARRAY, "uimageCubeArray", - "ivec3(0, 0, 0)", false, false}; + constexpr TargetKind kKindCubeArray{ + "CubeArray", GL_TEXTURE_CUBE_MAP_ARRAY, "uimageCubeArray", "ivec3(0, 0, 0)", false, false}; constexpr TargetKind kKindRect{"Rect", GL_TEXTURE_RECTANGLE, "uimage2DRect", "ivec2(0, 0)", false, false}; constexpr TargetKind kKind2DMS{"2DMS", GL_TEXTURE_2D_MULTISAMPLE, "uimage2DMS", "ivec2(0, 0)", true, false}; - constexpr TargetKind kKind2DMSArray{"2DMSArray", GL_TEXTURE_2D_MULTISAMPLE_ARRAY, "uimage2DMSArray", - "ivec3(0, 0, 0)", true, false}; + constexpr TargetKind kKind2DMSArray{ + "2DMSArray", GL_TEXTURE_2D_MULTISAMPLE_ARRAY, "uimage2DMSArray", "ivec3(0, 0, 0)", true, false}; // A multisample image load/store takes the sample index as an extra argument; no other // kind does. Keeping that in one place stops the two spellings drifting apart. @@ -103,8 +102,8 @@ namespace MGITest { } std::string StoreStatement(const TargetKind& kind, const std::string& name, const char* value) { - return "imageStore(" + name + ", " + kind.coord + (kind.multisample ? ", 0, uvec4(" : ", uvec4(") + - value + ", 0, 0, 0));"; + return "imageStore(" + name + ", " + kind.coord + (kind.multisample ? ", 0, uvec4(" : ", uvec4(") + value + + ", 0, 0, 0));"; } const char* kComputePrologue = "#version 440 core\n" @@ -128,8 +127,8 @@ namespace MGITest { // backend which gets the store right and the load wrong (or the reverse) is not able to // cancel its own defect out. std::string SingleStoreSource(const TargetKind& kind) { - return std::string(kComputePrologue) + "layout (location = 0, r32ui) writeonly uniform " + - kind.imageType + " i0;\n\nvoid main()\n{\n " + StoreStatement(kind, "i0", "13u") + "\n}\n"; + return std::string(kComputePrologue) + "layout (location = 0, r32ui) writeonly uniform " + kind.imageType + + " i0;\n\nvoid main()\n{\n " + StoreStatement(kind, "i0", "13u") + "\n}\n"; } // The third direction, and the one neither of the two above can stand in for: an @@ -146,9 +145,9 @@ namespace MGITest { // Returns the value the texel held BEFORE the add, so one dispatch checks the atomic's // return value and the load case that follows checks its memory effect. std::string SingleAtomicSource(const TargetKind& kind) { - return std::string(kComputePrologue) + "layout (location = 0, r32ui) coherent uniform " + - kind.imageType + " i0;\n" + kResultBlock + "void main()\n{\n ssb.sum = imageAtomicAdd(i0, " + - kind.coord + (kind.multisample ? ", 0, " : ", ") + std::to_string(kAtomicAddend) + "u);\n}\n"; + return std::string(kComputePrologue) + "layout (location = 0, r32ui) coherent uniform " + kind.imageType + + " i0;\n" + kResultBlock + "void main()\n{\n ssb.sum = imageAtomicAdd(i0, " + kind.coord + + (kind.multisample ? ", 0, " : ", ") + std::to_string(kAtomicAddend) + "u);\n}\n"; } class ImageTargetKindScenario : public ScenarioTest { @@ -156,9 +155,12 @@ namespace MGITest { void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint t : m_textures) glDeleteTextures(1, &t); - for (GLuint b : m_buffers) glDeleteBuffers(1, &b); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint t : m_textures) + glDeleteTextures(1, &t); + for (GLuint b : m_buffers) + glDeleteBuffers(1, &b); m_programs.clear(); m_textures.clear(); m_buffers.clear(); @@ -171,8 +173,7 @@ namespace MGITest { glBindImageTexture(static_cast(unit), 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); } glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } bool ImagesAreUsable() const { @@ -180,8 +181,7 @@ namespace MGITest { glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits >= 1 && maxComputeImageUniforms >= 1; } @@ -189,8 +189,7 @@ namespace MGITest { bool MultisampleImagesAreUsable() const { GLint maxImageSamples = 0; glGetIntegerv(GL_MAX_IMAGE_SAMPLES, &maxImageSamples); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageSamples > 0; } @@ -307,8 +306,8 @@ namespace MGITest { case GL_TEXTURE_2D_ARRAY: case GL_TEXTURE_3D: case GL_TEXTURE_CUBE_MAP_ARRAY: - glTexSubImage3D(kind.target, 0, 0, 0, 0, kExtent, kExtent, kExtent, GL_RED_INTEGER, - GL_UNSIGNED_INT, texels.data()); + glTexSubImage3D(kind.target, 0, 0, 0, 0, kExtent, kExtent, kExtent, GL_RED_INTEGER, GL_UNSIGNED_INT, + texels.data()); break; default: break; @@ -521,26 +520,26 @@ namespace MGITest { // Exactly what the conformance case does with each of its eleven uniforms, but alone, so a // failure names the kind. -#define MGL_DEFINE_LOAD_CASE(CaseName, Kind) \ - TEST_F(ImageTargetKindScenario, Loads##CaseName) { \ - if (!Ready()) return; \ - if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ - if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ - GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ - "here and never asks for a multisample one"; \ - } \ - RunLoadCase(Kind); \ +#define MGL_DEFINE_LOAD_CASE(CaseName, Kind) \ + TEST_F(ImageTargetKindScenario, Loads##CaseName) { \ + if (!Ready()) return; \ + if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ + if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ + GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ + "here and never asks for a multisample one"; \ + } \ + RunLoadCase(Kind); \ } -#define MGL_DEFINE_STORE_CASE(CaseName, Kind) \ - TEST_F(ImageTargetKindScenario, Stores##CaseName) { \ - if (!Ready()) return; \ - if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ - if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ - GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ - "here and never asks for a multisample one"; \ - } \ - RunStoreCase(Kind); \ +#define MGL_DEFINE_STORE_CASE(CaseName, Kind) \ + TEST_F(ImageTargetKindScenario, Stores##CaseName) { \ + if (!Ready()) return; \ + if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ + if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ + GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ + "here and never asks for a multisample one"; \ + } \ + RunStoreCase(Kind); \ } MGL_DEFINE_LOAD_CASE(Texture1D, kKind1D) @@ -581,11 +580,11 @@ namespace MGITest { // the driver as a scalar against an iimage2D and took the whole fragment stage - and its six // other images - with it. -#define MGL_DEFINE_ATOMIC_CASE(CaseName, Kind) \ - TEST_F(ImageTargetKindScenario, AtomicallyAddsTo##CaseName) { \ - if (!Ready()) return; \ - if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ - RunAtomicCase(Kind); \ +#define MGL_DEFINE_ATOMIC_CASE(CaseName, Kind) \ + TEST_F(ImageTargetKindScenario, AtomicallyAddsTo##CaseName) { \ + if (!Ready()) return; \ + if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ + RunAtomicCase(Kind); \ } MGL_DEFINE_ATOMIC_CASE(Texture1D, kKind1D) @@ -600,15 +599,15 @@ namespace MGITest { // which the spec says is to be ignored outright rather than honoured or rejected - and // which DirectGLES used to forward to the ES driver as written. -#define MGL_DEFINE_LAYER_SWEEP_CASE(CaseName, Kind) \ - TEST_F(ImageTargetKindScenario, IgnoresLayerFor##CaseName) { \ - if (!Ready()) return; \ - if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ - if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ - GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ - "here and never asks for a multisample one"; \ - } \ - RunNonLayerableLayerSweepCase(Kind); \ +#define MGL_DEFINE_LAYER_SWEEP_CASE(CaseName, Kind) \ + TEST_F(ImageTargetKindScenario, IgnoresLayerFor##CaseName) { \ + if (!Ready()) return; \ + if (!ImagesAreUsable()) GTEST_SKIP() << "no compute image uniforms"; \ + if ((Kind).multisample && !MultisampleImagesAreUsable()) { \ + GTEST_SKIP() << "GL_MAX_IMAGE_SAMPLES is 0, so the conformance case substitutes a plain 2D image " \ + "here and never asks for a multisample one"; \ + } \ + RunNonLayerableLayerSweepCase(Kind); \ } MGL_DEFINE_LAYER_SWEEP_CASE(Texture2D, kKind2D) @@ -643,7 +642,7 @@ namespace MGITest { // at index five and drop it on exactly the devices most likely to get it wrong. A test // that quietly stops covering its own subject is worse than one that fails. const bool multisample = MultisampleImagesAreUsable(); - std::vector kinds{kKind1DArray, kKindBuffer, kKind2D, kKind1D, kKind2DArray, + std::vector kinds{kKind1DArray, kKindBuffer, kKind2D, kKind1D, kKind2DArray, kKind3D, kKindCube, kKindRect, kKindCubeArray}; if (multisample) { kinds.push_back(kKind2DMS); @@ -654,11 +653,9 @@ namespace MGITest { glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); GLint maxImageUnits = 0; glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); - while (glGetError() != GL_NO_ERROR) { - } - const std::size_t count = - std::min(kinds.size(), static_cast(std::max(0, std::min(maxComputeImageUniforms, - maxImageUnits)))); + while (glGetError() != GL_NO_ERROR) {} + const std::size_t count = std::min( + kinds.size(), static_cast(std::max(0, std::min(maxComputeImageUniforms, maxImageUnits)))); if (count == 0) GTEST_SKIP() << "no image units"; // Named, not silently dropped: `expected` is computed over whatever survives, so a // truncated run is self-consistently green and would otherwise never say what it stopped diff --git a/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp index 419ca7b0..59372f72 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/InPassClearScenario.cpp @@ -135,17 +135,18 @@ void main() { o0 = uColor; o1 = uColor; } glDisable(GL_BLEND); glDisable(GL_SCISSOR_TEST); - Quad(1.0f, 0.0f, 0.0f, 1.0f); // red everywhere + Quad(1.0f, 0.0f, 0.0f, 1.0f); // red everywhere glEnable(GL_SCISSOR_TEST); - glScissor(0, 0, kSize / 2, kSize); // left half + glScissor(0, 0, kSize / 2, kSize); // left half glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); // green, left half - glScissor(0, 0, kSize, kSize / 4); // bottom quarter - Quad(0.0f, 0.0f, 1.0f, 1.0f); // blue, bottom quarter (scissor clips the draw) + glClear(GL_COLOR_BUFFER_BIT); // green, left half + glScissor(0, 0, kSize, kSize / 4); // bottom quarter + Quad(0.0f, 0.0f, 1.0f, 1.0f); // blue, bottom quarter (scissor clips the draw) glDisable(GL_SCISSOR_TEST); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(Near(ReadAt(GL_COLOR_ATTACHMENT0, kSize / 4, kSize * 3 / 4), 0, 255, 0, 255)) << "left top: the clear"; + EXPECT_TRUE(Near(ReadAt(GL_COLOR_ATTACHMENT0, kSize / 4, kSize * 3 / 4), 0, 255, 0, 255)) + << "left top: the clear"; EXPECT_TRUE(Near(ReadAt(GL_COLOR_ATTACHMENT0, kSize * 3 / 4, kSize * 3 / 4), 255, 0, 0, 255)) << "right top: the first draw, outside the clear's scissor box"; EXPECT_TRUE(Near(ReadAt(GL_COLOR_ATTACHMENT0, kSize / 4, kSize / 8), 0, 0, 255, 255)) diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IndexedDrawFamilyScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IndexedDrawFamilyScenario.cpp index 64b9a013..3afa00f2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IndexedDrawFamilyScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IndexedDrawFamilyScenario.cpp @@ -74,11 +74,11 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // indexed draws). Vertices 8..13 and 14..19: the same two quads as six vertices each // (for the arrays draws). x spans [-0.9, -0.1] and [0.1, 0.9]; y spans [-0.8, 0.8]. constexpr Vertex kVertices[20] = { - {-0.9f, -0.8f}, {-0.1f, -0.8f}, {-0.1f, 0.8f}, {-0.9f, 0.8f}, // 0..3 left - {0.1f, -0.8f}, {0.9f, -0.8f}, {0.9f, 0.8f}, {0.1f, 0.8f}, // 4..7 right + {-0.9f, -0.8f}, {-0.1f, -0.8f}, {-0.1f, 0.8f}, {-0.9f, 0.8f}, // 0..3 left + {0.1f, -0.8f}, {0.9f, -0.8f}, {0.9f, 0.8f}, {0.1f, 0.8f}, // 4..7 right {-0.9f, -0.8f}, {-0.1f, -0.8f}, {-0.1f, 0.8f}, // 8..13 left, arrays - {-0.1f, 0.8f}, {-0.9f, 0.8f}, {-0.9f, -0.8f}, - {0.1f, -0.8f}, {0.9f, -0.8f}, {0.9f, 0.8f}, // 14..19 right, arrays + {-0.1f, 0.8f}, {-0.9f, 0.8f}, {-0.9f, -0.8f}, {0.1f, -0.8f}, + {0.9f, -0.8f}, {0.9f, 0.8f}, // 14..19 right, arrays {0.9f, 0.8f}, {0.1f, 0.8f}, {0.1f, -0.8f}, }; @@ -149,14 +149,14 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } void ExpectLeft(const Image& image, const char* color, const std::string& when) { const int w = image.Width(); const int h = image.Height(); - EXPECT_TRUE(RegionIsMostly(image, (w * 20) / 100, (w * 30) / 100, (h * 40) / 100, - (h * 60) / 100, color, 0.0, when + " (left quad)")); + EXPECT_TRUE(RegionIsMostly(image, (w * 20) / 100, (w * 30) / 100, (h * 40) / 100, (h * 60) / 100, color, + 0.0, when + " (left quad)")); } void ExpectRight(const Image& image, const char* color, const std::string& when) { const int w = image.Width(); const int h = image.Height(); - EXPECT_TRUE(RegionIsMostly(image, (w * 70) / 100, (w * 80) / 100, (h * 40) / 100, - (h * 60) / 100, color, 0.0, when + " (right quad)")); + EXPECT_TRUE(RegionIsMostly(image, (w * 70) / 100, (w * 80) / 100, (h * 40) / 100, (h * 60) / 100, color, + 0.0, when + " (right quad)")); } void ExpectCorner(const Image& image, const char* color, const std::string& when) { const int w = image.Width(); @@ -180,8 +180,7 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } TEST_F(IndexedDrawFamilyScenario, AnElementBufferDrawElementsOffsetSelectsTheRightQuad) { if (!Ready() || IsSkipped()) return; const Image image = ClearThenDrawThenRead([] { - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, - reinterpret_cast(kRightRunByteOffset)); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, reinterpret_cast(kRightRunByteOffset)); }); EXPECT_EQ(FirstGLError(), 0u); ExpectRight(image, "green", "glDrawElements at byte offset 12"); @@ -212,9 +211,8 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // the picture to the right quad. TEST_F(IndexedDrawFamilyScenario, DrawElementsBaseVertexMovesTheSameIndicesToTheOtherQuad) { if (!Ready() || IsSkipped()) return; - const Image image = ClearThenDrawThenRead([] { - glDrawElementsBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 4); - }); + const Image image = + ClearThenDrawThenRead([] { glDrawElementsBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 4); }); EXPECT_EQ(FirstGLError(), 0u); ExpectRight(image, "green", "glDrawElementsBaseVertex(basevertex = 4)"); ExpectLeft(image, "blue", "a base vertex of 4 must not paint the left quad"); @@ -237,17 +235,15 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } // second onto the right box, so an InstanceCount that did not cross paints one quad. TEST_F(IndexedDrawFamilyScenario, DrawElementsInstancedBaseVertexPaintsOneQuadPerInstance) { if (!Ready() || IsSkipped()) return; - const Image two = ClearThenDrawThenRead([] { - glDrawElementsInstancedBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 2, 0); - }); + const Image two = ClearThenDrawThenRead( + [] { glDrawElementsInstancedBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 2, 0); }); EXPECT_EQ(FirstGLError(), 0u); ExpectLeft(two, "green", "instance 0 of the left quad"); ExpectRight(two, "green", "instance 1 of the left quad, shifted by gl_InstanceID"); ExpectCorner(two, "blue", "instanced draw"); - const Image one = ClearThenDrawThenRead([] { - glDrawElementsInstancedBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 1, 0); - }); + const Image one = ClearThenDrawThenRead( + [] { glDrawElementsInstancedBaseVertex(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr, 1, 0); }); ExpectLeft(one, "green", "one instance"); ExpectRight(one, "blue", "one instance must not paint the right quad"); } @@ -292,9 +288,8 @@ void main() { oColor = vec4(0.0, 1.0, 0.0, 1.0); } glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_indirect); glBufferData(GL_DRAW_INDIRECT_BUFFER, GLsizeiptr(sizeof(commands)), commands, GL_STATIC_DRAW); ASSERT_EQ(FirstGLError(), 0u) << "building the indirect buffer"; - const Image both = ClearThenDrawThenRead([] { - glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, nullptr, 2, 0); - }); + const Image both = + ClearThenDrawThenRead([] { glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, nullptr, 2, 0); }); EXPECT_EQ(FirstGLError(), 0u); ExpectLeft(both, "green", "indirect command 0"); ExpectRight(both, "green", "indirect command 1 (firstIndex 6)"); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IntegerBorderColorScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IntegerBorderColorScenario.cpp index 136f92ba..e2e44c18 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IntegerBorderColorScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IntegerBorderColorScenario.cpp @@ -90,10 +90,11 @@ void main() if (!Ready()) return; // 2x2 RGBA32I. Integer textures are not filterable, so NEAREST is mandatory. - const std::int32_t texels[4][4] = {{kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, - {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, - {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, - {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}}; + const std::int32_t texels[4][4] = { + {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, + {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, + {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}, + {kInsideTexel[0], kInsideTexel[1], kInsideTexel[2], kInsideTexel[3]}}; glGenTextures(1, &m_sourceTexture); glBindTexture(GL_TEXTURE_2D, m_sourceTexture); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32I, 2, 2); @@ -198,8 +199,8 @@ void main() // A narrow-format source built on demand, for the clamp cases. Returns the texture, which // the caller owns until TearDown deletes it through m_narrowTexture. - void MakeNarrowSource(GLenum internalFormat, GLenum clientFormat, const void* texels, - const GLint* border, bool borderIsUnsigned) { + void MakeNarrowSource(GLenum internalFormat, GLenum clientFormat, const void* texels, const GLint* border, + bool borderIsUnsigned) { glGenTextures(1, &m_narrowTexture); glBindTexture(GL_TEXTURE_2D, m_narrowTexture); glTexStorage2D(GL_TEXTURE_2D, 1, internalFormat, 2, 2); @@ -222,10 +223,10 @@ void main() // The narrow sources are single-channel, so only component 0 carries anything, and the // sampler declaration has to match the format's signedness. std::vector RenderNarrowBorder(bool isUnsignedSampler) { - const std::string fragment = - std::string("#version 330 core\n\nuniform ") + (isUnsignedSampler ? "usampler2D" : "isampler2D") + - " smp;\nuniform vec2 uCoord;\n\nout int out_color;\n\nvoid main()\n{\n" - " out_color = int(texture(smp, uCoord).x);\n}\n"; + const std::string fragment = std::string("#version 330 core\n\nuniform ") + + (isUnsignedSampler ? "usampler2D" : "isampler2D") + + " smp;\nuniform vec2 uCoord;\n\nout int out_color;\n\nvoid main()\n{\n" + " out_color = int(texture(smp, uCoord).x);\n}\n"; std::string error; const unsigned int program = CompileProgram(kVertexSource, fragment.c_str(), &error); if (program == 0) { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/InvalidImageUnitScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/InvalidImageUnitScenario.cpp index d2f9754a..23d9aa6d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/InvalidImageUnitScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/InvalidImageUnitScenario.cpp @@ -152,11 +152,11 @@ void main() { } enum class Invalidity { - MissingMipLevel, // mutable, level 0 only, MAX_LEVEL 7, bound at level 2 (the CTS shape) - PastImmutableLevels, // glTexStorage2D with 2 levels, bound at level 2 - PastArrayLayers, // 2D array of 2 slices, bound non-layered at layer 2 - NoStorage, // a named texture that was never given an image - PastViewWindow, // a 1-level view of a 3-level owner, bound at view level 1 + MissingMipLevel, // mutable, level 0 only, MAX_LEVEL 7, bound at level 2 (the CTS shape) + PastImmutableLevels, // glTexStorage2D with 2 levels, bound at level 2 + PastArrayLayers, // 2D array of 2 slices, bound non-layered at layer 2 + NoStorage, // a named texture that was never given an image + PastViewWindow, // a 1-level view of a 3-level owner, bound at view level 1 }; class InvalidImageUnitScenario : public ScenarioTest { @@ -201,8 +201,7 @@ void main() { static bool LimitIsAtLeast(GLenum limit, GLint wanted) { GLint value = 0; glGetIntegerv(limit, &value); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return value >= wanted; } @@ -230,8 +229,8 @@ void main() { GLint bindLevel = 0; GLint bindLayer = 0; GLenum readTarget = GL_TEXTURE_2D; - GLuint readTexture = 0; // the texture whose texels are checked (a view's owner) - std::vector definedLevels; // levels of readTexture that hold `magic` + GLuint readTexture = 0; // the texture whose texels are checked (a view's owner) + std::vector definedLevels; // levels of readTexture that hold `magic` int layers = 1; GLubyte magic = 0; }; @@ -244,8 +243,7 @@ void main() { made.texture = NewTexture(); glBindTexture(GL_TEXTURE_2D, made.texture); const auto texels = Pattern(kEdge, kEdge, 1, magic); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kEdge, kEdge, 0, GL_RGBA, GL_UNSIGNED_BYTE, - texels.data()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kEdge, kEdge, 0, GL_RGBA, GL_UNSIGNED_BYTE, texels.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); @@ -372,8 +370,8 @@ void main() { glBindImageTexture(1, completeSource, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); glBindImageTexture(2, invalidDestination.texture, invalidDestination.bindLevel, GL_FALSE, invalidDestination.bindLayer, GL_READ_WRITE, GL_RGBA8); - glBindImageTexture(3, invalidSource.texture, invalidSource.bindLevel, GL_FALSE, - invalidSource.bindLayer, GL_READ_WRITE, GL_RGBA8); + glBindImageTexture(3, invalidSource.texture, invalidSource.bindLevel, GL_FALSE, invalidSource.bindLayer, + GL_READ_WRITE, GL_RGBA8); // A binding that is invalid at ACCESS time is still a legal bind (8.26 validates // only the unit, the level's sign, the layer's sign, the access and the format). ASSERT_EQ(FirstGLError(), 0u) << "glBindImageTexture raised a GL error (" << what << ")"; @@ -421,8 +419,8 @@ void main() { } if (nonZero != 0) { const std::size_t at = static_cast((firstY * kEdge + firstX) * 4); - ADD_FAILURE() << nonZero << " of " << kEdge * kEdge << " texels loaded through the invalid " - << what << " unit were not zero; first at (" << firstX << ", " << firstY << ") = (" + ADD_FAILURE() << nonZero << " of " << kEdge * kEdge << " texels loaded through the invalid " << what + << " unit were not zero; first at (" << firstX << ", " << firstY << ") = (" << int(loaded[at]) << ", " << int(loaded[at + 1]) << ", " << int(loaded[at + 2]) << ", " << int(loaded[at + 3]) << ") (R = 17 means the pass never ran)"; } @@ -433,12 +431,13 @@ void main() { const auto expected = Pattern(edge, edge, invalidDestination.layers, invalidDestination.magic); const auto actual = ReadLevel(invalidDestination.readTarget, invalidDestination.readTexture, level, invalidDestination.layers); - EXPECT_EQ(actual, expected) << "a store through the invalid " << what - << " unit modified level " << level << " of the texture it names"; + EXPECT_EQ(actual, expected) << "a store through the invalid " << what << " unit modified level " + << level << " of the texture it names"; } // (3) The valid half of the same pass is intact: the complete source was only read. - EXPECT_EQ(ReadLevel(GL_TEXTURE_2D, completeSource, 0, 1), Pattern(kEdge, kEdge, 1, kCompleteSourceMagic)) + EXPECT_EQ(ReadLevel(GL_TEXTURE_2D, completeSource, 0, 1), + Pattern(kEdge, kEdge, 1, kCompleteSourceMagic)) << "the complete source changed (" << what << ")"; EXPECT_EQ(FirstGLError(), 0u) << "the readbacks raised a GL error (" << what << ")"; } @@ -491,8 +490,9 @@ void main() { // the units no longer alias - but A's load of A's own store in the same pass is // not: that is the CONTRACT-P7 §12 residual, and here it is what PROVES the knob // reached the server (the null-descriptor arm would read zero there too). - const bool privatePlaceholderArm = m_wire && Gl().BackendName() == "DirectVulkan" && - SplitLane::MarkerIsOne("MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER"); + const bool privatePlaceholderArm = + m_wire && Gl().BackendName() == "DirectVulkan" && + SplitLane::MarkerIsOne("MGITEST_MAGMA_FORCE_PRIVATE_IMAGE_PLACEHOLDER"); const GLuint loadedThroughB = MakeComplete2D(kCompleteDestinationMagic); const GLuint loadedThroughA = MakeComplete2D(kCompleteSourceMagic); InvalidTexture invalidA{}; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IoBlockNameCollisionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IoBlockNameCollisionScenario.cpp index 050a5d8e..0d301d43 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IoBlockNameCollisionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IoBlockNameCollisionScenario.cpp @@ -230,17 +230,15 @@ void main() glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel); GLint maxGeometryOutputVertices = 0; glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxGeometryOutputVertices); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxTessGenLevel >= 1 && maxGeometryOutputVertices >= 4; } GLuint BuildPipeline(const char* tessEvalSource, const char* geometrySource) { - const GLenum stages[] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, - GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER, - GL_FRAGMENT_SHADER}; - const char* const sources[] = {kVertexSource, kTessControlSource, tessEvalSource, - geometrySource, kFragmentSource}; + const GLenum stages[] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, + GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; + const char* const sources[] = {kVertexSource, kTessControlSource, tessEvalSource, geometrySource, + kFragmentSource}; GLuint shaders[5] = {0, 0, 0, 0, 0}; bool ok = true; @@ -297,16 +295,13 @@ void main() return pixel; } - static bool IsGreen(const Rgba8& pixel) { - return pixel.r < 64 && pixel.g > 192 && pixel.b < 64; - } + static bool IsGreen(const Rgba8& pixel) { return pixel.r < 64 && pixel.g > 192 && pixel.b < 64; } const std::string& BuildLog() const { return m_buildLog; } static GLenum FirstGLError() { const GLenum first = glGetError(); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return first; } @@ -346,8 +341,7 @@ void main() const GLuint program = BuildPipeline(kDistinctTessEvalSource, kDistinctGeometrySource); if (program == 0) { GTEST_SKIP() << "this stack cannot build a five-stage tessellation+geometry program on " - << Gl().BackendName() << ", so there is no block to carry through: " - << BuildLog(); + << Gl().BackendName() << ", so there is no block to carry through: " << BuildLog(); } const Rgba8 centre = DrawAndReadCentre(program); @@ -363,26 +357,25 @@ void main() // DISTINCT-name build succeeds does a failure of the colliding one mean something. if (BuildPipeline(kDistinctTessEvalSource, kDistinctGeometrySource) == 0) { GTEST_SKIP() << "this stack cannot build a five-stage tessellation+geometry program on " - << Gl().BackendName() << ", so there is no block to carry through: " - << BuildLog(); + << Gl().BackendName() << ", so there is no block to carry through: " << BuildLog(); } // Legal desktop GLSL: input and output block names live in separate namespaces, so // the evaluation stage below declares TcsData twice and must still compile. The // control above having built is what makes this assertion about the NAME. const GLuint program = BuildPipeline(kCollidingTessEvalSource, kCollidingGeometrySource); - ASSERT_NE(program, 0u) - << "an interface block name reused across the two directions of one stage is legal " - "desktop GLSL, but the program did not build: " - << BuildLog(); + ASSERT_NE(program, 0u) << "an interface block name reused across the two directions of one stage is legal " + "desktop GLSL, but the program did not build: " + << BuildLog(); const Rgba8 centre = DrawAndReadCentre(program); EXPECT_EQ(FirstGLError(), 0u); EXPECT_TRUE(IsGreen(centre)) << "the payload did not survive the stage that names its input and output block " "the same: " - << centre << " (blue: nothing drew; red: the plain varying was lost too; black: " - "the block arrived empty)"; + << centre + << " (blue: nothing drew; red: the plain varying was lost too; black: " + "the block arrived empty)"; } } // namespace diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPFirstReductionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPFirstReductionScenario.cpp index 714a4846..6bddf586 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPFirstReductionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPFirstReductionScenario.cpp @@ -108,7 +108,8 @@ namespace MGITest { bool DrainGlErrors() { bool hadError = false; - while (glGetError() != GL_NO_ERROR) hadError = true; + while (glGetError() != GL_NO_ERROR) + hadError = true; return hadError; } @@ -116,7 +117,8 @@ namespace MGITest { GLint extensionCount = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &extensionCount); for (GLint i = 0; i < extensionCount; ++i) { - const auto* extension = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); + const auto* extension = + reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); if (extension != nullptr && std::string(extension) == wanted) return true; } return false; @@ -140,19 +142,15 @@ namespace MGITest { // the pack itself replays through the MOBILEGL_ITRP_FIX scratch patch, which // this probe deliberately does not model. The width only gates the domain; // lane placement and group counts still come from observed values alone. - bool SubgroupWidthInSourceDomain() const { - return subgroupSize >= 16 && subgroupSize <= 256; - } + bool SubgroupWidthInSourceDomain() const { return subgroupSize >= 16 && subgroupSize <= 256; } bool SupportsProbe() const { const auto stages = static_cast(supportedStages); const auto features = static_cast(supportedFeatures); - return !queryHadError && subgroupExtension && - (stages & GL_COMPUTE_SHADER_BIT) != 0 && + return !queryHadError && subgroupExtension && (stages & GL_COMPUTE_SHADER_BIT) != 0 && (features & (GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR)) == (GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR) && - SubgroupWidthInSourceDomain() && - maxComputeStorageBlocks >= 2 && maxStorageBindings >= 2 && + SubgroupWidthInSourceDomain() && maxComputeStorageBlocks >= 2 && maxStorageBindings >= 2 && maxWorkGroupInvocations >= static_cast(kInvocationCount) && maxWorkGroupSize[0] >= 32 && maxWorkGroupSize[1] >= 16 && maxWorkGroupSize[2] >= 1; } @@ -172,10 +170,9 @@ namespace MGITest { missing.emplace_back("basic|arithmetic in GL_SUBGROUP_SUPPORTED_FEATURES_KHR"); } if (!SubgroupWidthInSourceDomain()) { - missing.emplace_back( - "GL_SUBGROUP_SIZE_KHR in [16, 256] (iterationRP's source contract needs " - "gl_NumSubgroups in [2, 32] for 512 invocations; width " + - std::to_string(subgroupSize) + " is outside the fixture's domain)"); + missing.emplace_back("GL_SUBGROUP_SIZE_KHR in [16, 256] (iterationRP's source contract needs " + "gl_NumSubgroups in [2, 32] for 512 invocations; width " + + std::to_string(subgroupSize) + " is outside the fixture's domain)"); } if (maxComputeStorageBlocks < 2 || maxStorageBindings < 2) { missing.emplace_back("two compute SSBO bindings"); @@ -213,18 +210,15 @@ namespace MGITest { void PrintMetadata(const CapabilityInfo& info, std::ostream& output) { output << "IterationRPFirstReductionScenario metadata: " - << "GL_SUBGROUP_SIZE_KHR=" << info.subgroupSize - << ", GL_SUBGROUP_SUPPORTED_STAGES_KHR=0x" << std::hex - << static_cast(info.supportedStages) - << ", GL_SUBGROUP_SUPPORTED_FEATURES_KHR=0x" - << static_cast(info.supportedFeatures) << std::dec - << ", subgroupExtension=" << info.subgroupExtension + << "GL_SUBGROUP_SIZE_KHR=" << info.subgroupSize << ", GL_SUBGROUP_SUPPORTED_STAGES_KHR=0x" + << std::hex << static_cast(info.supportedStages) + << ", GL_SUBGROUP_SUPPORTED_FEATURES_KHR=0x" << static_cast(info.supportedFeatures) + << std::dec << ", subgroupExtension=" << info.subgroupExtension << ", GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS=" << info.maxComputeStorageBlocks << ", GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS=" << info.maxStorageBindings << ", GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS=" << info.maxWorkGroupInvocations - << ", GL_MAX_COMPUTE_WORK_GROUP_SIZE=" << info.maxWorkGroupSize[0] << 'x' - << info.maxWorkGroupSize[1] << 'x' << info.maxWorkGroupSize[2] - << ", queryHadError=" << info.queryHadError << '\n'; + << ", GL_MAX_COMPUTE_WORK_GROUP_SIZE=" << info.maxWorkGroupSize[0] << 'x' << info.maxWorkGroupSize[1] + << 'x' << info.maxWorkGroupSize[2] << ", queryHadError=" << info.queryHadError << '\n'; } bool DumpRequested() { @@ -439,7 +433,8 @@ layout(std430, binding = 0) readonly buffer Input { << ",0)"; return Failure("record identity", message.str()); } - const float expectedInput = ExpectedInput(mode, static_cast(localIndex), sampledLuminance); + const float expectedInput = + ExpectedInput(mode, static_cast(localIndex), sampledLuminance); const float actualInput = output.reduction[slot].x; if (!SameBits(actualInput, expectedInput)) { std::ostringstream message; @@ -480,8 +475,8 @@ layout(std430, binding = 0) readonly buffer Input { } if (subgroup.z >= reportedNumSubgroups) { std::ostringstream message; - message << "localIndex " << localIndex << " reported subgroupID=" << subgroup.z - << " outside [0, " << (reportedNumSubgroups - 1u) << ']'; + message << "localIndex " << localIndex << " reported subgroupID=" << subgroup.z << " outside [0, " + << (reportedNumSubgroups - 1u) << ']'; return Failure("observed topology", message.str()); } if (subgroup.w >= subgroup.x) { @@ -495,8 +490,8 @@ layout(std430, binding = 0) readonly buffer Input { for (std::uint32_t subgroupID = 0; subgroupID < reportedNumSubgroups; ++subgroupID) { if (subgroupSlots[subgroupID].empty()) { std::ostringstream message; - message << "reported gl_NumSubgroups=" << reportedNumSubgroups - << " but subgroupID " << subgroupID << " has no recorded members"; + message << "reported gl_NumSubgroups=" << reportedNumSubgroups << " but subgroupID " << subgroupID + << " has no recorded members"; return Failure("observed topology", message.str()); } auto& members = subgroupSlots[subgroupID]; @@ -604,8 +599,7 @@ layout(std430, binding = 0) readonly buffer Input { std::array mergedPrefix = nativePrefix; std::vector cache = nativeSubtotal; std::uint32_t loopLength = std::bit_width(reportedNumSubgroups) - 1u; - loopLength += - static_cast(reportedNumSubgroups - (1u << (loopLength - 1u)) > 0u); + loopLength += static_cast(reportedNumSubgroups - (1u << (loopLength - 1u)) > 0u); for (std::uint32_t scanStage = 0u; scanStage < loopLength; ++scanStage) { std::vector cacheAfterStage = cache; for (std::uint32_t subgroupID = 0; subgroupID < reportedNumSubgroups; ++subgroupID) { @@ -652,8 +646,8 @@ layout(std430, binding = 0) readonly buffer Input { const float actualAverage = output.finalAverage[slotForLocal[localIndex]]; if (!SameBits(actualAverage, firstAverage)) { std::ostringstream message; - message << "finalAverage differs: localIndex 0 has " << FormatFloat(firstAverage) - << ", localIndex " << localIndex << " has " << FormatFloat(actualAverage); + message << "finalAverage differs: localIndex 0 has " << FormatFloat(firstAverage) << ", localIndex " + << localIndex << " has " << FormatFloat(actualAverage); return Failure("final average", message.str()); } } @@ -667,15 +661,14 @@ layout(std430, binding = 0) readonly buffer Input { } const std::size_t index511Slot = slotForLocal[kInvocationCount - 1u]; const UVec4& index511Subgroup = output.subgroup[index511Slot]; - ownerResult.index511IsSourceLastLaneWriter = - index511Subgroup.w == index511Subgroup.x - 1u; - ownerResult.index511IsHighestSubgroupMember = - index511Subgroup.z == ownerResult.highestObservedSubgroup; + ownerResult.index511IsSourceLastLaneWriter = index511Subgroup.w == index511Subgroup.x - 1u; + ownerResult.index511IsHighestSubgroupMember = index511Subgroup.z == ownerResult.highestObservedSubgroup; if (!ownerResult.index511IsSourceLastLaneWriter || !ownerResult.index511IsHighestSubgroupMember) { std::ostringstream message; message << "iterationRP topology incompatibility: localIndex 511 is sourceLastLaneWriter=" - << ownerResult.index511IsSourceLastLaneWriter << ", highestSubgroupMember=" - << ownerResult.index511IsHighestSubgroupMember << " (subgroupID=" << index511Subgroup.z + << ownerResult.index511IsSourceLastLaneWriter + << ", highestSubgroupMember=" << ownerResult.index511IsHighestSubgroupMember + << " (subgroupID=" << index511Subgroup.z << ", highest observed subgroupID=" << ownerResult.highestObservedSubgroup << ')'; ownerResult.ok = false; ownerResult.phase = "final average"; @@ -684,9 +677,11 @@ layout(std430, binding = 0) readonly buffer Input { } float total = 0.0f; - for (const float subtotal : nativeSubtotal) total += subtotal; + for (const float subtotal : nativeSubtotal) + total += subtotal; float sampledExpectedTotal = 0.0f; - for (std::size_t i = 0; i < kInvocationCount; ++i) sampledExpectedTotal += sampledLuminance; + for (std::size_t i = 0; i < kInvocationCount; ++i) + sampledExpectedTotal += sampledLuminance; const float expectedTotal = mode == InputMode::IndexedSsbo ? 131328.0f : sampledExpectedTotal; if (!SameBits(total, expectedTotal) || !SameBits(mergedPrefix[index511Slot], expectedTotal)) { std::ostringstream message; @@ -711,8 +706,8 @@ layout(std430, binding = 0) readonly buffer Input { return ownerResult; } - void DumpProbe(const ProbeOutput& output, const CapabilityInfo& capabilities, const ValidationResult& validation, - bool includeScanStages) { + void DumpProbe(const ProbeOutput& output, const CapabilityInfo& capabilities, + const ValidationResult& validation, bool includeScanStages) { PrintMetadata(capabilities, std::cout); if (validation.ok) { std::cout << "IterationRPFirstReductionScenario firstFailure=none\n"; @@ -826,7 +821,8 @@ layout(std430, binding = 0) readonly buffer Input { if (mode == InputMode::IndexedSsbo) { std::array values{}; - for (std::size_t i = 0; i < values.size(); ++i) values[i] = static_cast(i + 1u); + for (std::size_t i = 0; i < values.size(); ++i) + values[i] = static_cast(i + 1u); glGenBuffers(1, &m_inputBuffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_inputBuffer); glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(values), values.data(), GL_STATIC_DRAW); @@ -888,9 +884,9 @@ layout(std430, binding = 0) readonly buffer Input { RecordProperty("iterationrp_highest_observed_subgroup", std::to_string(validation.highestObservedSubgroup)); std::cout << "IterationRPFirstReductionScenario owner: localIndex511 sourceLastLaneWriter=" - << validation.index511IsSourceLastLaneWriter << ", highestSubgroupMember=" - << validation.index511IsHighestSubgroupMember << ", highestObservedSubgroup=" - << validation.highestObservedSubgroup << '\n'; + << validation.index511IsSourceLastLaneWriter + << ", highestSubgroupMember=" << validation.index511IsHighestSubgroupMember + << ", highestObservedSubgroup=" << validation.highestObservedSubgroup << '\n'; } if (!validation.ok || DumpRequested()) { DumpProbe(output, m_capabilities, validation, validation.scanStageMismatch || DumpRequested()); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPProgram203Scenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPProgram203Scenario.cpp index d934b508..adcf82b2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPProgram203Scenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPProgram203Scenario.cpp @@ -37,10 +37,8 @@ namespace MGITest { constexpr int kSceneHeight = 480; constexpr int kPixelDataWidth = 512; constexpr int kPixelDataHeight = 513; - constexpr std::size_t kSceneTexelCount = - static_cast(kSceneWidth) * kSceneHeight; - constexpr std::size_t kPixelDataTexelCount = - static_cast(kPixelDataWidth) * kPixelDataHeight; + constexpr std::size_t kSceneTexelCount = static_cast(kSceneWidth) * kSceneHeight; + constexpr std::size_t kPixelDataTexelCount = static_cast(kPixelDataWidth) * kPixelDataHeight; struct Rgba32f { float r, g, b, a; @@ -200,8 +198,8 @@ void main() { const float dy = static_cast(y - 4); base += 0.65f * std::exp(-(dx * dx + dy * dy) / 18.0f); if (((x + y * 17) % 113) == 0) base += 1.75f; - texels[static_cast(y) * kSceneWidth + x] = - {base * 0.83f, base * 1.07f, base * 1.31f, 1.0f}; + texels[static_cast(y) * kSceneWidth + x] = {base * 0.83f, base * 1.07f, base * 1.31f, + 1.0f}; } } return texels; @@ -222,8 +220,7 @@ void main() { if (halfExponent < -10) return static_cast(sign); mantissa |= 0x800000u; const unsigned shift = static_cast(14 - halfExponent); - const std::uint32_t rounded = mantissa + ((1u << (shift - 1u)) - 1u) + - ((mantissa >> shift) & 1u); + const std::uint32_t rounded = mantissa + ((1u << (shift - 1u)) - 1u) + ((mantissa >> shift) & 1u); return static_cast(sign | (rounded >> shift)); } mantissa += 0xfffu + ((mantissa >> 13u) & 1u); @@ -251,8 +248,7 @@ void main() { return golden; } - GLuint MakeTexture(GLenum internalFormat, GLenum format, GLenum type, int width, int height, - const void* data) { + GLuint MakeTexture(GLenum internalFormat, GLenum format, GLenum type, int width, int height, const void* data) { GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); @@ -260,8 +256,7 @@ void main() { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), width, height, 0, format, - type, data); + glTexImage2D(GL_TEXTURE_2D, 0, static_cast(internalFormat), width, height, 0, format, type, data); return texture; } @@ -277,8 +272,7 @@ void main() { glUniform1i(glGetUniformLocation(program, "img_pixelData2D"), 0); glUniform1i(glGetUniformLocation(program, "frameCounter"), 100); glUniform1f(glGetUniformLocation(program, "frameTime"), 1.0f / 60.0f); - glUniform1f(glGetUniformLocation(program, "aspectRatio"), - static_cast(kSceneWidth) / kSceneHeight); + glUniform1f(glGetUniformLocation(program, "aspectRatio"), static_cast(kSceneWidth) / kSceneHeight); glUniform2f(glGetUniformLocation(program, "pixelSize"), 1.0f / kSceneWidth, 1.0f / kSceneHeight); glUniform1f(glGetUniformLocation(program, "nightVision"), 0.23f); glUniform1f(glGetUniformLocation(program, "darknessLightFactor"), 0.08f); @@ -305,8 +299,7 @@ void main() { glGetIntegerv(GL_SUBGROUP_SUPPORTED_STAGES_KHR, &stages); glGetIntegerv(GL_SUBGROUP_SUPPORTED_FEATURES_KHR, &features); glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &invocations); - const GLbitfield required = - GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR; + const GLbitfield required = GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR; if ((static_cast(stages) & GL_COMPUTE_SHADER_BIT) == 0 || (static_cast(features) & required) != required || invocations < 512) { GTEST_SKIP() << "requires 512-invocation basic+arithmetic compute subgroups"; @@ -320,8 +313,7 @@ void main() { const std::vector pixelData = MakePixelDataInput(); m_scene = MakeTexture(GL_RGBA16F, GL_RGBA, GL_FLOAT, kSceneWidth, kSceneHeight, scene.data()); m_originalOutput = - MakeTexture(GL_RG16F, GL_RG, GL_HALF_FLOAT, kPixelDataWidth, kPixelDataHeight, - pixelData.data()); + MakeTexture(GL_RG16F, GL_RG, GL_HALF_FLOAT, kPixelDataWidth, kPixelDataHeight, pixelData.data()); ASSERT_EQ(FirstGLError(), static_cast(GL_NO_ERROR)); } @@ -360,19 +352,18 @@ void main() { RecordProperty("program203_output_height", kPixelDataHeight); RecordProperty("program203_compared_texels", static_cast(actual.size())); RecordProperty("program203_mismatch_texels", static_cast(mismatchTexels)); - std::cout << "IterationRPProgram203Scenario complete-output actualExposureBits=(0x" << std::hex - << actual[0].r << ", 0x" << actual[0].g << ") goldenExposureBits=(0x" << expected[0].r - << ", 0x" << expected[0].g << std::dec << ") mismatches=" << mismatchTexels << '/' - << actual.size() << '\n'; + std::cout << "IterationRPProgram203Scenario complete-output actualExposureBits=(0x" << std::hex << actual[0].r + << ", 0x" << actual[0].g << ") goldenExposureBits=(0x" << expected[0].r << ", 0x" << expected[0].g + << std::dec << ") mismatches=" << mismatchTexels << '/' << actual.size() << '\n'; if (firstMismatch != actual.size()) { const std::size_t x = firstMismatch % kPixelDataWidth; const std::size_t y = firstMismatch / kPixelDataWidth; - ADD_FAILURE() << "complete Program 203 output differs at " << x << ',' << y - << ": actual half bits=(0x" << std::hex << actual[firstMismatch].r << ", 0x" - << actual[firstMismatch].g << ") golden half bits=(0x" << expected[firstMismatch].r - << ", 0x" << expected[firstMismatch].g << std::dec << "); mismatched " - << mismatchTexels << " of " << actual.size() << " texels"; + ADD_FAILURE() << "complete Program 203 output differs at " << x << ',' << y << ": actual half bits=(0x" + << std::hex << actual[firstMismatch].r << ", 0x" << actual[firstMismatch].g + << ") golden half bits=(0x" << expected[firstMismatch].r << ", 0x" + << expected[firstMismatch].g << std::dec << "); mismatched " << mismatchTexels << " of " + << actual.size() << " texels"; } EXPECT_EQ(mismatchTexels, 0u); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPScratchFixScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPScratchFixScenario.cpp index c6f5b23e..f3175ccd 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/IterationRPScratchFixScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/IterationRPScratchFixScenario.cpp @@ -273,8 +273,7 @@ void main() { // average is built from; without FixIterationRPSubgroupScratchPass an 8-lane // device writes prefixSumCache[32..63] out of bounds and this comparison fails. EXPECT_EQ(block.total, kExpectedTotal) - << "workgroup reduction produced " << block.total << " with gl_NumSubgroups=" - << block.numSubgroups; + << "workgroup reduction produced " << block.total << " with gl_NumSubgroups=" << block.numSubgroups; } // The pack's second instance of the same bug, and the one that kept the CI @@ -296,7 +295,6 @@ void main() { // Without the patch an 8-lane device writes prefixSumCache[64..127] out of // bounds and this comparison fails. EXPECT_EQ(block.total, kWideExpectedTotal) - << "workgroup reduction produced " << block.total << " with gl_NumSubgroups=" - << block.numSubgroups; + << "workgroup reduction produced " << block.total << " with gl_NumSubgroups=" << block.numSubgroups; } } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/LargeArenaAdoptionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/LargeArenaAdoptionScenario.cpp index ba4ee71e..e75284ea 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/LargeArenaAdoptionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/LargeArenaAdoptionScenario.cpp @@ -100,8 +100,8 @@ void main() { word = 0xC0FFEEu; } // which also set MOBILEGL_IPC_ADOPT_TIER=0; a harness marker, never read by the library. constexpr const char* kAdoptTierRefusalMarker = "MGITEST_ADOPT_TIER_REFUSAL"; // P11 B2: set by the shared-segment `.AdoptTier0.` / `.AdoptT0Disallowed.` entries to the reason word the - // server gives for not granting T0 ("no AHardwareBuffer" on a host, "in process" where the backend cannot hand an - // in-process client its stores, "disallowed" with its allow switch off). + // server gives for not granting T0 ("no AHardwareBuffer" on a host, "in process" where the backend cannot hand + // an in-process client its stores, "disallowed" with its allow switch off). constexpr const char* kAdoptT0FallbackMarker = "MGITEST_ADOPT_T0_FALLBACK"; // Draws issued against the arena inside the counted window. One definition, many draws: // "one per definition" (1) and "one per draw" (kDrawsInTheWindow) have to be different @@ -148,8 +148,7 @@ void main() { word = 0xC0FFEEu; } void ConfigureVertexArray(GLuint vao) { glBindVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, m_arena); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), - reinterpret_cast(kVertexOffset)); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(kVertexOffset)); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(kVertexOffset + 2 * sizeof(float))); glEnableVertexAttribArray(0); @@ -215,8 +214,8 @@ void main() { word = 0xC0FFEEu; } void UploadQuad(float r, float g, float b) { const auto vertices = QuadVertices(r, g, b); glBindBuffer(GL_ARRAY_BUFFER, m_arena); - glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, - GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); + glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, GLsizeiptr(vertices.size() * sizeof(Vertex)), + vertices.data()); } void DrawQuad(GLuint vao = 0) { @@ -264,8 +263,7 @@ void main() { word = 0xC0FFEEu; } std::array CenterPixel() { std::array px = {0, 0, 0, 0}; - glReadPixels(Gl().Width() / 2, Gl().Height() / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - px.data()); + glReadPixels(Gl().Width() / 2, Gl().Height() / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px.data()); return px; } @@ -278,8 +276,7 @@ void main() { word = 0xC0FFEEu; } ASSERT_TRUE(PeekBufferIsAdoptedPersistentMap(m_arena, &adopted)) << "no frontend BufferObject behind the arena " << when; const std::string landed = adopted ? "adopted" : "emulated"; - RecordProperty(std::string("arena_arm_") + (m_armPeeks++ == 0 ? "defined" : "used"), - landed.c_str()); + RecordProperty(std::string("arena_arm_") + (m_armPeeks++ == 0 ? "defined" : "used"), landed.c_str()); EXPECT_EQ(declared, landed) << "the lane declared the " << declared << " arm and the " << (kArenaBytes >> 20) << " MiB arena is " << landed << " " << when @@ -380,10 +377,14 @@ void main() { word = 0xC0FFEEu; } EXPECT_EQ(FirstGLError(), 0u); constexpr std::array sizes = { - kArenaBytes, kArenaBytes + 4096, kArenaBytes - 4096, + kArenaBytes, + kArenaBytes + 4096, + kArenaBytes - 4096, }; constexpr std::array, 3> colors = {{ - {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}, {1.f, 0.f, 0.f}, + {0.f, 1.f, 0.f}, + {0.f, 0.f, 1.f}, + {1.f, 0.f, 0.f}, }}; for (std::size_t i = 0; i < sizes.size(); ++i) { SCOPED_TRACE(sizes[i]); @@ -454,8 +455,7 @@ void main() { word = 0xC0FFEEu; } const auto green = QuadVertices(0.f, 1.f, 0.f); vertices.insert(vertices.end(), green.begin(), green.end()); glBindBuffer(GL_ARRAY_BUFFER, m_arena); - glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, - GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); + glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); GLuint indices = 0; glGenBuffers(1, &indices); @@ -465,7 +465,10 @@ void main() { word = 0xC0FFEEu; } // changes. The small final store also exercises returning to shadow storage. glBindBuffer(GL_COPY_WRITE_BUFFER, indices); constexpr std::array sizes = { - kArenaBytes, kArenaBytes, kArenaBytes + 4096, 4096, + kArenaBytes, + kArenaBytes, + kArenaBytes + 4096, + 4096, }; for (std::size_t i = 0; i < sizes.size(); ++i) { SCOPED_TRACE(sizes[i]); @@ -497,11 +500,9 @@ void main() { word = 0xC0FFEEu; } const auto vertices = QuadVertices(0.25f, 0.5f, 0.75f); glBindBuffer(GL_ARRAY_BUFFER, m_arena); - glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, - GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); + glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); std::vector read(vertices.size()); - glGetBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, - GLsizeiptr(read.size() * sizeof(Vertex)), read.data()); + glGetBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, GLsizeiptr(read.size() * sizeof(Vertex)), read.data()); EXPECT_EQ(FirstGLError(), 0u); EXPECT_EQ(0, std::memcmp(read.data(), vertices.data(), read.size() * sizeof(Vertex))) << "GetBufferSubData of the adopted arena returned different bytes than the SubData wrote"; @@ -539,8 +540,7 @@ void main() { word = 0xC0FFEEu; } unsigned int marker = 0; glGetBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(marker), &marker); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_EQ(marker, 0xC0FFEEu) - << "the compute write into the adopted arena did not reach the CPU readback"; + EXPECT_EQ(marker, 0xC0FFEEu) << "the compute write into the adopted arena did not reach the CPU readback"; } // G10, the per-adoption half: ONE storage definition of an arena costs ONE map_persistent @@ -628,7 +628,8 @@ void main() { word = 0xC0FFEEu; } "emulated)"; } if (!PersistentMapPeekAvailable()) { - GTEST_SKIP() << "this lane declares the " << declared << " arm but Harness/PersistentMapPeek " + GTEST_SKIP() << "this lane declares the " << declared + << " arm but Harness/PersistentMapPeek " "cannot look in this build (on Android this module links the shipping " "library, built -fvisibility=hidden). 'Could not look' is not 'it was " << declared << "'."; @@ -675,7 +676,8 @@ void main() { word = 0xC0FFEEu; } const char* tier = std::getenv(kAdoptTierRefusalMarker); if (tier == nullptr || tier[0] == '\0') { GTEST_SKIP() << "runs only in the `.AdoptTier0.` tcp entries, which set " - "MOBILEGL_IPC_ADOPT_TIER=0 and " << kAdoptTierRefusalMarker << "=T0"; + "MOBILEGL_IPC_ADOPT_TIER=0 and " + << kAdoptTierRefusalMarker << "=T0"; } if (PipeStatsWindow::LibraryLogPath().empty()) { GTEST_SKIP() << "the lane configured no MOBILEGL_LOG_FILE_PATH, and the client's log is " @@ -692,10 +694,12 @@ void main() { word = 0xC0FFEEu; } const std::string log = PipeStatsWindow::ReadWholeFile(PipeStatsWindow::LibraryLogPath()); const std::string line = std::string("Refuse{AdoptTierOnStream, \"") + tier + "\"}"; std::size_t count = 0; - for (auto at = log.find(line); at != std::string::npos; at = log.find(line, at + 1)) ++count; + for (auto at = log.find(line); at != std::string::npos; at = log.find(line, at + 1)) + ++count; RecordProperty("adopt_tier_refusals", static_cast(count)); EXPECT_EQ(count, 1u) << "the client names MOBILEGL_IPC_ADOPT_TIER on a stream exactly once, at " - "its handshake (" << line << "), in " << PipeStatsWindow::LibraryLogPath(); + "its handshake (" + << line << "), in " << PipeStatsWindow::LibraryLogPath(); EXPECT_EQ(log.find("Fatal{UnimplementedAdoptTier"), std::string::npos) << log; } @@ -711,7 +715,8 @@ void main() { word = 0xC0FFEEu; } const char* why = std::getenv(kAdoptT0FallbackMarker); if (why == nullptr || why[0] == '\0') { GTEST_SKIP() << "runs only in the shared-segment `.AdoptTier0.` / `.AdoptT0Disallowed.` entries, " - "which set MOBILEGL_IPC_ADOPT_TIER=0 and " << kAdoptT0FallbackMarker; + "which set MOBILEGL_IPC_ADOPT_TIER=0 and " + << kAdoptT0FallbackMarker; } if (PipeStatsWindow::LibraryLogPath().empty()) { GTEST_SKIP() << "the lane configured no MOBILEGL_LOG_FILE_PATH, and the two logs are where the " @@ -728,7 +733,8 @@ void main() { word = 0xC0FFEEu; } const auto countOf = [](const std::string& text, const std::string& needle) { std::size_t n = 0; - for (auto at = text.find(needle); at != std::string::npos; at = text.find(needle, at + 1)) ++n; + for (auto at = text.find(needle); at != std::string::npos; at = text.find(needle, at + 1)) + ++n; return n; }; const std::string client = PipeStatsWindow::ReadWholeFile(PipeStatsWindow::LibraryLogPath()); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentBarrierScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentBarrierScenario.cpp index 9a147160..20dbca86 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentBarrierScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentBarrierScenario.cpp @@ -151,8 +151,8 @@ void main() { o_color = vec4(0.1, 0.2, 0.8, 1.0); } glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); for (int layer = 0; layer < kLayers; ++layer) { const std::vector texels(static_cast(kWidth) * kHeight, LayerFill(layer)); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kWidth, kHeight, 1, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kWidth, kHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); } glBindTexture(GL_TEXTURE_2D_ARRAY, 0); return texture; @@ -200,8 +200,8 @@ void main() { o_color = vec4(0.1, 0.2, 0.8, 1.0); } // a failure - reported here rather than silently reduced to pixels[0]. for (std::size_t i = 1; i < pixels.size(); ++i) { EXPECT_TRUE(pixels[i] == pixels[0]) - << "layer " << layer << " is not uniform: texel 0 is " << Describe(pixels[0]) << ", texel " - << i << " is " << Describe(pixels[i]); + << "layer " << layer << " is not uniform: texel 0 is " << Describe(pixels[0]) << ", texel " << i + << " is " << Describe(pixels[i]); } return pixels[0]; } @@ -384,8 +384,7 @@ void main() { o_color = vec4(0.1, 0.2, 0.8, 1.0); } for (int level = 0; level < 3; ++level) { const int size = 16 >> level; const std::vector initial(size * size, LayerFill(level)); - glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, size, size, - GL_RGBA, GL_UNSIGNED_BYTE, initial.data()); + glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, initial.data()); } GLuint fbo = 0; glGenFramebuffers(1, &fbo); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentShapeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentShapeScenario.cpp index ece0124c..d5acddfc 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentShapeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/LayeredAttachmentShapeScenario.cpp @@ -115,8 +115,8 @@ namespace MGITest { } std::string Describe(const Rgba8& color) { - return "(" + std::to_string(color.r) + ", " + std::to_string(color.g) + ", " + - std::to_string(color.b) + ", " + std::to_string(color.a) + ")"; + return "(" + std::to_string(color.r) + ", " + std::to_string(color.g) + ", " + std::to_string(color.b) + + ", " + std::to_string(color.a) + ")"; } // A full-viewport triangle built from gl_VertexID, so nothing here needs a vertex buffer @@ -215,11 +215,14 @@ void main() if (!Ready()) return; glUseProgram(0); glBindFramebuffer(GL_FRAMEBUFFER, 0); - for (const GLuint fbo : m_fbos) glDeleteFramebuffers(1, &fbo); + for (const GLuint fbo : m_fbos) + glDeleteFramebuffers(1, &fbo); m_fbos.clear(); - for (const GLuint texture : m_textures) glDeleteTextures(1, &texture); + for (const GLuint texture : m_textures) + glDeleteTextures(1, &texture); m_textures.clear(); - for (const GLuint program : m_programs) glDeleteProgram(program); + for (const GLuint program : m_programs) + glDeleteProgram(program); m_programs.clear(); glBindVertexArray(0); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); @@ -230,8 +233,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // 0 on a DirectGLES driver without GL_EXT_geometry_shader and on a DirectVulkan @@ -283,14 +285,17 @@ void main() if (ok && geometrySource != nullptr) ok = compile(GL_GEOMETRY_SHADER, geometrySource); if (ok) ok = compile(GL_FRAGMENT_SHADER, fragmentSource); if (!ok) { - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); return 0; } const GLuint program = glCreateProgram(); - for (const GLuint shader : shaders) glAttachShader(program, shader); + for (const GLuint shader : shaders) + glAttachShader(program, shader); glLinkProgram(program); - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); if (linked == GL_FALSE) { @@ -323,11 +328,10 @@ void main() glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, kExtent, kExtent, k3DSlices); - const std::vector seed( - static_cast(kExtent) * kExtent * k3DSlices * 4, kPoison); + const std::vector seed(static_cast(kExtent) * kExtent * k3DSlices * 4, kPoison); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kExtent, kExtent, k3DSlices, GL_RGBA, - GL_UNSIGNED_BYTE, seed.data()); + glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, kExtent, kExtent, k3DSlices, GL_RGBA, GL_UNSIGNED_BYTE, + seed.data()); glBindTexture(GL_TEXTURE_3D, 0); return texture; } @@ -339,11 +343,11 @@ void main() glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, GL_RGBA8, kExtent, kExtent, kCubeLayerFaces); - const std::vector seed( - static_cast(kExtent) * kExtent * kCubeLayerFaces * 4, kPoison); + const std::vector seed(static_cast(kExtent) * kExtent * kCubeLayerFaces * 4, + kPoison); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kExtent, kExtent, kCubeLayerFaces, - GL_RGBA, GL_UNSIGNED_BYTE, seed.data()); + glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kExtent, kExtent, kCubeLayerFaces, GL_RGBA, + GL_UNSIGNED_BYTE, seed.data()); glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0); return texture; } @@ -379,8 +383,7 @@ void main() glTexStorage2D(GL_TEXTURE_CUBE_MAP, 1, GL_RGBA8, kExtent, kExtent); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for (int face = 0; face < 6; ++face) { - const std::vector seed(static_cast(kExtent) * kExtent, - ExpectedColor(face, 0)); + const std::vector seed(static_cast(kExtent) * kExtent, ExpectedColor(face, 0)); glTexSubImage2D(static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, 0, 0, kExtent, kExtent, GL_RGBA, GL_UNSIGNED_BYTE, seed.data()); } @@ -398,8 +401,8 @@ void main() glTexParameteri(GL_TEXTURE_1D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); const std::vector seed(static_cast(kExtent) * kOneDArrayLayers * 4, kPoison); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA8, kExtent, kOneDArrayLayers, 0, GL_RGBA, - GL_UNSIGNED_BYTE, seed.data()); + glTexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA8, kExtent, kOneDArrayLayers, 0, GL_RGBA, GL_UNSIGNED_BYTE, + seed.data()); glBindTexture(GL_TEXTURE_1D_ARRAY, 0); return texture; } @@ -500,16 +503,14 @@ void main() const Rgba8 expected = ExpectedColor(layer, pass); for (int y = 0; y < kExtent; ++y) { for (int x = 0; x < kExtent; ++x) { - const std::size_t index = - (static_cast(layer) * kExtent + y) * kExtent + x; + const std::size_t index = (static_cast(layer) * kExtent + y) * kExtent + x; const Rgba8 actual = texels[index]; if (actual == expected) continue; - ADD_FAILURE() - << what << ": layer " << layer << " texel (" << x << ", " << y << ") is " - << Describe(actual) << ", expected " << Describe(expected) - << (actual.r == kPoison && actual.g == kPoison - ? " - the poison, so nothing was ever rendered into this layer" - : ""); + ADD_FAILURE() << what << ": layer " << layer << " texel (" << x << ", " << y << ") is " + << Describe(actual) << ", expected " << Describe(expected) + << (actual.r == kPoison && actual.g == kPoison + ? " - the poison, so nothing was ever rendered into this layer" + : ""); // One message per layer is enough to say what happened. y = kExtent; break; @@ -653,8 +654,7 @@ void main() const Rgba8& target = (slice == kSubjectSlice) ? expected : poison; for (int y = 0; y < kExtent; ++y) { for (int x = 0; x < kExtent; ++x) { - const std::size_t index = - (static_cast(slice) * kExtent + y) * kExtent + x; + const std::size_t index = (static_cast(slice) * kExtent + y) * kExtent + x; const Rgba8 actual = texels[index]; if (actual == target) continue; ADD_FAILURE() << "slice " << slice << " texel (" << x << ", " << y << ") is " @@ -806,8 +806,7 @@ void main() const GLuint depthStencil = MakeCubeArrayDepth(GL_DEPTH24_STENCIL8); if (const GLenum error = FirstGLError()) { - GTEST_SKIP() << "no depth-stencil GL_TEXTURE_CUBE_MAP_ARRAY on this backend: " - << GLErrorName(error); + GTEST_SKIP() << "no depth-stencil GL_TEXTURE_CUBE_MAP_ARRAY on this backend: " << GLErrorName(error); } const std::string geometrySource = MakeGeometrySource(kCubeLayerFaces); @@ -1100,8 +1099,8 @@ void main() glPixelStorei(GL_PACK_ALIGNMENT, 1); for (int face = 0; face < 6; ++face) { std::vector texels(static_cast(kExtent) * kExtent, Rgba8{}); - glGetTexImage(static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glGetTexImage(static_cast(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); EXPECT_EQ(FirstGLError(), 0u) << "glGetTexImage of face " << kFaceNames[face] << " errored"; ExpectFaceColor(texels, face, "glGetTexImage(GL_TEXTURE_CUBE_MAP_)"); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/LayeredTextureReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/LayeredTextureReadbackScenario.cpp index 51dd9405..06dfa3b4 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/LayeredTextureReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/LayeredTextureReadbackScenario.cpp @@ -109,8 +109,10 @@ void main() void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint t : m_textures) glDeleteTextures(1, &t); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint t : m_textures) + glDeleteTextures(1, &t); m_programs.clear(); m_textures.clear(); GLint maxImageUnits = 0; @@ -118,8 +120,7 @@ void main() for (GLint unit = 0; unit < maxImageUnits; ++unit) { glBindImageTexture(static_cast(unit), 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } bool ImagesAreUsable() const { @@ -127,8 +128,7 @@ void main() glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits > kImageUnit && maxComputeImageUniforms >= 1; } @@ -270,8 +270,7 @@ void main() for (int layerFace = 0; layerFace < kCubeLayerFaces; ++layerFace) { for (int y = 0; y < kExtent; ++y) { for (int x = 0; x < kExtent; ++x) { - const std::size_t index = - (static_cast(layerFace) * kExtent + y) * kExtent + x; + const std::size_t index = (static_cast(layerFace) * kExtent + y) * kExtent + x; EXPECT_EQ(texels[index], ExpectedCubeArrayTexel(x, y, layerFace)) << "layer-face " << layerFace << " texel (" << x << ", " << y << ") read back " << (texels[index] == kSeed ? "the seed (the store never reached it, or the readback " diff --git a/MobileGL/MG_IntegrationTest/Scenarios/MagmaRunAheadScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/MagmaRunAheadScenario.cpp index 0e7dee30..045c9100 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/MagmaRunAheadScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/MagmaRunAheadScenario.cpp @@ -28,318 +28,385 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -constexpr int kWidth = 32, kHeight = 8; -constexpr const char* kVertexId = R"(#version 430 core + namespace { + constexpr int kWidth = 32, kHeight = 8; + constexpr const char* kVertexId = R"(#version 430 core void main() { vec2 p[3] = vec2[3](vec2(-1,-1),vec2(3,-1),vec2(-1,3)); gl_Position=vec4(p[gl_VertexID],0,1); })"; -using Pixel = std::array; -constexpr Pixel red{255,0,0,255}, green{0,255,0,255}, blue{0,0,255,255}, yellow{255,255,0,255}, black{0,0,0,255}; + using Pixel = std::array; + constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}, yellow{255, 255, 0, 255}, + black{0, 0, 0, 255}; -struct HeldApply { - ~HeldApply() { ReleaseSplitApplyHoldForTesting(); } - void Release() { ReleaseSplitApplyHoldForTesting(); } -}; + struct HeldApply { + ~HeldApply() { ReleaseSplitApplyHoldForTesting(); } + void Release() { ReleaseSplitApplyHoldForTesting(); } + }; -class MagmaRunAheadScenario : public ScenarioTest { -protected: - GLuint target = 0; - std::vector buffers, programs, textures, fbos, vaos; - SplitRuntimeState held{}; + class MagmaRunAheadScenario : public ScenarioTest { + protected: + GLuint target = 0; + std::vector buffers, programs, textures, fbos, vaos; + SplitRuntimeState held{}; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - if (!SplitLane::MarkerIsOne("MGITEST_MAGMA_RUNAHEAD_LANE")) - GTEST_SKIP() << "dedicated Magma run-ahead lane only"; - ASSERT_EQ(Gl().BackendName(), "DirectVulkan"); - const auto runtime = PeekSplitRuntime(); - ASSERT_TRUE(runtime.peekAvailable && runtime.sessionActive && runtime.transportResolved); - RecordProperty("run_ahead_armed", runtime.runAheadArmed ? "1" : "0"); - ASSERT_TRUE(runtime.presentCredit == 1 || runtime.presentCredit == 3); - target = NewFbo(NewTexture(kWidth, kHeight, nullptr)); - glBindFramebuffer(GL_FRAMEBUFFER, target); - glViewport(0, 0, kWidth, kHeight); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0,0,0,1); - glClear(GL_COLOR_BUFFER_BIT); - GLuint vao = 0; - glGenVertexArrays(1, &vao); - vaos.push_back(vao); - glBindVertexArray(vao); - } - void TearDown() override { - ReleaseSplitApplyHoldForTesting(); - if (!Ready()) return; - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if (!buffers.empty()) glDeleteBuffers(GLsizei(buffers.size()), buffers.data()); - if (!vaos.empty()) glDeleteVertexArrays(GLsizei(vaos.size()), vaos.data()); - if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); - if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); - for (GLuint program : programs) glDeleteProgram(program); - } - GLuint NewBuffer(GLenum target, GLsizeiptr size, const void* bytes) { - GLuint buffer = 0; - glGenBuffers(1, &buffer); - buffers.push_back(buffer); - glBindBuffer(target, buffer); - glBufferData(target, size, bytes, GL_DYNAMIC_DRAW); - return buffer; - } - GLuint NewTexture(int width, int height, const void* pixels) { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA8,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,pixels); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); - return texture; - } - GLuint NewFbo(GLuint texture) { - GLuint fbo = 0; - glGenFramebuffers(1,&fbo); - fbos.push_back(fbo); - glBindFramebuffer(GL_FRAMEBUFFER,fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0); - EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - return fbo; - } - GLuint Link(std::initializer_list> stages) { - const GLuint program = glCreateProgram(); - programs.push_back(program); - for (const auto& [kind, source] : stages) { - const GLuint shader = glCreateShader(kind); - glShaderSource(shader,1,&source,nullptr); - glCompileShader(shader); - GLint ok = GL_FALSE; - glGetShaderiv(shader,GL_COMPILE_STATUS,&ok); - EXPECT_EQ(ok,GL_TRUE) << "run-ahead shader compile"; - glAttachShader(program,shader); - glDeleteShader(shader); - } - glLinkProgram(program); - GLint ok = GL_FALSE; - glGetProgramiv(program,GL_LINK_STATUS,&ok); - EXPECT_EQ(ok,GL_TRUE) << "run-ahead program link"; - return program; - } - void HoldNextBatch() { - // Fully drain SETUP, then hold after one real clear batch. No wait/readback - // is inserted among the operations whose queued ordering is under test. - glClear(GL_COLOR_BUFFER_BIT); - glFinish(); - ASSERT_TRUE(ArmSplitApplyHoldForTesting()); - glClear(GL_COLOR_BUFFER_BIT); - ASSERT_TRUE(WaitForSplitApplyHoldForTesting()) - << "the clear waited for apply instead of returning while its server batch was held"; - held = PeekSplitRuntime(); - } - void ExpectQueued(unsigned int minimumRecords) { - const auto now = PeekSplitRuntime(); - EXPECT_TRUE(now.runAheadArmed) << "Magma production session did not arm run-ahead"; - EXPECT_TRUE(SplitApplyHoldIsActiveForTesting()) << "client waited for apply instead of running ahead"; - EXPECT_EQ(now.appliedSeq, held.appliedSeq) << "held server advanced unexpectedly"; - EXPECT_GE(now.emitSeq, held.emitSeq + minimumRecords); - EXPECT_GT(now.emitSeq, now.appliedSeq) << "no real client lead over the apply watermark"; - RecordProperty("observed_queued_records", std::to_string(now.emitSeq - now.appliedSeq)); - } - void DrawTile(int tile) { - glViewport(tile * 8,0,8,kHeight); - glDrawArrays(GL_TRIANGLES,0,3); - } - void ExpectPixel(int x, int y, Pixel expected, const char* why) { - Pixel actual{}; - glReadPixels(x,y,1,1,GL_RGBA,GL_UNSIGNED_BYTE,actual.data()); - EXPECT_EQ(actual,expected) << why; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; - } -}; + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + if (!SplitLane::MarkerIsOne("MGITEST_MAGMA_RUNAHEAD_LANE")) + GTEST_SKIP() << "dedicated Magma run-ahead lane only"; + ASSERT_EQ(Gl().BackendName(), "DirectVulkan"); + const auto runtime = PeekSplitRuntime(); + ASSERT_TRUE(runtime.peekAvailable && runtime.sessionActive && runtime.transportResolved); + RecordProperty("run_ahead_armed", runtime.runAheadArmed ? "1" : "0"); + ASSERT_TRUE(runtime.presentCredit == 1 || runtime.presentCredit == 3); + target = NewFbo(NewTexture(kWidth, kHeight, nullptr)); + glBindFramebuffer(GL_FRAMEBUFFER, target); + glViewport(0, 0, kWidth, kHeight); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + vaos.push_back(vao); + glBindVertexArray(vao); + } + void TearDown() override { + ReleaseSplitApplyHoldForTesting(); + if (!Ready()) return; + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (!buffers.empty()) glDeleteBuffers(GLsizei(buffers.size()), buffers.data()); + if (!vaos.empty()) glDeleteVertexArrays(GLsizei(vaos.size()), vaos.data()); + if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); + if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); + for (GLuint program : programs) + glDeleteProgram(program); + } + GLuint NewBuffer(GLenum target, GLsizeiptr size, const void* bytes) { + GLuint buffer = 0; + glGenBuffers(1, &buffer); + buffers.push_back(buffer); + glBindBuffer(target, buffer); + glBufferData(target, size, bytes, GL_DYNAMIC_DRAW); + return buffer; + } + GLuint NewTexture(int width, int height, const void* pixels) { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + return texture; + } + GLuint NewFbo(GLuint texture) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + fbos.push_back(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + return fbo; + } + GLuint Link(std::initializer_list> stages) { + const GLuint program = glCreateProgram(); + programs.push_back(program); + for (const auto& [kind, source] : stages) { + const GLuint shader = glCreateShader(kind); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + GLint ok = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE) << "run-ahead shader compile"; + glAttachShader(program, shader); + glDeleteShader(shader); + } + glLinkProgram(program); + GLint ok = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE) << "run-ahead program link"; + return program; + } + void HoldNextBatch() { + // Fully drain SETUP, then hold after one real clear batch. No wait/readback + // is inserted among the operations whose queued ordering is under test. + glClear(GL_COLOR_BUFFER_BIT); + glFinish(); + ASSERT_TRUE(ArmSplitApplyHoldForTesting()); + glClear(GL_COLOR_BUFFER_BIT); + ASSERT_TRUE(WaitForSplitApplyHoldForTesting()) + << "the clear waited for apply instead of returning while its server batch was held"; + held = PeekSplitRuntime(); + } + void ExpectQueued(unsigned int minimumRecords) { + const auto now = PeekSplitRuntime(); + EXPECT_TRUE(now.runAheadArmed) << "Magma production session did not arm run-ahead"; + EXPECT_TRUE(SplitApplyHoldIsActiveForTesting()) << "client waited for apply instead of running ahead"; + EXPECT_EQ(now.appliedSeq, held.appliedSeq) << "held server advanced unexpectedly"; + EXPECT_GE(now.emitSeq, held.emitSeq + minimumRecords); + EXPECT_GT(now.emitSeq, now.appliedSeq) << "no real client lead over the apply watermark"; + RecordProperty("observed_queued_records", std::to_string(now.emitSeq - now.appliedSeq)); + } + void DrawTile(int tile) { + glViewport(tile * 8, 0, 8, kHeight); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + void ExpectPixel(int x, int y, Pixel expected, const char* why) { + Pixel actual{}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); + EXPECT_EQ(actual, expected) << why; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; + } + }; -TEST_F(MagmaRunAheadScenario, QueuedBufferVersionsAndDeletedNameReuseKeepTheirPixels) { - if (!Ready() || IsSkipped()) return; - const char* vs = R"(#version 430 core + TEST_F(MagmaRunAheadScenario, QueuedBufferVersionsAndDeletedNameReuseKeepTheirPixels) { + if (!Ready() || IsSkipped()) return; + const char* vs = R"(#version 430 core layout(location=0) in vec4 color; out vec4 v; void main(){vec2 p[3]=vec2[3](vec2(-1,-1),vec2(3,-1),vec2(-1,3));gl_Position=vec4(p[gl_VertexID],0,1);v=color;})"; - const char* fs = "#version 430 core\nin vec4 v;layout(location=0) out vec4 c;void main(){c=v;}"; - glUseProgram(Link({{GL_VERTEX_SHADER,vs},{GL_FRAGMENT_SHADER,fs}})); - const auto colors = [](float r,float g,float b) { return std::array{r,g,b,1,r,g,b,1,r,g,b,1}; }; - const auto r=colors(1,0,0), g=colors(0,1,0), b=colors(0,0,1), y=colors(1,1,0); - const GLuint buffer=NewBuffer(GL_ARRAY_BUFFER,sizeof(r),r.data()); - glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,16,nullptr); glEnableVertexAttribArray(0); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - DrawTile(0); - glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(g),g.data()); DrawTile(1); - ExpectQueued(2); hold.Release(); - // Resource creation requires a reply, and storage definitions may require an - // allocation acknowledgement. Do not hold the apply owner across those legal - // waits. There is still no GPU readback/Finish before all four draws, so the - // old GPU store must survive respecify and deletion until its draws complete. - glBufferData(GL_ARRAY_BUFFER,sizeof(b),b.data(),GL_STREAM_DRAW); DrawTile(2); - glDeleteBuffers(1,&buffer); - // Request the recycled name through the public allocator. Record reuse must - // have a distinct lifetime even though the application-visible name is equal. - GLuint replacement=0; - glGenBuffers(1,&replacement); - EXPECT_EQ(replacement,buffer) << "the intended GL-name reuse did not occur"; - if (replacement!=buffer) buffers.push_back(replacement); - glBindBuffer(GL_ARRAY_BUFFER,replacement); - glBufferData(GL_ARRAY_BUFFER,sizeof(y),y.data(),GL_DYNAMIC_DRAW); - glVertexAttribPointer(0,4,GL_FLOAT,GL_FALSE,16,nullptr); DrawTile(3); - ExpectPixel(4,4,red,"original buffer version"); ExpectPixel(12,4,green,"SubData version"); - ExpectPixel(20,4,blue,"respecified store"); ExpectPixel(28,4,yellow,"recreated GL name"); -} + const char* fs = "#version 430 core\nin vec4 v;layout(location=0) out vec4 c;void main(){c=v;}"; + glUseProgram(Link({{GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, fs}})); + const auto colors = [](float r, float g, float b) { + return std::array{r, g, b, 1, r, g, b, 1, r, g, b, 1}; + }; + const auto r = colors(1, 0, 0), g = colors(0, 1, 0), b = colors(0, 0, 1), y = colors(1, 1, 0); + const GLuint buffer = NewBuffer(GL_ARRAY_BUFFER, sizeof(r), r.data()); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 16, nullptr); + glEnableVertexAttribArray(0); + HeldApply hold; + ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); + DrawTile(0); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(g), g.data()); + DrawTile(1); + ExpectQueued(2); + hold.Release(); + // Resource creation requires a reply, and storage definitions may require an + // allocation acknowledgement. Do not hold the apply owner across those legal + // waits. There is still no GPU readback/Finish before all four draws, so the + // old GPU store must survive respecify and deletion until its draws complete. + glBufferData(GL_ARRAY_BUFFER, sizeof(b), b.data(), GL_STREAM_DRAW); + DrawTile(2); + glDeleteBuffers(1, &buffer); + // Request the recycled name through the public allocator. Record reuse must + // have a distinct lifetime even though the application-visible name is equal. + GLuint replacement = 0; + glGenBuffers(1, &replacement); + EXPECT_EQ(replacement, buffer) << "the intended GL-name reuse did not occur"; + if (replacement != buffer) buffers.push_back(replacement); + glBindBuffer(GL_ARRAY_BUFFER, replacement); + glBufferData(GL_ARRAY_BUFFER, sizeof(y), y.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 16, nullptr); + DrawTile(3); + ExpectPixel(4, 4, red, "original buffer version"); + ExpectPixel(12, 4, green, "SubData version"); + ExpectPixel(20, 4, blue, "respecified store"); + ExpectPixel(28, 4, yellow, "recreated GL name"); + } -TEST_F(MagmaRunAheadScenario, QueuedProgramRebindsKeepEachUniformSnapshot) { - if (!Ready() || IsSkipped()) return; - const char* a="#version 430 core\nuniform vec4 tint;layout(location=0) out vec4 c;void main(){c=tint;}"; - const char* b="#version 430 core\nuniform vec4 tint;layout(location=0) out vec4 c;void main(){c=tint.bgra;}"; - const GLuint pa=Link({{GL_VERTEX_SHADER,kVertexId},{GL_FRAGMENT_SHADER,a}}), pb=Link({{GL_VERTEX_SHADER,kVertexId},{GL_FRAGMENT_SHADER,b}}); - const GLint la=glGetUniformLocation(pa,"tint"), lb=glGetUniformLocation(pb,"tint"); - ASSERT_GE(la,0); ASSERT_GE(lb,0); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - glUseProgram(pa); glUniform4f(la,1,0,0,1); DrawTile(0); - glUseProgram(pb); glUniform4f(lb,0,1,0,1); DrawTile(1); - glUseProgram(pa); glUniform4f(la,0,0,1,1); DrawTile(2); - glUseProgram(pb); glUniform4f(lb,0,1,1,1); DrawTile(3); - ExpectQueued(4); hold.Release(); - ExpectPixel(4,4,red,"program A first uniform"); ExpectPixel(12,4,green,"program B first uniform"); - ExpectPixel(20,4,blue,"program A rebound"); ExpectPixel(28,4,yellow,"program B swizzled second uniform"); -} + TEST_F(MagmaRunAheadScenario, QueuedProgramRebindsKeepEachUniformSnapshot) { + if (!Ready() || IsSkipped()) return; + const char* a = "#version 430 core\nuniform vec4 tint;layout(location=0) out vec4 c;void main(){c=tint;}"; + const char* b = + "#version 430 core\nuniform vec4 tint;layout(location=0) out vec4 c;void main(){c=tint.bgra;}"; + const GLuint pa = Link({{GL_VERTEX_SHADER, kVertexId}, {GL_FRAGMENT_SHADER, a}}), + pb = Link({{GL_VERTEX_SHADER, kVertexId}, {GL_FRAGMENT_SHADER, b}}); + const GLint la = glGetUniformLocation(pa, "tint"), lb = glGetUniformLocation(pb, "tint"); + ASSERT_GE(la, 0); + ASSERT_GE(lb, 0); + HeldApply hold; + ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); + glUseProgram(pa); + glUniform4f(la, 1, 0, 0, 1); + DrawTile(0); + glUseProgram(pb); + glUniform4f(lb, 0, 1, 0, 1); + DrawTile(1); + glUseProgram(pa); + glUniform4f(la, 0, 0, 1, 1); + DrawTile(2); + glUseProgram(pb); + glUniform4f(lb, 0, 1, 1, 1); + DrawTile(3); + ExpectQueued(4); + hold.Release(); + ExpectPixel(4, 4, red, "program A first uniform"); + ExpectPixel(12, 4, green, "program B first uniform"); + ExpectPixel(20, 4, blue, "program A rebound"); + ExpectPixel(28, 4, yellow, "program B swizzled second uniform"); + } -TEST_F(MagmaRunAheadScenario, QueuedGpuWritesReadBackTheLastDispatchBytes) { - if (!Ready() || IsSkipped()) return; - const char* cs=R"(#version 430 core + TEST_F(MagmaRunAheadScenario, QueuedGpuWritesReadBackTheLastDispatchBytes) { + if (!Ready() || IsSkipped()) return; + const char* cs = R"(#version 430 core layout(local_size_x=1) in;layout(std430,binding=0) buffer Out{uint value[];};uniform uint seed; void main(){uint i=gl_GlobalInvocationID.x;value[i]=seed+i;})"; - const GLuint program=Link({{GL_COMPUTE_SHADER,cs}}); - const GLint seed=glGetUniformLocation(program,"seed"); ASSERT_GE(seed,0); - std::array data{}; - const GLuint buffer=NewBuffer(GL_SHADER_STORAGE_BUFFER,sizeof(data),data.data()); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER,0,buffer); glUseProgram(program); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - glUniform1ui(seed,0x11110000u); glDispatchCompute(32,1,1); - glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); - glUniform1ui(seed,0x22220000u); glDispatchCompute(32,1,1); - ExpectQueued(2); hold.Release(); - glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER,0,sizeof(data),data.data()); - for (GLuint i=0;i data{}; + const GLuint buffer = NewBuffer(GL_SHADER_STORAGE_BUFFER, sizeof(data), data.data()); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer); + glUseProgram(program); + HeldApply hold; + ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); + glUniform1ui(seed, 0x11110000u); + glDispatchCompute(32, 1, 1); + glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT); + glUniform1ui(seed, 0x22220000u); + glDispatchCompute(32, 1, 1); + ExpectQueued(2); + hold.Release(); + glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, sizeof(data), data.data()); + for (GLuint i = 0; i < data.size(); ++i) + EXPECT_EQ(data[i], 0x22220000u + i) << i; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + } -TEST_F(MagmaRunAheadScenario, MixedNativeAndConvertedGpuVertexStreamsKeepTheirReservation) { - if (!Ready() || IsSkipped()) return; - const char* cs=R"(#version 430 core + TEST_F(MagmaRunAheadScenario, MixedNativeAndConvertedGpuVertexStreamsKeepTheirReservation) { + if (!Ready() || IsSkipped()) return; + const char* cs = R"(#version 430 core layout(local_size_x=1) in;layout(std430,binding=0) buffer Out{uint words[];}; void main(){vec2 p[3]=vec2[3](vec2(-1,-1),vec2(0,-1),vec2(-1,1)); for(uint i=0u;i<3u;++i){words[4u*i]=floatBitsToUint(p[i].x);words[4u*i+1u]=floatBitsToUint(p[i].y); words[4u*i+2u]=0u;words[4u*i+3u]=0x3ff00000u;}})"; - const char* vs=R"(#version 430 core + const char* vs = R"(#version 430 core layout(location=0) in vec2 position;layout(location=1) in float code;out vec4 v; void main(){gl_Position=vec4(position,0,1);v=vec4(1.0-float(code),float(code),0,1);})"; - const char* fs="#version 430 core\nin vec4 v;layout(location=0) out vec4 c;void main(){c=v;}"; - const GLuint compute=Link({{GL_COMPUTE_SHADER,cs}}), graphics=Link({{GL_VERTEX_SHADER,vs},{GL_FRAGMENT_SHADER,fs}}); - struct Vertex { GLfloat x,y; GLdouble code; }; - static_assert(sizeof(Vertex)==16); - std::array vertices{}; - const GLuint buffer=NewBuffer(GL_ARRAY_BUFFER,sizeof(vertices),vertices.data()); - glVertexAttribPointer(0,2,GL_FLOAT,GL_FALSE,16,nullptr); glEnableVertexAttribArray(0); - glVertexAttribPointer(1,1,GL_DOUBLE,GL_FALSE,16,reinterpret_cast(8)); glEnableVertexAttribArray(1); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER,0,buffer); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - glUseProgram(compute); glDispatchCompute(1,1,1); - glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); - glUseProgram(graphics); glDrawArrays(GL_TRIANGLES,0,3); - vertices={Vertex{0,-1,0},Vertex{1,-1,0},Vertex{1,1,0}}; - glBufferSubData(GL_ARRAY_BUFFER,0,sizeof(vertices),vertices.data()); - glDrawArrays(GL_TRIANGLES,0,3); - ExpectQueued(3); hold.Release(); - ExpectPixel(2,1,green,"native position reservation survives the other attribute's Double conversion read"); - ExpectPixel(29,1,red,"later SubData reaches only the later draw"); - glBindBufferBase(GL_SHADER_STORAGE_BUFFER,0,0); -} + const char* fs = "#version 430 core\nin vec4 v;layout(location=0) out vec4 c;void main(){c=v;}"; + const GLuint compute = Link({{GL_COMPUTE_SHADER, cs}}), + graphics = Link({{GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, fs}}); + struct Vertex { + GLfloat x, y; + GLdouble code; + }; + static_assert(sizeof(Vertex) == 16); + std::array vertices{}; + const GLuint buffer = NewBuffer(GL_ARRAY_BUFFER, sizeof(vertices), vertices.data()); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, nullptr); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 1, GL_DOUBLE, GL_FALSE, 16, reinterpret_cast(8)); + glEnableVertexAttribArray(1); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer); + HeldApply hold; + ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); + glUseProgram(compute); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL_BUFFER_UPDATE_BARRIER_BIT); + glUseProgram(graphics); + glDrawArrays(GL_TRIANGLES, 0, 3); + vertices = {Vertex{0, -1, 0}, Vertex{1, -1, 0}, Vertex{1, 1, 0}}; + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data()); + glDrawArrays(GL_TRIANGLES, 0, 3); + ExpectQueued(3); + hold.Release(); + ExpectPixel(2, 1, green, + "native position reservation survives the other attribute's Double conversion read"); + ExpectPixel(29, 1, red, "later SubData reaches only the later draw"); + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, 0); + } -TEST_F(MagmaRunAheadScenario, TextureUploadCannotOvertakeThePreviouslyQueuedDraw) { - if (!Ready() || IsSkipped()) return; - const char* fs="#version 430 core\nuniform sampler2D src;layout(location=0) out vec4 c;void main(){c=texture(src,vec2(0.5));}"; - glUseProgram(Link({{GL_VERTEX_SHADER,kVertexId},{GL_FRAGMENT_SHADER,fs}})); - const std::array oldPixels{255,0,0,255,255,0,0,255,255,0,0,255,255,0,0,255}; - const std::array newPixels{0,255,0,255,0,255,0,255,0,255,0,255,0,255,0,255}; - const GLuint source=NewTexture(2,2,oldPixels.data()); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - DrawTile(0); ExpectQueued(1); hold.Release(); - // Texture uploads have a wire reply. That is not a GPU completion barrier for - // the prior draw; no ReadPixels/Finish is permitted before the second draw. - glBindTexture(GL_TEXTURE_2D,source); - glTexSubImage2D(GL_TEXTURE_2D,0,0,0,2,2,GL_RGBA,GL_UNSIGNED_BYTE,newPixels.data()); - DrawTile(1); - ExpectPixel(4,4,red,"draw issued before texture update"); - ExpectPixel(12,4,green,"draw issued after texture update"); -} + TEST_F(MagmaRunAheadScenario, TextureUploadCannotOvertakeThePreviouslyQueuedDraw) { + if (!Ready() || IsSkipped()) return; + const char* fs = "#version 430 core\nuniform sampler2D src;layout(location=0) out vec4 c;void " + "main(){c=texture(src,vec2(0.5));}"; + glUseProgram(Link({{GL_VERTEX_SHADER, kVertexId}, {GL_FRAGMENT_SHADER, fs}})); + const std::array oldPixels{255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255}; + const std::array newPixels{0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255}; + const GLuint source = NewTexture(2, 2, oldPixels.data()); + HeldApply hold; + ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); + DrawTile(0); + ExpectQueued(1); + hold.Release(); + // Texture uploads have a wire reply. That is not a GPU completion barrier for + // the prior draw; no ReadPixels/Finish is permitted before the second draw. + glBindTexture(GL_TEXTURE_2D, source); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, newPixels.data()); + DrawTile(1); + ExpectPixel(4, 4, red, "draw issued before texture update"); + ExpectPixel(12, 4, green, "draw issued after texture update"); + } -TEST_F(MagmaRunAheadScenario, GpuClearSurvivesTheFirstStorageImageUpgrade) { - if (!Ready() || IsSkipped()) return; - std::array bytes{}; - for (size_t i=0;i bytes{}; + for (size_t i = 0; i < bytes.size(); i += 4) { + bytes[i] = 255; + bytes[i + 3] = 255; + } + const GLuint source = NewTexture(4, 4, bytes.data()), sourceFbo = NewFbo(source); + const char* cs = R"(#version 430 core layout(local_size_x=1) in;layout(rgba8,binding=0) uniform writeonly image2D img; void main(){imageStore(img,ivec2(0),vec4(0,0,1,1));})"; - const GLuint compute=Link({{GL_COMPUTE_SHADER,cs}}); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - glClearColor(0,1,0,1); glClear(GL_COLOR_BUFFER_BIT); ExpectQueued(1); hold.Release(); - glBindFramebuffer(GL_FRAMEBUFFER,target); - glBindImageTexture(0,source,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA8); - glUseProgram(compute); glDispatchCompute(1,1,1); - glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); - glBindFramebuffer(GL_READ_FRAMEBUFFER,sourceFbo); - ExpectPixel(0,0,blue,"compute writes the new STORAGE image"); - ExpectPixel(2,2,green,"usage upgrade preserves the earlier GPU clear, not the old red CPU upload"); - glBindImageTexture(0,0,0,GL_FALSE,0,GL_WRITE_ONLY,GL_RGBA8); -} - -TEST_F(MagmaRunAheadScenario, PresentCreditActuallyParksTheClientAtItsConfiguredLimit) { - if (!Ready() || IsSkipped()) return; - glBindFramebuffer(GL_FRAMEBUFFER,0); - HeldApply hold; ASSERT_NO_FATAL_FAILURE(HoldNextBatch()); - const auto before=PeekSplitRuntime(); - ASSERT_EQ(before.presentAckSerial,0u) << "this isolated case has not presented yet"; - for (unsigned i=0;i sawPark{false}, stop{false}; - std::thread observer([&] { - const auto until=std::chrono::steady_clock::now()+std::chrono::seconds(1); - while (!stop.load() && std::chrono::steady_clock::now() sawPark{false}, stop{false}; + std::thread observer([&] { + const auto until = std::chrono::steady_clock::now() + std::chrono::seconds(1); + while (!stop.load() && std::chrono::steady_clock::now() < until) { + if (SplitProducerIsParkedForTesting()) { + sawPark.store(true); + break; + } + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + ReleaseSplitApplyHoldForTesting(); + }); + Gl().EndFrame(); // credit + 1 must park before it can publish its present. + stop.store(true); + observer.join(); + hold.Release(); + EXPECT_TRUE(sawPark.load()) << "no real producer park while the present window was full"; + const auto issuedBeforeFinish = PeekSplitRuntime().emitSeq; + glFinish(); + const auto after = PeekSplitRuntime(); + EXPECT_EQ(after.presentCreditWaits, before.presentCreditWaits + 1); + EXPECT_EQ(after.presentAckSerial, before.presentCredit + 1); + // Finish waits for prior work, then asynchronously destroys its internal fence. + // That cleanup may still be queued when Finish returns. + EXPECT_GE(after.appliedSeq, issuedBeforeFinish); + RecordProperty("observed_present_credit", std::to_string(before.presentCredit)); + RecordProperty("observed_producer_park", sawPark.load() ? "yes" : "no"); + } + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/MagmaVertexLayoutScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/MagmaVertexLayoutScenario.cpp index 7647e792..ea1082c4 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/MagmaVertexLayoutScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/MagmaVertexLayoutScenario.cpp @@ -44,14 +44,14 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { + namespace { - constexpr int kWidth = 16, kHeight = 16; + constexpr int kWidth = 16, kHeight = 16; - // Reads BOTH attributes, so an array masked out of the vertex input state is one the - // program actually consumes and the draw must decline rather than silently substitute the - // current attribute value. - constexpr const char* kVertexReadsBoth = R"(#version 430 core + // Reads BOTH attributes, so an array masked out of the vertex input state is one the + // program actually consumes and the draw must decline rather than silently substitute the + // current attribute value. + constexpr const char* kVertexReadsBoth = R"(#version 430 core layout(location=0) in vec2 inPos; layout(location=1) in vec3 inTint; out vec3 vTint; @@ -60,9 +60,9 @@ void main() { gl_Position = vec4(inPos, 0.0, 1.0); })"; - // Reads ONLY location 0. An enabled-but-unread array is not part of this program's vertex - // input at all, so neither arm has anything to mask and the draw must land. - constexpr const char* kVertexReadsPositionOnly = R"(#version 430 core + // Reads ONLY location 0. An enabled-but-unread array is not part of this program's vertex + // input at all, so neither arm has anything to mask and the draw must land. + constexpr const char* kVertexReadsPositionOnly = R"(#version 430 core layout(location=0) in vec2 inPos; out vec3 vTint; void main() { @@ -70,151 +70,153 @@ void main() { gl_Position = vec4(inPos, 0.0, 1.0); })"; - constexpr const char* kFragment = R"(#version 430 core + constexpr const char* kFragment = R"(#version 430 core in vec3 vTint; layout(location=0) out vec4 color; void main() { color = vec4(vTint, 1.0); })"; - class MagmaVertexLayoutScenario : public ScenarioTest { - protected: - GLuint m_vao = 0, m_positions = 0, m_fixedAttrib = 0, m_program = 0; + class MagmaVertexLayoutScenario : public ScenarioTest { + protected: + GLuint m_vao = 0, m_positions = 0, m_fixedAttrib = 0, m_program = 0; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - if (Gl().BackendName() != "DirectVulkan") { - GTEST_SKIP() << "the format map under test is Magma's; Espryt hands GL_FIXED to a " - "driver that supports it"; + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + if (Gl().BackendName() != "DirectVulkan") { + GTEST_SKIP() << "the format map under test is Magma's; Espryt hands GL_FIXED to a " + "driver that supports it"; + } + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glViewport(0, 0, kWidth, kHeight); + } + + void TearDown() override { + if (m_program) glDeleteProgram(m_program); + if (m_vao) glDeleteVertexArrays(1, &m_vao); + if (m_positions) glDeleteBuffers(1, &m_positions); + if (m_fixedAttrib) glDeleteBuffers(1, &m_fixedAttrib); + ScenarioTest::TearDown(); + } + + GLuint MakeProgram(const char* vertexSource) { + const GLuint vs = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vs, 1, &vertexSource, nullptr); + glCompileShader(vs); + GLint ok = GL_FALSE; + glGetShaderiv(vs, GL_COMPILE_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE) << "vertex shader did not compile"; + const GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fs, 1, &kFragment, nullptr); + glCompileShader(fs); + glGetShaderiv(fs, GL_COMPILE_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE) << "fragment shader did not compile"; + const GLuint program = glCreateProgram(); + glAttachShader(program, vs); + glAttachShader(program, fs); + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE) << "program did not link"; + glDeleteShader(vs); + glDeleteShader(fs); + return program; + } + + // A full-viewport triangle in location 0 and a GL_FIXED size-3 array in location 1. + // GL_FIXED is 16.16 fixed point; the bytes are never fetched on the arm under test + // (the attribute is masked out before any binding is built), so their VALUE matters + // only for the unread case, where nothing reads them either. + void BuildVaoWithAFixedAttribute() { + const float positions[6] = {-1.0f, -1.0f, 3.0f, -1.0f, -1.0f, 3.0f}; + const GLint fixedTint[9] = {0, 1 << 16, 0, 0, 1 << 16, 0, 0, 1 << 16, 0}; + + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + + glGenBuffers(1, &m_positions); + glBindBuffer(GL_ARRAY_BUFFER, m_positions); + glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(0); + + glGenBuffers(1, &m_fixedAttrib); + glBindBuffer(GL_ARRAY_BUFFER, m_fixedAttrib); + glBufferData(GL_ARRAY_BUFFER, sizeof(fixedTint), fixedTint, GL_STATIC_DRAW); + // THE SHAPE UNDER TEST: size 3, GL_FIXED. ToVkVertexFormat has no Fixed32 arm, so + // this resolves to VK_FORMAT_UNDEFINED and the wire builder's `format-map` reason + // fires - the reason that used to abort the session. + glVertexAttribPointer(1, 3, GL_FIXED, GL_FALSE, 0, nullptr); + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, 0); + } + + std::array ClearDrawRead(GLuint program) { + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); // blue: the "the draw did not land" colour + glClear(GL_COLOR_BUFFER_BIT); + glUseProgram(program); + glBindVertexArray(m_vao); + glDrawArrays(GL_TRIANGLES, 0, 3); + std::array pixel{}; + glReadPixels(kWidth / 2, kHeight / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + glBindVertexArray(0); + glUseProgram(0); + return pixel; + } + }; + + // ------------------------------------------------------------------------------------ + // THE RETIREMENT ITSELF. The program READS the unmappable array, so the attribute is + // masked out of the vertex input state and the draw is then declined - loudly, once, and + // without touching the session. Before this package the same GL killed the process. + // + // THE ASSERTION IS THAT THE PROCESS IS STILL HERE and that the framebuffer still holds the + // clear, which together say "declined" rather than "rendered wrong pixels". A gtest case + // cannot assert the absence of an abort any more directly than by reaching its next + // statement: the old behaviour took the whole binary down, so every case after this one in + // the lane was equally the control. + TEST_F(MagmaVertexLayoutScenario, AnUnmappableVertexFormatDeclinesTheDrawInsteadOfEndingTheSession) { + if (!Ready() || IsSkipped()) return; + m_program = MakeProgram(kVertexReadsBoth); + BuildVaoWithAFixedAttribute(); + const auto pixel = ClearDrawRead(m_program); + + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) + << "a declined draw must not invent a GL error: GL has no error for 'this " + "implementation cannot fetch that array', and the monolith arm raises none"; + const int blue[4] = {0, 0, 255, 255}; + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(pixel[i], blue[i], 1) + << "component " << i + << ": the draw was expected to DECLINE (leaving the clear " + "colour) because location 1 is an enabled GL_FIXED array the program reads " + "and Vulkan has no format for it. A non-clear pixel means the attribute was " + "silently replaced by the current vertex attribute value, which is the exact " + "wrong-pixels outcome the monolith arm's pre-flight refuses."; } - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glViewport(0, 0, kWidth, kHeight); } - void TearDown() override { - if (m_program) glDeleteProgram(m_program); - if (m_vao) glDeleteVertexArrays(1, &m_vao); - if (m_positions) glDeleteBuffers(1, &m_positions); - if (m_fixedAttrib) glDeleteBuffers(1, &m_fixedAttrib); - ScenarioTest::TearDown(); + // ------------------------------------------------------------------------------------ + // THE OTHER HALF, and it is what keeps the case above from being satisfied by "decline + // every draw that mentions GL_FIXED". The array is still enabled and still unmappable; the + // program simply does not read it, so it is not part of this program's vertex input on + // either arm and the draw must land normally. + TEST_F(MagmaVertexLayoutScenario, AnUnreadUnmappableArrayDoesNotStopTheRestOfTheDraw) { + if (!Ready() || IsSkipped()) return; + m_program = MakeProgram(kVertexReadsPositionOnly); + BuildVaoWithAFixedAttribute(); + const auto pixel = ClearDrawRead(m_program); + + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the draw should have been ordinary"; + const int green[4] = {0, 255, 0, 255}; + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(pixel[i], green[i], 1) + << "component " << i + << ": an enabled but UNREAD GL_FIXED array must not cost " + "the draw. Blue here means the whole draw was declined for an attribute " + "that is not part of this program's vertex input at all."; + } } - GLuint MakeProgram(const char* vertexSource) { - const GLuint vs = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vs, 1, &vertexSource, nullptr); - glCompileShader(vs); - GLint ok = GL_FALSE; - glGetShaderiv(vs, GL_COMPILE_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE) << "vertex shader did not compile"; - const GLuint fs = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fs, 1, &kFragment, nullptr); - glCompileShader(fs); - glGetShaderiv(fs, GL_COMPILE_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE) << "fragment shader did not compile"; - const GLuint program = glCreateProgram(); - glAttachShader(program, vs); - glAttachShader(program, fs); - glLinkProgram(program); - glGetProgramiv(program, GL_LINK_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE) << "program did not link"; - glDeleteShader(vs); - glDeleteShader(fs); - return program; - } - - // A full-viewport triangle in location 0 and a GL_FIXED size-3 array in location 1. - // GL_FIXED is 16.16 fixed point; the bytes are never fetched on the arm under test - // (the attribute is masked out before any binding is built), so their VALUE matters - // only for the unread case, where nothing reads them either. - void BuildVaoWithAFixedAttribute() { - const float positions[6] = {-1.0f, -1.0f, 3.0f, -1.0f, -1.0f, 3.0f}; - const GLint fixedTint[9] = {0, 1 << 16, 0, 0, 1 << 16, 0, 0, 1 << 16, 0}; - - glGenVertexArrays(1, &m_vao); - glBindVertexArray(m_vao); - - glGenBuffers(1, &m_positions); - glBindBuffer(GL_ARRAY_BUFFER, m_positions); - glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); - glEnableVertexAttribArray(0); - - glGenBuffers(1, &m_fixedAttrib); - glBindBuffer(GL_ARRAY_BUFFER, m_fixedAttrib); - glBufferData(GL_ARRAY_BUFFER, sizeof(fixedTint), fixedTint, GL_STATIC_DRAW); - // THE SHAPE UNDER TEST: size 3, GL_FIXED. ToVkVertexFormat has no Fixed32 arm, so - // this resolves to VK_FORMAT_UNDEFINED and the wire builder's `format-map` reason - // fires - the reason that used to abort the session. - glVertexAttribPointer(1, 3, GL_FIXED, GL_FALSE, 0, nullptr); - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, 0); - } - - std::array ClearDrawRead(GLuint program) { - glClearColor(0.0f, 0.0f, 1.0f, 1.0f); // blue: the "the draw did not land" colour - glClear(GL_COLOR_BUFFER_BIT); - glUseProgram(program); - glBindVertexArray(m_vao); - glDrawArrays(GL_TRIANGLES, 0, 3); - std::array pixel{}; - glReadPixels(kWidth / 2, kHeight / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - glBindVertexArray(0); - glUseProgram(0); - return pixel; - } - }; - - // ------------------------------------------------------------------------------------ - // THE RETIREMENT ITSELF. The program READS the unmappable array, so the attribute is - // masked out of the vertex input state and the draw is then declined - loudly, once, and - // without touching the session. Before this package the same GL killed the process. - // - // THE ASSERTION IS THAT THE PROCESS IS STILL HERE and that the framebuffer still holds the - // clear, which together say "declined" rather than "rendered wrong pixels". A gtest case - // cannot assert the absence of an abort any more directly than by reaching its next - // statement: the old behaviour took the whole binary down, so every case after this one in - // the lane was equally the control. - TEST_F(MagmaVertexLayoutScenario, AnUnmappableVertexFormatDeclinesTheDrawInsteadOfEndingTheSession) { - if (!Ready() || IsSkipped()) return; - m_program = MakeProgram(kVertexReadsBoth); - BuildVaoWithAFixedAttribute(); - const auto pixel = ClearDrawRead(m_program); - - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) - << "a declined draw must not invent a GL error: GL has no error for 'this " - "implementation cannot fetch that array', and the monolith arm raises none"; - const int blue[4] = {0, 0, 255, 255}; - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR(pixel[i], blue[i], 1) - << "component " << i << ": the draw was expected to DECLINE (leaving the clear " - "colour) because location 1 is an enabled GL_FIXED array the program reads " - "and Vulkan has no format for it. A non-clear pixel means the attribute was " - "silently replaced by the current vertex attribute value, which is the exact " - "wrong-pixels outcome the monolith arm's pre-flight refuses."; - } - } - - // ------------------------------------------------------------------------------------ - // THE OTHER HALF, and it is what keeps the case above from being satisfied by "decline - // every draw that mentions GL_FIXED". The array is still enabled and still unmappable; the - // program simply does not read it, so it is not part of this program's vertex input on - // either arm and the draw must land normally. - TEST_F(MagmaVertexLayoutScenario, AnUnreadUnmappableArrayDoesNotStopTheRestOfTheDraw) { - if (!Ready() || IsSkipped()) return; - m_program = MakeProgram(kVertexReadsPositionOnly); - BuildVaoWithAFixedAttribute(); - const auto pixel = ClearDrawRead(m_program); - - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the draw should have been ordinary"; - const int green[4] = {0, 255, 0, 255}; - for (int i = 0; i < 4; ++i) { - EXPECT_NEAR(pixel[i], green[i], 1) - << "component " << i << ": an enabled but UNREAD GL_FIXED array must not cost " - "the draw. Blue here means the whole draw was declined for an attribute " - "that is not part of this program's vertex input at all."; - } - } - -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireCacheScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireCacheScenario.cpp index 37bec058..4577ec60 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireCacheScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireCacheScenario.cpp @@ -22,339 +22,367 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -using Pixel = std::array; -constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}; -constexpr Pixel yellow{255, 255, 0, 255}, black{0, 0, 0, 255}; -constexpr int kWidth = 32, kHeight = 8; -constexpr const char* kVertex = R"(#version 430 core + namespace { + using Pixel = std::array; + constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}; + constexpr Pixel yellow{255, 255, 0, 255}, black{0, 0, 0, 255}; + constexpr int kWidth = 32, kHeight = 8; + constexpr const char* kVertex = R"(#version 430 core void main() { vec2 p[3] = vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1,3)); gl_Position = vec4(p[gl_VertexID], 0, 1); })"; -constexpr const char* kSample2D = R"(#version 430 core + constexpr const char* kSample2D = R"(#version 430 core uniform sampler2D src; layout(location=0) out vec4 color; void main() { color = texelFetch(src, ivec2(0), 0); })"; -constexpr const char* kSampleArray = R"(#version 430 core + constexpr const char* kSampleArray = R"(#version 430 core uniform sampler2DArray src; layout(location=0) out vec4 color; void main() { color = texelFetch(src, ivec3(0,0,1), 0); })"; -constexpr const char* kTint = R"(#version 430 core + constexpr const char* kTint = R"(#version 430 core uniform vec4 tint; layout(location=0) out vec4 color; void main() { color = tint; })"; -class MagmaWireCacheScenario : public ScenarioTest { -protected: - GLuint target = 0; - std::vector textures, fbos, programs, vaos; + class MagmaWireCacheScenario : public ScenarioTest { + protected: + GLuint target = 0; + std::vector textures, fbos, programs, vaos; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - if (!SplitLane::MarkerIsOne("MGITEST_MAGMA_CACHES_LANE")) - GTEST_SKIP() << "dedicated Magma cache lane only"; - ASSERT_EQ(Gl().BackendName(), "DirectVulkan"); - const auto runtime = PeekSplitRuntime(); - ASSERT_TRUE(runtime.peekAvailable && runtime.sessionActive && runtime.transportResolved); - ASSERT_EQ(runtime.transportName, "inproc"); - ASSERT_TRUE(SplitLane::MarkerIsOne("MOBILEGL_IPC_ROLE_SPLIT_STATE")); - ASSERT_TRUE(SplitLane::MarkerIsOne("MOBILEGL_IPC_STRICT_ERRORS")); - glActiveTexture(GL_TEXTURE0); - glBindSampler(0, 0); - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glDisable(GL_RASTERIZER_DISCARD); - glDisable(GL_FRAMEBUFFER_SRGB); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - GLuint vao = 0; - glGenVertexArrays(1, &vao); - vaos.push_back(vao); - glBindVertexArray(vao); - target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); - } + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + if (!SplitLane::MarkerIsOne("MGITEST_MAGMA_CACHES_LANE")) + GTEST_SKIP() << "dedicated Magma cache lane only"; + ASSERT_EQ(Gl().BackendName(), "DirectVulkan"); + const auto runtime = PeekSplitRuntime(); + ASSERT_TRUE(runtime.peekAvailable && runtime.sessionActive && runtime.transportResolved); + ASSERT_EQ(runtime.transportName, "inproc"); + ASSERT_TRUE(SplitLane::MarkerIsOne("MOBILEGL_IPC_ROLE_SPLIT_STATE")); + ASSERT_TRUE(SplitLane::MarkerIsOne("MOBILEGL_IPC_STRICT_ERRORS")); + glActiveTexture(GL_TEXTURE0); + glBindSampler(0, 0); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glDisable(GL_RASTERIZER_DISCARD); + glDisable(GL_FRAMEBUFFER_SRGB); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + GLuint vao = 0; + glGenVertexArrays(1, &vao); + vaos.push_back(vao); + glBindVertexArray(vao); + target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); + } - void TearDown() override { - if (!Ready()) return; - glDisable(GL_FRAMEBUFFER_SRGB); - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if (!vaos.empty()) glDeleteVertexArrays(GLsizei(vaos.size()), vaos.data()); - if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); - if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); - for (GLuint program : programs) glDeleteProgram(program); - } + void TearDown() override { + if (!Ready()) return; + glDisable(GL_FRAMEBUFFER_SRGB); + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (!vaos.empty()) glDeleteVertexArrays(GLsizei(vaos.size()), vaos.data()); + if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); + if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); + for (GLuint program : programs) + glDeleteProgram(program); + } - static std::vector Pixels(int width, int height, Pixel color) { - return std::vector(size_t(width) * size_t(height), color); - } + static std::vector Pixels(int width, int height, Pixel color) { + return std::vector(size_t(width) * size_t(height), color); + } - GLuint TextureName() { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - return texture; - } + GLuint TextureName() { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + return texture; + } - void DefineTexture(GLuint texture, int width, int height, Pixel color, GLenum format = GL_RGBA8) { - const auto pixels = Pixels(width, height, color); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - } + void DefineTexture(GLuint texture, int width, int height, Pixel color, GLenum format = GL_RGBA8) { + const auto pixels = Pixels(width, height, color); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + } - static void Nearest(GLenum target) { - glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } + static void Nearest(GLenum target) { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } - GLuint NewTexture(int width, int height, Pixel color, GLenum format = GL_RGBA8) { - const GLuint texture = TextureName(); - DefineTexture(texture, width, height, color, format); - Nearest(GL_TEXTURE_2D); - return texture; - } + GLuint NewTexture(int width, int height, Pixel color, GLenum format = GL_RGBA8) { + const GLuint texture = TextureName(); + DefineTexture(texture, width, height, color, format); + Nearest(GL_TEXTURE_2D); + return texture; + } - GLuint NewArray() { - const GLuint texture = TextureName(); - glBindTexture(GL_TEXTURE_2D_ARRAY, texture); - glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); - Nearest(GL_TEXTURE_2D_ARRAY); - for (int level = 0; level < 2; ++level) { - const int size = 8 >> level; - const auto pixels = Pixels(size, size, black); - for (int layer = 0; layer < 2; ++layer) - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, layer, size, size, 1, - GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + GLuint NewArray() { + const GLuint texture = TextureName(); + glBindTexture(GL_TEXTURE_2D_ARRAY, texture); + glTexStorage3D(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA8, 8, 8, 2); + Nearest(GL_TEXTURE_2D_ARRAY); + for (int level = 0; level < 2; ++level) { + const int size = 8 >> level; + const auto pixels = Pixels(size, size, black); + for (int layer = 0; layer < 2; ++layer) + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, layer, size, size, 1, GL_RGBA, + GL_UNSIGNED_BYTE, pixels.data()); + } + return texture; + } + + GLuint NewFramebuffer(GLuint texture = 0) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + fbos.push_back(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + if (texture) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + } + return fbo; + } + + GLuint Link(const char* fragment) { + const GLuint program = glCreateProgram(); + programs.push_back(program); + const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; + const std::array sources{kVertex, fragment}; + for (size_t i = 0; i < kinds.size(); ++i) { + const GLuint shader = glCreateShader(kinds[i]); + glShaderSource(shader, 1, &sources[i], nullptr); + glCompileShader(shader); + GLint ok = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + std::array log{}; + if (!ok) glGetShaderInfoLog(shader, GLsizei(log.size()), nullptr, log.data()); + EXPECT_EQ(ok, GL_TRUE) << log.data(); + glAttachShader(program, shader); + glDeleteShader(shader); + } + glLinkProgram(program); + GLint ok = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + std::array log{}; + if (!ok) glGetProgramInfoLog(program, GLsizei(log.size()), nullptr, log.data()); + EXPECT_EQ(ok, GL_TRUE) << log.data(); + return ok ? program : 0; + } + + static void DrawTile(int tile) { + glViewport(tile * 8, 0, 8, kHeight); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + static void Tint(GLint location, Pixel color) { + glUniform4f(location, color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, color[3] / 255.0f); + } + + void ExpectPixel(int x, int y, Pixel expected, const char* why, int tolerance = 0) { + Pixel actual{}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); + for (size_t channel = 0; channel < actual.size(); ++channel) + EXPECT_NEAR(int(actual[channel]), int(expected[channel]), tolerance) + << why << " channel=" << channel; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; + } + }; + + TEST_F(MagmaWireCacheScenario, SamplerSwizzleAtoBtoAKeepsEachQueuedPixel) { + if (!Ready() || IsSkipped()) return; + const GLuint program = Link(kSample2D); + ASSERT_NE(program, 0u); + NewTexture(2, 2, red); + glUseProgram(program); + glBindFramebuffer(GL_FRAMEBUFFER, target); + const GLint identity[]{GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; + const GLint swapped[]{GL_GREEN, GL_RED, GL_BLUE, GL_ALPHA}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, identity); + DrawTile(0); + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swapped); + DrawTile(1); + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, identity); + DrawTile(2); + DrawTile(3); // The unchanged view is eligible for actual cache reuse too. + ExpectPixel(4, 4, red, "swizzle A before B"); + ExpectPixel(12, 4, green, "swizzle B has its own descriptor"); + ExpectPixel(20, 4, red, "swizzle A restored"); + ExpectPixel(28, 4, red, "unchanged A reused"); } - return texture; - } - GLuint NewFramebuffer(GLuint texture = 0) { - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - fbos.push_back(fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - if (texture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + TEST_F(MagmaWireCacheScenario, SamplerRespecifyKeepsOldAndNewNativeImages) { + if (!Ready() || IsSkipped()) return; + const GLuint program = Link(kSample2D); + ASSERT_NE(program, 0u); + const GLuint source = NewTexture(2, 2, red); + glUseProgram(program); + glBindFramebuffer(GL_FRAMEBUFFER, target); + DrawTile(0); + // TexImage may require a wire reply; it must still preserve the old GPU + // image referenced by the first draw. There is no intervening GPU readback. + DefineTexture(source, 4, 4, green); + DrawTile(1); + DefineTexture(source, 2, 2, blue); + DrawTile(2); + ExpectPixel(4, 4, red, "old allocation before first respecify"); + ExpectPixel(12, 4, green, "larger replacement allocation"); + ExpectPixel(20, 4, blue, "original dimensions with a new allocation"); } - return fbo; - } - GLuint Link(const char* fragment) { - const GLuint program = glCreateProgram(); - programs.push_back(program); - const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; - const std::array sources{kVertex, fragment}; - for (size_t i = 0; i < kinds.size(); ++i) { - const GLuint shader = glCreateShader(kinds[i]); - glShaderSource(shader, 1, &sources[i], nullptr); - glCompileShader(shader); - GLint ok = GL_FALSE; - glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); - std::array log{}; - if (!ok) glGetShaderInfoLog(shader, GLsizei(log.size()), nullptr, log.data()); - EXPECT_EQ(ok, GL_TRUE) << log.data(); - glAttachShader(program, shader); - glDeleteShader(shader); + TEST_F(MagmaWireCacheScenario, SamplerViewsDistinguishMipLayerAndViewType) { + if (!Ready() || IsSkipped()) return; + const GLuint sample2D = Link(kSample2D), sampleArray = Link(kSampleArray); + ASSERT_NE(sample2D, 0u); + ASSERT_NE(sampleArray, 0u); + const GLuint root = NewArray(); + const auto upload = [&](int level, int layer, Pixel color) { + const int size = 8 >> level; + const auto pixels = Pixels(size, size, color); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, layer, size, size, 1, GL_RGBA, GL_UNSIGNED_BYTE, + pixels.data()); + }; + upload(0, 0, red); + upload(0, 1, green); + upload(1, 1, yellow); + const GLuint viewA = TextureName(), viewB = TextureName(); + glTextureView(viewA, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 0, 1); + glTextureView(viewB, GL_TEXTURE_2D, root, GL_RGBA8, 1, 1, 1, 1); + for (GLuint view : {viewA, viewB}) { + glBindTexture(GL_TEXTURE_2D, view); + Nearest(GL_TEXTURE_2D); + } + glBindFramebuffer(GL_FRAMEBUFFER, target); + glUseProgram(sample2D); + glBindTexture(GL_TEXTURE_2D, viewA); + DrawTile(0); + glBindTexture(GL_TEXTURE_2D, viewB); + DrawTile(1); + glBindTexture(GL_TEXTURE_2D, viewA); + DrawTile(2); + glUseProgram(sampleArray); + glBindTexture(GL_TEXTURE_2D_ARRAY, root); + DrawTile(3); + ExpectPixel(4, 4, red, "2D view of root mip 0 layer 0"); + ExpectPixel(12, 4, yellow, "2D view of root mip 1 layer 1"); + ExpectPixel(20, 4, red, "first subresource view restored"); + ExpectPixel(28, 4, green, "array view type retains layer selection"); } - glLinkProgram(program); - GLint ok = GL_FALSE; - glGetProgramiv(program, GL_LINK_STATUS, &ok); - std::array log{}; - if (!ok) glGetProgramInfoLog(program, GLsizei(log.size()), nullptr, log.data()); - EXPECT_EQ(ok, GL_TRUE) << log.data(); - return ok ? program : 0; - } - static void DrawTile(int tile) { - glViewport(tile * 8, 0, 8, kHeight); - glDrawArrays(GL_TRIANGLES, 0, 3); - } + TEST_F(MagmaWireCacheScenario, FramebuffersAtoBtoAKeepDistinctAttachments) { + if (!Ready() || IsSkipped()) return; + const GLuint program = Link(kTint); + ASSERT_NE(program, 0u); + const GLint tint = glGetUniformLocation(program, "tint"); + ASSERT_GE(tint, 0); + const GLuint a = NewFramebuffer(NewTexture(kWidth, kHeight, black)); + const GLuint b = NewFramebuffer(NewTexture(kWidth, kHeight, black)); + glUseProgram(program); + glBindFramebuffer(GL_FRAMEBUFFER, a); + Tint(tint, red); + DrawTile(0); + glBindFramebuffer(GL_FRAMEBUFFER, b); + Tint(tint, green); + DrawTile(1); + glBindFramebuffer(GL_FRAMEBUFFER, a); + Tint(tint, blue); + DrawTile(2); + ExpectPixel(4, 4, red, "A retains its first draw"); + ExpectPixel(12, 4, black, "B draw did not use A's compatible framebuffer"); + ExpectPixel(20, 4, blue, "A selected again after B"); + glBindFramebuffer(GL_FRAMEBUFFER, b); + ExpectPixel(4, 4, black, "first A draw did not reach B"); + ExpectPixel(12, 4, green, "B owns its draw"); + ExpectPixel(20, 4, black, "last A draw did not reach B"); + } - static void Tint(GLint location, Pixel color) { - glUniform4f(location, color[0] / 255.0f, color[1] / 255.0f, - color[2] / 255.0f, color[3] / 255.0f); - } + TEST_F(MagmaWireCacheScenario, FramebufferReattachmentDistinguishesMipAndLayer) { + if (!Ready() || IsSkipped()) return; + const GLuint program = Link(kTint); + ASSERT_NE(program, 0u); + const GLint tint = glGetUniformLocation(program, "tint"); + ASSERT_GE(tint, 0); + const GLuint root = NewArray(); + NewFramebuffer(); + const auto attach = [&](int level, int layer) { + glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, level, layer); + }; + glUseProgram(program); + attach(0, 0); + glViewport(0, 0, 8, 8); + Tint(tint, red); + glDrawArrays(GL_TRIANGLES, 0, 3); + attach(1, 1); + glViewport(0, 0, 4, 4); + Tint(tint, green); + glDrawArrays(GL_TRIANGLES, 0, 3); + attach(0, 1); + glViewport(0, 0, 8, 8); + Tint(tint, blue); + glDrawArrays(GL_TRIANGLES, 0, 3); + attach(0, 0); + glViewport(0, 0, 4, 8); + Tint(tint, yellow); + glDrawArrays(GL_TRIANGLES, 0, 3); + ExpectPixel(2, 4, yellow, "restored mip 0 layer 0 left half"); + ExpectPixel(6, 4, red, "restored mip 0 layer 0 preserves first draw"); + attach(1, 1); + ExpectPixel(2, 2, green, "smaller mip 1 layer 1 attachment"); + attach(0, 1); + ExpectPixel(6, 4, blue, "same mip with a different layer"); + attach(1, 0); + ExpectPixel(2, 2, black, "untouched mip 1 layer 0"); + } - void ExpectPixel(int x, int y, Pixel expected, const char* why, int tolerance = 0) { - Pixel actual{}; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); - for (size_t channel = 0; channel < actual.size(); ++channel) - EXPECT_NEAR(int(actual[channel]), int(expected[channel]), tolerance) << why << " channel=" << channel; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; - } -}; + TEST_F(MagmaWireCacheScenario, FramebufferSrgbPolicyAtoBtoAUsesDistinctFormats) { + if (!Ready() || IsSkipped()) return; + const GLuint program = Link(kTint); + ASSERT_NE(program, 0u); + const GLint tint = glGetUniformLocation(program, "tint"); + ASSERT_GE(tint, 0); + NewFramebuffer(NewTexture(kWidth, kHeight, black, GL_SRGB8_ALPHA8)); + glUseProgram(program); + glUniform4f(tint, 0.5f, 0.5f, 0.5f, 1.0f); + DrawTile(0); + glEnable(GL_FRAMEBUFFER_SRGB); + DrawTile(1); + glDisable(GL_FRAMEBUFFER_SRGB); + DrawTile(2); + ExpectPixel(4, 4, Pixel{128, 128, 128, 255}, "linear writes before conversion", 1); + ExpectPixel(12, 4, Pixel{188, 188, 188, 255}, "sRGB attachment write conversion", 1); + ExpectPixel(20, 4, Pixel{128, 128, 128, 255}, "linear attachment format restored", 1); + } -TEST_F(MagmaWireCacheScenario, SamplerSwizzleAtoBtoAKeepsEachQueuedPixel) { - if (!Ready() || IsSkipped()) return; - const GLuint program = Link(kSample2D); - ASSERT_NE(program, 0u); - NewTexture(2, 2, red); - glUseProgram(program); - glBindFramebuffer(GL_FRAMEBUFFER, target); - const GLint identity[]{GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA}; - const GLint swapped[]{GL_GREEN, GL_RED, GL_BLUE, GL_ALPHA}; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, identity); - DrawTile(0); - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swapped); - DrawTile(1); - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, identity); - DrawTile(2); - DrawTile(3); // The unchanged view is eligible for actual cache reuse too. - ExpectPixel(4, 4, red, "swizzle A before B"); - ExpectPixel(12, 4, green, "swizzle B has its own descriptor"); - ExpectPixel(20, 4, red, "swizzle A restored"); - ExpectPixel(28, 4, red, "unchanged A reused"); -} - -TEST_F(MagmaWireCacheScenario, SamplerRespecifyKeepsOldAndNewNativeImages) { - if (!Ready() || IsSkipped()) return; - const GLuint program = Link(kSample2D); - ASSERT_NE(program, 0u); - const GLuint source = NewTexture(2, 2, red); - glUseProgram(program); - glBindFramebuffer(GL_FRAMEBUFFER, target); - DrawTile(0); - // TexImage may require a wire reply; it must still preserve the old GPU - // image referenced by the first draw. There is no intervening GPU readback. - DefineTexture(source, 4, 4, green); - DrawTile(1); - DefineTexture(source, 2, 2, blue); - DrawTile(2); - ExpectPixel(4, 4, red, "old allocation before first respecify"); - ExpectPixel(12, 4, green, "larger replacement allocation"); - ExpectPixel(20, 4, blue, "original dimensions with a new allocation"); -} - -TEST_F(MagmaWireCacheScenario, SamplerViewsDistinguishMipLayerAndViewType) { - if (!Ready() || IsSkipped()) return; - const GLuint sample2D = Link(kSample2D), sampleArray = Link(kSampleArray); - ASSERT_NE(sample2D, 0u); - ASSERT_NE(sampleArray, 0u); - const GLuint root = NewArray(); - const auto upload = [&](int level, int layer, Pixel color) { - const int size = 8 >> level; - const auto pixels = Pixels(size, size, color); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, layer, size, size, 1, - GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - }; - upload(0, 0, red); - upload(0, 1, green); - upload(1, 1, yellow); - const GLuint viewA = TextureName(), viewB = TextureName(); - glTextureView(viewA, GL_TEXTURE_2D, root, GL_RGBA8, 0, 1, 0, 1); - glTextureView(viewB, GL_TEXTURE_2D, root, GL_RGBA8, 1, 1, 1, 1); - for (GLuint view : {viewA, viewB}) { - glBindTexture(GL_TEXTURE_2D, view); - Nearest(GL_TEXTURE_2D); - } - glBindFramebuffer(GL_FRAMEBUFFER, target); - glUseProgram(sample2D); - glBindTexture(GL_TEXTURE_2D, viewA); - DrawTile(0); - glBindTexture(GL_TEXTURE_2D, viewB); - DrawTile(1); - glBindTexture(GL_TEXTURE_2D, viewA); - DrawTile(2); - glUseProgram(sampleArray); - glBindTexture(GL_TEXTURE_2D_ARRAY, root); - DrawTile(3); - ExpectPixel(4, 4, red, "2D view of root mip 0 layer 0"); - ExpectPixel(12, 4, yellow, "2D view of root mip 1 layer 1"); - ExpectPixel(20, 4, red, "first subresource view restored"); - ExpectPixel(28, 4, green, "array view type retains layer selection"); -} - -TEST_F(MagmaWireCacheScenario, FramebuffersAtoBtoAKeepDistinctAttachments) { - if (!Ready() || IsSkipped()) return; - const GLuint program = Link(kTint); - ASSERT_NE(program, 0u); - const GLint tint = glGetUniformLocation(program, "tint"); - ASSERT_GE(tint, 0); - const GLuint a = NewFramebuffer(NewTexture(kWidth, kHeight, black)); - const GLuint b = NewFramebuffer(NewTexture(kWidth, kHeight, black)); - glUseProgram(program); - glBindFramebuffer(GL_FRAMEBUFFER, a); Tint(tint, red); DrawTile(0); - glBindFramebuffer(GL_FRAMEBUFFER, b); Tint(tint, green); DrawTile(1); - glBindFramebuffer(GL_FRAMEBUFFER, a); Tint(tint, blue); DrawTile(2); - ExpectPixel(4, 4, red, "A retains its first draw"); - ExpectPixel(12, 4, black, "B draw did not use A's compatible framebuffer"); - ExpectPixel(20, 4, blue, "A selected again after B"); - glBindFramebuffer(GL_FRAMEBUFFER, b); - ExpectPixel(4, 4, black, "first A draw did not reach B"); - ExpectPixel(12, 4, green, "B owns its draw"); - ExpectPixel(20, 4, black, "last A draw did not reach B"); -} - -TEST_F(MagmaWireCacheScenario, FramebufferReattachmentDistinguishesMipAndLayer) { - if (!Ready() || IsSkipped()) return; - const GLuint program = Link(kTint); - ASSERT_NE(program, 0u); - const GLint tint = glGetUniformLocation(program, "tint"); - ASSERT_GE(tint, 0); - const GLuint root = NewArray(); - NewFramebuffer(); - const auto attach = [&](int level, int layer) { - glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, root, level, layer); - }; - glUseProgram(program); - attach(0, 0); glViewport(0, 0, 8, 8); Tint(tint, red); glDrawArrays(GL_TRIANGLES, 0, 3); - attach(1, 1); glViewport(0, 0, 4, 4); Tint(tint, green); glDrawArrays(GL_TRIANGLES, 0, 3); - attach(0, 1); glViewport(0, 0, 8, 8); Tint(tint, blue); glDrawArrays(GL_TRIANGLES, 0, 3); - attach(0, 0); glViewport(0, 0, 4, 8); Tint(tint, yellow); glDrawArrays(GL_TRIANGLES, 0, 3); - ExpectPixel(2, 4, yellow, "restored mip 0 layer 0 left half"); - ExpectPixel(6, 4, red, "restored mip 0 layer 0 preserves first draw"); - attach(1, 1); ExpectPixel(2, 2, green, "smaller mip 1 layer 1 attachment"); - attach(0, 1); ExpectPixel(6, 4, blue, "same mip with a different layer"); - attach(1, 0); ExpectPixel(2, 2, black, "untouched mip 1 layer 0"); -} - -TEST_F(MagmaWireCacheScenario, FramebufferSrgbPolicyAtoBtoAUsesDistinctFormats) { - if (!Ready() || IsSkipped()) return; - const GLuint program = Link(kTint); - ASSERT_NE(program, 0u); - const GLint tint = glGetUniformLocation(program, "tint"); - ASSERT_GE(tint, 0); - NewFramebuffer(NewTexture(kWidth, kHeight, black, GL_SRGB8_ALPHA8)); - glUseProgram(program); - glUniform4f(tint, 0.5f, 0.5f, 0.5f, 1.0f); - DrawTile(0); - glEnable(GL_FRAMEBUFFER_SRGB); DrawTile(1); - glDisable(GL_FRAMEBUFFER_SRGB); DrawTile(2); - ExpectPixel(4, 4, Pixel{128, 128, 128, 255}, "linear writes before conversion", 1); - ExpectPixel(12, 4, Pixel{188, 188, 188, 255}, "sRGB attachment write conversion", 1); - ExpectPixel(20, 4, Pixel{128, 128, 128, 255}, "linear attachment format restored", 1); -} - -TEST_F(MagmaWireCacheScenario, FramebufferRespecifyRebindsTheNewNativeImage) { - if (!Ready() || IsSkipped()) return; - const GLuint paint = Link(kTint), sample = Link(kSample2D); - ASSERT_NE(paint, 0u); - ASSERT_NE(sample, 0u); - const GLint tint = glGetUniformLocation(paint, "tint"); - ASSERT_GE(tint, 0); - const GLuint source = NewTexture(8, 8, black), sourceFbo = NewFramebuffer(source); - const auto drawThenSample = [&](Pixel color, int width, int tile) { - glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo); - glUseProgram(paint); Tint(tint, color); - glViewport(0, 0, width, 8); glDrawArrays(GL_TRIANGLES, 0, 3); - glBindFramebuffer(GL_FRAMEBUFFER, target); - glUseProgram(sample); glBindTexture(GL_TEXTURE_2D, source); DrawTile(tile); - }; - drawThenSample(red, 8, 0); - DefineTexture(source, 16, 8, black); - drawThenSample(green, 16, 1); - DefineTexture(source, 8, 8, black); - drawThenSample(blue, 8, 2); - ExpectPixel(4, 4, red, "pixels produced by the original attachment allocation"); - ExpectPixel(12, 4, green, "same FBO writes the larger replacement allocation"); - ExpectPixel(20, 4, blue, "same dimensions cannot revive the retired attachment"); -} -} // namespace + TEST_F(MagmaWireCacheScenario, FramebufferRespecifyRebindsTheNewNativeImage) { + if (!Ready() || IsSkipped()) return; + const GLuint paint = Link(kTint), sample = Link(kSample2D); + ASSERT_NE(paint, 0u); + ASSERT_NE(sample, 0u); + const GLint tint = glGetUniformLocation(paint, "tint"); + ASSERT_GE(tint, 0); + const GLuint source = NewTexture(8, 8, black), sourceFbo = NewFramebuffer(source); + const auto drawThenSample = [&](Pixel color, int width, int tile) { + glBindFramebuffer(GL_FRAMEBUFFER, sourceFbo); + glUseProgram(paint); + Tint(tint, color); + glViewport(0, 0, width, 8); + glDrawArrays(GL_TRIANGLES, 0, 3); + glBindFramebuffer(GL_FRAMEBUFFER, target); + glUseProgram(sample); + glBindTexture(GL_TEXTURE_2D, source); + DrawTile(tile); + }; + drawThenSample(red, 8, 0); + DefineTexture(source, 16, 8, black); + drawThenSample(green, 16, 1); + DefineTexture(source, 8, 8, black); + drawThenSample(blue, 8, 2); + ExpectPixel(4, 4, red, "pixels produced by the original attachment allocation"); + ExpectPixel(12, 4, green, "same FBO writes the larger replacement allocation"); + ExpectPixel(20, 4, blue, "same dimensions cannot revive the retired attachment"); + } + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireReclaimScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireReclaimScenario.cpp index 729a1e01..5f131415 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireReclaimScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/MagmaWireReclaimScenario.cpp @@ -101,7 +101,7 @@ namespace MGITest { constexpr std::size_t kAbaPinStoreBytes = static_cast(kMiB); constexpr std::size_t kAbaNewStoreBytes = 64 * 1024; constexpr GLuint64 kAbaWaitNs = 2000000000ull; // 2 s: a 64x16 draw on lavapipe retires in ms - constexpr int kDescriptorDraws = 2049; // one past UniformManager's wire epoch budget + constexpr int kDescriptorDraws = 2049; // one past UniformManager's wire epoch budget constexpr const char* kVertex = R"(#version 330 core layout(location = 0) in vec2 aPos; @@ -134,7 +134,8 @@ void main() { fragColor = color[0]; })"; const float l = -1.0f + 2.0f * static_cast(x0) / kWidth; const float r = -1.0f + 2.0f * static_cast(x1) / kWidth; const float quad[12] = {l, -1.0f, r, -1.0f, r, 1.0f, l, -1.0f, r, 1.0f, l, 1.0f}; - for (int i = 0; i < 12; ++i) out[i] = quad[i]; + for (int i = 0; i < 12; ++i) + out[i] = quad[i]; } // A colour per strip that no neighbour shares and the clear (all zero) is not. @@ -171,7 +172,8 @@ void main() { fragColor = color[0]; })"; GTEST_SKIP() << "the reclaim under test is the Magma wire arm's VkBufferManager"; } if (std::getenv(kLaneMarker) == nullptr) { - GTEST_SKIP() << kLaneMarker << " is not set: this run has no private stats " + GTEST_SKIP() << kLaneMarker + << " is not set: this run has no private stats " "channel to read the server's wbuf[] gauges from"; } const SplitRuntimeState runtime = PeekSplitRuntime(); @@ -270,7 +272,8 @@ void main() { fragColor = color[0]; })"; ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); EXPECT_TRUE(Near(image.At(kWidth / 2, kHeight / 2), StripColor(7))) << "read " << image.At(kWidth / 2, kHeight / 2) << ", wanted " << StripColor(7) - << ": the draw from the last of " << kUndrawnRespecifies << " respecified stores did " + << ": the draw from the last of " << kUndrawnRespecifies + << " respecified stores did " "not land - the store it names was reclaimed, or never reached"; const PipeStatsWindow::Window window = CloseFrameAndReadServerWindow(mark); @@ -286,7 +289,8 @@ void main() { fragColor = color[0]; })"; << "the Magma server held " << livePeak << " VkBuffers at once for " << wireBuffers << " wire buffer record(s) inside one frame of " << kUndrawnRespecifies << " glBufferData calls. Orphans no GPU command named are dead at the park; a count " - "that tracks the respecifies is the bsl-esc-menu-854 leak (ID-P7-32). " << window.line; + "that tracks the respecifies is the bsl-esc-menu-854 leak (ID-P7-32). " + << window.line; } // -------------------------------------------------------------------------------------- @@ -340,15 +344,14 @@ void main() { fragColor = color[0]; })"; // One store past the budget is the most a park can add before the watermark answers it. const long long deferredBound = static_cast(budget + kDrawnStoreBytes); EXPECT_LE(deferredPeak, deferredBound) - << "the Magma server parked " << deferredPeak << " bytes of orphaned stores inside one " - "frame against a MOBILEGL_IPC_WIRE_DEFERRED_MB budget of " << budget - << " bytes (+ one " << kDrawnStoreBytes << "-byte store). " << window.line; - const long long liveBound = - wireBuffers + static_cast(budget / kDrawnStoreBytes) + 2; + << "the Magma server parked " << deferredPeak + << " bytes of orphaned stores inside one " + "frame against a MOBILEGL_IPC_WIRE_DEFERRED_MB budget of " + << budget << " bytes (+ one " << kDrawnStoreBytes << "-byte store). " << window.line; + const long long liveBound = wireBuffers + static_cast(budget / kDrawnStoreBytes) + 2; EXPECT_LE(livePeak, liveBound) - << "the Magma server held " << livePeak << " VkBuffers at once; the records (" - << wireBuffers << ") plus a budget's worth of parked stores allow " << liveBound - << ". " << window.line; + << "the Magma server held " << livePeak << " VkBuffers at once; the records (" << wireBuffers + << ") plus a budget's worth of parked stores allow " << liveBound << ". " << window.line; } // -------------------------------------------------------------------------------------- @@ -370,8 +373,8 @@ void main() { fragColor = color[0]; })"; ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); const Rgba8 want = StripColor((kSmallDrawnRespecifies - 1) % 200); EXPECT_TRUE(Near(image.At(kWidth / 2, kHeight / 2), want)) - << "read " << image.At(kWidth / 2, kHeight / 2) << ", wanted " << want - << ": the last of " << kSmallDrawnRespecifies << " draws did not land on top"; + << "read " << image.At(kWidth / 2, kHeight / 2) << ", wanted " << want << ": the last of " + << kSmallDrawnRespecifies << " draws did not land on top"; const PipeStatsWindow::Window window = CloseFrameAndReadServerWindow(mark); ASSERT_TRUE(window.found) << "no 'MGPipe stats:' window in the server log " @@ -390,9 +393,10 @@ void main() { fragColor = color[0]; })"; // records is the most the arm can hold. const long long liveBound = wireBuffers + kDeferredCountCeiling + 2; EXPECT_LE(livePeak, liveBound) - << "the Magma server held " << livePeak << " VkBuffers at once after " - << kSmallDrawnRespecifies << " small respecify-and-draw rounds in one frame; the " - "records plus the " << kDeferredCountCeiling << "-store ceiling allow " << liveBound + << "the Magma server held " << livePeak << " VkBuffers at once after " << kSmallDrawnRespecifies + << " small respecify-and-draw rounds in one frame; the " + "records plus the " + << kDeferredCountCeiling << "-store ceiling allow " << liveBound << ". A count that tracks the rounds is the bsl-esc-menu-854 shape. " << window.line; } @@ -484,8 +488,10 @@ void main() { fragColor = color[0]; })"; // Step 5: D1's batch retires; the memos survive because step 4 is still recorded. const GLenum waited = glClientWaitSync(fence, 0, kAbaWaitNs); ASSERT_TRUE(waited == GL_ALREADY_SIGNALED || waited == GL_CONDITION_SATISFIED) - << "glClientWaitSync returned " << waited << ": the batch that drew from the first " - "store did not retire within " << (kAbaWaitNs / 1000000) << " ms"; + << "glClientWaitSync returned " << waited + << ": the batch that drew from the first " + "store did not retire within " + << (kAbaWaitNs / 1000000) << " ms"; // Step 6: the vertex store's park sweeps S1 out - the destroy the memos must notice. glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STREAM_DRAW); // Step 7: a new store through the freed handle value, holding B. @@ -513,8 +519,9 @@ void main() { fragColor = color[0]; })"; glDeleteBuffers(1, &ubo); glDeleteProgram(program); const PipeStatsWindow::Window window = CloseFrameAndReadServerWindow(mark); - if (window.found) RecordProperty("wire_stores_peak", - static_cast(PipeStatsWindow::CounterOrAbsent(window, "wlivepk"))); + if (window.found) + RecordProperty("wire_stores_peak", + static_cast(PipeStatsWindow::CounterOrAbsent(window, "wlivepk"))); } // M3: a fixed SSBO store and one live layout, but 2049 distinct aligned @@ -551,8 +558,7 @@ void main() { fragColor = color[0]; })"; glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast(bytes.size()), bytes.data(), GL_STATIC_DRAW); glUseProgram(program); for (int i = 0; i < kDescriptorDraws; ++i) { - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, storage, - static_cast(stride * i), 16); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, storage, static_cast(stride * i), 16); glDrawArrays(GL_TRIANGLES, 0, 6); } const Image image = ReadPixels(kWidth, kHeight); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/MultiDrawScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/MultiDrawScenario.cpp index 4c221aec..fc6d313d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/MultiDrawScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/MultiDrawScenario.cpp @@ -538,8 +538,7 @@ void main() { EXPECT_EQ(FirstGLError(), expected) << what; // FirstGLError stops at the first one; make sure nothing else is queued so the // next case starts clean. - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} }; glDrawElementsBaseVertex(GL_TRIANGLES, -1, GL_UNSIGNED_INT, nullptr, 0); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/NonCoreImageFormatScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/NonCoreImageFormatScenario.cpp index a037f3da..eae325a9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/NonCoreImageFormatScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/NonCoreImageFormatScenario.cpp @@ -71,8 +71,10 @@ namespace MGITest { void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint t : m_textures) glDeleteTextures(1, &t); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint t : m_textures) + glDeleteTextures(1, &t); m_programs.clear(); m_textures.clear(); GLint maxImageUnits = 0; @@ -80,8 +82,7 @@ namespace MGITest { for (GLint unit = 0; unit < maxImageUnits; ++unit) { glBindImageTexture(static_cast(unit), 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI); } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } bool ImagesAreUsable() const { @@ -89,8 +90,7 @@ namespace MGITest { glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits > static_cast(kWideUnit) && maxComputeImageUniforms >= 2; } @@ -129,8 +129,7 @@ namespace MGITest { // with a value no dispatch writes, so "the store never happened" and "the store wrote // the right thing" cannot be confused - which matters here more than usual, because // the failure this scenario exists for is a dispatch that silently does nothing. - GLuint MakeTexture(GLenum internalFormat, GLenum uploadFormat, GLenum uploadType, - const void* seed) { + GLuint MakeTexture(GLenum internalFormat, GLenum uploadFormat, GLenum uploadType, const void* seed) { GLuint texture = 0; glGenTextures(1, &texture); m_textures.push_back(texture); @@ -145,15 +144,13 @@ namespace MGITest { if (seed != nullptr) { glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kExtent, kExtent, uploadFormat, uploadType, seed); } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return texture; } void BindImage(GLuint unit, GLuint texture, GLenum internalFormat, GLenum access) { glBindImageTexture(unit, texture, 0, GL_FALSE, 0, access, internalFormat); - ASSERT_EQ(FirstGLError(), 0u) - << "glBindImageTexture refused format " << std::hex << internalFormat; + ASSERT_EQ(FirstGLError(), 0u) << "glBindImageTexture refused format " << std::hex << internalFormat; } void Dispatch(GLuint program) { @@ -168,8 +165,7 @@ namespace MGITest { std::vector m_textures; std::vector ReadFloats(GLuint texture, GLenum format, int componentsPerTexel) { - std::vector texels(static_cast(kExtent) * kExtent * componentsPerTexel, - -12345.0f); + std::vector texels(static_cast(kExtent) * kExtent * componentsPerTexel, -12345.0f); glBindTexture(GL_TEXTURE_2D, texture); glGetTexImage(GL_TEXTURE_2D, 0, format, GL_FLOAT, texels.data()); if (const GLenum error = FirstGLError()) { @@ -182,14 +178,13 @@ namespace MGITest { // addressed as array layers. The target the allTargets walkers reach last and the one // that has caught the most emulation bugs, because it is the only one whose ES // equivalent is a 2D array with a different addressing rule from the GL name. - GLuint MakeCubeArrayTexture(GLenum internalFormat, GLenum uploadFormat, GLenum uploadType, - const void* seed, int cubeCount) { + GLuint MakeCubeArrayTexture(GLenum internalFormat, GLenum uploadFormat, GLenum uploadType, const void* seed, + int cubeCount) { GLuint texture = 0; glGenTextures(1, &texture); m_textures.push_back(texture); glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture); - glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, internalFormat, kExtent, kExtent, - 6 * cubeCount); + glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, internalFormat, kExtent, kExtent, 6 * cubeCount); if (const GLenum error = FirstGLError()) { ADD_FAILURE() << "allocating cube-array storage errored with " << GLErrorName(error); return 0; @@ -197,11 +192,10 @@ namespace MGITest { glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); if (seed != nullptr) { - glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kExtent, kExtent, - 6 * cubeCount, uploadFormat, uploadType, seed); - } - while (glGetError() != GL_NO_ERROR) { + glTexSubImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, 0, 0, 0, kExtent, kExtent, 6 * cubeCount, + uploadFormat, uploadType, seed); } + while (glGetError() != GL_NO_ERROR) {} return texture; } @@ -212,8 +206,7 @@ namespace MGITest { } // Sets `name` from `values`, which must hold 4 * count floats. - void SetVec4Array(GLuint program, const char* name, const std::vector& values, - int count) { + void SetVec4Array(GLuint program, const char* name, const std::vector& values, int count) { glUseProgram(program); const GLint location = glGetUniformLocation(program, name); ASSERT_GE(location, 0) << "the uniform array '" << name << "' was not reflected"; @@ -222,10 +215,9 @@ namespace MGITest { glUseProgram(0); } - std::vector ReadFloatsFrom(GLenum target, GLuint texture, GLenum format, - int componentsPerTexel, int texelCount) { - std::vector texels(static_cast(texelCount) * componentsPerTexel, - -12345.0f); + std::vector ReadFloatsFrom(GLenum target, GLuint texture, GLenum format, int componentsPerTexel, + int texelCount) { + std::vector texels(static_cast(texelCount) * componentsPerTexel, -12345.0f); glBindTexture(target, texture); glGetTexImage(target, 0, format, GL_FLOAT, texels.data()); if (const GLenum error = FirstGLError()) { @@ -559,8 +551,7 @@ void main() EXPECT_EQ(loaded[texel * 4 + 2], 0u) << "texel " << texel << ": imageLoad on a one-channel format must report 0 for blue"; EXPECT_EQ(loaded[texel * 4 + 3], 1u) - << "texel " << texel - << ": imageLoad on an INTEGER format without alpha must report the integer 1"; + << "texel " << texel << ": imageLoad on an INTEGER format without alpha must report the integer 1"; } } @@ -645,16 +636,25 @@ void main() long code; }; const Boundary boundaries[kTexels] = { - {0.0f, 0}, {1.0f, 65535}, {0.5f, 32768}, {0.25f, 16384}, - {0.75f, 49151}, {-0.5f, 0}, {2.0f, 65535}, {-1.0f, 0}, - {1.0f / 131072.0f, 0}, // 0.4999923 of a code: rounds DOWN - {3.0f / 131072.0f, 1}, // 1.4999771 of a code: rounds DOWN to 1 - {1.0f / 65535.0f, 1}, // exactly one code - {32767.0f / 65535.0f, 32767}, {32768.0f / 65535.0f, 32768}, + {0.0f, 0}, + {1.0f, 65535}, + {0.5f, 32768}, + {0.25f, 16384}, + {0.75f, 49151}, + {-0.5f, 0}, + {2.0f, 65535}, + {-1.0f, 0}, + {1.0f / 131072.0f, 0}, // 0.4999923 of a code: rounds DOWN + {3.0f / 131072.0f, 1}, // 1.4999771 of a code: rounds DOWN to 1 + {1.0f / 65535.0f, 1}, // exactly one code + {32767.0f / 65535.0f, 32767}, + {32768.0f / 65535.0f, 32768}, // 0.125 * 65535 = 8191.875 and 0.875 * 65535 = 57343.125 - neither is a tie, and // both round DOWN, which is the pair that catches a conversion that scaled by 2^b // instead of 2^b - 1. - {65534.0f / 65535.0f, 65534}, {0.125f, 8192}, {0.875f, 57343}, + {65534.0f / 65535.0f, 65534}, + {0.125f, 8192}, + {0.875f, 57343}, }; std::vector values(static_cast(kTexels) * 4u, 0.0f); for (int texel = 0; texel < kTexels; ++texel) { @@ -710,13 +710,12 @@ void main() // The seed walks the whole signed range, INCLUDING -32768, whose decode is the one // value the division alone gets wrong. - const GLshort seedCodes[kTexels] = {0, 32767, -32767, -32768, 1, -1, 16384, -16384, - 12345, -12345, 32766, -32766, 255, -256, 4095, -4096}; + const GLshort seedCodes[kTexels] = {0, 32767, -32767, -32768, 1, -1, 16384, -16384, + 12345, -12345, 32766, -32766, 255, -256, 4095, -4096}; std::vector seed(static_cast(kTexels) * 4u, 0); for (int texel = 0; texel < kTexels; ++texel) { seed[texel * 4 + 0] = seedCodes[texel]; - seed[texel * 4 + 1] = static_cast(-seedCodes[texel] == -32768 ? 32767 - : -seedCodes[texel]); + seed[texel * 4 + 1] = static_cast(-seedCodes[texel] == -32768 ? 32767 : -seedCodes[texel]); seed[texel * 4 + 2] = seedCodes[(texel + 1) % kTexels]; seed[texel * 4 + 3] = seedCodes[(texel + 2) % kTexels]; } @@ -761,8 +760,7 @@ void main() for (int texel = 0; texel < kTexels; ++texel) { for (int channel = 0; channel < 4; ++channel) { const GLshort code = seed[texel * 4 + channel]; - const float expected = - std::max(static_cast(code) / static_cast(kSnorm16Max), -1.0f); + const float expected = std::max(static_cast(code) / static_cast(kSnorm16Max), -1.0f); EXPECT_FLOAT_EQ(loaded[texel * 4 + channel], expected) << "texel " << texel << " channel " << channel << " code " << code; } @@ -775,17 +773,27 @@ void main() long code; }; const Boundary boundaries[kTexels] = { - {0.0f, 0}, {1.0f, 32767}, {-1.0f, -32767}, {0.5f, 16384}, - {-0.5f, -16384}, {2.0f, 32767}, {-2.0f, -32767}, {0.25f, 8192}, - {-0.25f, -8192}, {1.0f / 32767.0f, 1}, {-1.0f / 32767.0f, -1}, + {0.0f, 0}, + {1.0f, 32767}, + {-1.0f, -32767}, + {0.5f, 16384}, + {-0.5f, -16384}, + {2.0f, 32767}, + {-2.0f, -32767}, + {0.25f, 8192}, + {-0.25f, -8192}, + {1.0f / 32767.0f, 1}, + {-1.0f / 32767.0f, -1}, // Three quarters of a code, not half: GL leaves the direction of a TIE to the // implementation ("if two values are equally near, the implementation may choose // either"), and Magma hands these formats to Vulkan unemulated, so a value exactly // on 0.5 of a code is the one thing the two backends are allowed to disagree // about. Every entry here is off a tie except the ones at 0.5 and 0.25 of the // RANGE, whose products (16383.5 and 8192) round the same way under either rule. - {0.75f / 32767.0f, 1}, {-0.75f / 32767.0f, -1}, - {16383.0f / 32767.0f, 16383}, {-16383.0f / 32767.0f, -16383}, + {0.75f / 32767.0f, 1}, + {-0.75f / 32767.0f, -1}, + {16383.0f / 32767.0f, 16383}, + {-16383.0f / 32767.0f, -16383}, {0.125f, 4096}, }; std::vector values(static_cast(kTexels) * 4u, 0.0f); @@ -806,10 +814,8 @@ void main() loaded = ReadFloats(wide, GL_RGBA, 4); for (int texel = 0; texel < kTexels; ++texel) { for (int channel = 0; channel < 4; ++channel) { - EXPECT_EQ(std::lround(loaded[texel * 4 + channel] * kSnorm16Max), - boundaries[texel].code) - << "texel " << texel << " channel " << channel << " stored " - << boundaries[texel].value; + EXPECT_EQ(std::lround(loaded[texel * 4 + channel] * kSnorm16Max), boundaries[texel].code) + << "texel " << texel << " channel " << channel << " stored " << boundaries[texel].value; } } } @@ -837,8 +843,7 @@ void main() seedCodes[texel * 4 + 3] = a; } const std::vector wideSeed(static_cast(kTexels) * 4u, -1.0f); - const GLuint narrow = - MakeTexture(GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, seed.data()); + const GLuint narrow = MakeTexture(GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, seed.data()); const GLuint wide = MakeTexture(GL_RGBA32F, GL_RGBA, GL_FLOAT, wideSeed.data()); if (narrow == 0 || wide == 0) return; @@ -910,8 +915,7 @@ void main() if (!ImagesAreUsable()) GTEST_SKIP() << "no image load/store on this driver"; GLint maxComputeImageUniforms = 0; glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} constexpr int kCubes = 2; constexpr int kLayerFaces = 6 * kCubes; @@ -923,16 +927,14 @@ void main() for (int texel = 0; texel < kTexels; ++texel) { seed[texel] = static_cast((texel * 5477u) & 0xFFFFu); } - const GLuint narrow = - MakeCubeArrayTexture(GL_R16, GL_RED, GL_UNSIGNED_SHORT, seed.data(), kCubes); + const GLuint narrow = MakeCubeArrayTexture(GL_R16, GL_RED, GL_UNSIGNED_SHORT, seed.data(), kCubes); if (narrow == 0) return; // THE UPLOAD, read back through glGetTexImage across every layer-face. A carrier that // widened the storage but seeded only the first face leaves the rest at zero, which is // what the per-layer readback path is there to catch, and this target is the only one // whose readback goes layer by layer. - const std::vector uploaded = - ReadFloatsFrom(GL_TEXTURE_CUBE_MAP_ARRAY, narrow, GL_RED, 1, kTexels); + const std::vector uploaded = ReadFloatsFrom(GL_TEXTURE_CUBE_MAP_ARRAY, narrow, GL_RED, 1, kTexels); for (int texel = 0; texel < kTexels; ++texel) { EXPECT_EQ(std::lround(uploaded[texel] * kUnorm16Max), seed[texel]) << "texel " << texel << " of " << kTexels; @@ -1011,8 +1013,7 @@ void main() if (!ImagesAreUsable()) GTEST_SKIP() << "no image load/store on this driver"; GLint maxTextureBufferSize = 0; glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} if (maxTextureBufferSize <= 0) GTEST_SKIP() << "no buffer textures on this driver"; constexpr int kBufferTexels = 4; @@ -1059,20 +1060,18 @@ void main() std::vector readback(seed.size(), -12345.0f); glBindBuffer(GL_TEXTURE_BUFFER, buffer); - glGetBufferSubData(GL_TEXTURE_BUFFER, 0, - static_cast(readback.size() * sizeof(float)), readback.data()); + glGetBufferSubData(GL_TEXTURE_BUFFER, 0, static_cast(readback.size() * sizeof(float)), + readback.data()); EXPECT_EQ(FirstGLError(), 0u) << "reading the buffer back errored"; for (int texel = 0; texel < kBufferTexels; ++texel) { - EXPECT_FLOAT_EQ(readback[texel * 2 + 0], static_cast(texel + 1)) - << "texel " << texel << " red"; + EXPECT_FLOAT_EQ(readback[texel * 2 + 0], static_cast(texel + 1)) << "texel " << texel << " red"; EXPECT_FLOAT_EQ(readback[texel * 2 + 1], 100.0f) << "texel " << texel << " green"; } glBindBuffer(GL_TEXTURE_BUFFER, 0); glDeleteBuffers(1, &buffer); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // The SAME buffer texture read through BOTH doors at once, which is the shape the split @@ -1100,8 +1099,7 @@ void main() if (!ImagesAreUsable()) GTEST_SKIP() << "no image load/store on this driver"; GLint maxTextureBufferSize = 0; glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} if (maxTextureBufferSize <= 0) GTEST_SKIP() << "no buffer textures on this driver"; constexpr int kBufferTexels = 4; @@ -1183,8 +1181,8 @@ void main() std::vector readback(answerSeed.size(), -54321.0f); glBindBuffer(GL_TEXTURE_BUFFER, answerBuffer); - glGetBufferSubData(GL_TEXTURE_BUFFER, 0, - static_cast(readback.size() * sizeof(float)), readback.data()); + glGetBufferSubData(GL_TEXTURE_BUFFER, 0, static_cast(readback.size() * sizeof(float)), + readback.data()); EXPECT_EQ(FirstGLError(), 0u) << "reading the answers back errored"; for (int texel = 0; texel < kBufferTexels; ++texel) { @@ -1206,8 +1204,7 @@ void main() glBindBuffer(GL_TEXTURE_BUFFER, 0); glDeleteBuffers(1, &buffer); glDeleteBuffers(1, &answerBuffer); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // The other consumer of the same texture. A widened texture's ES storage really does have diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp index 27d7f706..e726d9bb 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ObjectSubsystemControlScenario.cpp @@ -234,8 +234,7 @@ void main() { oColor = texture(uTex, vUv); } // EARLIEST rather than any: a spelling that also occurs later in (Espryt's // bit-10-requires-bit-11 sentence says "only bit 11 mints sampler CSOs" in its reason) // must not be able to satisfy an ordering the first occurrence does not. - std::size_t EarliestSpellingOffset(const std::string& line, - const std::vector& spellings) { + std::size_t EarliestSpellingOffset(const std::string& line, const std::vector& spellings) { std::size_t earliest = std::string::npos; for (const std::string& spelling : spellings) { const std::size_t at = line.find(spelling); @@ -248,28 +247,25 @@ void main() { oColor = texture(uTex, vUv); } // The matching line, or an empty string. Returned rather than a bool so the case can print // what it found: a reader of a green refusal lane must be able to see the sentence. - std::string FindTheRefusalLine(const std::string& log, - const std::vector& bitThatWasSet, + std::string FindTheRefusalLine(const std::string& log, const std::vector& bitThatWasSet, const std::string& neededMaskHex) { const std::string missingField = SaysWhichIsMissing(neededMaskHex); std::size_t pos = 0; while (pos <= log.size()) { const std::size_t newline = log.find('\n', pos); - const std::string line = log.substr( - pos, newline == std::string::npos ? std::string::npos : newline - pos); + const std::string line = + log.substr(pos, newline == std::string::npos ? std::string::npos : newline - pos); const bool atErrorSeverity = line.find("/ERROR]") != std::string::npos; const std::size_t refusedAt = line.find(kSaysItRefused); const std::size_t setAt = EarliestSpellingOffset(line, bitThatWasSet); const std::size_t setClauseAt = line.find(kSaysWhichIsSet); const std::size_t missingAt = line.find(missingField); - const bool everyPartIsThere = - refusedAt != std::string::npos && setAt != std::string::npos && - setClauseAt != std::string::npos && missingAt != std::string::npos; + const bool everyPartIsThere = refusedAt != std::string::npos && setAt != std::string::npos && + setClauseAt != std::string::npos && missingAt != std::string::npos; // " ... is set but ... missing )", strictly in that order. // Swapping the caller's two arguments breaks the chain, which is the whole of // F-v2-m1. - const bool inTheRightDirection = - everyPartIsThere && setAt < setClauseAt && setClauseAt < missingAt; + const bool inTheRightDirection = everyPartIsThere && setAt < setClauseAt && setClauseAt < missingAt; if (atErrorSeverity && inTheRightDirection) { return line; } @@ -333,7 +329,8 @@ void main() { oColor = texture(uTex, vUv); } void SkipUnlessTheLaneIsAssertableHere(bool needsTheEmitters) { if (m_lane.empty()) { GTEST_SKIP() << "runs only in its own lane: the three ObjectSubsystemControl. " - "ctest entries set " << kLaneMarker + "ctest entries set " + << kLaneMarker << " together with the MOBILEGL_PIPE_PUSH bitmask that arm means, " "MOBILEGL_PIPE_STATS=1, MOBILEGL_PIPE_STATS_PERIOD=1 and a " "private MOBILEGL_LOG_FILE_PATH. None of that is configured in " @@ -383,8 +380,8 @@ void main() { oColor = texture(uTex, vUv); } } glGenTextures(1, &m_texture); glBindTexture(GL_TEXTURE_2D, m_texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextureSize, kTextureSize, 0, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); @@ -401,13 +398,12 @@ void main() { oColor = texture(uTex, vUv); } // framebuffer, which is exactly what a per-target set_framebuffer_state counts. glGenTextures(1, &m_attachment); glBindTexture(GL_TEXTURE_2D, m_attachment); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextureSize, kTextureSize, 0, GL_RGBA, - GL_UNSIGNED_BYTE, nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kTextureSize, kTextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, + nullptr); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - m_attachment, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_attachment, 0); BindDefaultFramebuffer(); for (int draw = 0; draw < kDrawsInTheWindow; ++draw) { @@ -430,8 +426,7 @@ void main() { oColor = texture(uTex, vUv); } // same something. const std::uint8_t green[4] = {0, 255, 0, 255}; glBindTexture(GL_TEXTURE_2D, m_texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, draw % kTextureSize, 0, 1, 1, GL_RGBA, - GL_UNSIGNED_BYTE, green); + glTexSubImage2D(GL_TEXTURE_2D, 0, draw % kTextureSize, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, green); } } @@ -508,35 +503,31 @@ void main() { oColor = texture(uTex, vUv); } const long long samplerStates = PipeStatsWindow::CounterOrAbsent(window, "sse"); const long long shaderImages = PipeStatsWindow::CounterOrAbsent(window, "sie"); const long long clientUploads = PipeStatsWindow::CounterOrAbsent(window, "ctu"); - ASSERT_GE(framebuffer, 0) - << "the summary line carries no fbe= field, so this build's PipeStats has no P4a " - "emission counters to read: " - << window.line; + ASSERT_GE(framebuffer, 0) << "the summary line carries no fbe= field, so this build's PipeStats has no P4a " + "emission counters to read: " + << window.line; ASSERT_GE(samplerViews, 0) << "no sve= field: " << window.line; ASSERT_GE(samplerStates, 0) << "no sse= field: " << window.line; ASSERT_GE(shaderImages, 0) << "no sie= field: " << window.line; ASSERT_GE(clientUploads, 0) << "no ctu= field: " << window.line; - const long long total = framebuffer + samplerViews + samplerStates + shaderImages + - clientUploads; + const long long total = framebuffer + samplerViews + samplerStates + shaderImages + clientUploads; if (m_lane == kLaneOn) { - EXPECT_GT(total, 0) - << "with bits 9|10|11|12 SET the four P4a families are the path this workload " - "takes - a user framebuffer bound and unbound " - << kDrawsInTheWindow - << " times, a texture with parameters and a sub-region upload per iteration, an " - "explicit sampler object on the unit and a program with a default-uniform " - "write - so the window's emit[] bracket must carry something. All five " - "reading zero means the emitters never ran on the arm that is supposed to run " - "them. It reported: " - << window.line; + EXPECT_GT(total, 0) << "with bits 9|10|11|12 SET the four P4a families are the path this workload " + "takes - a user framebuffer bound and unbound " + << kDrawsInTheWindow + << " times, a texture with parameters and a sub-region upload per iteration, an " + "explicit sampler object on the unit and a program with a default-uniform " + "write - so the window's emit[] bracket must carry something. All five " + "reading zero means the emitters never ran on the arm that is supposed to run " + "them. It reported: " + << window.line; // The framebuffer family on its own, because it is the one that would be hidden by // a large upload count: a suppressor that stopped suppressing shows up as fbe // tracking the DRAW count, and a family that never emitted shows up as zero. - EXPECT_GT(framebuffer, 0) - << "fbe= is zero on the ON lane: set_framebuffer_state never went out even " - "though the workload bound a user framebuffer and the default framebuffer " - << kDrawsInTheWindow << " times each. " << window.line; + EXPECT_GT(framebuffer, 0) << "fbe= is zero on the ON lane: set_framebuffer_state never went out even " + "though the workload bound a user framebuffer and the default framebuffer " + << kDrawsInTheWindow << " times each. " << window.line; } else if (m_lane == kLaneOff) { EXPECT_EQ(total, 0) << "with bits 9|10|11|12 CLEARED (MOBILEGL_PIPE_PUSH=0x1ff, P3a's default) the " @@ -556,9 +547,8 @@ void main() { oColor = texture(uTex, vUv); } } // ... and the picture is the same whichever arm ran. - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, "green", 0.0, - "the sampled draw [" + m_lane + "]")) + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, "green", + 0.0, "the sampled draw [" + m_lane + "]")) << "the subsystem bits changed what is DRAWN, which is not an A/B - the handle path " "and the legacy path must produce the same pixels from the same texture, sampler " "and framebuffer."; @@ -606,9 +596,8 @@ void main() { oColor = texture(uTex, vUv); } { const std::string& backend = Gl().BackendName(); const std::string marker = - "MGITEST_HANDLE_REKEY_OBJECTS_" + (backend == "DirectVulkan" - ? std::string("DirectVulkan") - : std::string("DirectGLES")); + "MGITEST_HANDLE_REKEY_OBJECTS_" + + (backend == "DirectVulkan" ? std::string("DirectVulkan") : std::string("DirectGLES")); if (!BuildMarkerIsSet(marker.c_str())) { GTEST_SKIP() << "subsystem not implemented on this tree: no source under " "MobileGL/MG_Backend/" @@ -643,8 +632,7 @@ void main() { oColor = texture(uTex, vUv); } // ONE LINE, at ERROR severity, saying it refused and naming BOTH bits. See // FindTheRefusalLine: a substring search over the whole file cannot go red for the // reason this case claims (F-M6). - const std::string refusal = - FindTheRefusalLine(log, SamplerBitSpellings(), kTextureResourceBitMask); + const std::string refusal = FindTheRefusalLine(log, SamplerBitSpellings(), kTextureResourceBitMask); EXPECT_FALSE(refusal.empty()) << "MOBILEGL_PIPE_PUSH=0x9ff sets the sampler subsystem (bit 11) without the texture " "resource subsystem (bit 10) it depends on, and no single ERROR line of the " @@ -662,9 +650,8 @@ void main() { oColor = texture(uTex, vUv); } RecordProperty("refusal_line", refusal.c_str()); } - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, "green", 0.0, - "the sampled draw [refused]")) + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, "green", + 0.0, "the sampled draw [refused]")) << "the refused configuration did not draw what every other lane draws. A refusal is " "supposed to run the LEGACY arm, which is the arm that ships in a pull build - so " "the pixels are the one thing it may not change."; @@ -723,9 +710,8 @@ void main() { oColor = texture(uTex, vUv); } { const std::string& backend = Gl().BackendName(); const std::string marker = - "MGITEST_HANDLE_REKEY_OBJECTS_" + (backend == "DirectVulkan" - ? std::string("DirectVulkan") - : std::string("DirectGLES")); + "MGITEST_HANDLE_REKEY_OBJECTS_" + + (backend == "DirectVulkan" ? std::string("DirectVulkan") : std::string("DirectGLES")); if (!BuildMarkerIsSet(marker.c_str())) { GTEST_SKIP() << "subsystem not implemented on this tree: no source under " "MobileGL/MG_Backend/" @@ -770,8 +756,7 @@ void main() { oColor = texture(uTex, vUv); } // (Managers.cpp, PipeSubsystemDependencyMissing via // ResolveTextureResourceSubsystemArm). The row requires bit 7 as well; 0x5ff carries // it, so the missing field is bit 11 alone. - const std::string refusal = - FindTheRefusalLine(log, TextureResourceBitSpellings(), kSamplerBitMask); + const std::string refusal = FindTheRefusalLine(log, TextureResourceBitSpellings(), kSamplerBitMask); EXPECT_FALSE(refusal.empty()) << "MOBILEGL_PIPE_PUSH=0x5ff sets the texture resource subsystem (bit 10) without " "the sampler subsystem (bit 11) that MGPTextureParams::BuiltinSampler depends on, " @@ -788,9 +773,8 @@ void main() { oColor = texture(uTex, vUv); } RecordProperty("refusal_line", refusal.c_str()); } - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, "green", 0.0, - "the sampled draw [refused-texture]")) + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, "green", + 0.0, "the sampled draw [refused-texture]")) << "the refused configuration did not draw what every other lane draws. A refusal is " "supposed to run the LEGACY arm, which is the arm that ships in a pull build - so " "the pixels are the one thing it may not change."; @@ -837,8 +821,8 @@ void main() { oColor = texture(uTex, vUv); } SkipUnlessTheLaneIsAssertableHere(/*needsTheEmitters=*/false); if (IsSkipped()) return; if (!LaneIsAConsumerLane(m_lane)) { - GTEST_SKIP() << "runs only in the two consumer lanes (MGITEST_OBJECT_SUBSYSTEM_LANE=" - << kLaneConsumer << " / " << kLaneNoConsumer + GTEST_SKIP() << "runs only in the two consumer lanes (MGITEST_OBJECT_SUBSYSTEM_LANE=" << kLaneConsumer + << " / " << kLaneNoConsumer << "), which pin MOBILEGL_PIPE_PUSH at the phase default on the two " "backends. Every other lane configures a mask or a backend whose " "emission shape is a different question."; @@ -867,28 +851,25 @@ void main() { oColor = texture(uTex, vUv); } // what happened since - which is the number this case is about either way. const unsigned long long fired = after >= before ? after - before : after; - std::cout << "[ ObjectSubsystemControl ] " << m_lane - << " lane: applier RefusedNoConsumer " << before << " -> " << after - << " over the workload (delta " << fired << ")" << std::endl; + std::cout << "[ ObjectSubsystemControl ] " << m_lane << " lane: applier RefusedNoConsumer " << before + << " -> " << after << " over the workload (delta " << fired << ")" << std::endl; RecordProperty("refused_no_consumer_delta", static_cast(fired)); - EXPECT_EQ(fired, 0u) - << "the applier's no-consumer BELT fired " << fired - << " time(s) during this workload on the " << m_lane - << " lane. The belt exists so that a P4a record arriving on a backend that " - "registered no MGPipeResourceOps is refused rather than accepted - and an " - "accepted record is what makes the client clear the dirty flags whose texels " - "nobody then uploads (ID-39: sixty-six DirectVulkan cases, all texture-upload " - "shaped). A non-zero count means the CLIENT'S GATE let an emission through and " - "only the belt caught it: the two are supposed to agree, and PipeFill's " - "P4aFamilyHasItsConsumer() is where they stopped."; + EXPECT_EQ(fired, 0u) << "the applier's no-consumer BELT fired " << fired + << " time(s) during this workload on the " << m_lane + << " lane. The belt exists so that a P4a record arriving on a backend that " + "registered no MGPipeResourceOps is refused rather than accepted - and an " + "accepted record is what makes the client clear the dirty flags whose texels " + "nobody then uploads (ID-39: sixty-six DirectVulkan cases, all texture-upload " + "shaped). A non-zero count means the CLIENT'S GATE let an emission through and " + "only the belt caught it: the two are supposed to agree, and PipeFill's " + "P4aFamilyHasItsConsumer() is where they stopped."; // The pixels, on both lanes, for the reason every arm of this file asserts them: a // backend running its legacy path because no consumer is registered must draw exactly // what a backend running the handle arm draws. - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, "green", 0.0, - "the sampled draw [" + m_lane + "]")) + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, "green", + 0.0, "the sampled draw [" + m_lane + "]")) << "the " << m_lane << " lane did not draw what every other lane draws, so whatever the counter says, " "this configuration is not running the workload correctly."; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/OrientationScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/OrientationScenario.cpp index 93ba6ae1..f90a8b1f 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/OrientationScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/OrientationScenario.cpp @@ -231,8 +231,8 @@ void main() { EXPECT_TRUE(RegionIsMostly(image, inset, w / 2 - inset, inset, h / 2 - inset, "blue", 0.0, when)); EXPECT_TRUE(RegionIsMostly(image, w / 2 + inset, w - inset, inset, h / 2 - inset, "green", 0.0, when)); EXPECT_TRUE(RegionIsMostly(image, inset, w / 2 - inset, h / 2 + inset, h - inset, "red", 0.0, when)); - EXPECT_TRUE(RegionIsMostly(image, w / 2 + inset, w - inset, h / 2 + inset, h - inset, "white", 0.0, - when)); + EXPECT_TRUE( + RegionIsMostly(image, w / 2 + inset, w - inset, h / 2 + inset, h - inset, "white", 0.0, when)); } unsigned int m_program = 0; @@ -292,8 +292,7 @@ void main() { const Image offscreen = OffscreenPass(); const Image presented = DefaultFramebufferPass(); - EXPECT_EQ(offscreen.QuadrantSignature(), kUprightSignature) - << "render-to-texture pass rendered flipped"; + EXPECT_EQ(offscreen.QuadrantSignature(), kUprightSignature) << "render-to-texture pass rendered flipped"; EXPECT_EQ(presented.QuadrantSignature(), kUprightSignature) << "the default-framebuffer pass that follows a render-to-texture pass " "rendered upside down"; @@ -450,10 +449,10 @@ void main() { // Nothing may have been painted outside the viewport. This is what catches the // mirrored placement: the drawn band would sit at GL rows [41, 83) instead. - EXPECT_TRUE(RegionIsMostly(whole, 0, Gl().Width() - 1, 0, kSubY - 2, "black", 0.0, - "below the sub-viewport")); - EXPECT_TRUE(RegionIsMostly(whole, 0, Gl().Width() - 1, kSubY + kSubH + 1, Gl().Height() - 1, "black", - 0.0, "above the sub-viewport")); + EXPECT_TRUE( + RegionIsMostly(whole, 0, Gl().Width() - 1, 0, kSubY - 2, "black", 0.0, "below the sub-viewport")); + EXPECT_TRUE(RegionIsMostly(whole, 0, Gl().Width() - 1, kSubY + kSubH + 1, Gl().Height() - 1, "black", 0.0, + "above the sub-viewport")); } // Readback: a full-viewport draw read back through a sub-rect must return the requested @@ -529,12 +528,11 @@ void main() { ClearTo(0.0f, 0.0f, 0.0f, 1.0f); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_offscreen.fbo); - glBlitFramebuffer(kSubX, kSubY, kSubX + kSubW, kSubY + kSubH, kSubX, kSubY, kSubX + kSubW, - kSubY + kSubH, GL_COLOR_BUFFER_BIT, GL_NEAREST); + glBlitFramebuffer(kSubX, kSubY, kSubX + kSubW, kSubY + kSubH, kSubX, kSubY, kSubX + kSubW, kSubY + kSubH, + GL_COLOR_BUFFER_BIT, GL_NEAREST); const unsigned int blitError = FirstGLError(); if (blitError != GL_NO_ERROR) { - GTEST_SKIP() << "this backend refused the default-framebuffer blit: " - << GLErrorName(blitError); + GTEST_SKIP() << "this backend refused the default-framebuffer blit: " << GLErrorName(blitError); } glBindFramebuffer(GL_FRAMEBUFFER, m_offscreen.fbo); @@ -543,8 +541,7 @@ void main() { const Image landed = CropRect(blitted, kSubX, kSubY, kSubW, kSubH); const Image expected = CropRect(whole, kSubX, kSubY, kSubW, kSubH); - EXPECT_FALSE(landed == VFlip(expected)) - << "ORIENTATION: the blitted band arrived upside down"; + EXPECT_FALSE(landed == VFlip(expected)) << "ORIENTATION: the blitted band arrived upside down"; EXPECT_TRUE(landed == expected) << "the blitted sub-rect differs from the same sub-rect of a direct read in " << landed.ByteDiffCount(expected) << " bytes"; @@ -576,8 +573,8 @@ void main() { << "an FBO sub-viewport draw must land in GL rows [y0, y0+h) upright"; EXPECT_TRUE(RegionIsMostly(placedWhole, 0, m_offscreen.width - 1, 0, kSubY - 2, "black", 0.0, "below an FBO sub-viewport")); - EXPECT_TRUE(RegionIsMostly(placedWhole, 0, m_offscreen.width - 1, kSubY + kSubH + 1, - m_offscreen.height - 1, "black", 0.0, "above an FBO sub-viewport")); + EXPECT_TRUE(RegionIsMostly(placedWhole, 0, m_offscreen.width - 1, kSubY + kSubH + 1, m_offscreen.height - 1, + "black", 0.0, "above an FBO sub-viewport")); } } // namespace diff --git a/MobileGL/MG_IntegrationTest/Scenarios/OversizedRecordingSplitScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/OversizedRecordingSplitScenario.cpp index 2be186c3..7b37be01 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/OversizedRecordingSplitScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/OversizedRecordingSplitScenario.cpp @@ -133,9 +133,13 @@ void main() { o_color = vec4(1.0); } // mark and the read cover both roles, as PrimitivesGeneratedNoXfbScenario's arming does; a // read of the literal base found no file and reported an unsplit renderer. In the pull // build the client role is the whole log. - std::string LibraryLogPath() { return PipeStatsWindow::LibraryLogPath(); } + std::string LibraryLogPath() { + return PipeStatsWindow::LibraryLogPath(); + } - PipeStatsWindow::LogMark LibraryLogSize() { return PipeStatsWindow::MarkLaneLog(); } + PipeStatsWindow::LogMark LibraryLogSize() { + return PipeStatsWindow::MarkLaneLog(); + } std::string LibraryLogSince(const PipeStatsWindow::LogMark& mark) { return PipeStatsWindow::ReadLaneLogSince(mark); @@ -176,18 +180,17 @@ void main() { o_color = vec4(1.0); } } // Every pixel of columns [x0, x1] (all rows) is `expected`, each channel within `tolerance`. - ::testing::AssertionResult ColumnsAre(const Image& image, int x0, int x1, const Rgba8& expected, - int tolerance, const char* what) { + ::testing::AssertionResult ColumnsAre(const Image& image, int x0, int x1, const Rgba8& expected, int tolerance, + const char* what) { int wrong = 0; int firstX = -1, firstY = -1; Rgba8 firstSeen{}; for (int y = 0; y < kSide; ++y) { for (int x = x0; x <= x1; ++x) { const Rgba8 seen = image.At(x, y); - const bool close = std::abs(seen.r - expected.r) <= tolerance && - std::abs(seen.g - expected.g) <= tolerance && - std::abs(seen.b - expected.b) <= tolerance && - std::abs(seen.a - expected.a) <= tolerance; + const bool close = + std::abs(seen.r - expected.r) <= tolerance && std::abs(seen.g - expected.g) <= tolerance && + std::abs(seen.b - expected.b) <= tolerance && std::abs(seen.a - expected.a) <= tolerance; if (!close) { if (wrong == 0) { firstX = x; @@ -243,9 +246,12 @@ void main() { o_color = vec4(1.0); } if (m_rectProgram != 0) glDeleteProgram(m_rectProgram); if (m_vbo != 0) glDeleteBuffers(1, &m_vbo); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); - for (GLuint rbo : m_renderbuffers) glDeleteRenderbuffers(1, &rbo); - for (GLuint fbo : m_framebuffers) glDeleteFramebuffers(1, &fbo); - for (ColorFbo& target : m_colorTargets) DestroyColorFbo(target); + for (GLuint rbo : m_renderbuffers) + glDeleteRenderbuffers(1, &rbo); + for (GLuint fbo : m_framebuffers) + glDeleteFramebuffers(1, &fbo); + for (ColorFbo& target : m_colorTargets) + DestroyColorFbo(target); } ScenarioTest::TearDown(); } @@ -263,7 +269,8 @@ void main() { o_color = vec4(1.0); } const std::string appended = LibraryLogSince(m_logBefore); EXPECT_NE(appended.find("draws into one command buffer; submitting it"), std::string::npos) << "MOBILEGL_MAGMA_MAX_DRAWS_PER_COMMAND_BUFFER=" << budget << " and this case recorded more " - << "than " << kFillers << " draws in one frame, but DirectVulkan never reported splitting the " + << "than " << kFillers + << " draws in one frame, but DirectVulkan never reported splitting the " "recording. A renderer that does not split grows a single command buffer without bound " "(rd12 on Adreno: ~40k driver mappings, then SIGABRT). See " "VulkanRenderer::SplitOversizedRecording. Log appended by this case:\n" diff --git a/MobileGL/MG_IntegrationTest/Scenarios/P4aFinalFixScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/P4aFinalFixScenario.cpp index a4e4b414..3066fea3 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/P4aFinalFixScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/P4aFinalFixScenario.cpp @@ -117,8 +117,7 @@ void main() { oColor = texture(uTex, vUv); } // The "other" texture: a complete, single-level white texture, so a draw that // samples it is a verb the texture under test is not reached by. m_other = MakeLevel0(255, 255, 255, /*maxLevel=*/0); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } void TearDown() override { @@ -132,8 +131,7 @@ void main() { oColor = texture(uTex, vUv); } if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); if (m_quadBuffer != 0) glDeleteBuffers(1, &m_quadBuffer); if (m_program != 0) glDeleteProgram(m_program); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // The C-1 and M-A pictures are about Espryt's consumption of the texture records; @@ -237,8 +235,7 @@ void main() { oColor = texture(uTex, vUv); } void FenceTheApplierRead() { GLubyte fencePixel[4]{}; glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, fencePixel); - ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) - << "the applier-read fence left a GL error behind"; + ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the applier-read fence left a GL error behind"; } // The white-box gate of the M-A case: true when the applier holds a record for the @@ -530,8 +527,7 @@ void main() { oColor = texture(uTex, vUv); } if (IsSkipped()) return; GLint maxImageUnits = 0; glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} if (maxImageUnits < 1) { GTEST_SKIP() << "no image units"; return; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp index b88191f2..0b423ad2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/P4aSeamAuditScenario.cpp @@ -139,8 +139,7 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glDisable(GL_SCISSOR_TEST); glDisable(GL_DEPTH_TEST); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } void TearDown() override { @@ -149,8 +148,7 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glBindVertexArray(0); if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); glBindFramebuffer(GL_FRAMEBUFFER, 0); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // The F-3 cases are about Espryt's four cross-object masks, which are its own @@ -219,8 +217,7 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms); GLint maxBufferSize = 0; glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxBufferSize); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxImageUnits >= 2 && maxComputeImageUniforms >= 2 && maxBufferSize >= 4; } @@ -571,7 +568,7 @@ void main() { imageStore(i1, 0, imageLoad(i0, 0) + uvec4(2u, 0u, 0u, 0u)); } glGenTextures(1, &touch3D); glBindTexture(GL_TEXTURE_3D, touch3D); const std::uint8_t blue[2 * 2 * 2 * 4] = {0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, - 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255}; + 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255}; glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, blue); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PackBufferReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PackBufferReadbackScenario.cpp index 7722112f..2c180761 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PackBufferReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PackBufferReadbackScenario.cpp @@ -58,8 +58,8 @@ namespace MGITest { // Integers below 2^24 in every component, so float storage is exact and a byte swap of // any of them is visibly different from the value. std::array PatternRgba32f(int x, int y) { - return {static_cast(x + 1), static_cast(y + 3), - static_cast(x * 64 + y), static_cast(1000 + x * y)}; + return {static_cast(x + 1), static_cast(y + 3), static_cast(x * 64 + y), + static_cast(1000 + x * y)}; } struct PackState { @@ -98,10 +98,12 @@ namespace MGITest { ResetPack(); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - for (GLuint buffer : m_buffers) glDeleteBuffers(1, &buffer); + for (GLuint buffer : m_buffers) + glDeleteBuffers(1, &buffer); glBindFramebuffer(GL_FRAMEBUFFER, 0); if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo); - for (GLuint texture : m_textures) glDeleteTextures(1, &texture); + for (GLuint texture : m_textures) + glDeleteTextures(1, &texture); m_buffers.clear(); m_textures.clear(); m_fbo = 0; @@ -130,20 +132,20 @@ namespace MGITest { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // The bytes a pack state makes a width x height read occupy, from the first byte the // read may touch (GL 4.6 §8.4.4.1 / §18.2): the skips, then the padded rows, the last // row tight. static std::size_t PackedExtent(const PackState& pack, int width, int height, std::size_t bpp) { - const std::size_t rowPixels = pack.rowLength > 0 ? static_cast(pack.rowLength) - : static_cast(width); + const std::size_t rowPixels = + pack.rowLength > 0 ? static_cast(pack.rowLength) : static_cast(width); const std::size_t align = static_cast(pack.alignment); const std::size_t stride = (rowPixels * bpp + align - 1) / align * align; - return static_cast(pack.skipRows) * stride + static_cast(pack.skipPixels) * bpp + - static_cast(height - 1) * stride + static_cast(width) * bpp; + return static_cast(pack.skipRows) * stride + + static_cast(pack.skipPixels) * bpp + static_cast(height - 1) * stride + + static_cast(width) * bpp; } GLuint NewTexture() { @@ -209,7 +211,8 @@ namespace MGITest { ResetPack(); std::size_t lead = 0; - for (std::size_t i = 0; i < kOffset; ++i) lead += actual[i] != kFill ? 1 : 0; + for (std::size_t i = 0; i < kOffset; ++i) + lead += actual[i] != kFill ? 1 : 0; EXPECT_EQ(lead, 0u) << pack.name << ": " << lead << " bytes in front of the read's offset moved"; std::size_t bad = 0, first = 0; for (std::size_t i = 0; i < extent; ++i) { @@ -231,9 +234,10 @@ namespace MGITest { AttachPatternRgba8(37, 23); if (HasFatalFailure()) return; for (const PackState& pack : kPackStates) { - ExpectPackBufferMatchesClientMemory(pack, 2, 1, 31, 19, GL_UNSIGNED_BYTE, 4, [](std::vector& out) { - glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), out.data()); - }); + ExpectPackBufferMatchesClientMemory( + pack, 2, 1, 31, 19, GL_UNSIGNED_BYTE, 4, [](std::vector& out) { + glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), out.data()); + }); if (HasFatalFailure()) return; } } @@ -246,13 +250,14 @@ namespace MGITest { AttachPatternRgba8(37, 23); if (HasFatalFailure()) return; for (const PackState& pack : {kPackStates[0], kPackStates[5]}) { - ExpectPackBufferMatchesClientMemory(pack, 0, 0, 37, 23, GL_UNSIGNED_BYTE, 4, [](std::vector& out) { - void* mapped = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), - GL_MAP_READ_BIT); - ASSERT_NE(mapped, nullptr); - std::memcpy(out.data(), mapped, out.size()); - glUnmapBuffer(GL_PIXEL_PACK_BUFFER); - }); + ExpectPackBufferMatchesClientMemory( + pack, 0, 0, 37, 23, GL_UNSIGNED_BYTE, 4, [](std::vector& out) { + void* mapped = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), + GL_MAP_READ_BIT); + ASSERT_NE(mapped, nullptr); + std::memcpy(out.data(), mapped, out.size()); + glUnmapBuffer(GL_PIXEL_PACK_BUFFER); + }); if (HasFatalFailure()) return; } } @@ -276,9 +281,10 @@ namespace MGITest { glReadBuffer(GL_COLOR_ATTACHMENT0); ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); const PackState swapped{"swap bytes, row length 17", 4, 17, 1, 1, GL_TRUE}; - ExpectPackBufferMatchesClientMemory(swapped, 0, 0, kWidth, kHeight, GL_FLOAT, 16, [](std::vector& out) { - glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), out.data()); - }); + ExpectPackBufferMatchesClientMemory( + swapped, 0, 0, kWidth, kHeight, GL_FLOAT, 16, [](std::vector& out) { + glGetBufferSubData(GL_PIXEL_PACK_BUFFER, 0, static_cast(out.size()), out.data()); + }); } // glGetTexImage into a pack buffer, 2D and layered: the layered case is the one whose @@ -324,7 +330,8 @@ namespace MGITest { glPixelStorei(GL_PACK_SKIP_IMAGES, shape.skipImages); const std::size_t rowPixels = shape.rowLength > 0 ? static_cast(shape.rowLength) : kWidth; const std::size_t stride = (rowPixels * 4 + shape.alignment - 1) / shape.alignment * shape.alignment; - const std::size_t imageRows = shape.imageHeight > 0 ? static_cast(shape.imageHeight) : kHeight; + const std::size_t imageRows = + shape.imageHeight > 0 ? static_cast(shape.imageHeight) : kHeight; const std::size_t imageStride = stride * imageRows; const std::size_t extent = static_cast(shape.skipImages) * imageStride + static_cast(shape.depth - 1) * imageStride + @@ -342,7 +349,8 @@ namespace MGITest { glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); ResetPack(); std::size_t bad = 0, first = 0; - for (std::size_t i = 0; i < kOffset; ++i) bad += actual[i] != kFill ? 1 : 0; + for (std::size_t i = 0; i < kOffset; ++i) + bad += actual[i] != kFill ? 1 : 0; EXPECT_EQ(bad, 0u) << shape.name << ": bytes in front of the offset moved"; bad = 0; for (std::size_t i = 0; i < extent; ++i) @@ -361,7 +369,8 @@ namespace MGITest { constexpr int kWidth = 29, kHeight = 17; AttachPatternRgba8(kWidth, kHeight); if (HasFatalFailure()) return; - const GLuint buffer = NewBuffer(GL_PIXEL_PACK_BUFFER, static_cast(kWidth) * kHeight * 4, kFill); + const GLuint buffer = + NewBuffer(GL_PIXEL_PACK_BUFFER, static_cast(kWidth) * kHeight * 4, kFill); glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); @@ -381,7 +390,8 @@ namespace MGITest { for (int y = 0; y < kHeight; ++y) for (int x = 0; x < kWidth; ++x) { const auto expected = PatternRgba8(x, y, 0); - if (std::memcmp(actual.data() + (static_cast(y) * kWidth + x) * 4, expected.data(), 4) != 0 && + if (std::memcmp(actual.data() + (static_cast(y) * kWidth + x) * 4, expected.data(), + 4) != 0 && bad++ == 0) first = "(" + std::to_string(x) + "," + std::to_string(y) + ")"; } @@ -433,7 +443,8 @@ namespace MGITest { ASSERT_EQ(FirstGLError(), static_cast(GL_NO_ERROR)); constexpr int kReads = 4; const SplitRuntimeState before = PeekSplitRuntime(); - for (int i = 0; i < kReads; ++i) glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + for (int i = 0; i < kReads; ++i) + glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); const SplitRuntimeState after = PeekSplitRuntime(); ASSERT_EQ(FirstGLError(), static_cast(GL_NO_ERROR)); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PackedWordReadbackScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PackedWordReadbackScenario.cpp index 0527a15e..e1da335c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PackedWordReadbackScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PackedWordReadbackScenario.cpp @@ -76,8 +76,7 @@ namespace MGITest { } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } void DeleteObjects() { @@ -123,8 +122,7 @@ namespace MGITest { // back, and there are none to compare if the copy never happened. GLenum CopyWholeImage(GLuint srcName, GLenum srcTarget, GLuint dstName, GLenum dstTarget) { DrainErrors(); - glCopyImageSubData(srcName, srcTarget, 0, 0, 0, 0, dstName, dstTarget, 0, 0, 0, 0, kExtent, kExtent, - 1); + glCopyImageSubData(srcName, srcTarget, 0, 0, 0, 0, dstName, dstTarget, 0, 0, 0, 0, kExtent, kExtent, 1); const GLenum error = glGetError(); EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "the copy recorded more than one error"; return error; @@ -133,8 +131,7 @@ namespace MGITest { static void ExpectEveryTexel(const std::vector& words, GLuint expected, const char* what) { for (std::size_t i = 0; i < words.size(); ++i) { ASSERT_EQ(words[i], expected) - << what << ": texel " << i << " read 0x" << std::hex << words[i] << ", expected 0x" - << expected; + << what << ": texel " << i << " read 0x" << std::hex << words[i] << ", expected 0x" << expected; } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp index c0d6f2b1..ec5ed806 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PartialUploadOverGpuContentScenario.cpp @@ -75,7 +75,12 @@ void main() { // How a value in [0, 1] is spelled in the client data, and how the level is cleared and // read back. Integer levels spell 1 as kIntegerOne. - enum class Encoding { Unorm8, Snorm8, Float32, Uint8 }; + enum class Encoding { + Unorm8, + Snorm8, + Float32, + Uint8 + }; constexpr unsigned kIntegerOne = 9; struct Format { @@ -95,12 +100,18 @@ void main() { std::uint8_t* dst = out.data() + (static_cast(t) * f.components + c) * componentBytes; const float v = rgba[c]; switch (f.encoding) { - case Encoding::Unorm8: *dst = static_cast(std::lround(v * 255.0f)); break; + case Encoding::Unorm8: + *dst = static_cast(std::lround(v * 255.0f)); + break; case Encoding::Snorm8: *dst = static_cast(static_cast(std::lround(v * 127.0f))); break; - case Encoding::Float32: std::memcpy(dst, &v, sizeof(v)); break; - case Encoding::Uint8: *dst = static_cast(std::lround(v * kIntegerOne)); break; + case Encoding::Float32: + std::memcpy(dst, &v, sizeof(v)); + break; + case Encoding::Uint8: + *dst = static_cast(std::lround(v * kIntegerOne)); + break; } } } @@ -121,7 +132,8 @@ void main() { void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); + for (GLuint p : m_programs) + glDeleteProgram(p); if (!m_textures.empty()) glDeleteTextures(static_cast(m_textures.size()), m_textures.data()); if (m_vao) glDeleteVertexArrays(1, &m_vao); if (m_fbo) glDeleteFramebuffers(1, &m_fbo); @@ -209,7 +221,8 @@ void main() { std::vector texels(static_cast(width * height) * 4u, -7.0f); glReadPixels(0, 0, width, height, GL_RGBA, GL_FLOAT, texels.data()); for (int i = 0; i < width * height; ++i) - for (int c = 0; c < 3; ++c) rgb[i * 3 + c] = texels[i * 4 + c]; + for (int c = 0; c < 3; ++c) + rgb[i * 3 + c] = texels[i * 4 + c]; } EXPECT_EQ(FirstGLError(), 0u) << "readback"; return rgb; @@ -226,7 +239,8 @@ void main() { const float want[3] = {0.0f, inBox ? 0.0f : 1.0f, inBox ? 1.0f : 0.0f}; const float* got = &rgb[static_cast(y * width + x) * 3u]; bool texelBad = false; - for (int c = 0; c < 3; ++c) texelBad |= std::fabs(got[c] - want[c]) > 0.02f; + for (int c = 0; c < 3; ++c) + texelBad |= std::fabs(got[c] - want[c]) > 0.02f; if (texelBad && bad++ == 0) { first << "(" << x << "," << y << ") got " << got[0] << "," << got[1] << "," << got[2] << " want " << want[0] << "," << want[1] << "," << want[2]; @@ -321,7 +335,8 @@ void main() { const float want[3] = {0.0f, blueHere ? 0.0f : 1.0f, blueHere ? 1.0f : 0.0f}; const float* got = &rgb[static_cast(y * kEdge + x) * 3u]; bool texelBad = false; - for (int c = 0; c < 3; ++c) texelBad |= std::fabs(got[c] - want[c]) > 0.02f; + for (int c = 0; c < 3; ++c) + texelBad |= std::fabs(got[c] - want[c]) > 0.02f; if (texelBad && bad++ == 0) { first << "(" << x << "," << y << ") got " << got[0] << "," << got[1] << "," << got[2] << " want " << want[0] << "," << want[1] << "," << want[2]; @@ -429,7 +444,8 @@ void main() { const float want[3] = {0.0f, inBox ? 0.0f : 1.0f, inBox ? 1.0f : 0.0f}; const float* got = &rgb[static_cast(y * edge + x) * 3u]; bool texelBad = false; - for (int c = 0; c < 3; ++c) texelBad |= std::fabs(got[c] - want[c]) > 0.02f; + for (int c = 0; c < 3; ++c) + texelBad |= std::fabs(got[c] - want[c]) > 0.02f; if (!texelBad) continue; stale += got[0] > 0.5f; if (bad++ == 0) { @@ -671,13 +687,15 @@ void main() { if (!Ready()) return; std::vector boxes; for (int j = 0; j < 10; ++j) - for (int i = 0; i < 10; ++i) boxes.push_back({4 * i + 1, 4 * j + 1, 2, 2}); + for (int i = 0; i < 10; ++i) + boxes.push_back({4 * i + 1, 4 * j + 1, 2, 2}); RunScatteredWrites(40, boxes); } // Four 4x4 boxes around a one-texel cross: 64 of their union's 81 texels, past the 3/4 at // which the storage stops handing its rect list out. - TEST_F(PartialUploadOverGpuContentScenario, SubImagesFillingThreeQuartersOfTheirUnionKeepTheGpuClearBetweenThem) { + TEST_F(PartialUploadOverGpuContentScenario, + SubImagesFillingThreeQuartersOfTheirUnionKeepTheGpuClearBetweenThem) { if (!Ready()) return; RunScatteredWrites(16, {{2, 2, 4, 4}, {7, 2, 4, 4}, {2, 7, 4, 4}, {7, 7, 4, 4}}); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PassOrderScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PassOrderScenario.cpp index 236f2d5b..14567058 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PassOrderScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PassOrderScenario.cpp @@ -39,211 +39,211 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -using Pixel = std::array; -constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}, black{0, 0, 0, 255}; -constexpr int kWidth = 16, kHeight = 8, kSize = 8; -constexpr const char* kVertex = R"(#version 330 core + namespace { + using Pixel = std::array; + constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, blue{0, 0, 255, 255}, black{0, 0, 0, 255}; + constexpr int kWidth = 16, kHeight = 8, kSize = 8; + constexpr const char* kVertex = R"(#version 330 core void main() { vec2 p[3] = vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1,3)); gl_Position = vec4(p[gl_VertexID], 0, 1); })"; -constexpr const char* kSample = R"(#version 330 core + constexpr const char* kSample = R"(#version 330 core uniform sampler2D src; layout(location=0) out vec4 color; void main() { color = texelFetch(src, ivec2(gl_FragCoord.xy) % 8, 0); })"; -constexpr const char* kTint = R"(#version 330 core + constexpr const char* kTint = R"(#version 330 core uniform vec4 tint; layout(location=0) out vec4 color; void main() { color = tint; })"; -class PassOrderScenario : public ScenarioTest { -protected: - GLuint target = 0, sample = 0, tint = 0, vao = 0; - GLint tintLocation = -1; - std::vector textures, fbos; + class PassOrderScenario : public ScenarioTest { + protected: + GLuint target = 0, sample = 0, tint = 0, vao = 0; + GLint tintLocation = -1; + std::vector textures, fbos; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glActiveTexture(GL_TEXTURE0); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); - sample = Link(kSample); - tint = Link(kTint); - tintLocation = glGetUniformLocation(tint, "tint"); - } + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glActiveTexture(GL_TEXTURE0); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); + sample = Link(kSample); + tint = Link(kTint); + tintLocation = glGetUniformLocation(tint, "tint"); + } - void TearDown() override { - if (Ready()) { - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if (vao) glDeleteVertexArrays(1, &vao); - if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); - if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); - if (sample) glDeleteProgram(sample); - if (tint) glDeleteProgram(tint); + void TearDown() override { + if (Ready()) { + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (vao) glDeleteVertexArrays(1, &vao); + if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); + if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); + if (sample) glDeleteProgram(sample); + if (tint) glDeleteProgram(tint); + } + ScenarioTest::TearDown(); + } + + GLuint NewTexture(int width, int height, Pixel color) { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + const std::vector pixels(size_t(width) * size_t(height), color); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + return texture; + } + + GLuint NewFramebuffer(GLuint texture) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + fbos.push_back(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + return fbo; + } + + static GLuint Link(const char* fragment) { + const GLuint linked = glCreateProgram(); + const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; + const std::array sources{kVertex, fragment}; + for (size_t i = 0; i < kinds.size(); ++i) { + const GLuint shader = glCreateShader(kinds[i]); + glShaderSource(shader, 1, &sources[i], nullptr); + glCompileShader(shader); + glAttachShader(linked, shader); + glDeleteShader(shader); + } + glLinkProgram(linked); + GLint ok = GL_FALSE; + glGetProgramiv(linked, GL_LINK_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE); + return linked; + } + + // Fills the viewport (x, y, w, h) of `fbo` with `color` by drawing. + void Fill(GLuint fbo, Pixel color, int x, int y, int w, int h) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glUseProgram(tint); + glUniform4f(tintLocation, color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, color[3] / 255.0f); + glViewport(x, y, w, h); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + // Samples `texture` into the 8x8 tile `tile` of the target. + void SampleInto(GLuint texture, int tile) { + glBindFramebuffer(GL_FRAMEBUFFER, target); + glUseProgram(sample); + glBindTexture(GL_TEXTURE_2D, texture); + glViewport(tile * kSize, 0, kSize, kHeight); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + void ExpectPixel(int x, int y, Pixel expected, const char* why) { + glBindFramebuffer(GL_FRAMEBUFFER, target); + Pixel actual{}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); + for (size_t channel = 0; channel < actual.size(); ++channel) + EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; + } + }; + + TEST_F(PassOrderScenario, ATextureRenderedInOnePassIsSampledByTheNext) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewTexture(kSize, kSize, black); + const GLuint fbo = NewFramebuffer(texture); + glFinish(); + Fill(fbo, green, 0, 0, kSize, kSize); + SampleInto(texture, 0); + SampleInto(texture, 1); + ExpectPixel(4, 4, green, "the next pass samples what the previous pass rendered"); + ExpectPixel(12, 4, green, "a second draw in the same pass samples it too"); } - ScenarioTest::TearDown(); - } - GLuint NewTexture(int width, int height, Pixel color) { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - const std::vector pixels(size_t(width) * size_t(height), color); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - return texture; - } - - GLuint NewFramebuffer(GLuint texture) { - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - fbos.push_back(fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - return fbo; - } - - static GLuint Link(const char* fragment) { - const GLuint linked = glCreateProgram(); - const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; - const std::array sources{kVertex, fragment}; - for (size_t i = 0; i < kinds.size(); ++i) { - const GLuint shader = glCreateShader(kinds[i]); - glShaderSource(shader, 1, &sources[i], nullptr); - glCompileShader(shader); - glAttachShader(linked, shader); - glDeleteShader(shader); + TEST_F(PassOrderScenario, ATextureRenderedAgainAfterAnotherPassKeepsBothWrites) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewTexture(kSize, kSize, black); + const GLuint fbo = NewFramebuffer(texture); + glFinish(); + Fill(fbo, red, 0, 0, kSize, kSize); + Fill(target, black, 0, 0, kWidth, kHeight); // another pass in between + Fill(fbo, blue, 0, 0, kSize / 2, kSize); // the left half again + SampleInto(texture, 0); + ExpectPixel(1, 4, blue, "the second write to the texture lands over the first"); + ExpectPixel(6, 4, red, "the first write survives where the second did not reach"); } - glLinkProgram(linked); - GLint ok = GL_FALSE; - glGetProgramiv(linked, GL_LINK_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE); - return linked; - } - // Fills the viewport (x, y, w, h) of `fbo` with `color` by drawing. - void Fill(GLuint fbo, Pixel color, int x, int y, int w, int h) { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glUseProgram(tint); - glUniform4f(tintLocation, color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, color[3] / 255.0f); - glViewport(x, y, w, h); - glDrawArrays(GL_TRIANGLES, 0, 3); - } + TEST_F(PassOrderScenario, ATextureBlittedIntoIsSampledByTheNextDraw) { + if (!Ready() || IsSkipped()) return; + const GLuint source = NewTexture(kSize, kSize, green); + const GLuint sourceFbo = NewFramebuffer(source); + const GLuint texture = NewTexture(kSize, kSize, black); + const GLuint fbo = NewFramebuffer(texture); + glFinish(); + glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); + SampleInto(texture, 0); + ExpectPixel(4, 4, green, "the draw after the blit samples the blitted texels"); + } - // Samples `texture` into the 8x8 tile `tile` of the target. - void SampleInto(GLuint texture, int tile) { - glBindFramebuffer(GL_FRAMEBUFFER, target); - glUseProgram(sample); - glBindTexture(GL_TEXTURE_2D, texture); - glViewport(tile * kSize, 0, kSize, kHeight); - glDrawArrays(GL_TRIANGLES, 0, 3); - } + TEST_F(PassOrderScenario, ATextureCopiedIntoIsSampledByTheNextDraw) { + if (!Ready() || IsSkipped()) return; + const GLuint source = NewTexture(kSize, kSize, green); + const GLuint texture = NewTexture(kSize, kSize, black); + glFinish(); + SampleInto(texture, 1); // a pass is open on the target when the copy arrives + glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, kSize, kSize, 1); + SampleInto(texture, 0); + ExpectPixel(4, 4, green, "the draw after the copy samples the copied texels"); + ExpectPixel(12, 4, black, "the draw before the copy samples the old texels"); + } - void ExpectPixel(int x, int y, Pixel expected, const char* why) { - glBindFramebuffer(GL_FRAMEBUFFER, target); - Pixel actual{}; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); - for (size_t channel = 0; channel < actual.size(); ++channel) - EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; - } -}; + TEST_F(PassOrderScenario, ATextureBlittedWithinItselfIsSampledByTheNextDraw) { + if (!Ready() || IsSkipped()) return; + // GL allows a blit between non-overlapping regions of one image; ES 3.x makes identical read + // and draw buffers GL_INVALID_OPERATION, so DirectGLES stages the source through a scratch copy. + const GLuint texture = NewTexture(kSize, kSize, red); + const GLuint fbo = NewFramebuffer(texture); + glFinish(); + Fill(fbo, green, 0, 0, kSize / 2, kSize); + SampleInto(texture, 1); // a pass is open on the target when the blit arrives + // The left half onto the right half of the same image: one image is both ends of the + // transfer, so it stays in GENERAL and its only ordering edge is the memory barrier. + glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); + glBlitFramebuffer(0, 0, kSize / 2, kSize, kSize / 2, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); + SampleInto(texture, 0); + ExpectPixel(6, 4, green, "the draw after the blit samples the blitted half"); + ExpectPixel(14, 4, red, "the draw before the blit samples the right half's old texels"); + } -TEST_F(PassOrderScenario, ATextureRenderedInOnePassIsSampledByTheNext) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewTexture(kSize, kSize, black); - const GLuint fbo = NewFramebuffer(texture); - glFinish(); - Fill(fbo, green, 0, 0, kSize, kSize); - SampleInto(texture, 0); - SampleInto(texture, 1); - ExpectPixel(4, 4, green, "the next pass samples what the previous pass rendered"); - ExpectPixel(12, 4, green, "a second draw in the same pass samples it too"); -} + TEST_F(PassOrderScenario, ATextureSampledBeforeItIsRenderedIntoKeepsTheOldTexelsForThatDraw) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewTexture(kSize, kSize, red); + const GLuint fbo = NewFramebuffer(texture); + glFinish(); + SampleInto(texture, 0); + Fill(fbo, blue, 0, 0, kSize, kSize); + SampleInto(texture, 1); + ExpectPixel(4, 4, red, "the draw before the render samples the old texels"); + ExpectPixel(12, 4, blue, "the draw after the render samples the new texels"); + } -TEST_F(PassOrderScenario, ATextureRenderedAgainAfterAnotherPassKeepsBothWrites) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewTexture(kSize, kSize, black); - const GLuint fbo = NewFramebuffer(texture); - glFinish(); - Fill(fbo, red, 0, 0, kSize, kSize); - Fill(target, black, 0, 0, kWidth, kHeight); // another pass in between - Fill(fbo, blue, 0, 0, kSize / 2, kSize); // the left half again - SampleInto(texture, 0); - ExpectPixel(1, 4, blue, "the second write to the texture lands over the first"); - ExpectPixel(6, 4, red, "the first write survives where the second did not reach"); -} - -TEST_F(PassOrderScenario, ATextureBlittedIntoIsSampledByTheNextDraw) { - if (!Ready() || IsSkipped()) return; - const GLuint source = NewTexture(kSize, kSize, green); - const GLuint sourceFbo = NewFramebuffer(source); - const GLuint texture = NewTexture(kSize, kSize, black); - const GLuint fbo = NewFramebuffer(texture); - glFinish(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); - SampleInto(texture, 0); - ExpectPixel(4, 4, green, "the draw after the blit samples the blitted texels"); -} - -TEST_F(PassOrderScenario, ATextureCopiedIntoIsSampledByTheNextDraw) { - if (!Ready() || IsSkipped()) return; - const GLuint source = NewTexture(kSize, kSize, green); - const GLuint texture = NewTexture(kSize, kSize, black); - glFinish(); - SampleInto(texture, 1); // a pass is open on the target when the copy arrives - glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, kSize, kSize, 1); - SampleInto(texture, 0); - ExpectPixel(4, 4, green, "the draw after the copy samples the copied texels"); - ExpectPixel(12, 4, black, "the draw before the copy samples the old texels"); -} - -TEST_F(PassOrderScenario, ATextureBlittedWithinItselfIsSampledByTheNextDraw) { - if (!Ready() || IsSkipped()) return; - // GL allows a blit between non-overlapping regions of one image; ES 3.x makes identical read - // and draw buffers GL_INVALID_OPERATION, so DirectGLES stages the source through a scratch copy. - const GLuint texture = NewTexture(kSize, kSize, red); - const GLuint fbo = NewFramebuffer(texture); - glFinish(); - Fill(fbo, green, 0, 0, kSize / 2, kSize); - SampleInto(texture, 1); // a pass is open on the target when the blit arrives - // The left half onto the right half of the same image: one image is both ends of the - // transfer, so it stays in GENERAL and its only ordering edge is the memory barrier. - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo); - glBlitFramebuffer(0, 0, kSize / 2, kSize, kSize / 2, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); - SampleInto(texture, 0); - ExpectPixel(6, 4, green, "the draw after the blit samples the blitted half"); - ExpectPixel(14, 4, red, "the draw before the blit samples the right half's old texels"); -} - -TEST_F(PassOrderScenario, ATextureSampledBeforeItIsRenderedIntoKeepsTheOldTexelsForThatDraw) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewTexture(kSize, kSize, red); - const GLuint fbo = NewFramebuffer(texture); - glFinish(); - SampleInto(texture, 0); - Fill(fbo, blue, 0, 0, kSize, kSize); - SampleInto(texture, 1); - ExpectPixel(4, 4, red, "the draw before the render samples the old texels"); - ExpectPixel(12, 4, blue, "the draw after the render samples the new texels"); -} - -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp index 25e8f48f..cff262c9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PersistentCoherentMapScenario.cpp @@ -164,8 +164,7 @@ void main() { oColor = vec4(vColor, 1.0); } if (FirstGLError() != 0u) { glDeleteBuffers(1, &store.vbo); store.vbo = 0; - store.refusal = - "this driver has no immutable storage with GL_MAP_PERSISTENT_BIT|GL_MAP_COHERENT_BIT"; + store.refusal = "this driver has no immutable storage with GL_MAP_PERSISTENT_BIT|GL_MAP_COHERENT_BIT"; return store; } // THE ONE ACQUISITION. Everything below writes through this pointer and never maps @@ -184,8 +183,7 @@ void main() { oColor = vec4(vColor, 1.0); } void DescribeAttributes() { glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), nullptr); - glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), - reinterpret_cast(2 * sizeof(float))); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), reinterpret_cast(2 * sizeof(float))); glEnableVertexAttribArray(0); glEnableVertexAttribArray(1); } @@ -244,7 +242,7 @@ void main() { oColor = vec4(vColor, 1.0); } "build (on Android this module links the shipping " "libMobileGL.so, built -fvisibility=hidden, so no internal " "symbol resolves). 'Could not look' is not 'it was " - << declared << "'."; + << declared << "'."; } RecordProperty("persistent_map_arm", "unknown"); return; @@ -261,8 +259,8 @@ void main() { oColor = vec4(vColor, 1.0); } RecordProperty("persistent_map_arm", arm); if (declared.empty()) return; ASSERT_EQ(declared, std::string(arm)) - << "the lane declared the " << declared << " arm and IsBackendPersistentMapped() " - << when << " says " << arm + << "the lane declared the " << declared << " arm and IsBackendPersistentMapped() " << when + << " says " << arm << ". R-6 pins the split arm at adopt tier T2 = declined = emulated, and the two " "arms are completely different code paths: adopted means the CPU shadow was " "released and the application writes straight into storage the resource owner " @@ -372,8 +370,7 @@ void main() { oColor = vec4(vColor, 1.0); } // The whole surface minus an 8-pixel inset, so a primitive edge cannot contribute an // offender. - ::testing::AssertionResult WholeSurfaceIs(const Image& image, const char* color, - const std::string& when) { + ::testing::AssertionResult WholeSurfaceIs(const Image& image, const char* color, const std::string& when) { return RegionIsMostly(image, 8, image.Width() - 9, 8, image.Height() - 9, color, 0.0, when); } @@ -381,7 +378,11 @@ void main() { oColor = vec4(vColor, 1.0); } // declare none (the monolith counting lane does not), and the peek may be unable to // look at all, so the third state is not "assume emulated" - it is "there is no arm to // condition on", and AssertMembership records rather than asserts under it. - enum class ObservedArm { NotLookedAt, Adopted, Emulated }; + enum class ObservedArm { + NotLookedAt, + Adopted, + Emulated + }; ObservedArm m_observedArm = ObservedArm::NotLookedAt; unsigned int m_program = 0; @@ -439,8 +440,7 @@ void main() { oColor = vec4(vColor, 1.0); } WriteThroughTheMapThenDraw(m_map, 1.0f, 0.0f, 0.0f); const Image second = ReadPixels(Gl().Width(), Gl().Height()); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(WholeSurfaceIs(second, "red", - "frame 1's write through the SAME mapping, after a Present")); + EXPECT_TRUE(WholeSurfaceIs(second, "red", "frame 1's write through the SAME mapping, after a Present")); AssertOrRecordArm(m_vbo, "after a frame boundary and two draws"); Gl().EndFrame(); } @@ -514,8 +514,8 @@ void main() { oColor = vec4(vColor, 1.0); } RecordProperty("stats_counter_owner", "client process totals over the counted workload"); } const long long roundtrips = separateClientStats - ? static_cast(acquisitionsAfter - acquisitionsBefore) - : PipeStatsWindow::CounterOrAbsent(window, "mpr"); + ? static_cast(acquisitionsAfter - acquisitionsBefore) + : PipeStatsWindow::CounterOrAbsent(window, "mpr"); ASSERT_GE(roundtrips, 0) << "the summary line carries no mpr= field: " << window.line; EXPECT_EQ(roundtrips, kExpectedRoundtrips) << "one PERSISTENT|WRITE|COHERENT map of one freshly defined store is ONE acquisition " @@ -537,9 +537,8 @@ void main() { oColor = vec4(vColor, 1.0); } // cross-check. Deriving the arm from pmap would have made "the push was never wired" and // "the store was adopted" the same reading, and they are different defects with different // owners. - const double pushedBytes = separateClientStats - ? static_cast(pushedAfter - pushedBefore) - : PipeStatsWindow::CounterAsDoubleOrAbsent(window, "pmap"); + const double pushedBytes = separateClientStats ? static_cast(pushedAfter - pushedBefore) + : PipeStatsWindow::CounterAsDoubleOrAbsent(window, "pmap"); ASSERT_GE(pushedBytes, 0.0) << "the summary line carries no pmap= field: " << window.line; RecordProperty("persistent_map_push_bytes_per_frame", std::to_string(pushedBytes).c_str()); RecordProperty("map_persistent_roundtrips", std::to_string(roundtrips).c_str()); @@ -559,8 +558,7 @@ void main() { oColor = vec4(vColor, 1.0); } } else { // Recorded, not asserted: on this arm the number is whatever the driver's adoption // decision makes it, and it is the figure MEASUREMENTS wants beside the split one. - RecordProperty("persistent_map_push_note", - "recorded only - this lane declares no emulated arm"); + RecordProperty("persistent_map_push_note", "recorded only - this lane declares no emulated arm"); } unsigned int vbo = store.vbo; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp index b9b89b9a..d1b20ea6 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PipeVerifyArmingScenario.cpp @@ -119,8 +119,7 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } std::size_t pos = 0; while (pos < text.size()) { const std::size_t eol = text.find('\n', pos); - const std::string line = - text.substr(pos, eol == std::string::npos ? std::string::npos : eol - pos); + const std::string line = text.substr(pos, eol == std::string::npos ? std::string::npos : eol - pos); if (line.find(needle) != std::string::npos && line.find("where=read") != std::string::npos) { ++count; } @@ -135,8 +134,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } // The library log this process is writing, or an empty path when none was configured. static std::filesystem::path LibraryLogPath() { // P6: the path is a BASE NAME and the library writes one log per role; PipeStatsWindow - // derives the suffix, so the rule lives in one place. - return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); + // derives the suffix, so the rule lives in one place. + return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); } // ONE MARK PER ROLE. The library writes a log per role, so "how long is the log @@ -287,7 +286,9 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } EXPECT_NE(appended.find(expected), std::string::npos) << "MOBILEGL_PIPE_VERIFY_CORRUPT=" << knob << " perturbs that field in the snapshot arm before every entry compare, so a working " - "comparator must have reported " << expected << "...\". It reported nothing, which " + "comparator must have reported " + << expected + << "...\". It reported nothing, which " "means the comparator is not comparing - and every green entry in this lane is " "green for no reason. Log appended by this case:\n" << appended; @@ -325,7 +326,9 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } const char* knob = std::getenv("MOBILEGL_PIPE_VERIFY_CORRUPT"); if (knob == nullptr || std::string(knob) != kPackField) { GTEST_SKIP() << "this case is the compare-at-read hook's negative control and needs " - "MOBILEGL_PIPE_VERIFY_CORRUPT=" << kPackField << " for the whole " + "MOBILEGL_PIPE_VERIFY_CORRUPT=" + << kPackField + << " for the whole " "process, which is what the VerifySplitReadCorrupted. ctest entries " "set; no other field is read by the server inside a window whose " "oracle the hook rewrites"; @@ -348,13 +351,14 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } ASSERT_NO_FATAL_FAILURE(DrawOneFrame()); const std::string server = MGITest::PipeStatsWindow::ReadServerLogSince(before); - const std::string expected = - std::string(kDifferPrefix) + ", \"" + kPackField + "@ReadPixels\""; + const std::string expected = std::string(kDifferPrefix) + ", \"" + kPackField + "@ReadPixels\""; const std::size_t at = server.find(expected); ASSERT_NE(at, std::string::npos) - << "MOBILEGL_PIPE_VERIFY_CORRUPT=" << kPackField << " perturbs the hook's ORACLE at " + << "MOBILEGL_PIPE_VERIFY_CORRUPT=" << kPackField + << " perturbs the hook's ORACLE at " "every backend read of that field, and the server's read_pixels reads it under " - "the server's own stamp - so the server role's log had to carry " << expected + "the server's own stamp - so the server role's log had to carry " + << expected << ", ...}. It carried nothing: either the compare-at-read hook is not running on " "the apply thread, or the neutral-pack window swallowed the perturbation, and " "in both cases every per-read green in this lane is green for no reason. Server " @@ -362,8 +366,7 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } << server; const std::size_t eol = server.find('\n', at); - const std::string line = - server.substr(at, eol == std::string::npos ? std::string::npos : eol - at); + const std::string line = server.substr(at, eol == std::string::npos ? std::string::npos : eol - at); EXPECT_NE(line.find("where=read"), std::string::npos) << "the server half reported the corrupted field, but not from the compare-at-read " "arm - `where=` says which comparator spoke, and only `read` is this case's " diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PipelineFailureScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PipelineFailureScenario.cpp index ae58b7ad..37accaff 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PipelineFailureScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PipelineFailureScenario.cpp @@ -111,8 +111,7 @@ void main() { // A solid red 2x2 texture, so the sampled colour is the same wherever the // (deliberately degenerate) coordinates land. - const unsigned char red[] = {255, 0, 0, 255, 255, 0, 0, 255, - 255, 0, 0, 255, 255, 0, 0, 255}; + const unsigned char red[] = {255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255}; glGenTextures(1, &m_texture); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, m_texture); @@ -186,10 +185,10 @@ void main() { const Image second = DrawOnce(); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << "the second draw errored"; - EXPECT_TRUE(RegionIsMostly(second, 2, second.Width() - 3, 2, second.Height() - 3, "red", 0.0, - "second draw")); - EXPECT_TRUE(second == first) << "the second draw differs from the first in " - << second.ByteDiffCount(first) << " bytes - the pipeline the second " + EXPECT_TRUE( + RegionIsMostly(second, 2, second.Width() - 3, 2, second.Height() - 3, "red", 0.0, "second draw")); + EXPECT_TRUE(second == first) << "the second draw differs from the first in " << second.ByteDiffCount(first) + << " bytes - the pipeline the second " "draw resolved is not the one the first draw used"; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PixelStoreSweepScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PixelStoreSweepScenario.cpp index 30670c59..d5b2ae49 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PixelStoreSweepScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PixelStoreSweepScenario.cpp @@ -66,11 +66,11 @@ namespace MGITest { GLint defaultValue; }; const PixelStoreMode kAllModes[] = { - {GL_UNPACK_SWAP_BYTES, 0}, {GL_UNPACK_LSB_FIRST, 0}, {GL_UNPACK_ROW_LENGTH, 0}, - {GL_UNPACK_IMAGE_HEIGHT, 0}, {GL_UNPACK_SKIP_ROWS, 0}, {GL_UNPACK_SKIP_PIXELS, 0}, - {GL_UNPACK_SKIP_IMAGES, 0}, {GL_UNPACK_ALIGNMENT, 4}, {GL_PACK_SWAP_BYTES, 0}, - {GL_PACK_LSB_FIRST, 0}, {GL_PACK_ROW_LENGTH, 0}, {GL_PACK_IMAGE_HEIGHT, 0}, - {GL_PACK_SKIP_ROWS, 0}, {GL_PACK_SKIP_PIXELS, 0}, {GL_PACK_SKIP_IMAGES, 0}, + {GL_UNPACK_SWAP_BYTES, 0}, {GL_UNPACK_LSB_FIRST, 0}, {GL_UNPACK_ROW_LENGTH, 0}, + {GL_UNPACK_IMAGE_HEIGHT, 0}, {GL_UNPACK_SKIP_ROWS, 0}, {GL_UNPACK_SKIP_PIXELS, 0}, + {GL_UNPACK_SKIP_IMAGES, 0}, {GL_UNPACK_ALIGNMENT, 4}, {GL_PACK_SWAP_BYTES, 0}, + {GL_PACK_LSB_FIRST, 0}, {GL_PACK_ROW_LENGTH, 0}, {GL_PACK_IMAGE_HEIGHT, 0}, + {GL_PACK_SKIP_ROWS, 0}, {GL_PACK_SKIP_PIXELS, 0}, {GL_PACK_SKIP_IMAGES, 0}, {GL_PACK_ALIGNMENT, 4}, }; @@ -82,28 +82,58 @@ namespace MGITest { GLint value; }; const SweepCase kSweep[] = { - {GL_UNPACK_ROW_LENGTH, 0}, {GL_UNPACK_ROW_LENGTH, 10}, {GL_UNPACK_ROW_LENGTH, 15}, - {GL_UNPACK_SKIP_ROWS, 0}, {GL_UNPACK_SKIP_ROWS, 1}, {GL_UNPACK_SKIP_ROWS, 2}, - {GL_UNPACK_SKIP_PIXELS, 0}, {GL_UNPACK_SKIP_PIXELS, 1}, {GL_UNPACK_SKIP_PIXELS, 2}, - {GL_UNPACK_ALIGNMENT, 1}, {GL_UNPACK_ALIGNMENT, 2}, {GL_UNPACK_ALIGNMENT, 4}, - {GL_UNPACK_ALIGNMENT, 8}, {GL_UNPACK_IMAGE_HEIGHT, 0}, {GL_UNPACK_IMAGE_HEIGHT, 10}, - {GL_UNPACK_IMAGE_HEIGHT, 15}, {GL_UNPACK_SKIP_IMAGES, 0}, {GL_UNPACK_SKIP_IMAGES, 1}, - {GL_UNPACK_SKIP_IMAGES, 2}, {GL_PACK_ROW_LENGTH, 0}, {GL_PACK_ROW_LENGTH, 10}, - {GL_PACK_ROW_LENGTH, 15}, {GL_PACK_SKIP_ROWS, 0}, {GL_PACK_SKIP_ROWS, 1}, - {GL_PACK_SKIP_ROWS, 2}, {GL_PACK_SKIP_PIXELS, 0}, {GL_PACK_SKIP_PIXELS, 1}, - {GL_PACK_SKIP_PIXELS, 2}, {GL_PACK_ALIGNMENT, 1}, {GL_PACK_ALIGNMENT, 2}, - {GL_PACK_ALIGNMENT, 4}, {GL_PACK_ALIGNMENT, 8}, + {GL_UNPACK_ROW_LENGTH, 0}, + {GL_UNPACK_ROW_LENGTH, 10}, + {GL_UNPACK_ROW_LENGTH, 15}, + {GL_UNPACK_SKIP_ROWS, 0}, + {GL_UNPACK_SKIP_ROWS, 1}, + {GL_UNPACK_SKIP_ROWS, 2}, + {GL_UNPACK_SKIP_PIXELS, 0}, + {GL_UNPACK_SKIP_PIXELS, 1}, + {GL_UNPACK_SKIP_PIXELS, 2}, + {GL_UNPACK_ALIGNMENT, 1}, + {GL_UNPACK_ALIGNMENT, 2}, + {GL_UNPACK_ALIGNMENT, 4}, + {GL_UNPACK_ALIGNMENT, 8}, + {GL_UNPACK_IMAGE_HEIGHT, 0}, + {GL_UNPACK_IMAGE_HEIGHT, 10}, + {GL_UNPACK_IMAGE_HEIGHT, 15}, + {GL_UNPACK_SKIP_IMAGES, 0}, + {GL_UNPACK_SKIP_IMAGES, 1}, + {GL_UNPACK_SKIP_IMAGES, 2}, + {GL_PACK_ROW_LENGTH, 0}, + {GL_PACK_ROW_LENGTH, 10}, + {GL_PACK_ROW_LENGTH, 15}, + {GL_PACK_SKIP_ROWS, 0}, + {GL_PACK_SKIP_ROWS, 1}, + {GL_PACK_SKIP_ROWS, 2}, + {GL_PACK_SKIP_PIXELS, 0}, + {GL_PACK_SKIP_PIXELS, 1}, + {GL_PACK_SKIP_PIXELS, 2}, + {GL_PACK_ALIGNMENT, 1}, + {GL_PACK_ALIGNMENT, 2}, + {GL_PACK_ALIGNMENT, 4}, + {GL_PACK_ALIGNMENT, 8}, // core-only, no ES equivalent - {GL_UNPACK_SWAP_BYTES, GL_FALSE}, {GL_UNPACK_SWAP_BYTES, GL_TRUE}, - {GL_UNPACK_LSB_FIRST, GL_FALSE}, {GL_UNPACK_LSB_FIRST, GL_TRUE}, - {GL_PACK_SWAP_BYTES, GL_FALSE}, {GL_PACK_SWAP_BYTES, GL_TRUE}, - {GL_PACK_LSB_FIRST, GL_FALSE}, {GL_PACK_LSB_FIRST, GL_TRUE}, - {GL_PACK_IMAGE_HEIGHT, 0}, {GL_PACK_IMAGE_HEIGHT, 10}, - {GL_PACK_IMAGE_HEIGHT, 15}, {GL_PACK_SKIP_IMAGES, 0}, - {GL_PACK_SKIP_IMAGES, 1}, {GL_PACK_SKIP_IMAGES, 2}, + {GL_UNPACK_SWAP_BYTES, GL_FALSE}, + {GL_UNPACK_SWAP_BYTES, GL_TRUE}, + {GL_UNPACK_LSB_FIRST, GL_FALSE}, + {GL_UNPACK_LSB_FIRST, GL_TRUE}, + {GL_PACK_SWAP_BYTES, GL_FALSE}, + {GL_PACK_SWAP_BYTES, GL_TRUE}, + {GL_PACK_LSB_FIRST, GL_FALSE}, + {GL_PACK_LSB_FIRST, GL_TRUE}, + {GL_PACK_IMAGE_HEIGHT, 0}, + {GL_PACK_IMAGE_HEIGHT, 10}, + {GL_PACK_IMAGE_HEIGHT, 15}, + {GL_PACK_SKIP_IMAGES, 0}, + {GL_PACK_SKIP_IMAGES, 1}, + {GL_PACK_SKIP_IMAGES, 2}, }; - std::size_t ImageBytes(int size) { return static_cast(size) * size * 4; } + std::size_t ImageBytes(int size) { + return static_cast(size) * size * 4; + } // Padded to kScratchBytes so it is safe to hand to an upload running under any of the // sweep's stride/skip settings. @@ -205,8 +235,8 @@ namespace MGITest { ASSERT_EQ(afterStatus, static_cast(GL_FRAMEBUFFER_COMPLETE)); const std::size_t diff = FirstDifference(baseline, after, ImageBytes(kTexSize)); ASSERT_EQ(diff, ImageBytes(kTexSize)) - << "default-state readback changed after setting and resetting 0x" << std::hex << sweep.mode - << std::dec << " = " << sweep.value << "; first differing byte " << diff << " (baseline " + << "default-state readback changed after setting and resetting 0x" << std::hex << sweep.mode << std::dec + << " = " << sweep.value << "; first differing byte " << diff << " (baseline " << static_cast(baseline[diff]) << ", now " << static_cast(after[diff]) << ")"; } @@ -305,8 +335,7 @@ namespace MGITest { // Four distinct opaque colours, so a row or a pixel landing in the wrong place is a // wrong VALUE and not just a wrong offset. const std::uint8_t texels[kW * kH * kBpp] = { - 0x11, 0x22, 0x33, 0xFF, 0x44, 0x55, 0x66, 0xFF, - 0x77, 0x88, 0x99, 0xFF, 0xAA, 0xBB, 0xCC, 0xFF, + 0x11, 0x22, 0x33, 0xFF, 0x44, 0x55, 0x66, 0xFF, 0x77, 0x88, 0x99, 0xFF, 0xAA, 0xBB, 0xCC, 0xFF, }; GLuint texture = 0, framebuffer = 0, pbo = 0; @@ -346,8 +375,8 @@ namespace MGITest { for (int y = 0; y < kH; ++y) { for (int x = 0; x < kW; ++x) { for (std::size_t c = 0; c < kBpp; ++c) { - const std::size_t at = kFirst + static_cast(y) * kStride + - static_cast(x) * kBpp + c; + const std::size_t at = + kFirst + static_cast(y) * kStride + static_cast(x) * kBpp + c; ASSERT_LT(at, kPboBytes); written[at] = true; EXPECT_EQ(readback[at], texels[(y * kW + x) * kBpp + c]) @@ -389,9 +418,9 @@ namespace MGITest { ASSERT_EQ(status, static_cast(GL_FRAMEBUFFER_COMPLETE)) << "iteration " << i; const std::size_t diff = FirstDifference(gradient, read, ImageBytes(kSize)); ASSERT_EQ(diff, ImageBytes(kSize)) - << "iteration " << i << " read back a different image than it uploaded; first differing byte " - << diff << " (uploaded " << static_cast(gradient[diff]) << ", read " - << static_cast(read[diff]) << ")"; + << "iteration " << i << " read back a different image than it uploaded; first differing byte " << diff + << " (uploaded " << static_cast(gradient[diff]) << ", read " << static_cast(read[diff]) + << ")"; ASSERT_EQ(FirstGLError(), 0u) << "iteration " << i; } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PointSizeDemotionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PointSizeDemotionScenario.cpp index 9e247b95..b5d02ee5 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PointSizeDemotionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PointSizeDemotionScenario.cpp @@ -180,8 +180,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static bool BackendHostsTessellation() { @@ -231,8 +230,8 @@ void main() for (const GLuint shader : shaders) { glAttachShader(program, shader); } - glTransformFeedbackVaryings(program, static_cast(varyings.size()), - varyings.data(), GL_INTERLEAVED_ATTRIBS); + glTransformFeedbackVaryings(program, static_cast(varyings.size()), varyings.data(), + GL_INTERLEAVED_ATTRIBS); glLinkProgram(program); GLint linked = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &linked); @@ -258,8 +257,8 @@ void main() GLuint xfbBuffer = 0; glGenBuffers(1, &xfbBuffer); glBindBuffer(GL_ARRAY_BUFFER, xfbBuffer); - glBufferData(GL_ARRAY_BUFFER, static_cast(capturedFloats * sizeof(float)), - poison.data(), GL_STATIC_COPY); + glBufferData(GL_ARRAY_BUFFER, static_cast(capturedFloats * sizeof(float)), poison.data(), + GL_STATIC_COPY); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer); @@ -273,20 +272,17 @@ void main() std::vector readback(capturedFloats, kPoison); glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(capturedFloats * sizeof(float)), - readback.data()); + static_cast(capturedFloats * sizeof(float)), readback.data()); glUseProgram(0); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); glDeleteBuffers(1, &xfbBuffer); return readback; } - static ::testing::AssertionResult ComponentIs(const std::vector& data, - std::size_t index, float expected, - float epsilon = 1e-4f) { + static ::testing::AssertionResult ComponentIs(const std::vector& data, std::size_t index, + float expected, float epsilon = 1e-4f) { if (index >= data.size()) { - return ::testing::AssertionFailure() - << "component " << index << " is past the capture buffer"; + return ::testing::AssertionFailure() << "component " << index << " is past the capture buffer"; } const float actual = data[index]; if (actual == kPoison) { @@ -307,8 +303,8 @@ void main() // snapshot may satisfy an assertion. static std::filesystem::path LibraryLogPath() { // P6: the path is a BASE NAME and the library writes one log per role; PipeStatsWindow - // derives the suffix, so the rule lives in one place. - return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); + // derives the suffix, so the rule lives in one place. + return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); } // ONE MARK PER ROLE. The library writes a log per role, so "how long is the log @@ -342,8 +338,8 @@ void main() TEST_F(PointSizeDemotionScenario, TheValueSurvivesTheFiveStageChainIntoTheCapture) { if (!Ready()) return; if (!BackendHostsTessellation()) { - GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" - << Gl().RendererString() << ")"; + GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" << Gl().RendererString() + << ")"; } glPatchParameteri(GL_PATCH_VERTICES, 3); DrainErrors(); @@ -367,8 +363,8 @@ void main() TEST_F(PointSizeDemotionScenario, TheEvaluationStageOwnsTheCaptureWithoutAGeometryStage) { if (!Ready()) return; if (!BackendHostsTessellation()) { - GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" - << Gl().RendererString() << ")"; + GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" << Gl().RendererString() + << ")"; } glPatchParameteri(GL_PATCH_VERTICES, 3); DrainErrors(); @@ -389,8 +385,8 @@ void main() GLsizei nameLength = 0; GLsizei varyingSize = 0; GLenum varyingType = 0; - glGetTransformFeedbackVarying(program, 0, sizeof(varyingName), &nameLength, &varyingSize, - &varyingType, varyingName); + glGetTransformFeedbackVarying(program, 0, sizeof(varyingName), &nameLength, &varyingSize, &varyingType, + varyingName); EXPECT_STREQ(varyingName, "gl_PointSize"); EXPECT_EQ(varyingType, static_cast(GL_FLOAT)); EXPECT_EQ(glGetError(), GL_NO_ERROR); @@ -446,11 +442,10 @@ void main() {GL_GEOMETRY_SHADER, kReadOnlyGeometrySource}, {GL_FRAGMENT_SHADER, kEchoFragmentSource}}, {"g_echo", "gl_PointSize"}); - ASSERT_NE(program, 0u) - << "the capture set failed to link. On a demoting configuration this is the " - "capture request never reaching the demotion, so the point-size capture was " - "respelled to a carrier no stage declares. Build log: " - << m_buildLog; + ASSERT_NE(program, 0u) << "the capture set failed to link. On a demoting configuration this is the " + "capture request never reaching the demotion, so the point-size capture was " + "respelled to a carrier no stage declares. Build log: " + << m_buildLog; const std::vector captured = RunCaptureSpan(program, GL_POINTS, 1, 2); EXPECT_TRUE(ComponentIs(captured, 0, 7.0f)) @@ -503,8 +498,7 @@ void main() // use still reaches the build the latched line fires in - and the capture must // STILL be right through the carrier. const std::vector captured = RunCaptureSpan(program, GL_POINTS, 1, 1); - EXPECT_TRUE(ComponentIs(captured, 0, 8.0f)) - << "the pinned-on lane did not even capture correctly"; + EXPECT_TRUE(ComponentIs(captured, 0, 8.0f)) << "the pinned-on lane did not even capture correctly"; EXPECT_EQ(glGetError(), GL_NO_ERROR); const std::string appended = LibraryLogSince(before); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp index f76ec1c9..506fd79f 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PoisonOmissionScenario.cpp @@ -118,8 +118,7 @@ namespace MGITest { // Set only in the re-executed child, so the worker case below runs in that process and skips // everywhere else (including in the ambient lanes, where it is registered like any other case). constexpr const char* kChildMarker = "MGITEST_POISON_OMISSION_CHILD"; - constexpr const char* kWorkerFilter = - "--gtest_filter=PoisonOmissionScenario.TheSequenceThePoisonControlsRun"; + constexpr const char* kWorkerFilter = "--gtest_filter=PoisonOmissionScenario.TheSequenceThePoisonControlsRun"; constexpr const char* kVS = R"(#version 330 core in vec2 aPos; @@ -215,7 +214,9 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } return log; } - std::string ReadChildLog() { return ReadChildLogHalves().All; } + std::string ReadChildLog() { + return ReadChildLogHalves().All; + } class PoisonOmissionScenario : public ScenarioTest { protected: @@ -317,7 +318,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } std::vector envp; envp.reserve(env.size() + 1); - for (std::string& entry : env) envp.push_back(entry.data()); + for (std::string& entry : env) + envp.push_back(entry.data()); envp.push_back(nullptr); std::string exe = "/proc/self/exe"; @@ -373,7 +375,9 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } TEST_F(PoisonOmissionScenario, TheSequenceThePoisonControlsRun) { if (std::getenv(kChildMarker) == nullptr) { GTEST_SKIP() << "this case is the body the two poison controls run in a child process; " - "it does nothing unless " << kChildMarker << " is set, which only the " + "it does nothing unless " + << kChildMarker + << " is set, which only the " "re-exec below does"; } if (!Ready()) return; @@ -412,8 +416,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } } if (pair == nullptr) { GTEST_SKIP() << "MOBILEGL_PIPE_POISON_OMIT is " << knob << ", but this case only knows " - << "how to provoke " << kOmittedPairs[0].Verb << ":" << kOmittedPairs[0].Field - << " and " << kOmittedPairs[1].Verb << ":" << kOmittedPairs[1].Field; + << "how to provoke " << kOmittedPairs[0].Verb << ":" << kOmittedPairs[0].Field << " and " + << kOmittedPairs[1].Verb << ":" << kOmittedPairs[1].Field; } const std::string expectedPair = std::string(pair->Field) + "@" + pair->Verb; @@ -431,7 +435,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } "through the accessor). Child log:\n" << childLog; EXPECT_EQ(WTERMSIG(status), SIGABRT) - << "the child died on signal " << WTERMSIG(status) << " rather than SIGABRT; the poison " + << "the child died on signal " << WTERMSIG(status) + << " rather than SIGABRT; the poison " "reports through MGLOG_F + std::abort(), so any other signal is a different crash. " "Child log:\n" << childLog; @@ -455,7 +460,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } // named here. if (pair->FiresOnTheServer) { EXPECT_NE(halves.Server.find(expectedFatal), std::string::npos) - << "the child aborted with " << expectedFatal << ", but NOT in its server-role log. " + << "the child aborted with " << expectedFatal + << ", but NOT in its server-role log. " "This pair is the split arm's control and its whole claim is that the stamp the " "SERVER writes at the verb boundary honours the omission - a firing anywhere " "else means the withheld stamp was the client's and the server-side half is " @@ -466,7 +472,8 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } #endif for (const char* earlier : pair->EarlierVerbs) { EXPECT_EQ(childLog.find(earlier), std::string::npos) - << "a verb that ran BEFORE the omitted one (" << earlier << ") also tripped the " + << "a verb that ran BEFORE the omitted one (" << earlier + << ") also tripped the " "poison, so the omission is not scoped to its verb: the fill classes are wrong, " "or the stamps are global. Child log:\n" << childLog; @@ -497,10 +504,9 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } const std::string childLog = ReadChildLog(); const std::string note = omissionArmed - ? std::string( - " NOTE: MOBILEGL_PIPE_POISON_OMIT is set in this process, so this failure is " - "what CI's negative control B is asking for - the poison IS armed, and this " - "entry going red is the proof.") + ? std::string(" NOTE: MOBILEGL_PIPE_POISON_OMIT is set in this process, so this failure is " + "what CI's negative control B is asking for - the poison IS armed, and this " + "entry going red is the proof.") : std::string(); ASSERT_TRUE(WIFEXITED(status)) << "with no omission armed, a draw followed by glGenerateMipmap must complete; the child " @@ -515,9 +521,7 @@ void main() { o_color = vec4(0.25, 0.5, 0.75, 1.0); } "HasFailure() ? 1 : 0), so its gtest output on this job's stderr names the line; " "anything else came from the harness. Child log:\n" << childLog; - EXPECT_EQ(childLog.find("Fatal{"), std::string::npos) - << "an unpoisoned run logged a Fatal:\n" - << childLog; + EXPECT_EQ(childLog.find("Fatal{"), std::string::npos) << "an unpoisoned run logged a Fatal:\n" << childLog; } #else diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PostLinkAttachScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PostLinkAttachScenario.cpp index d8e78823..f7582405 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PostLinkAttachScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PostLinkAttachScenario.cpp @@ -141,9 +141,11 @@ void main() void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (const GLuint program : m_programs) glDeleteProgram(program); + for (const GLuint program : m_programs) + glDeleteProgram(program); m_programs.clear(); - for (const GLuint shader : m_shaders) glDeleteShader(shader); + for (const GLuint shader : m_shaders) + glDeleteShader(shader); m_shaders.clear(); BindDefaultFramebuffer(); DestroyColorFbo(m_target); @@ -154,8 +156,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static bool BackendHostsGeometry() { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PrimitiveRestartScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PrimitiveRestartScenario.cpp index 2cf33b88..37f50f10 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PrimitiveRestartScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PrimitiveRestartScenario.cpp @@ -69,12 +69,12 @@ void main() constexpr GLfloat kVertices[] = { -0.9f, -0.9f, // 0 - left triangle -0.1f, -0.9f, // 1 - -0.9f, 0.9f, // 2 - 0.1f, -0.9f, // 3 - right triangle - 0.9f, -0.9f, // 4 - 0.9f, 0.9f, // 5 - 0.0f, 0.0f, // 6 - spare - 0.0f, 0.0f, // 7 - spare, and the application's restart index + -0.9f, 0.9f, // 2 + 0.1f, -0.9f, // 3 - right triangle + 0.9f, -0.9f, // 4 + 0.9f, 0.9f, // 5 + 0.0f, 0.0f, // 6 - spare + 0.0f, 0.0f, // 7 - spare, and the application's restart index }; constexpr GLuint kRestartIndex = 7; @@ -112,8 +112,7 @@ void main() glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTexture, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), - static_cast(GL_FRAMEBUFFER_COMPLETE)); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), static_cast(GL_FRAMEBUFFER_COMPLETE)); glViewport(0, 0, kSurface, kSurface); m_program = BuildProgram(); @@ -142,8 +141,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } GLuint BuildProgram() { @@ -178,8 +176,7 @@ void main() std::vector DrawAndRead() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); - glDrawElements(GL_TRIANGLE_STRIP, static_cast(std::size(kIndices)), GL_UNSIGNED_INT, - nullptr); + glDrawElements(GL_TRIANGLE_STRIP, static_cast(std::size(kIndices)), GL_UNSIGNED_INT, nullptr); std::vector pixels(static_cast(kSurface) * kSurface); glReadPixels(0, 0, kSurface, kSurface, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); return pixels; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/PrimitivesGeneratedNoXfbScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/PrimitivesGeneratedNoXfbScenario.cpp index 993a8cb7..6deafc2f 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/PrimitivesGeneratedNoXfbScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/PrimitivesGeneratedNoXfbScenario.cpp @@ -132,8 +132,7 @@ void main() { ScenarioTest::SetUp(); if (!Ready()) return; if (Gl().BackendName() != std::string("DirectVulkan")) { - GTEST_SKIP() << "the stream-query defect and its reroute are DirectVulkan's; " - << Gl().BackendName() + GTEST_SKIP() << "the stream-query defect and its reroute are DirectVulkan's; " << Gl().BackendName() << " answers this query from a different mechanism"; } glGenVertexArrays(1, &m_vao); @@ -166,18 +165,21 @@ void main() { for (const auto& [type, source] : stages) { const GLuint shader = CompileShaderStage(type, source, &m_buildLog); if (shader == 0) { - for (const GLuint built : shaders) glDeleteShader(built); + for (const GLuint built : shaders) + glDeleteShader(built); return 0; } shaders.push_back(shader); } const GLuint program = glCreateProgram(); - for (const GLuint shader : shaders) glAttachShader(program, shader); + for (const GLuint shader : shaders) + glAttachShader(program, shader); if (captureVarying != nullptr) { glTransformFeedbackVaryings(program, 1, &captureVarying, GL_INTERLEAVED_ATTRIBS); } glLinkProgram(program); - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); GLint status = GL_FALSE; glGetProgramiv(program, GL_LINK_STATUS, &status); if (status == GL_FALSE) { @@ -193,21 +195,17 @@ void main() { return program; } - GLuint BuildCaptureProgram() { - return BuildProgram({{GL_VERTEX_SHADER, kVertexSource}}, "vs_out_value"); - } + GLuint BuildCaptureProgram() { return BuildProgram({{GL_VERTEX_SHADER, kVertexSource}}, "vs_out_value"); } GLuint BuildTessellationProgram(bool withCaptureVarying = false) { GLint maxTessGenLevel = 0; glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} if (maxTessGenLevel < 1) return 0; return BuildProgram( {{GL_VERTEX_SHADER, kTessVertexSource}, {GL_TESS_CONTROL_SHADER, kTessControlSource}, - {GL_TESS_EVALUATION_SHADER, - withCaptureVarying ? kTessEvalCaptureSource : kTessEvalSource}}, + {GL_TESS_EVALUATION_SHADER, withCaptureVarying ? kTessEvalCaptureSource : kTessEvalSource}}, withCaptureVarying ? "te_out_value" : nullptr); } @@ -243,8 +241,7 @@ void main() { static GLenum DrainGLErrors() { const GLenum first = glGetError(); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return first; } @@ -252,8 +249,8 @@ void main() { static std::filesystem::path LibraryLogPath() { // P6: the path is a BASE NAME and the library writes one log per role; PipeStatsWindow - // derives the suffix, so the rule lives in one place. - return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); + // derives the suffix, so the rule lives in one place. + return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); } // ONE MARK PER ROLE. The library writes a log per role, so "how long is the log @@ -291,9 +288,8 @@ void main() { const GLuint generated = QueryGenerated([]() { glDrawArrays(GL_TRIANGLES, 0, 6); }); EXPECT_EQ(DrainGLErrors(), 0u); - EXPECT_EQ(generated, 2u) - << "GL_PRIMITIVES_GENERATED must count a draw made while transform feedback is " - "inactive (GL 4.6 core 13.4)"; + EXPECT_EQ(generated, 2u) << "GL_PRIMITIVES_GENERATED must count a draw made while transform feedback is " + "inactive (GL 4.6 core 13.4)"; } // THE CTS SHAPE (esextcTessellationShaderUtils.cpp, captureTessellationData): @@ -352,9 +348,8 @@ void main() { const GLuint generated = QueryGenerated([]() { glDrawArrays(GL_PATCHES, 0, 1); }); EXPECT_EQ(DrainGLErrors(), 0u); - EXPECT_EQ(generated, 1u) - << "a triangles-domain patch with every level 1 tessellates to exactly one " - "triangle, and GL_PRIMITIVES_GENERATED must say so with no capture active"; + EXPECT_EQ(generated, 1u) << "a triangles-domain patch with every level 1 tessellates to exactly one " + "triangle, and GL_PRIMITIVES_GENERATED must say so with no capture active"; } // One query span holding BOTH kinds of draw: an XFB-inactive draw, then a @@ -468,9 +463,8 @@ void main() { "on this stack, so the paused half proves nothing; the bring-up probe " "measures the same hole and the POST row reports it"; } - EXPECT_EQ(unpaused, 1u) - << "the control itself: a triangles-domain patch with every level 1 tessellates to " - "exactly one triangle"; + EXPECT_EQ(unpaused, 1u) << "the control itself: a triangles-domain patch with every level 1 tessellates to " + "exactly one triangle"; EXPECT_EQ(paused, unpaused) << "pausing the capture does not stop the tessellator from generating that triangle, " "and the frontend's CPU paused counter answers 0 for GL_PATCHES - so a paused " @@ -486,8 +480,7 @@ void main() { ASSERT_NE(program, 0u) << BuildLog(); glUseProgram(program); - const GLuint unpaused = - QueryGenerated([]() { glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 4); }); + const GLuint unpaused = QueryGenerated([]() { glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 4); }); BeginPausedSpan(); const GLuint paused = QueryGenerated([]() { glDrawArraysInstanced(GL_TRIANGLES, 0, 3, 4); }); EndPausedSpan(); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ProgramPipelineScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ProgramPipelineScenario.cpp index c764730b..451dd486 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ProgramPipelineScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ProgramPipelineScenario.cpp @@ -108,8 +108,10 @@ void main() if (!Ready()) return; glBindProgramPipeline(0); glUseProgram(0); - for (GLuint p : m_programs) glDeleteProgram(p); - for (GLuint p : m_pipelines) glDeleteProgramPipelines(1, &p); + for (GLuint p : m_programs) + glDeleteProgram(p); + for (GLuint p : m_pipelines) + glDeleteProgramPipelines(1, &p); m_programs.clear(); m_pipelines.clear(); } @@ -851,9 +853,9 @@ void main() { o_color = texture(u_tex, vec2(0.5)); } glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fs); std::string error; - const unsigned int monolithic = CompileProgram( - "#version 330 core\nin vec2 aPos;\nvoid main(){ gl_Position = vec4(aPos,0.0,1.0); }\n", - "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0,0.0,0.0,1.0); }\n", &error); + const unsigned int monolithic = + CompileProgram("#version 330 core\nin vec2 aPos;\nvoid main(){ gl_Position = vec4(aPos,0.0,1.0); }\n", + "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0,0.0,0.0,1.0); }\n", &error); ASSERT_NE(monolithic, 0u) << error; m_programs.push_back(monolithic); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/RelinkStageSetScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/RelinkStageSetScenario.cpp index eabcb0a0..fbd39a64 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/RelinkStageSetScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/RelinkStageSetScenario.cpp @@ -122,9 +122,11 @@ void main() void TearDown() override { if (!Ready()) return; glUseProgram(0); - for (const GLuint program : m_programs) glDeleteProgram(program); + for (const GLuint program : m_programs) + glDeleteProgram(program); m_programs.clear(); - for (const GLuint shader : m_shaders) glDeleteShader(shader); + for (const GLuint shader : m_shaders) + glDeleteShader(shader); m_shaders.clear(); BindDefaultFramebuffer(); DestroyColorFbo(m_target); @@ -135,8 +137,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } // The same real-backend probes the other stage-gated scenarios use: 0 on a diff --git a/MobileGL/MG_IntegrationTest/Scenarios/RenderbufferBlendFormatScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/RenderbufferBlendFormatScenario.cpp index ca3c6474..8d35cc39 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/RenderbufferBlendFormatScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/RenderbufferBlendFormatScenario.cpp @@ -116,8 +116,7 @@ void main() glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_renderbuffer); const GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} return status == GL_FRAMEBUFFER_COMPLETE; } @@ -146,10 +145,11 @@ void main() const Image image = ReadPixels(kExtent, kExtent); ASSERT_FALSE(image.Empty()) << what; const Rgba8 centre = image.At(kExtent / 2, kExtent / 2); - EXPECT_GT(int(centre.r), 40) << what << ": got " << centre << ", which is darker than a blend of " - "black and 50% white"; - EXPECT_LT(int(centre.r), 215) << what << ": got " << centre - << ", which is the source colour - blending was disabled"; + EXPECT_GT(int(centre.r), 40) << what << ": got " << centre + << ", which is darker than a blend of " + "black and 50% white"; + EXPECT_LT(int(centre.r), 215) + << what << ": got " << centre << ", which is the source colour - blending was disabled"; } } // namespace diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ResidentIndexScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ResidentIndexScenario.cpp index f07033db..210b39a6 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ResidentIndexScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ResidentIndexScenario.cpp @@ -106,7 +106,7 @@ void main() { oColor = vec4(vColor, 1.0); } std::vector Scene() { return {{-1, -1, 1, 0, 0}, {0, -1, 1, 0, 0}, {0, 1, 1, 0, 0}, {-1, 1, 1, 0, 0}, - {0, -1, 0, 1, 0}, {1, -1, 0, 1, 0}, {1, 1, 0, 1, 0}, {0, 1, 0, 1, 0}}; + {0, -1, 0, 1, 0}, {1, -1, 0, 1, 0}, {1, 1, 0, 1, 0}, {0, 1, 0, 1, 0}}; } class ResidentIndexScenario : public ScenarioTest { @@ -144,8 +144,7 @@ void main() { oColor = vec4(vColor, 1.0); } unsigned int vbo = 0; glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(vertices.size() * sizeof(V)), vertices.data(), - GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, GLsizeiptr(vertices.size() * sizeof(V)), vertices.data(), GL_STATIC_DRAW); return vbo; } @@ -192,9 +191,9 @@ void main() { oColor = vec4(vColor, 1.0); } GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT | GL_DYNAMIC_STORAGE_BIT; glBufferStorage(GL_ELEMENT_ARRAY_BUFFER, GLsizeiptr(sizeof(kLeft)), kLeft, storageFlags); if (FirstGLError() != GL_NO_ERROR) GTEST_SKIP() << "no immutable storage"; - auto* map = static_cast(glMapBufferRange( - GL_ELEMENT_ARRAY_BUFFER, 0, GLsizeiptr(sizeof(kLeft)), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); + auto* map = static_cast( + glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, GLsizeiptr(sizeof(kLeft)), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); ASSERT_NE(map, nullptr); const unsigned int vao = MakeVao(vbo, ebo); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ResourceSubsystemControlScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ResourceSubsystemControlScenario.cpp index f26b8dfe..d60e72af 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ResourceSubsystemControlScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ResourceSubsystemControlScenario.cpp @@ -227,8 +227,8 @@ void main() { oColor = vec4(vColor, 1.0); } glBindBuffer(GL_ARRAY_BUFFER, m_arena); glBufferData(GL_ARRAY_BUFFER, kArenaBytes, nullptr, GL_DYNAMIC_DRAW); const std::vector vertices = Quad(r, g, b); - glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, - GLsizeiptr(vertices.size() * sizeof(Vertex)), vertices.data()); + glBufferSubData(GL_ARRAY_BUFFER, kVertexOffset, GLsizeiptr(vertices.size() * sizeof(Vertex)), + vertices.data()); if (index == 0) { glBindVertexArray(m_vao); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), @@ -284,15 +284,15 @@ void main() { oColor = vec4(vColor, 1.0); } RecordProperty("stats_line", window.line.c_str()); const long long roundtrips = PipeStatsWindow::CounterOrAbsent(window, "mpr"); - ASSERT_GE(roundtrips, 0) - << "the summary line carries no mpr= field, so this build's PipeStats has no " - "map-persistent-roundtrips counter to read: " - << window.line; + ASSERT_GE(roundtrips, 0) << "the summary line carries no mpr= field, so this build's PipeStats has no " + "map-persistent-roundtrips counter to read: " + << window.line; if (m_lane == kLaneOn) { EXPECT_EQ(roundtrips, static_cast(kDefinitionsInTheWindow)) << "with bits 7|8 SET the resource family is the path a store definition takes, so " - "each of the " << kDefinitionsInTheWindow + "each of the " + << kDefinitionsInTheWindow << " definitions in this window is one map_persistent emission (mint or decline - " "both need an answer from the resource owner, D-B2). " << (kDefinitionsInTheWindow * kDrawsPerDefinition) @@ -316,9 +316,8 @@ void main() { oColor = vec4(vColor, 1.0); } // ... and the picture is the same whichever arm ran. The arena is drawn with one solid // colour, so both arms must read back exactly that. - EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, "green", 0.0, - "the arena draw [" + m_lane + "]")) + EXPECT_TRUE(RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, "green", + 0.0, "the arena draw [" + m_lane + "]")) << "the subsystem bits changed what is DRAWN, which is not an A/B - the handle path and " "the legacy path must produce the same pixels from the same arena."; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SampleVariablesScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SampleVariablesScenario.cpp index f0cbef3a..869f5fa9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SampleVariablesScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SampleVariablesScenario.cpp @@ -258,8 +258,7 @@ void main() { o_color = vec4(float(gl_NumSamples) * (16.0 / 255.0), 0.0, 0.0, 1. const Rgba8 centre = resolved.At(width / 2, height / 2); // The rate changes how OFTEN the shader runs, never what it computes - so the same // gl_NumSamples reading has to come back. - EXPECT_NEAR(centre.r, 16 * realizedSamples, 2) - << "the draw changed its result once sample shading was enabled"; + EXPECT_NEAR(centre.r, 16 * realizedSamples, 2) << "the draw changed its result once sample shading was enabled"; glMinSampleShading(0.0f); glDisable(GL_SAMPLE_SHADING); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SharedContextChangeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SharedContextChangeScenario.cpp index 4bd6b78a..0787d8bb 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SharedContextChangeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SharedContextChangeScenario.cpp @@ -203,8 +203,8 @@ void main() { ClearTo(0.0f, 0.0f, 1.0f, 1.0f); DrawQuad(program); const Image whole = ReadPixels(kFboSize, kFboSize); - EXPECT_TRUE(RegionIsMostly(whole, 0, kFboSize - 1, 0, kFboSize - 1, "green", 0.0, - "the resized renderbuffer through A")) + EXPECT_TRUE( + RegionIsMostly(whole, 0, kFboSize - 1, 0, kFboSize - 1, "green", 0.0, "the resized renderbuffer through A")) << "a corner left uncovered means A still recorded the renderbuffer's old extent"; glBindFramebuffer(GL_FRAMEBUFFER, 0); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SnormAttachmentScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SnormAttachmentScenario.cpp index 18dcaf85..c51d9bbd 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SnormAttachmentScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SnormAttachmentScenario.cpp @@ -218,8 +218,7 @@ void main() { oColor = vec4(uValue, 0.0, 0.0, 1.0); } << "an R8_SNORM colour attachment must be complete before any value can be asserted"; // Two 8-bit SNORM steps. A half float is exact here (worst case 0.03 of a step), so this // only has to catch a storage that quantizes harder than the format itself. - EXPECT_NEAR(red8, snorm8Expected, 2.0f / 127.0f) - << "R8_SNORM attachment lost its channel value"; + EXPECT_NEAR(red8, snorm8Expected, 2.0f / 127.0f) << "R8_SNORM attachment lost its channel value"; EXPECT_GT(red8, 0.5f) << "the draw never landed - this is the cleared texture, not the rendered one"; const float snorm16Expected = static_cast(kSnorm16Value) / 32767.0f; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SpirvShaderBinaryScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SpirvShaderBinaryScenario.cpp index 94d988a3..8dff5995 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SpirvShaderBinaryScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SpirvShaderBinaryScenario.cpp @@ -118,7 +118,6 @@ namespace MGITest { 0x00000009u, 0x0003003eu, 0x00000012u, 0x00000013u, 0x000100fdu, 0x00010038u, }; - // The quad the vertex module transforms. Full-viewport before the scale, so a scale of // 0.5 covers exactly the middle half of each axis and the corners stay background. const float kQuad[] = {-1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f}; @@ -127,8 +126,8 @@ namespace MGITest { constexpr unsigned int kScaleConstantId = 3; constexpr unsigned int kChannelConstantId = 7; - unsigned int MakeSpirvShader(GLenum type, const unsigned int* words, size_t wordCount, - unsigned int constantId, unsigned int constantValue, std::string* outLog) { + unsigned int MakeSpirvShader(GLenum type, const unsigned int* words, size_t wordCount, unsigned int constantId, + unsigned int constantValue, std::string* outLog) { const GLuint shader = glCreateShader(type); glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, words, static_cast(wordCount * sizeof(unsigned int))); @@ -273,15 +272,15 @@ namespace MGITest { const float half = 0.5f; std::memcpy(&halfBits, &half, sizeof(halfBits)); - const unsigned int vs = MakeSpirvShader(GL_VERTEX_SHADER, kVertexModule, - sizeof(kVertexModule) / sizeof(kVertexModule[0]), - kScaleConstantId, halfBits, &log); + const unsigned int vs = + MakeSpirvShader(GL_VERTEX_SHADER, kVertexModule, sizeof(kVertexModule) / sizeof(kVertexModule[0]), + kScaleConstantId, halfBits, &log); ASSERT_NE(vs, 0u) << "vertex: " << log; // Channel 1 is green; the module's own default is 0 (red), so a specialization that did // nothing paints the wrong colour. - const unsigned int fs = MakeSpirvShader(GL_FRAGMENT_SHADER, kFragmentModule, - sizeof(kFragmentModule) / sizeof(kFragmentModule[0]), - kChannelConstantId, 1u, &log); + const unsigned int fs = + MakeSpirvShader(GL_FRAGMENT_SHADER, kFragmentModule, sizeof(kFragmentModule) / sizeof(kFragmentModule[0]), + kChannelConstantId, 1u, &log); ASSERT_NE(fs, 0u) << "fragment: " << log; const GLuint program = glCreateProgram(); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayDynamicIndexScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayDynamicIndexScenario.cpp index cf32e660..1c1d4f48 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayDynamicIndexScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayDynamicIndexScenario.cpp @@ -120,9 +120,8 @@ void main() { GLuint buffer = 0; glGenBuffers(1, &buffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, - static_cast(contents.size() * sizeof(unsigned int)), contents.data(), - GL_DYNAMIC_COPY); + glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast(contents.size() * sizeof(unsigned int)), + contents.data(), GL_DYNAMIC_COPY); m_buffers.push_back(buffer); return buffer; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayLengthScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayLengthScenario.cpp index 59a1a4cb..bb092f4d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayLengthScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SsboArrayLengthScenario.cpp @@ -142,8 +142,8 @@ void main() { GLuint buffer = 0; glGenBuffers(1, &buffer); glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, - static_cast(elements) * kElementBytes, contents.data(), GL_DYNAMIC_COPY); + glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast(elements) * kElementBytes, + contents.data(), GL_DYNAMIC_COPY); m_buffers.push_back(buffer); return buffer; } @@ -155,8 +155,8 @@ void main() { glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); std::vector lengths(4, -1); glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, - static_cast(lengths.size() * sizeof(int)), lengths.data()); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, static_cast(lengths.size() * sizeof(int)), + lengths.data()); return lengths; } @@ -188,8 +188,8 @@ void main() { const std::vector lengths = RunAndReadLengths(output); EXPECT_EQ(FirstGLError(), 0u); EXPECT_EQ(lengths[0], 7) << "Input0 (no preamble, 7 elements) reported length " << lengths[0]; - EXPECT_EQ(lengths[1], 5) << "Input1 (1 ivec4 of preamble, 6 elements of storage) reported length " - << lengths[1] << "; 6 means the array's byte offset inside the block was ignored"; + EXPECT_EQ(lengths[1], 5) << "Input1 (1 ivec4 of preamble, 6 elements of storage) reported length " << lengths[1] + << "; 6 means the array's byte offset inside the block was ignored"; EXPECT_EQ(lengths[2], 3) << "Input23[0] (binding 2, 3 elements) reported length " << lengths[2]; EXPECT_EQ(lengths[3], 4) << "Input23[1] (binding 3, 4 elements) reported length " << lengths[3] << "; a block array's second element must resolve to the NEXT binding"; @@ -241,7 +241,7 @@ void main() { const GLuint program = CompileComputeProgram(kReadonlyWriteonlyComputeSource); ASSERT_NE(program, 0u) << m_buildLog; - const GLuint input = MakeStorageBuffer(6); // 6 ivec4 = 24 ints + const GLuint input = MakeStorageBuffer(6); // 6 ivec4 = 24 ints const GLuint output = MakeStorageBuffer(1); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 4, output); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SsboDeclarationFormScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SsboDeclarationFormScenario.cpp index ba89a3e1..4e68d557 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SsboDeclarationFormScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SsboDeclarationFormScenario.cpp @@ -173,8 +173,7 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } bool StorageBlocksInVertexStage() const { GLint blocks = 0; glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &blocks); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return blocks >= 1; } @@ -194,14 +193,13 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } &length, name); const GLenum props[4] = {GL_OFFSET, GL_ARRAY_SIZE, GL_ARRAY_STRIDE, GL_TOP_LEVEL_ARRAY_SIZE}; GLint values[4] = {-1, -1, -1, -1}; - glGetProgramResourceiv(program, GL_BUFFER_VARIABLE, static_cast(i), 4, props, - 4, nullptr, values); + glGetProgramResourceiv(program, GL_BUFFER_VARIABLE, static_cast(i), 4, props, 4, nullptr, + values); out += " " + std::string(name) + ": offset=" + std::to_string(values[0]) + " arraySize=" + std::to_string(values[1]) + " arrayStride=" + std::to_string(values[2]) + " topLevelArraySize=" + std::to_string(values[3]) + "\n"; } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return out; } @@ -215,7 +213,7 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } // The three corners of a triangle that covers the whole viewport, which is what // the block is expected to deliver to gl_Position. - const float positions[12] = {-1.0f, -1.0f, 0.0f, 1.0f, 3.0f, -1.0f, + const float positions[12] = {-1.0f, -1.0f, 0.0f, 1.0f, 3.0f, -1.0f, 0.0f, 1.0f, -1.0f, 3.0f, 0.0f, 1.0f}; GLuint buffer = 0; glGenBuffers(1, &buffer); @@ -280,12 +278,11 @@ void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); } } // namespace -#define MGL_SSBO_FORM_CASE(index, name) \ - TEST_F(SsboDeclarationFormScenario, name) { \ - if (!Ready()) return; \ - if (!StorageBlocksInVertexStage()) \ - GTEST_SKIP() << "no vertex-stage shader storage blocks"; \ - RunForm(index); \ +#define MGL_SSBO_FORM_CASE(index, name) \ + TEST_F(SsboDeclarationFormScenario, name) { \ + if (!Ready()) return; \ + if (!StorageBlocksInVertexStage()) GTEST_SKIP() << "no vertex-stage shader storage blocks"; \ + RunForm(index); \ } MGL_SSBO_FORM_CASE(0, InstanceNamedBlockWithNoBindingQualifier) diff --git a/MobileGL/MG_IntegrationTest/Scenarios/StorageBufferRegrowScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/StorageBufferRegrowScenario.cpp index 5c56898e..7e04efeb 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/StorageBufferRegrowScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/StorageBufferRegrowScenario.cpp @@ -70,8 +70,8 @@ void main() { // window would then be asserting that nothing happened. They GROW, which is the scenario's // subject: each glBufferData is a new storage definition, so each is one acquisition. constexpr int kAdoptedDefinitions = 3; - constexpr int kAdoptedBaseElements = 5 * 1024 * 1024; // 20 MiB of uint - constexpr int kAdoptedGrowthElements = 1024 * 1024; // + 4 MiB per definition + constexpr int kAdoptedBaseElements = 5 * 1024 * 1024; // 20 MiB of uint + constexpr int kAdoptedGrowthElements = 1024 * 1024; // + 4 MiB per definition // Enough dispatches per definition that "one per definition" and "one per dispatch" are // different numbers by a wide margin (3 vs 12), and few enough to stay cheap. constexpr int kDispatchesPerDefinition = 4; @@ -138,9 +138,8 @@ void main() { void RespecifyTo(int elements) { const std::vector zeros(static_cast(elements), 0u); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffer); - glBufferData(GL_SHADER_STORAGE_BUFFER, - static_cast(zeros.size() * sizeof(unsigned int)), zeros.data(), - GL_DYNAMIC_COPY); + glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast(zeros.size() * sizeof(unsigned int)), + zeros.data(), GL_DYNAMIC_COPY); } std::vector DispatchAndRead(int elements) { @@ -236,8 +235,7 @@ void main() { for (int i = 0; i < kLargeElements; ++i) { EXPECT_EQ(large[static_cast(i)], static_cast(i + 1)) << "element " << i << " (byte " << i * 4 << ") of the grown store came back as " - << large[static_cast(i)] - << "; zero from element " << kSmallElements + << large[static_cast(i)] << "; zero from element " << kSmallElements << " on means the shader still saw the pre-growth extent"; } @@ -274,8 +272,8 @@ void main() { // assertion over a workload that silently stopped functioning would be measuring nothing. std::vector values(static_cast(kDispatchedElements), 0xDEADBEEFu); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffer); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, - static_cast(values.size() * sizeof(unsigned int)), values.data()); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, static_cast(values.size() * sizeof(unsigned int)), + values.data()); EXPECT_EQ(FirstGLError(), 0u); for (int i = 0; i < kDispatchedElements; ++i) { EXPECT_EQ(values[static_cast(i)], static_cast(i + 1)) @@ -292,9 +290,9 @@ void main() { RecordProperty("stats_line", window.line.c_str()); const long long roundtrips = PipeStatsWindow::CounterOrAbsent(window, "mpr"); - ASSERT_GE(roundtrips, 0) - << "the summary line carries no mpr= field, so this build's PipeStats has no " - "map-persistent-roundtrips counter to read: " << window.line; + ASSERT_GE(roundtrips, 0) << "the summary line carries no mpr= field, so this build's PipeStats has no " + "map-persistent-roundtrips counter to read: " + << window.line; EXPECT_EQ(roundtrips, static_cast(kAdoptedDefinitions)) << "an adopted store costs ONE map_persistent per STORAGE DEFINITION " "(ARCHITECTURE.md §12). This window defined the store " diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SwizzleAccessRoutineScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SwizzleAccessRoutineScenario.cpp index 8750aa88..062c0ecf 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SwizzleAccessRoutineScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SwizzleAccessRoutineScenario.cpp @@ -196,10 +196,9 @@ void main() } ++offenders; } - EXPECT_EQ(offenders, 0u) - << routine.name << "(...)." << kChannels[channel] << " returned 0x" << std::hex << firstBad - << " instead of 0x" << expected << std::dec << " at texel " << firstIndex << " (" << offenders - << " of " << texels.size() << " wrong)"; + EXPECT_EQ(offenders, 0u) << routine.name << "(...)." << kChannels[channel] << " returned 0x" << std::hex + << firstBad << " instead of 0x" << expected << std::dec << " at texel " + << firstIndex << " (" << offenders << " of " << texels.size() << " wrong)"; } GLuint m_sourceTexture = 0; @@ -271,7 +270,7 @@ void main() // One routine from each side of the device's failure order, so a ceiling that takes the // vulnerable one down first is still caught. - const AccessRoutine& plain = kRoutines[0]; // texture + const AccessRoutine& plain = kRoutines[0]; // texture const AccessRoutine& explicitLod = kRoutines[1]; // textureLod constexpr int kIterations = 200; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/SyncWireScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/SyncWireScenario.cpp index f7146feb..2364d9fc 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/SyncWireScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/SyncWireScenario.cpp @@ -18,35 +18,36 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -class SyncWireScenario : public ScenarioTest {}; + class SyncWireScenario : public ScenarioTest {}; -TEST_F(SyncWireScenario, FenceWaitStatusAndDeletionCrossAndPreserveRenderedPixels) { - if (!Ready()) return; - const auto why = SplitRuntimeSkipReason(); - if (!why.empty()) GTEST_SKIP() << why; - glClearColor(0.25f, 0.5f, 0.75f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - const auto before = PeekSplitRuntime().emitSeq; - const auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - ASSERT_NE(sync, nullptr); - EXPECT_TRUE(glIsSync(sync)); - GLint status = 0; - glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &status); - EXPECT_TRUE(status == GL_SIGNALED || status == GL_UNSIGNALED); - const auto wait = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 5000000000ull); - EXPECT_TRUE(wait == GL_ALREADY_SIGNALED || wait == GL_CONDITION_SATISFIED) << wait; - glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); - glDeleteSync(sync); - EXPECT_FALSE(glIsSync(sync)); - // Create, the server-side wait and the delete always cross; since P10 the two polls may be - // answered from the server's fence report instead (FencePollScenario), so they are not counted. - EXPECT_GE(PeekSplitRuntime().emitSeq, before + 3); - GLubyte pixel[4]{}; - glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - const int expected[4] = {64, 128, 191, 255}; - for (int i = 0; i < 4; ++i) EXPECT_NEAR(pixel[i], expected[i], 1); - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); - // This one remains live until full session teardown, exercising server orphan cleanup. - ASSERT_NE(glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), nullptr); -} + TEST_F(SyncWireScenario, FenceWaitStatusAndDeletionCrossAndPreserveRenderedPixels) { + if (!Ready()) return; + const auto why = SplitRuntimeSkipReason(); + if (!why.empty()) GTEST_SKIP() << why; + glClearColor(0.25f, 0.5f, 0.75f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + const auto before = PeekSplitRuntime().emitSeq; + const auto sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + ASSERT_NE(sync, nullptr); + EXPECT_TRUE(glIsSync(sync)); + GLint status = 0; + glGetSynciv(sync, GL_SYNC_STATUS, 1, nullptr, &status); + EXPECT_TRUE(status == GL_SIGNALED || status == GL_UNSIGNALED); + const auto wait = glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, 5000000000ull); + EXPECT_TRUE(wait == GL_ALREADY_SIGNALED || wait == GL_CONDITION_SATISFIED) << wait; + glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(sync); + EXPECT_FALSE(glIsSync(sync)); + // Create, the server-side wait and the delete always cross; since P10 the two polls may be + // answered from the server's fence report instead (FencePollScenario), so they are not counted. + EXPECT_GE(PeekSplitRuntime().emitSeq, before + 3); + GLubyte pixel[4]{}; + glReadPixels(1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); + const int expected[4] = {64, 128, 191, 255}; + for (int i = 0; i < 4; ++i) + EXPECT_NEAR(pixel[i], expected[i], 1); + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); + // This one remains live until full session teardown, exercising server orphan cleanup. + ASSERT_NE(glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0), nullptr); + } } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TessellationDrawModeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TessellationDrawModeScenario.cpp index c4c9489f..d2feac86 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TessellationDrawModeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TessellationDrawModeScenario.cpp @@ -79,8 +79,8 @@ void main() glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); if (!BackendHostsTessellation()) { - GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" - << Gl().RendererString() << "); there is no patch draw to validate"; + GTEST_SKIP() << "no tessellation stages on " << Gl().BackendName() << " (" << Gl().RendererString() + << "); there is no patch draw to validate"; } } @@ -107,8 +107,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } GLuint BuildProgram(const std::vector>& stages) { @@ -128,16 +127,19 @@ void main() } } if (!ok) { - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); return 0; } const GLuint program = glCreateProgram(); - for (const GLuint shader : shaders) glAttachShader(program, shader); + for (const GLuint shader : shaders) + glAttachShader(program, shader); glLinkProgram(program); GLint linked = 0; glGetProgramiv(program, GL_LINK_STATUS, &linked); - for (const GLuint shader : shaders) glDeleteShader(shader); + for (const GLuint shader : shaders) + glDeleteShader(shader); if (!linked) { m_buildLog = InfoLog(program, false); glDeleteProgram(program); @@ -184,8 +186,8 @@ void main() glPatchParameteri(GL_PATCH_VERTICES, 1); DrainErrors(); - for (const GLenum mode : {static_cast(GL_POINTS), static_cast(GL_LINES), - static_cast(GL_TRIANGLES)}) { + for (const GLenum mode : + {static_cast(GL_POINTS), static_cast(GL_LINES), static_cast(GL_TRIANGLES)}) { glDrawArrays(mode, 0, 1); EXPECT_EQ(glGetError(), static_cast(GL_INVALID_OPERATION)) << "mode " << mode << " must not be accepted while tessellation is active"; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TessellationXfbCaptureScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TessellationXfbCaptureScenario.cpp index 675ebba3..96c15ca5 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TessellationXfbCaptureScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TessellationXfbCaptureScenario.cpp @@ -86,8 +86,7 @@ void main() } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static bool BackendHostsTessellation() { @@ -384,11 +383,12 @@ void main() glPatchParameteri(GL_PATCH_VERTICES, 1); DrainErrors(); - const GLuint program = BuildCaptureProgram({{GL_VERTEX_SHADER, kMinimalVertexSource}, - {GL_TESS_CONTROL_SHADER, kMinimalTessControlSource}, - {GL_TESS_EVALUATION_SHADER, kPositionAndPointSizeTessEvalSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}, - {"gl_Position", "gl_PointSize"}); + const GLuint program = + BuildCaptureProgram({{GL_VERTEX_SHADER, kMinimalVertexSource}, + {GL_TESS_CONTROL_SHADER, kMinimalTessControlSource}, + {GL_TESS_EVALUATION_SHADER, kPositionAndPointSizeTessEvalSource}, + {GL_FRAGMENT_SHADER, kFragmentSource}}, + {"gl_Position", "gl_PointSize"}); ASSERT_NE(program, 0u) << "program failed to build: " << m_buildLog; const std::vector captured = RunPatchCaptureSpan(program, GL_POINTS, 5 * 3); @@ -424,7 +424,8 @@ void main() std::string PayloadTessControlSource(int outputVertices, bool withPointSize) { const std::string perVertexTail = withPointSize ? " float gl_PointSize;\n" : ""; return R"(#version 420 core -layout(vertices = )" + std::to_string(outputVertices) + +layout(vertices = )" + + std::to_string(outputVertices) + R"() out; in gl_PerVertex { vec4 gl_Position; @@ -441,9 +442,10 @@ out OUT_TC } result[]; void main() { -)" + std::string(withPointSize - ? " gl_out[gl_InvocationID].gl_PointSize = 1.0 / float(gl_InvocationID + 1);\n" - : "") + +)" + + std::string(withPointSize + ? " gl_out[gl_InvocationID].gl_PointSize = 1.0 / float(gl_InvocationID + 1);\n" + : "") + R"( gl_out[gl_InvocationID].gl_Position = vec4(float(gl_InvocationID * 4 + 0), float(gl_InvocationID * 4 + 1), float(gl_InvocationID * 4 + 2), float(gl_InvocationID * 4 + 3)); result[gl_InvocationID].value1 = vec2(1.0 / float(gl_InvocationID + 1), 1.0 / float(gl_InvocationID + 2)); @@ -492,7 +494,8 @@ void main() te_value1 = vec2 (0.0); te_value2 = ivec4(0); - for (int n = 0; n < )" + std::to_string(inputVertices) + + for (int n = 0; n < )" + + std::to_string(inputVertices) + R"(; ++n) { )" + std::string(withPointSize ? " te_pointsize += gl_in [n].gl_PointSize;\n" : "") + @@ -640,8 +643,7 @@ void main() glEndTransformFeedback(); glDisable(GL_RASTERIZER_DISCARD); float captured = kPoison; - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(sizeof(float)), - &captured); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(sizeof(float)), &captured); glUseProgram(0); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0); glDeleteBuffers(1, &xfbBuffer); @@ -757,9 +759,8 @@ void main() glDisable(GL_RASTERIZER_DISCARD); ASSERT_EQ(glGetError(), GL_NO_ERROR) << "glEndTransformFeedback, iteration " << iteration; - const auto* mapped = - static_cast(glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, kBytes, - GL_MAP_READ_BIT)); + const auto* mapped = static_cast( + glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, kBytes, GL_MAP_READ_BIT)); ASSERT_EQ(glGetError(), GL_NO_ERROR) << "glMapBufferRange, iteration " << iteration; ASSERT_NE(mapped, nullptr) << "iteration " << iteration; const std::vector captured(mapped, mapped + kFloats); @@ -864,8 +865,8 @@ void main() GLuint xfbBuffer = 0; glGenBuffers(1, &xfbBuffer); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer); - glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(kFloats * sizeof(float)), - poison.data(), GL_STATIC_DRAW); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(kFloats * sizeof(float)), poison.data(), + GL_STATIC_DRAW); glBindVertexArray(m_vao); glUseProgram(0); @@ -878,8 +879,8 @@ void main() glDisable(GL_RASTERIZER_DISCARD); std::vector captured(kFloats, kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(kFloats * sizeof(float)), captured.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(kFloats * sizeof(float)), + captured.data()); EXPECT_TRUE(ComponentIs(captured, 0, 11.0f)); EXPECT_TRUE(ComponentIs(captured, 1, 12.0f)); EXPECT_TRUE(ComponentIs(captured, 2, 13.0f)); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureParamsWithoutASamplerViewScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureParamsWithoutASamplerViewScenario.cpp index b7e31ccc..8cc8281c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureParamsWithoutASamplerViewScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureParamsWithoutASamplerViewScenario.cpp @@ -267,8 +267,8 @@ void main() { GLuint texture = 0; glCreateTextures(GL_TEXTURE_2D, 1, &texture); glTextureStorage2D(texture, 1, GL_RGBA8, kTextureSize, kTextureSize); - glTextureSubImage2D(texture, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTextureSubImage2D(texture, 0, 0, 0, kTextureSize, kTextureSize, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); return texture; @@ -294,8 +294,8 @@ void main() { ::testing::AssertionResult WholeViewportIs(const Image& image, const char* expected, const std::string& when) { - return RegionIsMostly(image, kInset, image.Width() - kInset, kInset, - image.Height() - kInset, expected, 0.0, when); + return RegionIsMostly(image, kInset, image.Width() - kInset, kInset, image.Height() - kInset, expected, + 0.0, when); } // R -> ZERO and G -> ONE, so a RED texel samples as GREEN if and only if the swizzle @@ -311,7 +311,10 @@ void main() { // reading has to find on both sides of the seam. Two, because they are the two the // four cases use and because a peek that reported "some parameter" would be green for // a backend that applied the wrong one. - enum class MovedParameter { Swizzle, DepthStencilMode }; + enum class MovedParameter { + Swizzle, + DepthStencilMode + }; // ------------------------------------------------------------------------------ // G9's WHITE-BOX READING (ID-19). Called by every case at the point where its @@ -322,35 +325,34 @@ void main() { // `expectedDepthStencilMode` is GL_DEPTH_COMPONENT or GL_STENCIL_INDEX. Both are // always passed and `moved` says which one is the case's subject, so a reader of a // failure can see the untouched half beside the moved one. - void TakeTheWhiteBoxReadingBeforeAnySample(GLuint texture, GLenum target, - MovedParameter moved, - const GLint expectedSwizzle[4], - GLint expectedDepthStencilMode, + void TakeTheWhiteBoxReadingBeforeAnySample(GLuint texture, GLenum target, MovedParameter moved, + const GLint expectedSwizzle[4], GLint expectedDepthStencilMode, const char* whatMadeItReachable) { const char* const movedName = - moved == MovedParameter::Swizzle ? "GL_TEXTURE_SWIZZLE_*" - : "GL_DEPTH_STENCIL_TEXTURE_MODE"; + moved == MovedParameter::Swizzle ? "GL_TEXTURE_SWIZZLE_*" : "GL_DEPTH_STENCIL_TEXTURE_MODE"; PipeTextureParamsRecordPeek record{}; if (!PeekPipeTextureParamsRecord(static_cast(texture), &record)) { - DeclineTheWhiteBoxReading( - "no set_texture_params record for this texture in the applier. Either " - "there is no applier here (a PULL build: MGPipeApplierState is " - "#if MOBILEGL_PIPE_PUSH), or this lane's MOBILEGL_PIPE_PUSH leaves " - "kMGPipeSubsystemTextureResources (bit 10) clear, or no backend " - "registered MGPipeResourceOps so the client never emitted (c0f). The " - "end-to-end half of this case below is unaffected and still decides it."); + DeclineTheWhiteBoxReading("no set_texture_params record for this texture in the applier. Either " + "there is no applier here (a PULL build: MGPipeApplierState is " + "#if MOBILEGL_PIPE_PUSH), or this lane's MOBILEGL_PIPE_PUSH leaves " + "kMGPipeSubsystemTextureResources (bit 10) clear, or no backend " + "registered MGPipeResourceOps so the client never emitted (c0f). The " + "end-to-end half of this case below is unaffected and still decides it."); return; } // From here the reading WAS taken, so everything is a hard assertion: a record // that exists and does not carry the parameter is exactly the finding. EXPECT_NE(record.ParamsSerial, 0u) - << "the applier holds a resource record for texture " << texture - << " at handle {" << record.Slot << ", " << record.Gen + << "the applier holds a resource record for texture " << texture << " at handle {" << record.Slot + << ", " << record.Gen << "} but its ParamsSerial is 0, i.e. NO set_texture_params has ever been " - "applied to it - and this case moved " << movedName << " while the texture " - "was " << whatMadeItReachable + "applied to it - and this case moved " + << movedName + << " while the texture " + "was " + << whatMadeItReachable << ". A parameter change on a texture with no sampler view has to produce a " "record addressed BY RESOURCE (D10, D-E1); a zero here means the client " "never emitted one, which is the coupling P4a exists to remove reappearing " @@ -360,26 +362,23 @@ void main() { for (int channel = 0; channel < 4; ++channel) { EXPECT_EQ(record.Swizzle[channel], static_cast(expectedSwizzle[channel])) << "the applier's set_texture_params record for texture " << texture - << " carries the wrong swizzle in channel " << channel - << " (record 0x" << std::hex << record.Swizzle[channel] << ", expected 0x" - << expectedSwizzle[channel] << std::dec + << " carries the wrong swizzle in channel " << channel << " (record 0x" << std::hex + << record.Swizzle[channel] << ", expected 0x" << expectedSwizzle[channel] << std::dec << "). The record is what Espryt reads, so a wrong value here is a " "wrong value everywhere downstream of it."; } } else { EXPECT_EQ(record.DepthStencilMode, static_cast(expectedDepthStencilMode)) << "the applier's set_texture_params record for texture " << texture - << " carries GL_DEPTH_STENCIL_TEXTURE_MODE 0x" << std::hex - << record.DepthStencilMode << ", expected 0x" << expectedDepthStencilMode - << std::dec << "."; + << " carries GL_DEPTH_STENCIL_TEXTURE_MODE 0x" << std::hex << record.DepthStencilMode + << ", expected 0x" << expectedDepthStencilMode << std::dec << "."; } // (c) - and it is checked BEFORE (b) is read, because (b) reads the driver and a // reader of a failure needs to know the view question was answered on the state // this case built rather than on anything the peek did. bool hasSamplerView = true; - if (!PeekEsprytHasSamplerViewForTexture(static_cast(texture), - &hasSamplerView)) { + if (!PeekEsprytHasSamplerViewForTexture(static_cast(texture), &hasSamplerView)) { DeclineTheWhiteBoxReading( "Espryt holds no twin for this texture, so neither the sampler-view " "question nor the applied-value one can be asked here. On a backend other " @@ -388,38 +387,37 @@ void main() { return; } EXPECT_FALSE(hasSamplerView) - << "Espryt already holds a SAMPLER VIEW for texture " << texture - << ", which was " << whatMadeItReachable + << "Espryt already holds a SAMPLER VIEW for texture " << texture << ", which was " + << whatMadeItReachable << " and has never been bound to a sampler unit in this case. The whole claim " "of D10 is that a texture reached this way has no view, so if one exists " "the reading below cannot separate 'applied by resource' from 'applied " "through the view' and this case has stopped measuring G9."; EsprytAppliedTextureParamsPeek applied{}; - if (!PeekEsprytAppliedTextureParams(static_cast(texture), - static_cast(target), &applied)) { - DeclineTheWhiteBoxReading( - "Espryt's applied value could not be read back from the driver (no twin, " - "no ES name yet, or a target this peek has no binding query for)."); + if (!PeekEsprytAppliedTextureParams(static_cast(texture), static_cast(target), + &applied)) { + DeclineTheWhiteBoxReading("Espryt's applied value could not be read back from the driver (no twin, " + "no ES name yet, or a target this peek has no binding query for)."); return; } std::cout << "[ TextureParamsWithoutASamplerView ] white-box: texture " << texture - << " -> applier handle {" << record.Slot << ", " << record.Gen - << "} paramsSerial " << record.ParamsSerial << ", Espryt ES name " - << applied.BackendTextureId << ", sampler view: none, " << movedName - << " applied before any sample" << std::endl; + << " -> applier handle {" << record.Slot << ", " << record.Gen << "} paramsSerial " + << record.ParamsSerial << ", Espryt ES name " << applied.BackendTextureId + << ", sampler view: none, " << movedName << " applied before any sample" << std::endl; if (moved == MovedParameter::Swizzle) { for (int channel = 0; channel < 4; ++channel) { EXPECT_EQ(applied.Swizzle[channel], static_cast(expectedSwizzle[channel])) << "ESPRYT HAS NOT APPLIED THE SWIZZLE YET. Channel " << channel - << " of the driver texture (ES name " << applied.BackendTextureId - << ") reads 0x" << std::hex << applied.Swizzle[channel] << ", the " + << " of the driver texture (ES name " << applied.BackendTextureId << ") reads 0x" + << std::hex << applied.Swizzle[channel] << ", the " << "application set 0x" << expectedSwizzle[channel] << std::dec << ", and the applier's record already carries the right value - so " "the record reached the server and the server has not pushed it. " - "The texture was " << whatMadeItReachable + "The texture was " + << whatMadeItReachable << " and has NO sampler view (asserted above), which makes this " "exactly the deferred-to-first-view shape G9 exists to catch: the " "sample at the end of this case would repair it, and the " @@ -436,11 +434,12 @@ void main() { } EXPECT_EQ(applied.DepthStencilMode, static_cast(expectedDepthStencilMode)) << "ESPRYT HAS NOT APPLIED THE DEPTH/STENCIL ASPECT MODE YET. The driver " - "texture (ES name " << applied.BackendTextureId << ") reads 0x" - << std::hex << applied.DepthStencilMode << ", the application set 0x" - << expectedDepthStencilMode << std::dec + "texture (ES name " + << applied.BackendTextureId << ") reads 0x" << std::hex << applied.DepthStencilMode + << ", the application set 0x" << expectedDepthStencilMode << std::dec << ", and the applier's record already carries the right value. The " - "texture was " << whatMadeItReachable + "texture was " + << whatMadeItReachable << " and has no sampler view, so this is D-E3's gap measured directly " "rather than through a sample that would repair it: a driver left at " "GL_DEPTH_COMPONENT samples the DEPTH bits where the application asked " @@ -451,8 +450,7 @@ void main() { // Printed, recorded and named, never silent - a lane that stopped taking the reading // must be visible in the log. See this file's header for why it is not a GTEST_SKIP. void DeclineTheWhiteBoxReading(const std::string& why) { - std::cout << "[ TextureParamsWithoutASamplerView ] white-box reading DECLINED: " - << why << std::endl; + std::cout << "[ TextureParamsWithoutASamplerView ] white-box reading DECLINED: " << why << std::endl; RecordProperty("g9_white_box", "declined"); RecordProperty("g9_white_box_reason", why.c_str()); } @@ -506,8 +504,8 @@ void main() { // G9's white-box reading, taken here: the texture has been a draw attachment and // nothing else, and the sample below has not happened yet. - TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle, - kRedIntoGreenSwizzle, kUntouchedDepthStencilMode, + TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle, kRedIntoGreenSwizzle, + kUntouchedDepthStencilMode, "an attachment of the DRAW framebuffer and " "nothing else"); @@ -538,8 +536,7 @@ void main() { // default framebuffer for the whole window, which is the ONE thing that separates this // case from the one above. // ------------------------------------------------------------------------------------ - TEST_F(TextureParamsWithoutASamplerViewScenario, - AReadAttachmentOnlyTexturesDepthStencilModeReachesTheDriver) { + TEST_F(TextureParamsWithoutASamplerViewScenario, AReadAttachmentOnlyTexturesDepthStencilModeReachesTheDriver) { if (!Ready()) return; SkipWithoutDirectStateAccess(); if (IsSkipped()) return; @@ -614,8 +611,7 @@ void main() { // mode was set while the texture was reachable ONLY as a read attachment, and the // observation below is a sample that would repair an unsynced parameter on its way // to reporting it. - TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, - MovedParameter::DepthStencilMode, + TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::DepthStencilMode, kUntouchedSwizzle, GL_STENCIL_INDEX, "an attachment of the READ framebuffer and " "nothing else"); @@ -626,8 +622,7 @@ void main() { ClearTo(0.0f, 0.0f, 0.0f, 1.0f); glUseProgram(stencilProgram); glUniform1i(glGetUniformLocation(stencilProgram, "uTex"), 0); - glUniform1ui(glGetUniformLocation(stencilProgram, "uExpected"), - static_cast(kStencil)); + glUniform1ui(glGetUniformLocation(stencilProgram, "uExpected"), static_cast(kStencil)); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(m_vao); @@ -639,8 +634,7 @@ void main() { const GLenum sampleError = FirstGLError(); std::cout << "[ TextureParamsWithoutASamplerView ] read-attachment-only " "GL_DEPTH_STENCIL_TEXTURE_MODE=GL_STENCIL_INDEX, expecting stencil " - << kStencil << "; sample GL error 0x" << std::hex << sampleError << std::dec - << std::endl; + << kStencil << "; sample GL error 0x" << std::hex << sampleError << std::dec << std::endl; EXPECT_TRUE(WholeViewportIs(image, "green", "a texture that was only ever a READ attachment, sampled " @@ -687,8 +681,8 @@ void main() { glBindImageTexture(0, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); GLuint cleanup = texture; glDeleteTextures(1, &cleanup); - GTEST_SKIP() << "glBindImageTexture is not usable here (GL error 0x" << std::hex - << bindError << std::dec + GTEST_SKIP() << "glBindImageTexture is not usable here (GL error 0x" << std::hex << bindError + << std::dec << "), so the RequireImageBindableStorage transition this case is about " "cannot be reached"; } @@ -702,8 +696,8 @@ void main() { // minted and the driver's swizzle is the application's own - the composition // RecreateBackendTexture applies for a NON-core format would show up here as a // legitimate difference and this case deliberately does not use one. - TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle, - kRedIntoGreenSwizzle, kUntouchedDepthStencilMode, + TakeTheWhiteBoxReadingBeforeAnySample(texture, GL_TEXTURE_2D, MovedParameter::Swizzle, kRedIntoGreenSwizzle, + kUntouchedDepthStencilMode, "an image-unit binding and nothing else, across " "a RequireImageBindableStorage re-mint"); @@ -726,8 +720,7 @@ void main() { // DESTINATION, so the case also proves the copy itself still lands: a swizzled read of // the copied texel is only meaningful if the texel arrived. // ------------------------------------------------------------------------------------ - TEST_F(TextureParamsWithoutASamplerViewScenario, - ACopyImageEndpointOnlyTexturesParamsReachTheDriver) { + TEST_F(TextureParamsWithoutASamplerViewScenario, ACopyImageEndpointOnlyTexturesParamsReachTheDriver) { if (!Ready()) return; SkipWithoutDirectStateAccess(); if (IsSkipped()) return; @@ -737,21 +730,20 @@ void main() { // The parameter moves while the destination is reachable only as a copy endpoint... SwizzleRedIntoGreen(destination); - glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, destination, GL_TEXTURE_2D, 0, 0, 0, - 0, kTextureSize, kTextureSize, 1); + glCopyImageSubData(source, GL_TEXTURE_2D, 0, 0, 0, 0, destination, GL_TEXTURE_2D, 0, 0, 0, 0, kTextureSize, + kTextureSize, 1); const GLenum copyError = FirstGLError(); if (copyError != GLenum(GL_NO_ERROR)) { GLuint cleanup[2] = {source, destination}; glDeleteTextures(2, cleanup); - GTEST_SKIP() << "glCopyImageSubData is not usable here (GL error 0x" << std::hex - << copyError << std::dec << "), so there is no copy endpoint to be"; + GTEST_SKIP() << "glCopyImageSubData is not usable here (GL error 0x" << std::hex << copyError + << std::dec << "), so there is no copy endpoint to be"; } Gl().EndFrame(); // G9's white-box reading, on the DESTINATION - the endpoint whose parameters moved. - TakeTheWhiteBoxReadingBeforeAnySample(destination, GL_TEXTURE_2D, - MovedParameter::Swizzle, kRedIntoGreenSwizzle, - kUntouchedDepthStencilMode, + TakeTheWhiteBoxReadingBeforeAnySample(destination, GL_TEXTURE_2D, MovedParameter::Swizzle, + kRedIntoGreenSwizzle, kUntouchedDepthStencilMode, "a glCopyImageSubData destination and nothing " "else"); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp index bc0fb2dc..b7aa46c3 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureRemintPullScenario.cpp @@ -292,14 +292,16 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } } } } - const std::size_t readAt = static_cast((std::max(firstY, 0) * kEdge + std::max(firstX, 0)) * 4); - const std::size_t wantAt = static_cast((std::max(firstY, 0) * edge + std::max(firstX, 0)) * 4); + const std::size_t readAt = + static_cast((std::max(firstY, 0) * kEdge + std::max(firstX, 0)) * 4); + const std::size_t wantAt = + static_cast((std::max(firstY, 0) * edge + std::max(firstX, 0)) * 4); EXPECT_EQ(wrong, 0) << what << ": " << wrong << " channel(s) differ; first at (" << firstX << ", " << firstY << ") read " << int(read[readAt]) << "," << int(read[readAt + 1]) << "," << int(read[readAt + 2]) << "," << int(read[readAt + 3]) << " want " - << int(want[wantAt]) << "," << int(want[wantAt + 1]) << "," - << int(want[wantAt + 2]) << "," << int(want[wantAt + 3]) << " (backend " - << Gl().BackendName() << ", " << (m_wire ? "split" : "monolith") << ")"; + << int(want[wantAt]) << "," << int(want[wantAt + 1]) << "," << int(want[wantAt + 2]) + << "," << int(want[wantAt + 3]) << " (backend " << Gl().BackendName() << ", " + << (m_wire ? "split" : "monolith") << ")"; } // W2-d: where the peek can see the server's count (monolith, inproc) - and only on a @@ -400,8 +402,8 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } SyncBySampling(texture); ClearOnTheGpu(texture, 0, kGreen); SyncBySampling(texture); - ExpectTexels("a null-defined texture only the GPU wrote", ImageCopy(texture, 0, GL_RGBA8, "rgba8"), - kEdge, Solid(kEdge, 0, 255, 0, 255)); + ExpectTexels("a null-defined texture only the GPU wrote", ImageCopy(texture, 0, GL_RGBA8, "rgba8"), kEdge, + Solid(kEdge, 0, 255, 0, 255)); } TEST_F(TextureRemintPullScenario, APendingPartialUploadMergesOverGpuWrittenTexels) { @@ -427,8 +429,8 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } want[at + 2] = 255; } } - ExpectTexels("a pending partial upload over GPU texels", ImageCopy(texture, 0, GL_RGBA8, "rgba8"), - kEdge, want); + ExpectTexels("a pending partial upload over GPU texels", ImageCopy(texture, 0, GL_RGBA8, "rgba8"), kEdge, + want); } TEST_F(TextureRemintPullScenario, AGeneratedMipLevelSurvivesTheImageBind) { @@ -475,8 +477,8 @@ void main() { o_color = texelFetch(u_texture, ivec2(gl_FragCoord.xy) % 4, 0); } glBindTexture(GL_TEXTURE_2D, texture); glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kEdge / 2, kEdge / 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); glBindTexture(GL_TEXTURE_2D, 0); - ExpectTexels("level 0 beside a level defined after the last sync", - ImageCopy(texture, 0, GL_RGBA8, "rgba8"), kEdge, pattern); + ExpectTexels("level 0 beside a level defined after the last sync", ImageCopy(texture, 0, GL_RGBA8, "rgba8"), + kEdge, pattern); } // W2-a's extent half: the driver holds level 0 at its OLD extent. The new contents are diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp index 8da54c76..37ca778c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureUnitSamplingScenario.cpp @@ -97,8 +97,7 @@ void main() { o_color = texture(u_texture, vec2(0.5, 0.5)); } } static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static GLuint Compile(GLenum stage, const char* source) { diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadBetweenDrawsScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadBetweenDrawsScenario.cpp index c8eb35de..a084699d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadBetweenDrawsScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadBetweenDrawsScenario.cpp @@ -79,7 +79,8 @@ void main() { std::vector Solid(int texels, const Rgba& c) { std::vector out(static_cast(texels) * 4u); for (int i = 0; i < texels; ++i) - for (int k = 0; k < 4; ++k) out[static_cast(i) * 4u + k] = c[k]; + for (int k = 0; k < 4; ++k) + out[static_cast(i) * 4u + k] = c[k]; return out; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadShapeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadShapeScenario.cpp index 68badf83..b291c98f 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadShapeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureUploadShapeScenario.cpp @@ -261,7 +261,8 @@ void main() { oColor = texture(uTex, vUv); } void SkipUnlessTheLaneIsAssertableHere() { if (std::getenv(kLaneMarker) == nullptr) { GTEST_SKIP() << "runs only in its own lane: the TextureUploadShape. ctest entry " - "sets " << kLaneMarker + "sets " + << kLaneMarker << " together with MOBILEGL_PIPE_STATS=1, " "MOBILEGL_PIPE_STATS_PERIOD=1 and a private " "MOBILEGL_LOG_FILE_PATH. None of that is configured in the " @@ -314,8 +315,7 @@ void main() { oColor = texture(uTex, vUv); } glBindTexture(GL_TEXTURE_2D, 0); GLuint view = 0; glGenTextures(1, &view); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} glTextureView(view, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); const bool usable = glGetError() == GL_NO_ERROR; glDeleteTextures(1, &view); @@ -345,8 +345,8 @@ void main() { oColor = texture(uTex, vUv); } GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kAtlasSize, kAtlasSize, 0, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kAtlasSize, kAtlasSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); @@ -360,14 +360,12 @@ void main() { oColor = texture(uTex, vUv); } GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); - glTexStorage2D(GL_TEXTURE_2D, kViewedOwnerLevels, GL_RGBA8, kViewedOwnerSize, - kViewedOwnerSize); + glTexStorage2D(GL_TEXTURE_2D, kViewedOwnerLevels, GL_RGBA8, kViewedOwnerSize, kViewedOwnerSize); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); for (int level = 0; level < kViewedOwnerLevels; ++level) { const int size = kViewedOwnerSize >> level; const std::vector green = GreenTexels(size, size); - glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, - green.data()); + glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, size, size, GL_RGBA, GL_UNSIGNED_BYTE, green.data()); } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -395,13 +393,12 @@ void main() { oColor = texture(uTex, vUv); } void ScatterRectsInto(GLuint texture) { glBindTexture(GL_TEXTURE_2D, texture); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - const std::uint8_t patch[kRectSize * kRectSize * 4] = { - 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255}; + const std::uint8_t patch[kRectSize * kRectSize * 4] = {0, 255, 0, 255, 0, 255, 0, 255, + 0, 255, 0, 255, 0, 255, 0, 255}; for (int rect = 0; rect < kScatteredRects; ++rect) { const int x = kScatterMargin + ((rect * 7) % kScatterCells) * kRectSize; const int y = kScatterMargin + ((rect * 5) % kScatterCells) * kRectSize; - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, kRectSize, kRectSize, GL_RGBA, - GL_UNSIGNED_BYTE, patch); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, kRectSize, kRectSize, GL_RGBA, GL_UNSIGNED_BYTE, patch); } } @@ -496,8 +493,7 @@ void main() { oColor = texture(uTex, vUv); } // looking at the region at all. glBindTexture(GL_TEXTURE_2D, m_contiguous); const std::vector band = GreenTexels(kAtlasSize, 8); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kAtlasSize, 8, GL_RGBA, GL_UNSIGNED_BYTE, - band.data()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kAtlasSize, 8, GL_RGBA, GL_UNSIGNED_BYTE, band.data()); lastContiguous = DrawSampled(m_contiguous); // THE SAME SCATTERED PATTERN THROUGH A VIEW. The writes name the view's level 0; @@ -526,14 +522,11 @@ void main() { oColor = texture(uTex, vUv); } // two-sided comparison into `N == 0`. So each side is read from ITS OWN role's log, and // the server bracket falls back to the client log only where there is no server half - // the pull build and the monolith lane, where one file IS the whole run. - const PipeStatsWindow::Window clientWindow = - PipeStatsWindow::LastFromClientLogSince(statsMark); - const PipeStatsWindow::Window serverWindow = - PipeStatsWindow::LastFromServerLogSince(statsMark); + const PipeStatsWindow::Window clientWindow = PipeStatsWindow::LastFromClientLogSince(statsMark); + const PipeStatsWindow::Window serverWindow = PipeStatsWindow::LastFromServerLogSince(statsMark); const PipeStatsWindow::Window& shapeWindow = - (serverWindow.found && PipeStatsWindow::CounterOrAbsent(serverWindow, "emit") > 0) - ? serverWindow - : clientWindow; + (serverWindow.found && PipeStatsWindow::CounterOrAbsent(serverWindow, "emit") > 0) ? serverWindow + : clientWindow; // THE ASSERTION IS ON THE WINDOW THAT CARRIES THE BRACKET, not on the client's, and // moving to a marked read is what showed why. The client role emits NO summary window // of its own under inproc/spawn/tcp at the point this case reads - only the @@ -545,8 +538,7 @@ void main() { oColor = texture(uTex, vUv); } ASSERT_TRUE(shapeWindow.found) << "no 'MGPipe stats:' window was appended to either role's log by the swap that " "closes the counted window. client=" - << PipeStatsWindow::LibraryLogPath() - << " server=" << PipeStatsWindow::ServerLibraryLogPath() + << PipeStatsWindow::LibraryLogPath() << " server=" << PipeStatsWindow::ServerLibraryLogPath() << ". The shape could not be read at all - which is a different finding from a " "shape that read zero."; RecordProperty("stats_line_client", clientWindow.line.c_str()); @@ -574,20 +566,17 @@ void main() { oColor = texture(uTex, vUv); } !statsRuntime.transportResolved || statsRuntime.transportName == "inproc"; const bool ctuIsWindowAligned = oneProcessForCounters && shapeWindowCtu >= 0; const long long clientEmissions = - emitPeekClosed - ? static_cast(emitAfter.SubDataCount - emitBefore.SubDataCount) - : shapeWindowCtu; + emitPeekClosed ? static_cast(emitAfter.SubDataCount - emitBefore.SubDataCount) + : shapeWindowCtu; ASSERT_GE(emissions, 0) << "the summary line carries no tex[emit=]: " << shapeWindow.line; ASSERT_GE(box, 0) << "no box=: " << shapeWindow.line; ASSERT_GE(rect, 0) << "no rect=: " << shapeWindow.line; ASSERT_GE(jobs, 0) << "no jobs=: " << shapeWindow.line; std::cout << "[ TextureUploadShape ] backend=" << Gl().BackendName() << " frames=" << kFrames - << " scattered_rects_per_frame=" << kScatteredRects - << " textures=" << kTexturesInTheWindow - << " server[emit=" << emissions << " box=" << box << " rect=" << rect - << " jobs=" << jobs << "] client[emitter=" << clientEmissions - << " ctu_shape_window=" << shapeWindowCtu + << " scattered_rects_per_frame=" << kScatteredRects << " textures=" << kTexturesInTheWindow + << " server[emit=" << emissions << " box=" << box << " rect=" << rect << " jobs=" << jobs + << "] client[emitter=" << clientEmissions << " ctu_shape_window=" << shapeWindowCtu << " ctu_client_window=" << clientWindowCtu << "]" << std::endl; RecordProperty("ctu_shape_window", static_cast(shapeWindowCtu)); RecordProperty("ctu_client_window", static_cast(clientWindowCtu)); @@ -601,21 +590,22 @@ void main() { oColor = texture(uTex, vUv); } // 1. the workload really uploaded. Without this the assertions below are all 0 == 0 and // a run whose emitter was switched off records the same "healthy" shape as one that // worked. - ASSERT_GT(emissions, 0) - << "the server counted no texture upload emission at all over " << kFrames - << " frames of " << kScatteredRects - << " sub-regions each, plus a contiguous band and a view write. Either the uploads " - "never reached the backend or the counter stopped counting; in both cases every " - "shape number below would be a zero that means nothing. " - << shapeWindow.line; + ASSERT_GT(emissions, 0) << "the server counted no texture upload emission at all over " << kFrames + << " frames of " << kScatteredRects + << " sub-regions each, plus a contiguous band and a view write. Either the uploads " + "never reached the backend or the counter stopped counting; in both cases every " + "shape number below would be a zero that means nothing. " + << shapeWindow.line; // 2. the server bracket's own arithmetic. EXPECT_EQ(box + rect, emissions) << "tex[box=] + tex[rect=] must be tex[emit=]: every emission takes exactly one of " - "the two shapes. " << shapeWindow.line; + "the two shapes. " + << shapeWindow.line; EXPECT_GE(jobs, emissions) << "tex[jobs=] must be at least tex[emit=]: a box emission is one driver upload job " - "and a rect-list emission is N. " << shapeWindow.line; + "and a rect-list emission is N. " + << shapeWindow.line; // 3. THE GOLD TABLE (R-11 / D-D4). This is the assertion the three around it cannot // make: emit == box + rect and jobs >= emit are both satisfied by a shape that @@ -623,14 +613,15 @@ void main() { oColor = texture(uTex, vUv); } // direction on Mali and is invisible to SSIM. EXPECT_EQ(emissions, kGold.Emissions) << "the emission COUNT moved. One resource_subdata per (storage owner, upload " - "target, level) per frame is " << kTexturesInTheWindow << " textures x " << kFrames - << " frames = " << kGold.Emissions + "target, level) per frame is " + << kTexturesInTheWindow << " textures x " << kFrames << " frames = " << kGold.Emissions << ". A count above this says a level is being emitted more than once per validate " "point - the two-cursor shape, if the extra one is the viewed texture; a count " - "below says a level's record was dropped. " << shapeWindow.line; + "below says a level's record was dropped. " + << shapeWindow.line; EXPECT_EQ(box, kGold.BoxEmissions) - << "the BOX/RECT split moved: " << box << " box emissions against a gold of " - << kGold.BoxEmissions << ". " << shapeWindow.line; + << "the BOX/RECT split moved: " << box << " box emissions against a gold of " << kGold.BoxEmissions + << ". " << shapeWindow.line; EXPECT_EQ(rect, kGold.RectEmissions) << "the BOX/RECT split moved: " << rect << " rect-list emissions against a gold of " << kGold.RectEmissions @@ -639,9 +630,8 @@ void main() { oColor = texture(uTex, vUv); } "SSIM case can see it. If this is intended, re-measure the row and say so in " "docs/Disaggregated/MEASUREMENTS.md. " << shapeWindow.line; - EXPECT_EQ(jobs, kGold.Jobs) - << "the JOB count moved: " << jobs << " against a gold of " << kGold.Jobs - << ". Jobs are what Mali prices the upload by. " << shapeWindow.line; + EXPECT_EQ(jobs, kGold.Jobs) << "the JOB count moved: " << jobs << " against a gold of " << kGold.Jobs + << ". Jobs are what Mali prices the upload by. " << shapeWindow.line; // 4. the two sides agree, WHEN THERE ARE TWO SIDES - and "there are two sides" is // answered by the BUILD, not by the number (review F-m7). @@ -657,14 +647,13 @@ void main() { oColor = texture(uTex, vUv); } // THAT PROBE HITS ON THIS TREE: MG_Impl/Pipe/TextureEmit.h's EmitOneLevel increments // it on every piece. The else-arm below is retained because it is what makes the // arming decision falsifiable in both directions, not because it is reachable here. - const bool clientEmitterExists = - BuildMarkerIsSet("MGITEST_PIPE_CLIENT_TEXTURE_UPLOAD_EMITTER_PRESENT"); + const bool clientEmitterExists = BuildMarkerIsSet("MGITEST_PIPE_CLIENT_TEXTURE_UPLOAD_EMITTER_PRESENT"); ASSERT_GE(clientEmissions, 0) << "the client half of the comparison could not be read at all. The in-process " "emitter peek " << (emitPeekLive ? "opened but did not close" : TextureEmitPeekSkipReason()) - << ", and no role's summary line carried a ctu= field either. client=" - << clientWindow.line << " shape=" << shapeWindow.line; + << ", and no role's summary line carried a ctu= field either. client=" << clientWindow.line + << " shape=" << shapeWindow.line; RecordProperty("client_emitter_present", clientEmitterExists ? 1 : 0); RecordProperty("client_half_source", emitPeekClosed ? "emitter peek" : "ctu= field"); if (ctuIsWindowAligned) { @@ -676,32 +665,34 @@ void main() { oColor = texture(uTex, vUv); } << "the published ctu= field and MGPipeTextureEmitter's own SubDataCount " "disagree in a window that covers the same work. EmitOneLevel increments " "both on the same line, so this is the counter's publication and not the " - "emission shape. " << shapeWindow.line; + "emission shape. " + << shapeWindow.line; } else { - RecordProperty("ctu_note", - "the client's summary window is not aligned with the server's in " - "this arm (two processes, two Present cadences), so ctu= is " - "recorded and the assertion reads the in-process emitter instead"); + RecordProperty("ctu_note", "the client's summary window is not aligned with the server's in " + "this arm (two processes, two Present cadences), so ctu= is " + "recorded and the assertion reads the in-process emitter instead"); } if (clientEmitterExists) { EXPECT_GT(clientEmissions, 0) << "a MG_Impl/Pipe source emits CallClass::ClientTextureUploadEmissions on this " - "tree, and the SERVER counted " << emissions + "tree, and the SERVER counted " + << emissions << " texture upload emissions for this workload, but the client counted NONE. An " "emitter that has stopped emitting reads exactly like no emitter at all in " "this field, which is why this case asks the build rather than the number. " << clientWindow.line; EXPECT_EQ(clientEmissions, emissions) - << "the CLIENT counted " << clientEmissions - << " texture upload records and the SERVER counted " << emissions + << "the CLIENT counted " << clientEmissions << " texture upload records and the SERVER counted " + << emissions << " for the same workload in the same window. The two counting the same records " "is the entire reason both are published (D-L): a divergence here is an " "emission-shape divergence that SSIM is blind to and that costs ~+6 ms/frame " - "on Mali when it goes the wrong way. client=" << clientWindow.line - << " server=" << shapeWindow.line; + "on Mali when it goes the wrong way. client=" + << clientWindow.line << " server=" << shapeWindow.line; EXPECT_EQ(clientEmissions, kGold.Emissions) << "the client's own count against the gold row, so that a run where BOTH sides " - "moved together is still red. " << clientWindow.line; + "moved together is still red. " + << clientWindow.line; } else { // Not merely "not asserted": on a tree with no client emitter the counter must be // ZERO, and a non-zero one would mean the probe is looking for the wrong symbol - @@ -709,21 +700,21 @@ void main() { oColor = texture(uTex, vUv); } // is mis-armed. EXPECT_EQ(clientEmissions, 0) << "no MG_Impl/Pipe source emits CallClass::ClientTextureUploadEmissions on this " - "tree, yet the client counted " << clientEmissions + "tree, yet the client counted " + << clientEmissions << " of them. Something is incrementing that counter which this build's probe " "cannot see, so the probe is looking for the wrong symbol and this case's " - "arming decision is unreliable in both directions. " << clientWindow.line; + "arming decision is unreliable in both directions. " + << clientWindow.line; RecordProperty("client_side", "absent"); } // The pixels, so that a recorded shape cannot be the shape of a workload that drew // nothing. EXPECT_TRUE(RegionIsMostly(lastScattered, kInset, lastScattered.Width() - kInset, kInset, - lastScattered.Height() - kInset, "green", 0.0, - "the scattered-rect atlas")); + lastScattered.Height() - kInset, "green", 0.0, "the scattered-rect atlas")); EXPECT_TRUE(RegionIsMostly(lastContiguous, kInset, lastContiguous.Width() - kInset, kInset, - lastContiguous.Height() - kInset, "green", 0.0, - "the contiguous-band atlas")); + lastContiguous.Height() - kInset, "green", 0.0, "the contiguous-band atlas")); EXPECT_TRUE(RegionIsMostly(lastViewed, kInset, lastViewed.Width() - kInset, kInset, lastViewed.Height() - kInset, "green", 0.0, "the atlas written through a glTextureView")); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureViewAliasScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureViewAliasScenario.cpp index ff7b9e4c..19d116bb 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureViewAliasScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureViewAliasScenario.cpp @@ -119,8 +119,8 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } }; std::string Describe(const Rgba8& c) { - return "rgba(" + std::to_string(c.r) + "," + std::to_string(c.g) + "," + std::to_string(c.b) + - "," + std::to_string(c.a) + ")"; + return "rgba(" + std::to_string(c.r) + "," + std::to_string(c.g) + "," + std::to_string(c.b) + "," + + std::to_string(c.a) + ")"; } class TextureViewAliasScenario : public ScenarioTest { @@ -184,8 +184,7 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } glBindTexture(GL_TEXTURE_2D, 0); GLuint view = 0; glGenTextures(1, &view); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} glTextureView(view, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); const bool usable = glGetError() == GL_NO_ERROR; glDeleteTextures(1, &view); @@ -221,8 +220,8 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } } static Rgba8 LayerSeed(int layer) { - return Rgba8{static_cast(10 + layer * 20), - static_cast(200 - layer * 20), 30, 255}; + return Rgba8{static_cast(10 + layer * 20), static_cast(200 - layer * 20), + 30, 255}; } static std::vector Fill(Rgba8 colour) { @@ -240,8 +239,8 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } const std::vector texels = Fill(colour); glBindTexture(GL_TEXTURE_2D_ARRAY, owner); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kSize, kSize, 1, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kSize, kSize, 1, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); glBindTexture(GL_TEXTURE_2D_ARRAY, 0); } @@ -252,16 +251,14 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } const std::vector texels = Fill(colour); glBindTexture(GL_TEXTURE_2D, view); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE, - texels.data()); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSize, kSize, GL_RGBA, GL_UNSIGNED_BYTE, texels.data()); glBindTexture(GL_TEXTURE_2D, 0); } GLuint MakeLayerView(GLuint owner) { const GLuint view = MakeTexture(); glTextureView(view, GL_TEXTURE_2D, owner, GL_RGBA8, 0, 1, kViewLayer, 1); - EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)) - << "2D_ARRAY -> 2D is a legal view pair"; + EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "2D_ARRAY -> 2D is a legal view pair"; glBindTexture(GL_TEXTURE_2D, view); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -326,9 +323,8 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } } } EXPECT_EQ(offenders, 0) << what << ": " << offenders << " of " << total - << " pixels disagree; first at (" << firstX << ", " << firstY - << ") is " << Describe(firstOffender) << ", expected " - << Describe(expected) << " +/- 2"; + << " pixels disagree; first at (" << firstX << ", " << firstY << ") is " + << Describe(firstOffender) << ", expected " << Describe(expected) << " +/- 2"; } // ---- the emitter half ------------------------------------------------------------- @@ -350,8 +346,7 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } return after.RefusedSubDataCount - before.RefusedSubDataCount; } unsigned long long DescribedDelta() const { - return (after.CreateCount - before.CreateCount) + - (after.RespecifyCount - before.RespecifyCount); + return (after.CreateCount - before.CreateCount) + (after.RespecifyCount - before.RespecifyCount); } }; @@ -388,8 +383,7 @@ void main() { oColor = texture(uTex, vec3(vUv, float(uLayer))); } return; } EXPECT_GT(window.SubDataDelta(), 0u) - << what << ": the client texture emitter described " - << window.DescribedDelta() + << what << ": the client texture emitter described " << window.DescribedDelta() << " texture(s) across this draw and put NO resource_subdata on the wire. A " "drain that runs and emits nothing is exactly what a per-NAME emission " "cursor produces when a view and its owner each hold one: whichever cursor " diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TextureViewScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TextureViewScenario.cpp index 04c272f7..cbe353ec 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TextureViewScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TextureViewScenario.cpp @@ -181,8 +181,7 @@ void main() { glBindTexture(GL_TEXTURE_2D, 0); GLuint view = 0; glGenTextures(1, &view); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} glTextureView(view, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); const bool usable = glGetError() == GL_NO_ERROR; glDeleteTextures(1, &view); @@ -283,10 +282,10 @@ void main() { } } } - EXPECT_EQ(offenders, 0) << what << ": " << offenders << " of " - << (x1 - x0 + 1) * (y1 - y0 + 1) << " pixels disagree; first at (" << firstX - << ", " << firstY << ") is " << Describe(firstOffender) << ", expected " - << Describe(expected) << " +/- " << tolerance; + EXPECT_EQ(offenders, 0) << what << ": " << offenders << " of " << (x1 - x0 + 1) * (y1 - y0 + 1) + << " pixels disagree; first at (" << firstX << ", " << firstY << ") is " + << Describe(firstOffender) << ", expected " << Describe(expected) << " +/- " + << tolerance; } std::vector m_textures; @@ -541,7 +540,7 @@ void main() { // driver makes of a usampler2D fetch, the view has to make the same thing of it, or // it is not delivering the storage's bits. A view that samples zero, that lands on // the wrong texels, or that lost its format still fails. - constexpr std::uint32_t kExpectedWord = 0xFFC08040u; // little-endian A,B,G,R + constexpr std::uint32_t kExpectedWord = 0xFFC08040u; // little-endian A,B,G,R const GLuint reference = MakeImmutable2D(GL_R32UI, 1, kSize, kSize); std::vector words(static_cast(kSize) * kSize, kExpectedWord); glBindTexture(GL_TEXTURE_2D, reference); @@ -587,8 +586,9 @@ void main() { } EXPECT_EQ(mismatches, 0u) << "the GL_R32UI view of GL_RGBA8 storage must sample exactly what a real GL_R32UI texture " - "holding the same word does; view centre is " << Describe(throughView.At(kSize / 2, kSize / 2)) - << ", reference centre is " << Describe(referenceTexel); + "holding the same word does; view centre is " + << Describe(throughView.At(kSize / 2, kSize / 2)) << ", reference centre is " + << Describe(referenceTexel); DestroyColorFbo(destination); } @@ -711,8 +711,8 @@ void main() { ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)); const auto layerFill = [](int layer) { - return Rgba8{static_cast(10 + layer * 20), - static_cast(200 - layer * 20), 30, 255}; + return Rgba8{static_cast(10 + layer * 20), static_cast(200 - layer * 20), + 30, 255}; }; // Seeded by CPU sub-image rather than by rendering, deliberately: this scenario is // about the view's LAYER ORIGIN, and seeding through the GPU would additionally @@ -758,8 +758,7 @@ void main() { } glBindTexture(GL_TEXTURE_2D_ARRAY, view); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kSize, kSize, 1, GL_RGBA, GL_UNSIGNED_BYTE, - texels.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, kSize, kSize, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels.data()); glBindTexture(GL_TEXTURE_2D_ARRAY, 0); ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "writing through the view raised an error"; @@ -943,8 +942,8 @@ void main() { constexpr int kViewNumLayers = 2; // One colour per level, so a level that was redefined from the view's base extent reads // as another level's colour rather than as a plausible one. - const Rgba8 levelColour[kLevels] = {{255, 0, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}, - {255, 0, 255, 255}, {0, 255, 255, 255}}; + const Rgba8 levelColour[kLevels] = { + {255, 0, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}, {255, 0, 255, 255}, {0, 255, 255, 255}}; const GLuint storage = MakeTexture(); glBindTexture(GL_TEXTURE_2D_ARRAY, storage); @@ -959,8 +958,8 @@ void main() { texels[at + 2] = levelColour[level].b; texels[at + 3] = levelColour[level].a; } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, kLayers, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, kLayers, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); } ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "seeding the owner raised an error"; @@ -968,8 +967,8 @@ void main() { // owner's level 2 the source of the generation, and MAX_LEVEL 2 ends it at the owner's // level 3 - both expressed in the VIEW's coordinates. const GLuint view = MakeTexture(); - glTextureView(view, GL_TEXTURE_2D_ARRAY, storage, GL_RGBA8, kViewMinLevel, kViewNumLevels, - kViewMinLayer, kViewNumLayers); + glTextureView(view, GL_TEXTURE_2D_ARRAY, storage, GL_RGBA8, kViewMinLevel, kViewNumLevels, kViewMinLayer, + kViewNumLayers); ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)); glTextureParameteri(view, GL_TEXTURE_BASE_LEVEL, 1); glTextureParameteri(view, GL_TEXTURE_MAX_LEVEL, 2); @@ -1010,9 +1009,8 @@ void main() { glGetTexLevelParameteriv(GL_TEXTURE_2D_ARRAY, level, GL_TEXTURE_DEPTH, &depth); EXPECT_EQ(width, side) << "the owner's mip " << level << " must keep its own width"; EXPECT_EQ(height, side) << "the owner's mip " << level << " must keep its own height"; - ASSERT_EQ(depth, kLayers) - << "generation through a " << kViewNumLayers << "-layer view must not resize its owner's mip " - << level; + ASSERT_EQ(depth, kLayers) << "generation through a " << kViewNumLayers + << "-layer view must not resize its owner's mip " << level; for (int layer = 0; layer < kLayers; ++layer) { // Per-layer FBO readback rather than glGetTexImage: the question is what the // GPU holds, and a layer outside the view's window proves it by still holding @@ -1030,9 +1028,9 @@ void main() { const bool inWindow = level >= 2 && level <= 3 && layer >= kViewMinLayer && layer < kViewMinLayer + kViewNumLayers; const Rgba8 expected = inWindow ? Rgba8{0, 255, 0, 255} : levelColour[level]; - const std::string what = - "the owner's mip " + std::to_string(level) + ", layer " + std::to_string(layer) + - (inWindow ? ", inside the view's window" : ", outside the view's window"); + const std::string what = "the owner's mip " + std::to_string(level) + ", layer " + + std::to_string(layer) + + (inWindow ? ", inside the view's window" : ", outside the view's window"); ExpectRegion(image, 0, side - 1, 0, side - 1, expected, 2, what.c_str()); } } @@ -1078,8 +1076,7 @@ void main() { std::vector texels(static_cast(side) * side * kLayers * 4); for (int layer = 0; layer < kLayers; ++layer) { for (int t = 0; t < side * side; ++t) { - std::uint8_t* p = - texels.data() + (static_cast(layer) * side * side + t) * 4; + std::uint8_t* p = texels.data() + (static_cast(layer) * side * side + t) * 4; const Rgba8 colour = layerColour(layer); p[0] = colour.r; p[1] = colour.g; @@ -1087,21 +1084,19 @@ void main() { p[3] = colour.a; } } - glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, kLayers, GL_RGBA, - GL_UNSIGNED_BYTE, texels.data()); + glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, 0, side, side, kLayers, GL_RGBA, GL_UNSIGNED_BYTE, + texels.data()); } ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) << "seeding the owner raised an error"; const GLuint view = MakeTexture(); - glTextureView(view, GL_TEXTURE_2D_ARRAY, storage, GL_RGBA8, 0, kLevels, kViewMinLayer, - kViewNumLayers); + glTextureView(view, GL_TEXTURE_2D_ARRAY, storage, GL_RGBA8, 0, kLevels, kViewMinLayer, kViewNumLayers); ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)); glBindTexture(GL_TEXTURE_2D_ARRAY, view); const int side = kBase >> 2; // Exactly what a caller sizes for the view, plus two canary slices. - std::vector dst(static_cast(side) * side * 4 * - (kViewNumLayers + 2), + std::vector dst(static_cast(side) * side * 4 * (kViewNumLayers + 2), kCanaryByte); glGetTexImage(GL_TEXTURE_2D_ARRAY, 2, GL_RGBA, GL_UNSIGNED_BYTE, dst.data()); ASSERT_EQ(glGetError(), static_cast(GL_NO_ERROR)) @@ -1113,11 +1108,10 @@ void main() { static_cast(side) * side * 4); const bool inWindow = slice < kViewNumLayers; const Rgba8 expected = inWindow ? layerColour(kViewMinLayer + slice) : canary; - const std::string what = - inWindow ? "the view's slice " + std::to_string(slice) + ", the owner's layer " + - std::to_string(kViewMinLayer + slice) - : "slice " + std::to_string(slice) + - ", past the view's last layer: the readback must not write it"; + const std::string what = inWindow ? "the view's slice " + std::to_string(slice) + + ", the owner's layer " + std::to_string(kViewMinLayer + slice) + : "slice " + std::to_string(slice) + + ", past the view's last layer: the readback must not write it"; ExpectRegion(image, 0, side - 1, 0, side - 1, expected, 2, what.c_str()); } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp index e38084c8..52270ca6 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/TriangleScenario.cpp @@ -155,8 +155,8 @@ void main() { oColor = vec4(vColor, 1.0); } void ExpectTriangleInterior(const Image& image, const char* color, const std::string& when) { const int w = image.Width(); const int h = image.Height(); - EXPECT_TRUE(RegionIsMostly(image, (w * 45) / 100, (w * 55) / 100, (h * 20) / 100, - (h * 30) / 100, color, 0.0, when)); + EXPECT_TRUE(RegionIsMostly(image, (w * 45) / 100, (w * 55) / 100, (h * 20) / 100, (h * 30) / 100, color, + 0.0, when)); } // The bottom-left corner, which is below the triangle's base and left of its left @@ -258,8 +258,7 @@ void main() { oColor = vec4(vColor, 1.0); } // R-10's proof obligation over target B. Published in every split lane, small ring // included - the cap moves with MOBILEGL_IPC_RING_MB, so the SmallRing lane is also the // arm where a record closest to its cap would show up first. - WireLedger::ExpectMaxRecordBytesUnderCap( - "TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary"); + WireLedger::ExpectMaxRecordBytesUnderCap("TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary"); // Exit gate E3(e). Only the small-ring lane drives the overrun: at the default 8 MiB // the same loop would take eight times as long to say the same thing, and the point of @@ -282,8 +281,8 @@ void main() { oColor = vec4(vColor, 1.0); } glBindBuffer(GL_COPY_WRITE_BUFFER, 0); glDeleteBuffers(1, &pressureBuffer); Gl().EndFrame(); - WireLedger::ExpectSmallRingWrappedAtLeastOnce( - "TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary", driven); + WireLedger::ExpectSmallRingWrappedAtLeastOnce("TriangleScenario.TheSameVboAndVaoRedrawAcrossAFrameBoundary", + driven); } } @@ -365,10 +364,9 @@ void main() { oColor = vec4(vColor, 1.0); } ASSERT_TRUE(WaitForSplitAppliedForTesting(PeekSplitRuntime().emitSeq)); const PipeStatsWindow::Window window = PipeStatsWindow::LastFromLaneLog(); - ASSERT_TRUE(window.found) - << "the library emitted no `MGPipe stats:` line, so this lane's arming proof is " - "MISSING rather than zero - check MOBILEGL_PIPE_STATS=1, " - "MOBILEGL_PIPE_STATS_PERIOD=1 and this entry's private MOBILEGL_LOG_FILE_PATH"; + ASSERT_TRUE(window.found) << "the library emitted no `MGPipe stats:` line, so this lane's arming proof is " + "MISSING rather than zero - check MOBILEGL_PIPE_STATS=1, " + "MOBILEGL_PIPE_STATS_PERIOD=1 and this entry's private MOBILEGL_LOG_FILE_PATH"; const long long draws = PipeStatsWindow::CounterOrAbsent(window, "draws"); const long long stamps = PipeStatsWindow::CounterOrAbsent(window, "vbs"); @@ -380,14 +378,12 @@ void main() { oColor = vec4(vColor, 1.0); } EXPECT_GT(draws, 0) << "no draw reached the instrumented entry point in the window this " "case took, so there is nothing for the arming proof to be about: " << window.line; - EXPECT_GT(stamps, 0) - << "the server stamped NO verb boundary on a drawing split entry. Every strict check " - "- the poison, rsp, the BARRIER-PULLED verdict - is downstream of that stamp, so " - "a green strict lane containing only entries like this one would mean the knob " - "was never armed rather than that the debt was paid (ID-115): " - << window.line; - EXPECT_EQ(residual, 0) - << "P5f exit: a drawing frame still reads client residual state: " << window.line; + EXPECT_GT(stamps, 0) << "the server stamped NO verb boundary on a drawing split entry. Every strict check " + "- the poison, rsp, the BARRIER-PULLED verdict - is downstream of that stamp, so " + "a green strict lane containing only entries like this one would mean the knob " + "was never armed rather than that the debt was paid (ID-115): " + << window.line; + EXPECT_EQ(residual, 0) << "P5f exit: a drawing frame still reads client residual state: " << window.line; } } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/UnboundImageDescriptorScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/UnboundImageDescriptorScenario.cpp index b9e9de8a..41774ec2 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/UnboundImageDescriptorScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/UnboundImageDescriptorScenario.cpp @@ -261,8 +261,7 @@ void main() { static bool LimitIsAtLeastOne(GLenum limit) { GLint value = 0; glGetIntegerv(limit, &value); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return value >= 1; } @@ -303,9 +302,8 @@ void main() { const std::vector zeros(static_cast(kElements), 0u); glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_storage); - glBufferData(GL_SHADER_STORAGE_BUFFER, - static_cast(zeros.size() * sizeof(unsigned int)), zeros.data(), - GL_DYNAMIC_COPY); + glBufferData(GL_SHADER_STORAGE_BUFFER, static_cast(zeros.size() * sizeof(unsigned int)), + zeros.data(), GL_DYNAMIC_COPY); glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, m_storage); ASSERT_EQ(FirstGLError(), 0u) << "setting up the output buffer raised a GL error"; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/UnlocatedIoBlockScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/UnlocatedIoBlockScenario.cpp index b23c438e..97596c71 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/UnlocatedIoBlockScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/UnlocatedIoBlockScenario.cpp @@ -278,17 +278,15 @@ void main() glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel); GLint maxGeometryOutputVertices = 0; glGetIntegerv(GL_MAX_GEOMETRY_OUTPUT_VERTICES, &maxGeometryOutputVertices); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return maxTessGenLevel >= 1 && maxGeometryOutputVertices >= 4; } GLuint BuildPipeline(const char* tessEvalSource, const char* geometrySource) { - const GLenum stages[] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, - GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER, - GL_FRAGMENT_SHADER}; - const char* const sources[] = {kVertexSource, kTessControlSource, tessEvalSource, - geometrySource, kFragmentSource}; + const GLenum stages[] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, + GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; + const char* const sources[] = {kVertexSource, kTessControlSource, tessEvalSource, geometrySource, + kFragmentSource}; GLuint shaders[5] = {0, 0, 0, 0, 0}; bool ok = true; @@ -345,9 +343,7 @@ void main() return pixel; } - static bool IsGreen(const Rgba8& pixel) { - return pixel.r < 64 && pixel.g > 192 && pixel.b < 64; - } + static bool IsGreen(const Rgba8& pixel) { return pixel.r < 64 && pixel.g > 192 && pixel.b < 64; } const std::string& BuildLog() const { return m_buildLog; } @@ -356,8 +352,8 @@ void main() // fixture can reach, so the ctest entry sets it and this only reads it back. static std::filesystem::path LibraryLogPath() { // P6: the path is a BASE NAME and the library writes one log per role; PipeStatsWindow - // derives the suffix, so the rule lives in one place. - return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); + // derives the suffix, so the rule lives in one place. + return std::filesystem::path(MGITest::PipeStatsWindow::LibraryLogPath()); } // How many bytes the library log already holds. Everything this fixture asserts on @@ -382,8 +378,7 @@ void main() static GLenum FirstGLError() { const GLenum first = glGetError(); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} return first; } @@ -415,16 +410,15 @@ void main() const GLuint program = BuildPipeline(kDistinctTessEvalSource, kDistinctGeometrySource); if (program == 0) { GTEST_SKIP() << "this stack cannot build a five-stage tessellation+geometry program on " - << Gl().BackendName() << ", so there is no block to carry through: " - << BuildLog(); + << Gl().BackendName() << ", so there is no block to carry through: " << BuildLog(); } const Rgba8 centre = DrawAndReadCentre(program); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(centre)) - << "a four-boundary interface-block chain did not deliver its payload: " << centre - << " (blue: nothing drew; red: the plain varying was lost too; black: a block " - "member arrived wrong, i.e. the interface stopped matching)"; + EXPECT_TRUE(IsGreen(centre)) << "a four-boundary interface-block chain did not deliver its payload: " + << centre + << " (blue: nothing drew; red: the plain varying was lost too; black: a block " + "member arrived wrong, i.e. the interface stopped matching)"; } // The two repairs together. The rename is what makes the evaluation stage's two @@ -436,20 +430,17 @@ void main() if (BuildPipeline(kDistinctTessEvalSource, kDistinctGeometrySource) == 0) { GTEST_SKIP() << "this stack cannot build a five-stage tessellation+geometry program on " - << Gl().BackendName() << ", so there is no block to carry through: " - << BuildLog(); + << Gl().BackendName() << ", so there is no block to carry through: " << BuildLog(); } const GLuint program = BuildPipeline(kCollidingTessEvalSource, kCollidingGeometrySource); - ASSERT_NE(program, 0u) - << "an interface block name reused across the two directions of one stage is legal " - "desktop GLSL, but the program did not build: " - << BuildLog(); + ASSERT_NE(program, 0u) << "an interface block name reused across the two directions of one stage is legal " + "desktop GLSL, but the program did not build: " + << BuildLog(); const Rgba8 centre = DrawAndReadCentre(program); EXPECT_EQ(FirstGLError(), 0u); - EXPECT_TRUE(IsGreen(centre)) - << "the renamed-and-unlocated interface chain lost its payload: " << centre; + EXPECT_TRUE(IsGreen(centre)) << "the renamed-and-unlocated interface chain lost its payload: " << centre; } // THE ONE CASE THAT CAN FAIL WHEN THE REPAIR SILENTLY STOPS BEING ARMED. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/UnwrittenPositionOutputScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/UnwrittenPositionOutputScenario.cpp index fcd72ad1..d0179d06 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/UnwrittenPositionOutputScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/UnwrittenPositionOutputScenario.cpp @@ -206,8 +206,7 @@ void main() { glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, captureBytes, readback.data()); for (std::size_t i = 0; i < kCaptureFloats; ++i) { EXPECT_FLOAT_EQ(readback[userVaryingOffset + i], static_cast(i + 1)) - << what << ": captured float " << i << " came back as " - << readback[userVaryingOffset + i] + << what << ": captured float " << i << " came back as " << readback[userVaryingOffset + i] << "; the pre-fill value means the draw never produced a vertex, which is what an " "invalid shader module looks like from out here"; } @@ -248,8 +247,8 @@ void main() { GTEST_SKIP() << "capturing an unwritten gl_Position by name is a separate, known " << "DirectGLES gap; this case pins the DirectVulkan injection"; } - ExpectUserVaryingIsCaptured(kUnwrittenPositionVertexSource, {"gl_Position", "vs_out_value"}, - kCaptureFloats * 2, kCaptureFloats, "capturing an unwritten gl_Position"); + ExpectUserVaryingIsCaptured(kUnwrittenPositionVertexSource, {"gl_Position", "vs_out_value"}, kCaptureFloats * 2, + kCaptureFloats, "capturing an unwritten gl_Position"); } // Control: the same shader with the one assignment restored. Its block is never delisted, @@ -262,8 +261,8 @@ void main() { TEST_F(UnwrittenPositionOutputScenario, CapturingAWrittenPositionStillDraws) { if (!Ready() || IsSkipped()) return; - ExpectUserVaryingIsCaptured(kWrittenPositionVertexSource, {"gl_Position", "vs_out_value"}, - kCaptureFloats * 2, kCaptureFloats, "capturing a written gl_Position"); + ExpectUserVaryingIsCaptured(kWrittenPositionVertexSource, {"gl_Position", "vs_out_value"}, kCaptureFloats * 2, + kCaptureFloats, "capturing a written gl_Position"); } // Control: no gl_PerVertex redeclaration, so no Position annotation and nothing to delist. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/UploadOrderScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/UploadOrderScenario.cpp index 88344cca..c85d2aed 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/UploadOrderScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/UploadOrderScenario.cpp @@ -33,147 +33,147 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -namespace { -using Pixel = std::array; -constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, black{0, 0, 0, 255}; -constexpr int kWidth = 16, kHeight = 8; -constexpr const char* kVertex = R"(#version 330 core + namespace { + using Pixel = std::array; + constexpr Pixel red{255, 0, 0, 255}, green{0, 255, 0, 255}, black{0, 0, 0, 255}; + constexpr int kWidth = 16, kHeight = 8; + constexpr const char* kVertex = R"(#version 330 core void main() { vec2 p[3] = vec2[3](vec2(-1,-1), vec2(3,-1), vec2(-1,3)); gl_Position = vec4(p[gl_VertexID], 0, 1); })"; -constexpr const char* kSample = R"(#version 330 core + constexpr const char* kSample = R"(#version 330 core uniform sampler2D src; layout(location=0) out vec4 color; void main() { color = texelFetch(src, ivec2(0), 0); })"; -class UploadOrderScenario : public ScenarioTest { -protected: - GLuint target = 0, program = 0, vao = 0; - std::vector textures, fbos; + class UploadOrderScenario : public ScenarioTest { + protected: + GLuint target = 0, program = 0, vao = 0; + std::vector textures, fbos; - void SetUp() override { - ScenarioTest::SetUp(); - if (!Ready()) return; - glDisable(GL_SCISSOR_TEST); - glDisable(GL_DEPTH_TEST); - glDisable(GL_STENCIL_TEST); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glActiveTexture(GL_TEXTURE0); - glGenVertexArrays(1, &vao); - glBindVertexArray(vao); - target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); - program = Link(); - } + void SetUp() override { + ScenarioTest::SetUp(); + if (!Ready()) return; + glDisable(GL_SCISSOR_TEST); + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + glDisable(GL_BLEND); + glDisable(GL_CULL_FACE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glActiveTexture(GL_TEXTURE0); + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + target = NewFramebuffer(NewTexture(kWidth, kHeight, black)); + program = Link(); + } - void TearDown() override { - if (Ready()) { - glUseProgram(0); - glBindVertexArray(0); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - if (vao) glDeleteVertexArrays(1, &vao); - if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); - if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); - if (program) glDeleteProgram(program); + void TearDown() override { + if (Ready()) { + glUseProgram(0); + glBindVertexArray(0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (vao) glDeleteVertexArrays(1, &vao); + if (!fbos.empty()) glDeleteFramebuffers(GLsizei(fbos.size()), fbos.data()); + if (!textures.empty()) glDeleteTextures(GLsizei(textures.size()), textures.data()); + if (program) glDeleteProgram(program); + } + ScenarioTest::TearDown(); + } + + static std::vector Pixels(int width, int height, Pixel color) { + return std::vector(size_t(width) * size_t(height), color); + } + + GLuint NewTexture(int width, int height, Pixel color) { + GLuint texture = 0; + glGenTextures(1, &texture); + textures.push_back(texture); + const auto pixels = Pixels(width, height, color); + glBindTexture(GL_TEXTURE_2D, texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + return texture; + } + + static void Upload(GLuint texture, int width, int height, Pixel color) { + const auto pixels = Pixels(width, height, color); + glBindTexture(GL_TEXTURE_2D, texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + } + + GLuint NewFramebuffer(GLuint texture) { + GLuint fbo = 0; + glGenFramebuffers(1, &fbo); + fbos.push_back(fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); + return fbo; + } + + GLuint Link() { + const GLuint linked = glCreateProgram(); + const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; + const std::array sources{kVertex, kSample}; + for (size_t i = 0; i < kinds.size(); ++i) { + const GLuint shader = glCreateShader(kinds[i]); + glShaderSource(shader, 1, &sources[i], nullptr); + glCompileShader(shader); + glAttachShader(linked, shader); + glDeleteShader(shader); + } + glLinkProgram(linked); + GLint ok = GL_FALSE; + glGetProgramiv(linked, GL_LINK_STATUS, &ok); + EXPECT_EQ(ok, GL_TRUE); + return linked; + } + + // Samples `texture` into the 8x8 tile `tile` of the target. + void SampleInto(GLuint texture, int tile) { + glBindFramebuffer(GL_FRAMEBUFFER, target); + glUseProgram(program); + glBindTexture(GL_TEXTURE_2D, texture); + glViewport(tile * 8, 0, 8, kHeight); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + + void ExpectPixel(int x, int y, Pixel expected, const char* why) { + glBindFramebuffer(GL_FRAMEBUFFER, target); + Pixel actual{}; + glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); + for (size_t channel = 0; channel < actual.size(); ++channel) + EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; + EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; + } + }; + + TEST_F(UploadOrderScenario, AnUploadDoesNotReachBackIntoADrawThatSampledTheTextureBeforeIt) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewTexture(2, 2, red); + glFinish(); // everything above is submitted: the draw below opens a fresh recording + SampleInto(texture, 0); + Upload(texture, 2, 2, green); + SampleInto(texture, 1); + ExpectPixel(4, 4, red, "the draw issued before the upload samples the old texels"); + ExpectPixel(12, 4, green, "the draw issued after the upload samples the new texels"); } - ScenarioTest::TearDown(); - } - static std::vector Pixels(int width, int height, Pixel color) { - return std::vector(size_t(width) * size_t(height), color); - } - - GLuint NewTexture(int width, int height, Pixel color) { - GLuint texture = 0; - glGenTextures(1, &texture); - textures.push_back(texture); - const auto pixels = Pixels(width, height, color); - glBindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - return texture; - } - - static void Upload(GLuint texture, int width, int height, Pixel color) { - const auto pixels = Pixels(width, height, color); - glBindTexture(GL_TEXTURE_2D, texture); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); - } - - GLuint NewFramebuffer(GLuint texture) { - GLuint fbo = 0; - glGenFramebuffers(1, &fbo); - fbos.push_back(fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - EXPECT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum(GL_FRAMEBUFFER_COMPLETE)); - return fbo; - } - - GLuint Link() { - const GLuint linked = glCreateProgram(); - const std::array kinds{GL_VERTEX_SHADER, GL_FRAGMENT_SHADER}; - const std::array sources{kVertex, kSample}; - for (size_t i = 0; i < kinds.size(); ++i) { - const GLuint shader = glCreateShader(kinds[i]); - glShaderSource(shader, 1, &sources[i], nullptr); - glCompileShader(shader); - glAttachShader(linked, shader); - glDeleteShader(shader); + TEST_F(UploadOrderScenario, AnUploadLandsAfterAClearThatOpenedTheRecording) { + if (!Ready() || IsSkipped()) return; + const GLuint texture = NewTexture(2, 2, black); + const GLuint attachment = NewFramebuffer(texture); + glFinish(); // the clear below is the first command of a new recording + glBindFramebuffer(GL_FRAMEBUFFER, attachment); + glClearColor(0.0f, 0.0f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + Upload(texture, 2, 2, green); // after the clear, so it must win over the clear's blue + SampleInto(texture, 0); + ExpectPixel(4, 4, green, "the upload lands after the clear that preceded it"); + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); } - glLinkProgram(linked); - GLint ok = GL_FALSE; - glGetProgramiv(linked, GL_LINK_STATUS, &ok); - EXPECT_EQ(ok, GL_TRUE); - return linked; - } - // Samples `texture` into the 8x8 tile `tile` of the target. - void SampleInto(GLuint texture, int tile) { - glBindFramebuffer(GL_FRAMEBUFFER, target); - glUseProgram(program); - glBindTexture(GL_TEXTURE_2D, texture); - glViewport(tile * 8, 0, 8, kHeight); - glDrawArrays(GL_TRIANGLES, 0, 3); - } - - void ExpectPixel(int x, int y, Pixel expected, const char* why) { - glBindFramebuffer(GL_FRAMEBUFFER, target); - Pixel actual{}; - glReadPixels(x, y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actual.data()); - for (size_t channel = 0; channel < actual.size(); ++channel) - EXPECT_EQ(int(actual[channel]), int(expected[channel])) << why << " channel=" << channel; - EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)) << why; - } -}; - -TEST_F(UploadOrderScenario, AnUploadDoesNotReachBackIntoADrawThatSampledTheTextureBeforeIt) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewTexture(2, 2, red); - glFinish(); // everything above is submitted: the draw below opens a fresh recording - SampleInto(texture, 0); - Upload(texture, 2, 2, green); - SampleInto(texture, 1); - ExpectPixel(4, 4, red, "the draw issued before the upload samples the old texels"); - ExpectPixel(12, 4, green, "the draw issued after the upload samples the new texels"); -} - -TEST_F(UploadOrderScenario, AnUploadLandsAfterAClearThatOpenedTheRecording) { - if (!Ready() || IsSkipped()) return; - const GLuint texture = NewTexture(2, 2, black); - const GLuint attachment = NewFramebuffer(texture); - glFinish(); // the clear below is the first command of a new recording - glBindFramebuffer(GL_FRAMEBUFFER, attachment); - glClearColor(0.0f, 0.0f, 1.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - Upload(texture, 2, 2, green); // after the clear, so it must win over the clear's blue - SampleInto(texture, 0); - ExpectPixel(4, 4, green, "the upload lands after the clear that preceded it"); - glClearColor(0.0f, 0.0f, 0.0f, 0.0f); -} - -} // namespace + } // namespace } // namespace MGITest diff --git a/MobileGL/MG_IntegrationTest/Scenarios/VaoShadowScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/VaoShadowScenario.cpp index 679bf44e..2da81c9c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/VaoShadowScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/VaoShadowScenario.cpp @@ -65,13 +65,18 @@ void main() { o0 = vColor; } << "), want (" << r << ", " << g << ", " << b << ")"; } - Rgba8 At(int x, int y) { return ReadPixelsRect(x, y, 1, 1).At(0, 0); } + Rgba8 At(int x, int y) { + return ReadPixelsRect(x, y, 1, 1).At(0, 0); + } // Quad positions (x0..x1 by full height) as a triangle strip. - std::vector Quad(float x0, float x1) { return {x0, 0.0f, x1, 0.0f, x0, 1.0f, x1, 1.0f}; } + std::vector Quad(float x0, float x1) { + return {x0, 0.0f, x1, 0.0f, x0, 1.0f, x1, 1.0f}; + } std::vector Colors(float r, float g, float b) { std::vector c; - for (int i = 0; i < 4; ++i) c.insert(c.end(), {r, g, b, 1.0f}); + for (int i = 0; i < 4; ++i) + c.insert(c.end(), {r, g, b, 1.0f}); return c; } @@ -104,7 +109,8 @@ void main() { o0 = vColor; } GLuint b = 0; glGenBuffers(1, &b); glBindBuffer(GL_ARRAY_BUFFER, b); - glBufferData(GL_ARRAY_BUFFER, static_cast(data.size() * sizeof(float)), data.data(), GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, static_cast(data.size() * sizeof(float)), data.data(), + GL_STATIC_DRAW); return b; } @@ -211,7 +217,9 @@ void main() { o0 = vColor; } const GLuint pos = Buffer(Quad(0.0f, 0.5f)); // Per-vertex: 4 x red. Per-instance (divisor 1): instance 0 red, instance 1 green. std::vector c = Colors(1.0f, 0.0f, 0.0f); - c[4] = 0.0f; c[5] = 1.0f; c[6] = 0.0f; // second element green + c[4] = 0.0f; + c[5] = 1.0f; + c[6] = 0.0f; // second element green const GLuint colors = Buffer(c); glBindBuffer(GL_ARRAY_BUFFER, pos); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/VertexArrayEnableDisableScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/VertexArrayEnableDisableScenario.cpp index 938944b9..a7a39f53 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/VertexArrayEnableDisableScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/VertexArrayEnableDisableScenario.cpp @@ -81,8 +81,8 @@ namespace MGITest { // interpolated, so a driver is within its rights to reject the unqualified form // even with no matching fragment input. The capture reads the same value either // way, and the qualifier keeps this scenario portable off llvmpipe. - const std::string vertexSource = "#version 450\n\n" + declarations + - "flat out int sum;\n\nvoid main()\n{\n" + copies + "}\n"; + const std::string vertexSource = + "#version 450\n\n" + declarations + "flat out int sum;\n\nvoid main()\n{\n" + copies + "}\n"; const std::string fragmentSource = R"(#version 450 out vec4 color; @@ -148,7 +148,8 @@ void main() glGenBuffers(1, &m_vbo); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); std::vector reference(static_cast(m_attributeCount)); - for (int i = 0; i < m_attributeCount; ++i) reference[static_cast(i)] = i; + for (int i = 0; i < m_attributeCount; ++i) + reference[static_cast(i)] = i; glBufferData(GL_ARRAY_BUFFER, static_cast(reference.size() * sizeof(GLint)), reference.data(), GL_STATIC_DRAW); for (int i = 0; i < m_attributeCount; ++i) { @@ -196,7 +197,8 @@ void main() int ExpectedSum(int parity) const { int sum = 0; - for (int i = parity; i < m_attributeCount; i += 2) sum += i; + for (int i = parity; i < m_attributeCount; i += 2) + sum += i; return sum; } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/VertexAttribBindingScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/VertexAttribBindingScenario.cpp index ddd400b1..bfa2efd0 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/VertexAttribBindingScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/VertexAttribBindingScenario.cpp @@ -544,15 +544,18 @@ void main() { } )"; std::vector names; - for (int i = 0; i < 16; ++i) names.push_back("StageData.attrib[" + std::to_string(i) + "]"); + for (int i = 0; i < 16; ++i) + names.push_back("StageData.attrib[" + std::to_string(i) + "]"); std::vector varyings; - for (const auto& n : names) varyings.push_back(n.c_str()); + for (const auto& n : names) + varyings.push_back(n.c_str()); std::string log; const GLuint program = BuildCaptureProgram(vs, varyings, &log); ASSERT_NE(program, 0u) << "capture program did not link: " << log; - for (GLuint i = 0; i < 16; ++i) glVertexAttrib4f(i, 0.0f, 0.0f, 0.0f, 0.0f); + for (GLuint i = 0; i < 16; ++i) + glVertexAttrib4f(i, 0.0f, 0.0f, 0.0f, 0.0f); const float vertices[] = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f}; GLuint vbo = 0; @@ -580,8 +583,8 @@ void main() { glDisable(GL_RASTERIZER_DISCARD); std::vector data(poison.size(), kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(data.size() * sizeof(float)), data.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(data.size() * sizeof(float)), + data.data()); glUseProgram(0); // Element 0 of the array has no enabled array behind it, so it must deliver the @@ -629,9 +632,11 @@ void main() { } )"; std::vector names; - for (int i = 0; i < 16; ++i) names.push_back("StageData.attrib[" + std::to_string(i) + "]"); + for (int i = 0; i < 16; ++i) + names.push_back("StageData.attrib[" + std::to_string(i) + "]"); std::vector varyings; - for (const auto& n : names) varyings.push_back(n.c_str()); + for (const auto& n : names) + varyings.push_back(n.c_str()); std::string log; const GLuint program = BuildCaptureProgram(vs, varyings, &log); @@ -657,8 +662,8 @@ void main() { glDisable(GL_RASTERIZER_DISCARD); std::vector data(poison.size(), kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(data.size() * sizeof(float)), data.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(data.size() * sizeof(float)), + data.data()); glUseProgram(0); for (int element = 0; element < 16; ++element) { @@ -669,7 +674,8 @@ void main() { EXPECT_FLOAT_EQ(data[element * 4 + 3], base + 300.0f) << "element " << element; } - for (GLuint i = 0; i < 16; ++i) glVertexAttrib4f(i, 0.0f, 0.0f, 0.0f, 0.0f); + for (GLuint i = 0; i < 16; ++i) + glVertexAttrib4f(i, 0.0f, 0.0f, 0.0f, 0.0f); glDeleteProgram(program); } @@ -778,8 +784,8 @@ void main() { put(0, 8, &packedInt0, 4); const unsigned char bytes1[4] = {0xff, 0xff / 2, 0xff / 4, 0}; const unsigned int packedUint1 = 0u | (1023u << 10) | (511u << 20) | (2u << 30); - const unsigned int packedInt1 = (static_cast(-511) & 0x3ffu) | (511u << 10) | - (255u << 20) | (3u << 30); + const unsigned int packedInt1 = + (static_cast(-511) & 0x3ffu) | (511u << 10) | (255u << 20) | (3u << 30); put(1, 0, bytes1, 4); put(1, 4, &packedUint1, 4); put(1, 8, &packedInt1, 4); @@ -811,7 +817,8 @@ void main() { EXPECT_TRUE(Vec4Is(data, 1, 2, 0.5f, 1.0f, 0.0f, 2.0f / 3.0f)); EXPECT_TRUE(Vec4Is(data, 1, 3, 0.5f, 1.0f, -1.0f, -1.0f)); - for (GLuint i = 0; i < 4; ++i) glDisableVertexAttribArray(i); + for (GLuint i = 0; i < 4; ++i) + glDisableVertexAttribArray(i); glDeleteBuffers(1, &vbo); } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/ViewportArrayScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/ViewportArrayScenario.cpp index 967a8b30..37338ab4 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/ViewportArrayScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/ViewportArrayScenario.cpp @@ -67,8 +67,8 @@ namespace MGITest { namespace { constexpr int kViewportCount = 16; - constexpr int kGridSide = 4; // 4x4 grid of viewports - constexpr int kCellSize = 32; // ... each 32x32 + constexpr int kGridSide = 4; // 4x4 grid of viewports + constexpr int kCellSize = 32; // ... each 32x32 constexpr int kSurfaceSide = kGridSide * kCellSize; constexpr GLint kUnwritten = -1; @@ -168,8 +168,7 @@ void main() { fragColor = gl_FragCoord.z; } if (m_vao != 0) glDeleteVertexArrays(1, &m_vao); if (m_program != 0) glDeleteProgram(m_program); glBindFramebuffer(GL_FRAMEBUFFER, 0); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } // Every case starts from the same slate: this fixture shares its context with every @@ -474,7 +473,7 @@ void main() { fragColor = vec4(float(gsIndex) * 16.0 / 255.0, 0.0, 0.0, 1.0); } const GLint uViewport = glGetUniformLocation(singleProgram, "uViewport"); ASSERT_NE(uViewport, -1); - constexpr GLint kProbeIndex = 6; // grid cell (2, 1) + constexpr GLint kProbeIndex = 6; // grid cell (2, 1) constexpr int kProbeX = kProbeIndex % kGridSide; constexpr int kProbeY = kProbeIndex / kGridSide; @@ -640,8 +639,7 @@ void main() { fragColor = 7; } glBindTexture(GL_TEXTURE_2D, m_texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, kSurfaceSide, kSurfaceSide, 0, GL_RED_INTEGER, GL_INT, - nullptr); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R32I, kSurfaceSide, kSurfaceSide, 0, GL_RED_INTEGER, GL_INT, nullptr); glGenFramebuffers(1, &m_fbo); glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0); @@ -666,8 +664,7 @@ void main() { fragColor = 7; } glBindFramebuffer(GL_FRAMEBUFFER, 0); if (m_fbo != 0) glDeleteFramebuffers(1, &m_fbo); if (m_texture != 0) glDeleteTextures(1, &m_texture); - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} } static void ResetScissorState() { @@ -769,7 +766,8 @@ void main() { fragColor = 7; } const std::vector pixels = ReadTarget(); for (size_t i = 0; i < pixels.size(); ++i) { ASSERT_EQ(pixels[i], kUnwritten) - << "texel " << i << " was painted through a 0x0 scissor box: the empty rectangle was " + << "texel " << i + << " was painted through a 0x0 scissor box: the empty rectangle was " "substituted with the whole surface, inverting 'clip everything' into 'clip nothing'"; } } @@ -800,8 +798,9 @@ void main() { fragColor = 7; } const std::vector pixels = ReadTarget(); for (size_t i = 0; i < pixels.size(); ++i) { - ASSERT_EQ(pixels[i], kUnwritten) << "texel " << i << " was painted through a zero-extent indexed " - "scissor box"; + ASSERT_EQ(pixels[i], kUnwritten) << "texel " << i + << " was painted through a zero-extent indexed " + "scissor box"; } } diff --git a/MobileGL/MG_IntegrationTest/Scenarios/WindowPassGateScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/WindowPassGateScenario.cpp index 76f79466..e2d26322 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/WindowPassGateScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/WindowPassGateScenario.cpp @@ -85,14 +85,18 @@ void main() { o_color = texture(uTex, vUv); } // Saturated colours only: ColorName() names them exactly whatever the rounding. constexpr NamedColor kCycle[] = { - {1.0f, 0.0f, 0.0f, "red"}, {0.0f, 1.0f, 0.0f, "green"}, {0.0f, 0.0f, 1.0f, "blue"}, + {1.0f, 0.0f, 0.0f, "red"}, {0.0f, 1.0f, 0.0f, "green"}, {0.0f, 0.0f, 1.0f, "blue"}, {1.0f, 1.0f, 0.0f, "yellow"}, {1.0f, 1.0f, 1.0f, "white"}, }; constexpr int kCycleLength = static_cast(sizeof(kCycle) / sizeof(kCycle[0])); constexpr int kFrames = 7; - const NamedColor& Background(int frame) { return kCycle[frame % kCycleLength]; } - const NamedColor& Foreground(int frame) { return kCycle[(frame + 2) % kCycleLength]; } + const NamedColor& Background(int frame) { + return kCycle[frame % kCycleLength]; + } + const NamedColor& Foreground(int frame) { + return kCycle[(frame + 2) % kCycleLength]; + } // The NDC rectangle [x0, x1] x [y0, y1] in one colour; aPos at 0, aColor at 1. void DrawRect(unsigned int program, float x0, float y0, float x1, float y1, const NamedColor& c) { @@ -109,7 +113,8 @@ void main() { o_color = texture(uTex, vUv); } glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), nullptr); glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), reinterpret_cast(2 * sizeof(float))); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 6 * sizeof(float), + reinterpret_cast(2 * sizeof(float))); glUseProgram(program); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBindVertexArray(0); @@ -138,7 +143,10 @@ void main() { o_color = texture(uTex, vUv); } glDeleteVertexArrays(1, &vao); } - enum class WindowPass { Blit, SampledDraw }; + enum class WindowPass { + Blit, + SampledDraw + }; class WindowPassGateScenario : public ScenarioTest { protected: @@ -231,7 +239,8 @@ void main() { o_color = texture(uTex, vUv); } RecordProperty("arming_checked", armed ? "yes" : "no"); if (!armed) return; const std::string appended = PipeStatsWindow::ReadLaneLogSince(m_logBefore); - EXPECT_NE(appended.find("off-screen work goes to the queue ahead of its window pass"), std::string::npos) + EXPECT_NE(appended.find("off-screen work goes to the queue ahead of its window pass"), + std::string::npos) << "this case recorded off-screen work ahead of every window pass, but DirectVulkan never " "reported submitting it ahead of the acquire wait: the whole frame still queues behind " "the swapchain image's release (VulkanRenderer::SubmitAheadOfAcquiredImage). Log appended " diff --git a/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp index 1e286d24..7c11b2b9 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDispatchScenario.cpp @@ -187,8 +187,8 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; } } } - EXPECT_EQ(wrong, 0u) << what << ": expected exactly the " << grid[0] << "x" << grid[1] << "x" - << grid[2] << " grid marked once; first wrong " << first; + EXPECT_EQ(wrong, 0u) << what << ": expected exactly the " << grid[0] << "x" << grid[1] << "x" << grid[2] + << " grid marked once; first wrong " << first; } // Magma's server in THIS process: the inproc arm. Spawn and tcp servers are other @@ -286,7 +286,8 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; const WireIndirectCounters delta = Delta(); EXPECT_EQ(delta.nativeDispatches, 3u); EXPECT_EQ(delta.hostWaits, 0u) << "a host wait was taken"; - EXPECT_EQ(delta.barriers, 2u) << "exactly one barrier per write: the second dispatch reads no new write"; + EXPECT_EQ(delta.barriers, 2u) + << "exactly one barrier per write: the second dispatch reads no new write"; } ExpectGrid(first, {3, 2, 1}, "the first dispatch after the write"); ExpectGrid(again, {3, 2, 1}, "a second dispatch, no write between"); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp index d5b725ba..1b0f6dbe 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/WireIndirectDrawScenario.cpp @@ -78,8 +78,12 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; std::uint32_t padding[3]; }; - ArraysCommand Arrays(int strip) { return {kStripVertices, 1, kPad + kStripVertices * strip, 0}; } - ElementsCommand Elements(int strip) { return {kStripVertices, 1, kStripVertices * strip, 0, 0}; } + ArraysCommand Arrays(int strip) { + return {kStripVertices, 1, kPad + kStripVertices * strip, 0}; + } + ElementsCommand Elements(int strip) { + return {kStripVertices, 1, kStripVertices * strip, 0, 0}; + } using Strips = std::array; @@ -105,14 +109,15 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; vertices.insert(vertices.end(), quad, quad + 12); } std::vector indices; - for (std::uint32_t i = 0; i < kStripVertices * kStrips; ++i) indices.push_back(kPad + i); + for (std::uint32_t i = 0; i < kStripVertices * kStrips; ++i) + indices.push_back(kPad + i); glGenVertexArrays(1, &m_vao); glBindVertexArray(m_vao); glGenBuffers(1, &m_vbo); glBindBuffer(GL_ARRAY_BUFFER, m_vbo); - glBufferData(GL_ARRAY_BUFFER, static_cast(vertices.size() * sizeof(float)), - vertices.data(), GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, static_cast(vertices.size() * sizeof(float)), vertices.data(), + GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr); glGenBuffers(1, &m_ebo); @@ -254,14 +259,11 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; // The count forms: three commands each, a count word that allows fewer. Fill(m_parameter, GL_PARAMETER_BUFFER, std::vector{7, 2, 1}); Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, std::vector{Arrays(0), Arrays(1), Arrays(2)}); - image = Render([&] { - glMultiDrawArraysIndirectCount(GL_TRIANGLES, nullptr, 4, 3, 0); - }); + image = Render([&] { glMultiDrawArraysIndirectCount(GL_TRIANGLES, nullptr, 4, 3, 0); }); ExpectStrips(image, {true, true, false, false}, "glMultiDrawArraysIndirectCount, count word 2 of 3"); - Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, std::vector{Elements(3), Elements(2), Elements(1)}); - image = Render([&] { - glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 8, 3, 0); - }); + Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, + std::vector{Elements(3), Elements(2), Elements(1)}); + image = Render([&] { glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 8, 3, 0); }); ExpectStrips(image, {false, false, false, true}, "glMultiDrawElementsIndirectCount, count word 1 of 3"); EXPECT_EQ(FirstGLError(), GLenum(GL_NO_ERROR)); @@ -313,14 +315,15 @@ void main() { words[gl_GlobalInvocationID.x] = source[gl_GlobalInvocationID.x]; GTEST_SKIP() << "Espryt's monolith arm reads the count word from the frontend shadow without " "a sync (OQ15; recorded for P13, ID-P8-13)"; } - Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, std::vector{Elements(0), Elements(3), Elements(2)}); + Fill(m_indirect, GL_DRAW_INDIRECT_BUFFER, + std::vector{Elements(0), Elements(3), Elements(2)}); Fill(m_parameter, GL_PARAMETER_BUFFER, std::vector{3}); ComputeWrite(m_parameter, std::vector{1}); glMemoryBarrier(GL_COMMAND_BARRIER_BIT); - Image image = Render([&] { - glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 3, 0); - }); - ExpectStrips(image, {true, false, false, false}, "glMultiDrawElementsIndirectCount, shader-written count 1"); + Image image = + Render([&] { glMultiDrawElementsIndirectCount(GL_TRIANGLES, GL_UNSIGNED_INT, nullptr, 0, 3, 0); }); + ExpectStrips(image, {true, false, false, false}, + "glMultiDrawElementsIndirectCount, shader-written count 1"); if (!m_recordArm) { // DirectVulkan.cpp's monolith MultiDrawArraysIndirectCount reads this word from the // frontend shadow without SyncGpuWrites: recorded for dev (ID-P8-3), not this lane's. diff --git a/MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp index 984d0141..e09b5e25 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/WireMipShapeScenario.cpp @@ -136,7 +136,8 @@ namespace MGITest { if (got < want[c] - 2 || got > want[c] + 2) ok = false; } if (!ok && wrong++ == 0) { - for (int c = 0; c < 4; ++c) first[c] = pixels[base + static_cast(c)]; + for (int c = 0; c < 4; ++c) + first[c] = pixels[base + static_cast(c)]; } } EXPECT_EQ(wrong, 0u) << "level " << level << " slice " << z << ": expected (" << want[0] << "," @@ -168,7 +169,8 @@ namespace MGITest { if (!Ready() || IsSkipped()) return; if (!m_magma) GTEST_SKIP() << "Magma wire-arm decline (P8-SV): the knob and the depth shader arm it forces are " - "Magma's; Espryt (" << Gl().BackendName() << ") generates this chain itself"; + "Magma's; Espryt (" + << Gl().BackendName() << ") generates this chain itself"; const std::string tier = SplitLane::MarkerValue("MGITEST_MAGMA_FORCE_SHADER_MIPMAP"); if (tier.empty()) GTEST_SKIP() << "MGITEST_MAGMA_FORCE_SHADER_MIPMAP is unset: this driver blits depth natively, " @@ -203,8 +205,8 @@ namespace MGITest { } } EXPECT_EQ(wrong, 0u) << "level " << level << " tier=" << tier << ": a declined mip must leave " - << "every level as the application wrote it (expected " << want - << ", first wrong " << first << ")"; + << "every level as the application wrote it (expected " << want << ", first wrong " + << first << ")"; } glBindTexture(GL_TEXTURE_1D, 0); glDeleteTextures(1, &line); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/WriteRenameScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/WriteRenameScenario.cpp index 85a3ab8b..c950f90c 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/WriteRenameScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/WriteRenameScenario.cpp @@ -79,7 +79,9 @@ void main() { o0 = vColor * uTint; } << "), want (" << r << ", " << g << ", " << b << ")"; } - Rgba8 At(int x, int y) { return ReadPixelsRect(x, y, 1, 1).At(0, 0); } + Rgba8 At(int x, int y) { + return ReadPixelsRect(x, y, 1, 1).At(0, 0); + } struct Scene { GLuint program = 0, fbo = 0, color = 0, vao = 0, vbo = 0, ibo = 0, ubo = 0; diff --git a/MobileGL/MG_IntegrationTest/Scenarios/XfbAfterClipDistanceScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/XfbAfterClipDistanceScenario.cpp index 10691187..09d5bca4 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/XfbAfterClipDistanceScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/XfbAfterClipDistanceScenario.cpp @@ -143,17 +143,20 @@ namespace MGITest { break; case 1: source += "float f(int i)\n{\n return 0.25 + 0.75 * (float(i) + 1.0) * (float(gl_VertexID) + 1.0)" - " / (float(" + count + ") * float(" + std::to_string(vertexCount) + "));\n}\n"; + " / (float(" + + count + ") * float(" + std::to_string(vertexCount) + "));\n}\n"; break; default: source += "float f(int i)\n{\n return - 0.25 - 0.75 * (float(i) + 1.0) * (float(gl_VertexID) + 1.0)" - " / (float(" + count + ") * float(" + std::to_string(vertexCount) + "));\n}\n"; + " / (float(" + + count + ") * float(" + std::to_string(vertexCount) + "));\n}\n"; break; } source += "\nin vec4 position;\n\nvoid main()\n{\n"; if (redeclaration) { // Dynamic array setter. - source += " for(int i = 0; i < " + count + "; i++)\n {\n" + source += " for(int i = 0; i < " + count + + "; i++)\n {\n" " gl_ClipDistance[i] = f(i);\n }\n"; } else { // Static array setter, at the highest index this iteration enables. @@ -210,14 +213,12 @@ void main() glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer); glViewport(0, 0, framebufferSize, framebufferSize); - const unsigned lastFunction = - (primitiveIndex == stop.primitiveIndex) ? stop.clipFunction : 2u; + const unsigned lastFunction = (primitiveIndex == stop.primitiveIndex) ? stop.clipFunction : 2u; for (unsigned clipFunction = 0; clipFunction <= lastFunction; ++clipFunction) { const bool atStopFunction = primitiveIndex == stop.primitiveIndex && clipFunction == stop.clipFunction; for (unsigned redeclaration = 0; redeclaration < 2; ++redeclaration) { - const bool atStopRedeclaration = - atStopFunction && (redeclaration != 0) == stop.redeclaration; + const bool atStopRedeclaration = atStopFunction && (redeclaration != 0) == stop.redeclaration; const unsigned lastCount = atStopRedeclaration ? stop.clipCount : 8u; for (unsigned clipCount = 1; clipCount <= lastCount; ++clipCount) { std::string log; @@ -239,14 +240,12 @@ void main() glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); - glBufferData(GL_ARRAY_BUFFER, - static_cast(sizeof(float) * 4 * vertexCount), + glBufferData(GL_ARRAY_BUFFER, static_cast(sizeof(float) * 4 * vertexCount), kPositions[primitiveIndex], GL_STATIC_DRAW); const GLint location = glGetAttribLocation(program, "position"); if (location >= 0) { glEnableVertexAttribArray(static_cast(location)); - glVertexAttribPointer(static_cast(location), 4, GL_FLOAT, GL_FALSE, 0, - nullptr); + glVertexAttribPointer(static_cast(location), 4, GL_FLOAT, GL_FALSE, 0, nullptr); } glDrawArrays(primitiveType, 0, vertexCount); @@ -324,8 +323,16 @@ void main (void) // The skip_components capture layout, verbatim. std::vector SkipComponentsVaryings() { - return {"gl_SkipComponents1", "value1", "gl_SkipComponents2", "gl_SkipComponents1", "value2", - "gl_SkipComponents3", "gl_SkipComponents2", "value3", "gl_SkipComponents4", "value4"}; + return {"gl_SkipComponents1", + "value1", + "gl_SkipComponents2", + "gl_SkipComponents1", + "value2", + "gl_SkipComponents3", + "gl_SkipComponents2", + "value3", + "gl_SkipComponents4", + "value4"}; } constexpr unsigned kSkipComponentCount = 4 * 4 + (1 + 2 + 3 + 4 + 1 + 2); // 16 values + 13 skipped @@ -389,8 +396,8 @@ void main (void) glUseProgram(program); const std::vector vertices = { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, - 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, + 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, }; GLuint vao = 0; @@ -435,8 +442,8 @@ void main (void) // The streaming idiom: orphan the store, then upload only the part the // application cares about keeping. glBufferData(GL_ARRAY_BUFFER, storeSize, nullptr, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, 0, - static_cast(sizeof(float) * kOrphanStagedFloats), prefill.data()); + glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast(sizeof(float) * kOrphanStagedFloats), + prefill.data()); break; case CaptureStoreShape::OrphanedWhole: glBufferData(GL_ARRAY_BUFFER, storeSize, nullptr, GL_STATIC_DRAW); @@ -476,8 +483,8 @@ void main (void) // pre-fill, the 16 captured ones carry |vertex| * n. std::vector SkipComponentsExpected() { const std::vector vertices = { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, - 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, + 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, }; const unsigned floatCount = kSkipVertexCount * kSkipComponentCount; std::vector expected(floatCount); @@ -524,10 +531,11 @@ void main (void) } for (std::size_t i = 0; i < expected.size(); ++i) { if (std::fabs(captured[i] - expected[i]) > 0.0125f) { - return ::testing::AssertionFailure() - << "capture mismatch at index " << i << ": got " << captured[i] << ", expected " - << expected[i] << (anyWritten ? "" : " (the whole buffer is still the pre-fill: " - "NOTHING was captured)"); + return ::testing::AssertionFailure() << "capture mismatch at index " << i << ": got " << captured[i] + << ", expected " << expected[i] + << (anyWritten ? "" + : " (the whole buffer is still the pre-fill: " + "NOTHING was captured)"); } } return ::testing::AssertionSuccess(); @@ -545,15 +553,14 @@ void main (void) // wrong number, it is the pre-fill surviving where a varying belongs - the capture target // silently dropped and the buffer left holding its pre-draw bytes under GL_NO_ERROR. ::testing::AssertionResult CheckSkipComponentsOrphaned(const std::vector& captured, - unsigned stagedFloats) { + unsigned stagedFloats) { const std::vector expected = SkipComponentsExpected(); if (captured.size() != expected.size()) { return ::testing::AssertionFailure() << "readback size " << captured.size() << " != " << expected.size(); } for (std::size_t i = 0; i < expected.size(); ++i) { - const bool isCaptured = - SkipComponentsIndexIsCaptured(static_cast(i) % kSkipComponentCount); + const bool isCaptured = SkipComponentsIndexIsCaptured(static_cast(i) % kSkipComponentCount); if (i >= stagedFloats && !isCaptured) continue; if (std::fabs(captured[i] - expected[i]) > 0.0125f) { ::testing::AssertionResult failure = ::testing::AssertionFailure(); @@ -581,14 +588,14 @@ void main (void) // whole store, which is what the writeback crosses. void RunPlainCaptureIntoLargeRange(std::vector& outCaptured, std::string* buildLog) { outCaptured.clear(); - const GLuint program = BuildProgram(kXfbVertexSource, kXfbFragmentSource, {"value1"}, - GL_INTERLEAVED_ATTRIBS, buildLog); + const GLuint program = + BuildProgram(kXfbVertexSource, kXfbFragmentSource, {"value1"}, GL_INTERLEAVED_ATTRIBS, buildLog); ASSERT_NE(program, 0u) << "plain capture program failed to link: " << (buildLog ? *buildLog : ""); glUseProgram(program); const std::vector vertices = { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, - 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, + 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, }; GLuint vao = 0; @@ -608,7 +615,8 @@ void main (void) const unsigned capturedFloats = kSkipVertexCount * 4; const unsigned storeFloats = static_cast(kLargeCaptureRangeBytes / sizeof(float)); std::vector prefill(storeFloats); - for (unsigned i = 0; i < storeFloats; ++i) prefill[i] = -1.0f - static_cast(i); + for (unsigned i = 0; i < storeFloats; ++i) + prefill[i] = -1.0f - static_cast(i); GLuint captureBuffer = 0; glGenBuffers(1, &captureBuffer); @@ -646,8 +654,8 @@ void main (void) // value1 == |vertex| * 1, one tight vec4 record per vertex. ::testing::AssertionResult CheckPlainCapture(const std::vector& captured) { const std::vector vertices = { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, - 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -2.0f, 1.0f, -1.0f, 1.0f, -3.0f, 1.0f, + 1.0f, 1.0f, 4.0f, 1.0f, -1.0f, 1.0f, 5.0f, 1.0f, 1.0f, -1.0f, 6.0f, 1.0f, }; if (captured.size() != vertices.size()) { return ::testing::AssertionFailure() diff --git a/MobileGL/MG_IntegrationTest/Scenarios/XfbCaptureBufferReuseScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/XfbCaptureBufferReuseScenario.cpp index 7d44c3fb..1ba7d443 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/XfbCaptureBufferReuseScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/XfbCaptureBufferReuseScenario.cpp @@ -260,8 +260,8 @@ void main() { RunSpan(); std::vector readback(floats, kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(floats * sizeof(float)), readback.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(floats * sizeof(float)), + readback.data()); EXPECT_TRUE(CapturedIs(readback.data(), value)) << "span " << span; // The bytes past the one point the draw produced must still be the // poison the respecification put there, not whatever the previous diff --git a/MobileGL/MG_IntegrationTest/Scenarios/XfbPrimitiveQueryScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/XfbPrimitiveQueryScenario.cpp index 134d6419..a0251188 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/XfbPrimitiveQueryScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/XfbPrimitiveQueryScenario.cpp @@ -153,9 +153,8 @@ void main() { glGenBuffers(1, &buffer); glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer); const std::vector poison(vertexCapacity * kFloatsPerVertex, kPoison); - glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, - static_cast(vertexCapacity * kBytesPerVertex), poison.data(), - GL_DYNAMIC_DRAW); + glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, static_cast(vertexCapacity * kBytesPerVertex), + poison.data(), GL_DYNAMIC_DRAW); return buffer; } @@ -223,11 +222,11 @@ void main() { EXPECT_EQ(generated, 4u); std::vector readback(kDrawnPoints * kFloatsPerVertex, kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(kDrawnPoints * kBytesPerVertex), readback.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(kDrawnPoints * kBytesPerVertex), + readback.data()); for (int point = 0; point < kDrawnPoints; ++point) { - EXPECT_TRUE(CapturedVertexIs(readback.data() + static_cast(point) * kFloatsPerVertex, - point)); + EXPECT_TRUE( + CapturedVertexIs(readback.data() + static_cast(point) * kFloatsPerVertex, point)); } glDeleteBuffers(1, &captureBuffer); @@ -256,8 +255,8 @@ void main() { glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(kCapacityVertices * kBytesPerVertex), readback.data()); for (int point = 0; point < static_cast(kCapacityVertices); ++point) { - EXPECT_TRUE(CapturedVertexIs(readback.data() + static_cast(point) * kFloatsPerVertex, - point)); + EXPECT_TRUE( + CapturedVertexIs(readback.data() + static_cast(point) * kFloatsPerVertex, point)); } glDeleteBuffers(1, &captureBuffer); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/XfbRepeatedCaptureScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/XfbRepeatedCaptureScenario.cpp index 88c27b0f..e94c321d 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/XfbRepeatedCaptureScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/XfbRepeatedCaptureScenario.cpp @@ -181,8 +181,7 @@ void main() static constexpr int kInputVertices = 4; static void DrainErrors() { - for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && glGetError() != GL_NO_ERROR; ++i) {} } static bool BackendHostsGeometry() { @@ -234,8 +233,7 @@ void main() return buffer.data(); } - GLuint BuildCaptureProgram(const std::vector>& stages, - const char* varying) { + GLuint BuildCaptureProgram(const std::vector>& stages, const char* varying) { return BuildCaptureProgram(stages, std::vector{varying}); } @@ -308,8 +306,8 @@ void main() glDisable(GL_RASTERIZER_DISCARD); std::vector readback(capturedInts, kPoison); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(capturedInts * sizeof(int)), readback.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(capturedInts * sizeof(int)), + readback.data()); glUseProgram(0); glDeleteBuffers(1, &xfbBuffer); return readback; @@ -423,9 +421,9 @@ void main() GTEST_SKIP() << "no geometry stage on " << Gl().BackendName() << " (" << Gl().RendererString() << ")"; } - const GLuint program = BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}, - {GL_GEOMETRY_SHADER, kPointAmplifyingGeometrySource}}, - "gs_out_value"); + const GLuint program = BuildCaptureProgram( + {{GL_VERTEX_SHADER, kPassthroughVertexSource}, {GL_GEOMETRY_SHADER, kPointAmplifyingGeometrySource}}, + "gs_out_value"); ASSERT_NE(program, 0u) << "fragmentless capture program failed to build: " << m_buildLog; const std::vector expected = {10, 10, 11, 11, 12, 12, 13, 13}; @@ -472,8 +470,7 @@ void main() TEST_F(XfbRepeatedCaptureScenario, AVertexOnlyAdjacencyCaptureRecords) { if (!Ready()) GTEST_SKIP(); - const GLuint program = - BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, "vs_out_value"); + const GLuint program = BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, "vs_out_value"); ASSERT_NE(program, 0u) << "vertex-only capture program failed to build: " << m_buildLog; // Four vertices of GL_LINES_ADJACENCY are one line whose real endpoints are the @@ -524,8 +521,7 @@ void main() const GLsizeiptr captureBytes = static_cast(capturedInts * sizeof(int)); // Span A: an ordinary capture, so capture point 0 is left holding bufferA. - const GLuint programA = - BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, "vs_out_value"); + const GLuint programA = BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, "vs_out_value"); ASSERT_NE(programA, 0u) << "plain capture program failed to build: " << m_buildLog; std::vector poison(capturedInts, kPoison); @@ -551,8 +547,8 @@ void main() ASSERT_TRUE(CapturedIs(afterA, spanAExpected)) << "the setup span itself did not capture"; // Span B: gl_NextBuffer first, so buffer 0 captures nothing and only point 1 is bound. - const GLuint programB = BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, - {"gl_NextBuffer", "vs_out_value"}); + const GLuint programB = + BuildCaptureProgram({{GL_VERTEX_SHADER, kPassthroughVertexSource}}, {"gl_NextBuffer", "vs_out_value"}); if (programB == 0) { GTEST_SKIP() << "gl_NextBuffer capture lists are not linkable on " << Gl().BackendName() << " (" << Gl().RendererString() << "): " << m_buildLog; @@ -596,7 +592,6 @@ void main() glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, 0); glDeleteBuffers(1, &bufferA); glDeleteBuffers(1, &bufferB); - } // The control for all of the above: a span that never draws must leave the capture @@ -631,8 +626,8 @@ void main() glUseProgram(0); std::vector readback(capturedInts, 0); - glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, - static_cast(capturedInts * sizeof(int)), readback.data()); + glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, static_cast(capturedInts * sizeof(int)), + readback.data()); EXPECT_TRUE(CapturedNothing(readback)); EXPECT_EQ(glGetError(), GL_NO_ERROR); diff --git a/MobileGL/MG_Pipe/MGPipe.h b/MobileGL/MG_Pipe/MGPipe.h index 46d9302f..840bcb8f 100644 --- a/MobileGL/MG_Pipe/MGPipe.h +++ b/MobileGL/MG_Pipe/MGPipe.h @@ -103,11 +103,11 @@ namespace MobileGL::MG_Pipe { // between bits are in SubsystemDeps.def. inline constexpr Uint64 kMGPipeSubsystemFramebuffer = 1ull << 9; // set_framebuffer_state inline constexpr Uint64 kMGPipeSubsystemTextureResources = 1ull << 10; // texture + renderbuffer - // resource_*, set_texture_params + // resource_*, set_texture_params inline constexpr Uint64 kMGPipeSubsystemSamplers = 1ull << 11; // sampler CSO, sampler view, - // the three unit sets + // the three unit sets inline constexpr Uint64 kMGPipeSubsystemPrograms = 1ull << 12; // shader CSO, draw/dispatch - // program, global constants + // program, global constants // P5e's one (docs/archive/contracts/CONTRACT-P5E.md §1). The INDEXED BUFFER BINDING POINTS: the three // dirty bits NewConstBuffers / NewShaderBuffers / NewSoTargets, the set_shader_buffers // emitter, and the backend's four binding-point walks. It is its own bit and not part of @@ -133,16 +133,14 @@ namespace MobileGL::MG_Pipe { // had and nothing of mine" control, exactly as 0x7f was P3a's. inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP4a = 0x1fffull; // bits 0..12 static_assert(kMGPipeSubsystemsMigratedAtP4a == - (kMGPipeSubsystemsMigratedAtP3a | kMGPipeSubsystemFramebuffer | - kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers | - kMGPipeSubsystemPrograms), + (kMGPipeSubsystemsMigratedAtP3a | kMGPipeSubsystemFramebuffer | kMGPipeSubsystemTextureResources | + kMGPipeSubsystemSamplers | kMGPipeSubsystemPrograms), "the P4a phase constant and P4a's four subsystem bits have drifted"); // P5e's, and P4a's is NOT edited: 0x1fff stays P5e's "everything P4a had and nothing of // mine" control, exactly as 0x1ff was P4a's and 0x7f was P3a's. The push default becomes // 0x3fff, so the A/B that reproduces today's picture is one bit cleared. inline constexpr Uint64 kMGPipeSubsystemsMigratedAtP5e = 0x3fffull; // bits 0..13 - static_assert(kMGPipeSubsystemsMigratedAtP5e == - (kMGPipeSubsystemsMigratedAtP4a | kMGPipeSubsystemBufferBindings), + static_assert(kMGPipeSubsystemsMigratedAtP5e == (kMGPipeSubsystemsMigratedAtP4a | kMGPipeSubsystemBufferBindings), "the P5e phase constant and P5e's subsystem bit have drifted"); // ---- D-K2's dependency rule, in ONE place (P3b/P4b R-5) -------------------------------- diff --git a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.cpp b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.cpp index b0e45151..9b1f9da7 100644 --- a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.cpp +++ b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.cpp @@ -22,13 +22,19 @@ namespace MobileGL::MG_Pipe { namespace { // Half-local chunk index -> global chunk index. The halves alternate, so this is // arithmetic rather than a table. - constexpr SizeT GlobalPipelineChunk(SizeT halfIndex) { return halfIndex * 2 + 1; } - constexpr SizeT GlobalDynamicChunk(SizeT halfIndex) { return halfIndex * 2; } + constexpr SizeT GlobalPipelineChunk(SizeT halfIndex) { + return halfIndex * 2 + 1; + } + constexpr SizeT GlobalDynamicChunk(SizeT halfIndex) { + return halfIndex * 2; + } const Uint8* BytesOf(const RenderStateParameters& params) { return reinterpret_cast(¶ms); } - Uint8* BytesOf(RenderStateParameters& params) { return reinterpret_cast(¶ms); } + Uint8* BytesOf(RenderStateParameters& params) { + return reinterpret_cast(¶ms); + } SizeT BlobBytes(Uint32 chunkMask, SizeT halfCount, SizeT (*toGlobal)(SizeT)) { SizeT total = 0; @@ -63,8 +69,8 @@ namespace MobileGL::MG_Pipe { } } - Uint32 ChunksThatMoved(const RenderStateParameters& a, const RenderStateParameters& b, - SizeT halfCount, SizeT (*toGlobal)(SizeT)) { + Uint32 ChunksThatMoved(const RenderStateParameters& a, const RenderStateParameters& b, SizeT halfCount, + SizeT (*toGlobal)(SizeT)) { const Uint8* left = BytesOf(a); const Uint8* right = BytesOf(b); Uint32 mask = 0; @@ -103,13 +109,11 @@ namespace MobileGL::MG_Pipe { "kMGPipeDynamicChunks lost an entry"); void MGPipeGatherPipelineBytes(const RenderStateParameters& params, void* dst) { - Gather(params, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount, - GlobalPipelineChunk); + Gather(params, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount, GlobalPipelineChunk); } void MGPipeScatterPipelineBytes(const void* src, RenderStateParameters& dst) { - Scatter(src, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount, - GlobalPipelineChunk); + Scatter(src, AllChunks(kMGPipePipelineChunkCount), dst, kMGPipePipelineChunkCount, GlobalPipelineChunk); } SizeT MGPipePipelineChunkBlobBytes(Uint32 chunkMask) { @@ -145,8 +149,7 @@ namespace MobileGL::MG_Pipe { } Uint64 MGPipeHashPipelineBytes(const void* bytes) { - return static_cast( - XXH64(bytes, kMGPipePipelineChunkBytes, kMGPipeRenderStateChunkTableSeed)); + return static_cast(XXH64(bytes, kMGPipePipelineChunkBytes, kMGPipeRenderStateChunkTableSeed)); } Uint64 MGPipeComputePipelineSubsetHash(const RenderStateParameters& params) { diff --git a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h index b32b85db..2395ab6b 100644 --- a/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h +++ b/MobileGL/MG_Pipe/MGPipeRenderStateSpans.h @@ -86,7 +86,9 @@ namespace MobileGL::MG_Pipe { // is a transition between a run of BumpVersions()-written members and a run of // ++m_version-only members, so two adjacent chunks of the same half would mean a boundary // that separates nothing. - constexpr Bool MGPipeRenderStateChunkIsPipeline(SizeT index) { return (index % 2) == 1; } + constexpr Bool MGPipeRenderStateChunkIsPipeline(SizeT index) { + return (index % 2) == 1; + } constexpr MGPStateChunk MGPipeRenderStateChunkAt(SizeT index) { return MGPStateChunk{static_cast(kMGPipeRenderStateChunkBoundaries[index]), @@ -145,8 +147,7 @@ namespace MobileGL::MG_Pipe { // ---- the trip wires. A mistake in the table is a build break, here. ---- static_assert(kMGPipeRenderStateChunkBoundaries[0] == 0, "the chunk table must start at byte 0 of RenderStateParameters"); - static_assert(kMGPipeRenderStateChunkBoundaries[kMGPipeRenderStateChunkCount] == - sizeof(RenderStateParameters), + static_assert(kMGPipeRenderStateChunkBoundaries[kMGPipeRenderStateChunkCount] == sizeof(RenderStateParameters), "the chunk table must cover RenderStateParameters to its last byte"); static_assert(kMGPipePipelineChunkCount == 7); static_assert(kMGPipeDynamicChunkCount == 8); @@ -218,16 +219,14 @@ namespace MobileGL::MG_Pipe { namespace MGPipeRenderStateChunkDetail { inline constexpr Uint32 kAllPipelineHalfBits = static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1); - inline constexpr Uint32 kAllDynamicHalfBits = - static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1); + inline constexpr Uint32 kAllDynamicHalfBits = static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1); inline constexpr Uint32 kWidenedPipeline = MGPipeGlobalChunkBitsOfPipelineMask(kAllPipelineHalfBits); inline constexpr Uint32 kWidenedDynamic = MGPipeGlobalChunkBitsOfDynamicMask(kAllDynamicHalfBits); } // namespace MGPipeRenderStateChunkDetail - static_assert((MGPipeRenderStateChunkDetail::kWidenedPipeline & - MGPipeRenderStateChunkDetail::kWidenedDynamic) == 0, + static_assert((MGPipeRenderStateChunkDetail::kWidenedPipeline & MGPipeRenderStateChunkDetail::kWidenedDynamic) == 0, "the two half-local -> global widenings must not overlap"); - static_assert((MGPipeRenderStateChunkDetail::kWidenedPipeline | - MGPipeRenderStateChunkDetail::kWidenedDynamic) == kMGPipeAllGlobalChunks, + static_assert((MGPipeRenderStateChunkDetail::kWidenedPipeline | MGPipeRenderStateChunkDetail::kWidenedDynamic) == + kMGPipeAllGlobalChunks, "the two half-local -> global widenings must cover the whole chunk table"); static_assert(MGPipeRenderStateChunkBitsCovering(0, sizeof(RenderStateParameters)) == kMGPipeAllGlobalChunks, "every chunk must be covered by the whole block"); diff --git a/MobileGL/MG_Pipe/MGPipeTypes.h b/MobileGL/MG_Pipe/MGPipeTypes.h index 6d39e590..4360486e 100644 --- a/MobileGL/MG_Pipe/MGPipeTypes.h +++ b/MobileGL/MG_Pipe/MGPipeTypes.h @@ -149,7 +149,8 @@ namespace MobileGL::MG_Pipe { // See CONTRACT-MAGMA-RUNAHEAD.md for the Vulkan resource/submission contract. inline constexpr Uint64 MGPipeRunAheadCapBitsFor(BackendType type, Bool ready) { return (ready && (type == BackendType::DirectGLES || type == BackendType::DirectVulkan)) - ? static_cast(kCapRunAheadApply) : static_cast(kCapNone); + ? static_cast(kCapRunAheadApply) + : static_cast(kCapNone); } struct MGPCaps { @@ -209,8 +210,7 @@ namespace MobileGL::MG_Pipe { // predicate at the bottom of this header now names the buffer target explicitly (D-A2) and // may not reach into MG_Impl to do it. The static_assert is what keeps the two spellings // from drifting; nothing may open-code either. - inline constexpr Uint8 kMGPipeResourceTargetBuffer = - static_cast(MGPipeResourceTarget::Buffer); + inline constexpr Uint8 kMGPipeResourceTargetBuffer = static_cast(MGPipeResourceTarget::Buffer); static_assert(kMGPipeResourceTargetBuffer == static_cast(MGPipeResourceTarget::Buffer), "P3a's kMGPipeResourceTargetBuffer and MGPipeResourceTarget::Buffer have drifted"); @@ -334,10 +334,10 @@ namespace MobileGL::MG_Pipe { Uint32 Width, Height, Depth; Uint16 ArrayLayers, Levels, Samples; Uint8 FixedSampleLocations, Immutable; - Uint32 Usage; // BufferUsage - Uint32 StorageFlags; // glBufferStorage flags - Uint8 HasDefinedContent; // false after a NULL-data respecify - STORAGE-DEFINING - Uint8 ImageBindableHint; // client-side everImageBound; pre-emptive allocation + Uint32 Usage; // BufferUsage + Uint32 StorageFlags; // glBufferStorage flags + Uint8 HasDefinedContent; // false after a NULL-data respecify - STORAGE-DEFINING + Uint8 ImageBindableHint; // client-side everImageBound; pre-emptive allocation // The scope of this respecify. Zero: it redefines the whole resource and the applier // drops every pending upload. Non-zero: it redefines only the (RespecifiedUploadTarget, // RespecifiedLevel) pair below and drops only that level's uploads, so a per-level @@ -425,9 +425,8 @@ namespace MobileGL::MG_Pipe { return MGPipeRespecifyIsWholeResource(desc) || (desc.BufSize >> 32) == 0; } - inline constexpr void MGPipeSetRespecifiedLevel(MGPResourceDesc& desc, Uint16 uploadTarget, - Uint16 level, Uint32 width = 0, Uint32 height = 0, - Uint32 depth = 0) { + inline constexpr void MGPipeSetRespecifiedLevel(MGPResourceDesc& desc, Uint16 uploadTarget, Uint16 level, + Uint32 width = 0, Uint32 height = 0, Uint32 depth = 0) { desc.HasRespecifiedLevel = 1; desc.RespecifiedUploadTarget = uploadTarget; desc.RespecifiedLevel = level; @@ -437,8 +436,7 @@ namespace MobileGL::MG_Pipe { desc.BufSize = depth; } inline constexpr void MGPipeClearRespecifiedLevel(MGPResourceDesc& desc) { - if (desc.HasRespecifiedLevel != 0 && - desc.Target != static_cast(MGPipeResourceTarget::TexBuffer)) { + if (desc.HasRespecifiedLevel != 0 && desc.Target != static_cast(MGPipeResourceTarget::TexBuffer)) { desc.BufOffset = 0; desc.BufSize = 0; } @@ -581,7 +579,7 @@ namespace MobileGL::MG_Pipe { // for one value: SyncTextureParamsToBackend reads this record, SyncBuiltinSamplerToBackend // reads that CSO's SamplerParameters, and the two pushes stay two pushes. struct MGPTextureParams { - MGPipeHandle Res; // 0 + MGPipeHandle Res; // 0 // Kind SamplerCso. kMGPipeNullHandle is ILLEGAL - every texture object owns a sampler // object, so a null here is Fatal{ProtocolCorruption} rather than "no sampler". MGPipeHandle BuiltinSampler; // 8 @@ -590,12 +588,12 @@ namespace MobileGL::MG_Pipe { Uint8 DepthStencilMode; // 24 // Mirrors m_forceTextureParamsResync: the widened-channel carrier needs a swizzle // override that the frontend params version does not move for. - Uint8 ForceResync; // 25 + Uint8 ForceResync; // 25 // Mirrors m_forceSamplerResync, which had no wire spelling at all before P4a. What it // guards is not mis-filtering but an INCOMPLETE texture sampling (0,0,0,1) after a // driver re-mint, which is why it is a second bit and not folded into ForceResync. - Uint8 SamplerResync; // 26 - Uint8 Pad0; // 27 + Uint8 SamplerResync; // 26 + Uint8 Pad0; // 27 Float MinLod, MaxLod, LodBias; // 28 }; MGP_ASSERT_POD(MGPTextureParams, 40); @@ -659,8 +657,7 @@ namespace MobileGL::MG_Pipe { // ever reordered. inline constexpr Uint8 kMGPipeSurfaceKindNone = static_cast(MGPipeKind::None); inline constexpr Uint8 kMGPipeSurfaceKindTexture = static_cast(MGPipeKind::Texture); - inline constexpr Uint8 kMGPipeSurfaceKindRenderbuffer = - static_cast(MGPipeKind::Renderbuffer); + inline constexpr Uint8 kMGPipeSurfaceKindRenderbuffer = static_cast(MGPipeKind::Renderbuffer); static_assert(kMGPipeSurfaceKindNone == 0, "a zero-initialised MGPSurface must already be the empty attachment point"); @@ -669,8 +666,7 @@ namespace MobileGL::MG_Pipe { // therefore 0xFFFF in the field's Uint16 - a value no real target has, so a reader that // forgets to gate on Kind gets a nonsense target rather than a plausible wrong one. inline constexpr Uint16 kMGPipeSurfaceNoTextureTarget = 0xFFFF; - static_assert(kMGPipeSurfaceNoTextureTarget == - static_cast(MobileGL::TextureTarget::Unknown), + static_assert(kMGPipeSurfaceNoTextureTarget == static_cast(MobileGL::TextureTarget::Unknown), "kMGPipeSurfaceNoTextureTarget is TextureTarget::Unknown widened to the " "field, and MG_State moved Unknown off -1"); @@ -1215,8 +1211,7 @@ namespace MobileGL::MG_Pipe { // record's Target is exactly kMGPipeResourceTargetBuffer, whole field, upload byte and // all. TextureUploadTarget::Texture1D is 0, so the buffer case is the one place where the // packed form and a bare enumerator agree - and it has to stay that place. - static_assert(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, 0u) == - kMGPipeResourceTargetBuffer, + static_assert(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, 0u) == kMGPipeResourceTargetBuffer, "a buffer sub-data record's Target must stay exactly " "kMGPipeResourceTargetBuffer: the applier's SubDataNamesABuffer tests the " "whole field == 0"); @@ -1237,7 +1232,9 @@ namespace MobileGL::MG_Pipe { // it lands above the encodable bound, which the applier's bounds gate refuses. return static_cast(static_cast(record.UnionBox.X)); } - inline Uint64 MGPipeSubDataBufferSize(const MGPSubData& record) { return record.UnionBox.W; } + inline Uint64 MGPipeSubDataBufferSize(const MGPSubData& record) { + return record.UnionBox.W; + } // The per-record half of resource_respecify's kNeedsAck: the call flag says a record may // need an acknowledgement, and this decides. Only an immutable buffer (glBufferStorage) is a @@ -1314,7 +1311,7 @@ namespace MobileGL::MG_Pipe { // glClearNamedFramebuffer* entry points (section 4.4.4). struct MGPClear { MGPipeHandle Fbo; - Uint32 Kind; // kMGPipeClearKind*, below + Uint32 Kind; // kMGPipeClearKind*, below Int32 DrawBufferIndex; Uint32 BufferMask; // GL_COLOR_BUFFER_BIT etc. for the whole-framebuffer form Uint32 ValueClass; // kMGPipeClearValueClass*, below @@ -1766,7 +1763,6 @@ namespace MobileGL::MG_Pipe { }; MGP_ASSERT_POD(MGPSharedImageFdOffer, 32); - // set_context_values = opcode 79, P5c rv (CONTRACT-P5C.md §5.3): the rv field table as // ONE fixed-width POD. It carries every value-class field that no set_* call supplies - // the two texture-unit counters, the per-target touched-buffer-binding high-water marks @@ -1779,7 +1775,7 @@ namespace MobileGL::MG_Pipe { // deliberately carry NO field here: they are APPLIER_DERIVED, answered by the accessor // from the applier's own Serial (MG_Backend/MGPipe/PipeInputs.h). struct MGPContextValues { - Uint32 ActiveTextureUnit; // feeds GetActiveTextureUnit + Uint32 ActiveTextureUnit; // feeds GetActiveTextureUnit Uint32 MaxTouchedTextureUnit; // feeds GetMaxTouchedTextureUnit // 15 = BufferTarget::BufferTargetCount (BufferObject.h:15-33). MG_Pipe may not include // MG_State, so the pairing is asserted where both sides are visible (PipeFill.cpp). diff --git a/MobileGL/MG_Pipe/MGPipeValueTypes.h b/MobileGL/MG_Pipe/MGPipeValueTypes.h index e5138fce..c77bdbb0 100644 --- a/MobileGL/MG_Pipe/MGPipeValueTypes.h +++ b/MobileGL/MG_Pipe/MGPipeValueTypes.h @@ -425,32 +425,52 @@ namespace MobileGL { constexpr Uint8 MGPackShadingRate(GLenum rate) { switch (rate) { - case GL_SHADING_RATE_1X1_PIXELS_EXT: return 0x0u; - case GL_SHADING_RATE_1X2_PIXELS_EXT: return 0x1u; - case GL_SHADING_RATE_1X4_PIXELS_EXT: return 0x2u; - case GL_SHADING_RATE_2X1_PIXELS_EXT: return 0x4u; - case GL_SHADING_RATE_2X2_PIXELS_EXT: return 0x5u; - case GL_SHADING_RATE_2X4_PIXELS_EXT: return 0x6u; - case GL_SHADING_RATE_4X1_PIXELS_EXT: return 0x8u; - case GL_SHADING_RATE_4X2_PIXELS_EXT: return 0x9u; - case GL_SHADING_RATE_4X4_PIXELS_EXT: return 0xau; - default: return kMGInvalidShadingRateEncoding; + case GL_SHADING_RATE_1X1_PIXELS_EXT: + return 0x0u; + case GL_SHADING_RATE_1X2_PIXELS_EXT: + return 0x1u; + case GL_SHADING_RATE_1X4_PIXELS_EXT: + return 0x2u; + case GL_SHADING_RATE_2X1_PIXELS_EXT: + return 0x4u; + case GL_SHADING_RATE_2X2_PIXELS_EXT: + return 0x5u; + case GL_SHADING_RATE_2X4_PIXELS_EXT: + return 0x6u; + case GL_SHADING_RATE_4X1_PIXELS_EXT: + return 0x8u; + case GL_SHADING_RATE_4X2_PIXELS_EXT: + return 0x9u; + case GL_SHADING_RATE_4X4_PIXELS_EXT: + return 0xau; + default: + return kMGInvalidShadingRateEncoding; } } // The inverse; GL_NONE for an encoding no enum names (a 3 in either half, or > 0xa). constexpr GLenum MGUnpackShadingRate(Uint8 packed) { switch (packed) { - case 0x0u: return GL_SHADING_RATE_1X1_PIXELS_EXT; - case 0x1u: return GL_SHADING_RATE_1X2_PIXELS_EXT; - case 0x2u: return GL_SHADING_RATE_1X4_PIXELS_EXT; - case 0x4u: return GL_SHADING_RATE_2X1_PIXELS_EXT; - case 0x5u: return GL_SHADING_RATE_2X2_PIXELS_EXT; - case 0x6u: return GL_SHADING_RATE_2X4_PIXELS_EXT; - case 0x8u: return GL_SHADING_RATE_4X1_PIXELS_EXT; - case 0x9u: return GL_SHADING_RATE_4X2_PIXELS_EXT; - case 0xau: return GL_SHADING_RATE_4X4_PIXELS_EXT; - default: return GL_NONE; + case 0x0u: + return GL_SHADING_RATE_1X1_PIXELS_EXT; + case 0x1u: + return GL_SHADING_RATE_1X2_PIXELS_EXT; + case 0x2u: + return GL_SHADING_RATE_1X4_PIXELS_EXT; + case 0x4u: + return GL_SHADING_RATE_2X1_PIXELS_EXT; + case 0x5u: + return GL_SHADING_RATE_2X2_PIXELS_EXT; + case 0x6u: + return GL_SHADING_RATE_2X4_PIXELS_EXT; + case 0x8u: + return GL_SHADING_RATE_4X1_PIXELS_EXT; + case 0x9u: + return GL_SHADING_RATE_4X2_PIXELS_EXT; + case 0xau: + return GL_SHADING_RATE_4X4_PIXELS_EXT; + default: + return GL_NONE; } } @@ -630,21 +650,21 @@ namespace MobileGL { // LegacyStride / LegacyPointer are likewise absent - they are the glGetVertexAttrib* // query answers and stay client-side, because nothing but the query path reads them. struct MGPVertexAttribWire { - Uint64 Offset; // 0 - Int32 Stride; // 8 - Uint32 Type; // 12 DataType - Uint8 Size; // 16 1..4; GL_BGRA keeps 4 - Uint8 Enabled; // 17 - Uint8 Normalized; // 18 - Uint8 IsInteger; // 19 + Uint64 Offset; // 0 + Int32 Stride; // 8 + Uint32 Type; // 12 DataType + Uint8 Size; // 16 1..4; GL_BGRA keeps 4 + Uint8 Enabled; // 17 + Uint8 Normalized; // 18 + Uint8 IsInteger; // 19 // CARRIED SEPARATELY from Type == Float64, and it has to be: VertexAttribFormat( // GL_DOUBLE) also reads doubles from memory but asks for them converted to float, // while VertexAttribLFormat keeps all 64 bits. The backend's fp64 narrowing and its // Adreno disabled-attribute workaround both key on telling the two apart. - Uint8 IsLong; // 20 - Uint8 IsBgra; // 21 - Uint8 BindingIndex; // 22 which MGPVertexBuffer entry feeds it (< MAX_VERTEX_ATTRIBS) - Uint8 Pad0; // 23 + Uint8 IsLong; // 20 + Uint8 IsBgra; // 21 + Uint8 BindingIndex; // 22 which MGPVertexBuffer entry feeds it (< MAX_VERTEX_ATTRIBS) + Uint8 Pad0; // 23 }; // The ARB_vertex_attrib_binding view. Buffer identity is again in set_vertex_buffers. @@ -733,11 +753,9 @@ namespace MobileGL { // blob they ride in is memcpy'd, and a field silently changing width is a protocol break // no test would otherwise see. (MGP_ASSERT_POD is MGPipeTypes.h's and that header // includes this one, so the assertions are spelled out here instead.) - static_assert(std::is_trivially_copyable_v && - sizeof(MGPVertexAttribWire) == 24); + static_assert(std::is_trivially_copyable_v && sizeof(MGPVertexAttribWire) == 24); static_assert(std::is_standard_layout_v); - static_assert(std::is_trivially_copyable_v && - sizeof(MGPVertexBindingPointWire) == 16); + static_assert(std::is_trivially_copyable_v && sizeof(MGPVertexBindingPointWire) == 16); static_assert(std::is_standard_layout_v); } // namespace MobileGL #endif // MOBILEGL_MG_PIPE_VALUE_TYPES_H diff --git a/MobileGL/MG_Pipe/PipeApply.cpp b/MobileGL/MG_Pipe/PipeApply.cpp index c029dd5d..c5b3137d 100644 --- a/MobileGL/MG_Pipe/PipeApply.cpp +++ b/MobileGL/MG_Pipe/PipeApply.cpp @@ -174,7 +174,9 @@ namespace MobileGL::MG_Pipe { RecordDeath(family, line); } - bool MGPipeRecordLatched() { return g_recordLatchedHook != nullptr && g_recordLatchedHook(); } + bool MGPipeRecordLatched() { + return g_recordLatchedHook != nullptr && g_recordLatchedHook(); + } void MGPipeProtocolFatal(const char* what, const char* detail) { MGPipeRecordFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what, @@ -182,25 +184,38 @@ namespace MobileGL::MG_Pipe { } void MGPipeProtocolFatalAt(const char* what, Uint64 got, Uint64 expected) { - MGPipeRecordFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", - what, static_cast(got), static_cast(expected)); + MGPipeRecordFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what, + static_cast(got), static_cast(expected)); } - MGPipeRecordFailHook MGPipeRecordFailHookInstalled() { return g_recordFailHook; } - MGPipeRecordLatchHook MGPipeRecordLatchHookInstalled() { return g_recordLatchHook; } - MGPipeRecordLatchedHook MGPipeRecordLatchedHookInstalled() { return g_recordLatchedHook; } + MGPipeRecordFailHook MGPipeRecordFailHookInstalled() { + return g_recordFailHook; + } + MGPipeRecordLatchHook MGPipeRecordLatchHookInstalled() { + return g_recordLatchHook; + } + MGPipeRecordLatchedHook MGPipeRecordLatchedHookInstalled() { + return g_recordLatchedHook; + } // P13 W5: the client session's device-loss answer (PipeClientSeam.h). Constant-initialized for // the same static-init reason as the hooks above. namespace { constinit MGPipeClientDeviceLostProbe g_clientDeviceLostProbe = nullptr; } // namespace - void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe) { g_clientDeviceLostProbe = probe; } - Bool MGPipeClientDeviceLost() { return g_clientDeviceLostProbe != nullptr && g_clientDeviceLostProbe(); } + void MGPipeSetClientDeviceLostProbe(MGPipeClientDeviceLostProbe probe) { + g_clientDeviceLostProbe = probe; + } + Bool MGPipeClientDeviceLost() { + return g_clientDeviceLostProbe != nullptr && g_clientDeviceLostProbe(); + } namespace { constinit MGPipeClientLossConfirmer g_clientLossConfirmer = nullptr; } // namespace - void MGPipeSetClientLossConfirmer(MGPipeClientLossConfirmer confirmer) { g_clientLossConfirmer = confirmer; } + void MGPipeSetClientLossConfirmer(MGPipeClientLossConfirmer confirmer) { + g_clientLossConfirmer = confirmer; + } Bool MGPipeClientConfirmLossAfterDecline(Uint32 waitMs) { return g_clientLossConfirmer != nullptr && g_clientLossConfirmer(waitMs); } @@ -219,9 +234,13 @@ namespace MobileGL::MG_Pipe { MGPipeSessionFailHook g_sessionFailHook = nullptr; } // namespace - void MGPipeInstallSessionFailHook(MGPipeSessionFailHook hook) { g_sessionFailHook = hook; } + void MGPipeInstallSessionFailHook(MGPipeSessionFailHook hook) { + g_sessionFailHook = hook; + } - MGPipeSessionFailHook MGPipeSessionFailHookInstalled() { return g_sessionFailHook; } + MGPipeSessionFailHook MGPipeSessionFailHookInstalled() { + return g_sessionFailHook; + } void MGPipeSessionFail(MGPipeFatalFamily family, const char* fmt, ...) { // The same 512-byte buffer and the same fallback SessionFail and WireLogFatal use, so a @@ -232,8 +251,7 @@ namespace MobileGL::MG_Pipe { const int written = std::vsnprintf(line, sizeof(line), fmt, args); va_end(args); if (written < 0) { - std::snprintf(line, sizeof(line), - "MGPipe: unformattable Fatal diagnostic (format=%s)", fmt); + std::snprintf(line, sizeof(line), "MGPipe: unformattable Fatal diagnostic (format=%s)", fmt); } if (g_sessionFailHook != nullptr) g_sessionFailHook(family, line); // NO HOOK: exactly what the three funnels did before this seam existed - the line, then @@ -245,7 +263,6 @@ namespace MobileGL::MG_Pipe { std::abort(); } - namespace { MGPipeSessionLatchHook g_sessionLatchHook = nullptr; MGPipeSessionLatchArmedHook g_sessionLatchArmedHook = nullptr; @@ -256,10 +273,13 @@ namespace MobileGL::MG_Pipe { g_sessionLatchArmedHook = armed; } - MGPipeSessionLatchHook MGPipeSessionLatchHookInstalled() { return g_sessionLatchHook; } - - bool MGPipeSessionLatchArmed() { return g_sessionLatchArmedHook != nullptr && g_sessionLatchArmedHook(); } + MGPipeSessionLatchHook MGPipeSessionLatchHookInstalled() { + return g_sessionLatchHook; + } + bool MGPipeSessionLatchArmed() { + return g_sessionLatchArmedHook != nullptr && g_sessionLatchArmedHook(); + } #if MOBILEGL_BUILD_DISAGGREGATED bool MGPipeDebugDeviceLossDue() { @@ -348,8 +368,7 @@ namespace MobileGL::MG_Pipe { const int written = std::vsnprintf(line, sizeof(line), fmt, args); va_end(args); if (written < 0) { - std::snprintf(line, sizeof(line), - "MGPipe: unformattable Fatal diagnostic (format=%s)", fmt); + std::snprintf(line, sizeof(line), "MGPipe: unformattable Fatal diagnostic (format=%s)", fmt); } if (g_sessionLatchHook != nullptr) return g_sessionLatchHook(family, line); // NO HOOK: no session to latch, so the death the fail seam would have given. @@ -384,8 +403,7 @@ namespace MobileGL::MG_Pipe { // DeriveCapability's sources: the 25 plain bools, plus the three masks/arrays the // three special arms read. #define MGP_CAPABILITY_CHUNKS(capability) | MGP_CHUNKS_OF(capability##Enabled) - constexpr Uint32 kChunksCapabilityWalk = MGP_CHUNKS_OF(BlendStates) | - MGP_CHUNKS_OF(ScissorTestEnabledMask) | + constexpr Uint32 kChunksCapabilityWalk = MGP_CHUNKS_OF(BlendStates) | MGP_CHUNKS_OF(ScissorTestEnabledMask) | MGP_CHUNKS_OF(ClipDistanceEnabledMask) MGP_PLAIN_CAPABILITY_LIST(MGP_CAPABILITY_CHUNKS); #undef MGP_CAPABILITY_CHUNKS @@ -393,8 +411,7 @@ namespace MobileGL::MG_Pipe { // The patch trio's chunk, which is what arms the set_patch_state trip wire: the // question that wire asks is whether the chunk-P0 bytes in the working block are the // APPLIER'S, and only a scatter puts them there. - constexpr Uint32 kChunksPatchTrio = MGP_CHUNKS_OF(PatchVertices) | - MGP_CHUNKS_OF(PatchDefaultOuterLevel) | + constexpr Uint32 kChunksPatchTrio = MGP_CHUNKS_OF(PatchVertices) | MGP_CHUNKS_OF(PatchDefaultOuterLevel) | MGP_CHUNKS_OF(PatchDefaultInnerLevel); // Which chunks ONE capability's answer is read out of - the same sources @@ -458,8 +475,7 @@ namespace MobileGL::MG_Pipe { static void SetRenderStateParametersVersion(PipeInputs& inputs, Uint parameters) { inputs.m_renderStateParametersVersion = parameters; } - static void SetPatchState(PipeInputs& inputs, Uint vertices, const FloatVec4& outer, - const FloatVec2& inner) { + static void SetPatchState(PipeInputs& inputs, Uint vertices, const FloatVec4& outer, const FloatVec2& inner) { inputs.m_patchVertices = vertices; inputs.m_patchDefaultOuterLevel = outer; inputs.m_patchDefaultInnerLevel = inner; @@ -472,8 +488,7 @@ namespace MobileGL::MG_Pipe { inputs.m_activeTextureUnit = static_cast(values.ActiveTextureUnit); inputs.m_maxTouchedTextureUnit = static_cast(values.MaxTouchedTextureUnit); for (SizeT i = 0; i < PipeInputs::kBufferTargetCount; ++i) { - inputs.m_touchedBindingPointCount[i] = - static_cast(values.TouchedBufferBindingPointCount[i]); + inputs.m_touchedBindingPointCount[i] = static_cast(values.TouchedBufferBindingPointCount[i]); } inputs.m_transformFeedbackActive = values.IsTransformFeedbackActive != 0; inputs.m_transformFeedbackPaused = values.IsTransformFeedbackPaused != 0; @@ -777,7 +792,7 @@ namespace MobileGL::MG_Pipe { namespace { std::atomic g_compileEnvChangedHook{nullptr}; - } + } // namespace void MGPipeSetCompileEnvChangedHook(MGPipeCompileEnvChangedHook hook) { g_compileEnvChangedHook.store(hook, std::memory_order_release); @@ -905,8 +920,7 @@ namespace MobileGL::MG_Pipe { return &record; } - constexpr Uint32 kAllPipelineChunks = - static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1); + constexpr Uint32 kAllPipelineChunks = static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1); // ---------------------------------------------------------------------------- // P3a: resolving a handle, growing a slot table, and the bounds gate. @@ -1033,8 +1047,7 @@ namespace MobileGL::MG_Pipe { if (stored.Target != next.Target) return false; if (stored.StorageKind != next.StorageKind) return false; if (stored.InternalFormat != next.InternalFormat) return false; - if (stored.Width != next.Width || stored.Height != next.Height || - stored.Depth != next.Depth) { + if (stored.Width != next.Width || stored.Height != next.Height || stored.Depth != next.Depth) { return false; } if (stored.ArrayLayers != next.ArrayLayers || stored.Levels != next.Levels || @@ -1080,7 +1093,7 @@ namespace MobileGL::MG_Pipe { // buffer's do; the object counter beside it is for the five families that have no // resource call at all (framebuffer, sampler, sampler view, program, texture params). MGPipeResourceRecord* ResolveResourceIn(Vector& table, const char* call, - MGPipeHandle res) { + MGPipeHandle res) { MGPipeResourceRecord* record = FindIn(table, res); MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, res.Slot, res.Gen, kResourceRefusalNote); @@ -1095,8 +1108,8 @@ namespace MobileGL::MG_Pipe { template Record* ResolveObject(Vector& table, const char* call, MGPipeHandle handle) { Record* record = FindIn(table, handle); - MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, handle.Slot, - handle.Gen, kResourceRefusalNote); + MOBILEGL_ASSERT(record != nullptr, "%s named {slot=%u, gen=%u}: %s", call, handle.Slot, handle.Gen, + kResourceRefusalNote); if (record == nullptr) ++MGPipeApplier().RefusedObjectCalls; return record; } @@ -1160,7 +1173,9 @@ namespace MobileGL::MG_Pipe { // A box is EMPTY when any extent is zero, and an empty box unions to nothing. Written // once because the accumulation below needs it in three places. - Bool BoxIsEmpty(const MGPBox& box) { return box.W == 0 || box.H == 0 || box.D == 0; } + Bool BoxIsEmpty(const MGPBox& box) { + return box.W == 0 || box.H == 0 || box.D == 0; + } // A box stays inside the range one record can encode: the origin is a signed Int32 and // the extent an unsigned Uint32, so an origin plus an extent that leaves the positive @@ -1172,8 +1187,7 @@ namespace MobileGL::MG_Pipe { } Bool BoxIsEncodable(const MGPBox& box) { - return BoxIsEncodable(box.X, box.W) && BoxIsEncodable(box.Y, box.H) && - BoxIsEncodable(box.Z, box.D); + return BoxIsEncodable(box.X, box.W) && BoxIsEncodable(box.Y, box.H) && BoxIsEncodable(box.Z, box.D); } Bool AxisContains(Int32 outerOrigin, Uint32 outerExtent, Int32 innerOrigin, Uint32 innerExtent) { @@ -1237,8 +1251,8 @@ namespace MobileGL::MG_Pipe { const Int64 xEnd = std::max(static_cast(a.X) + a.W, static_cast(b.X) + b.W); const Int64 yEnd = std::max(static_cast(a.Y) + a.H, static_cast(b.Y) + b.H); const Int64 zEnd = std::max(static_cast(a.Z) + a.D, static_cast(b.Z) + b.D); - return MGPBox{x, y, z, static_cast(xEnd - x), static_cast(yEnd - y), - static_cast(zEnd - z)}; + return MGPBox{ + x, y, z, static_cast(xEnd - x), static_cast(yEnd - y), static_cast(zEnd - z)}; } // The server-side pending-upload set (ARCHITECTURE.md §6.1). The client clears a level's @@ -1300,30 +1314,27 @@ namespace MobileGL::MG_Pipe { // the wrong key; both show up only as missing pixels. One-directional: in-process emission // (MG_Impl/Pipe/TextureEmit.h) leaves the carrier clear and states the scope in the // pointer alone, which is legal. - void PinRespecifyScopeCoversItsDeclaration(const MGPResourceDesc& desc, - const MGPRespecifiedLevel* level, MGPipeHandle res, - const char* call) { + void PinRespecifyScopeCoversItsDeclaration(const MGPResourceDesc& desc, const MGPRespecifiedLevel* level, + MGPipeHandle res, const char* call) { if (MGPipeRespecifyIsWholeResource(desc)) return; const Uint16 declaredTarget = MGPipeRespecifiedUploadTargetOf(desc); const Uint16 declaredLevel = MGPipeRespecifiedLevelOf(desc); - if (level != nullptr && level->UploadTarget == declaredTarget && - level->Level == declaredLevel) { + if (level != nullptr && level->UploadTarget == declaredTarget && level->Level == declaredLevel) { return; } - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeRespecifyScope") - " %s {slot=%u, gen=%u}: the descriptor declares a per-level respecify " - "scope (target=%u, level=%u) and this call covers %s(target=%u, " - "level=%u), so a per-level producer is not covering the range it " - "declares", - call, res.Slot, res.Gen, static_cast(declaredTarget), - static_cast(declaredLevel), - level == nullptr ? "NOTHING " : "", - level == nullptr ? 0u : static_cast(level->UploadTarget), - level == nullptr ? 0u : static_cast(level->Level)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeRespecifyScope") " %s {slot=%u, gen=%u}: the descriptor declares a per-level respecify " + "scope (target=%u, level=%u) and this call covers %s(target=%u, " + "level=%u), so a per-level producer is not covering the range it " + "declares", + call, res.Slot, res.Gen, static_cast(declaredTarget), static_cast(declaredLevel), + level == nullptr ? "NOTHING " : "", level == nullptr ? 0u : static_cast(level->UploadTarget), + level == nullptr ? 0u : static_cast(level->Level)); } #else - void PinRespecifyScopeCoversItsDeclaration(const MGPResourceDesc&, const MGPRespecifiedLevel*, - MGPipeHandle, const char*) {} + void PinRespecifyScopeCoversItsDeclaration(const MGPResourceDesc&, const MGPRespecifiedLevel*, MGPipeHandle, + const char*) {} #endif #if MOBILEGL_PIPE_VERIFY @@ -1342,11 +1353,12 @@ namespace MobileGL::MG_Pipe { // can actually be non-zero. void PinLiveHostWritesNamesABuffer(const MGPSubData& record, const char* call) { if (record.HasLiveHostWrites == 0) return; - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeLiveHostWritesTarget") - " %s {slot=%u, gen=%u}: a record whose resource target is not a " - "buffer (target=%u) declares live host writes, and only the buffer " - "family has any", - call, record.Res.Slot, record.Res.Gen, record.Target); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeLiveHostWritesTarget") " %s {slot=%u, gen=%u}: a record whose resource target is not a " + "buffer (target=%u) declares live host writes, and only the buffer " + "family has any", + call, record.Res.Slot, record.Res.Gen, record.Target); } #else void PinLiveHostWritesNamesABuffer(const MGPSubData&, const char*) {} @@ -1372,12 +1384,12 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty write carries no bytes"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " %s {slot=%u, gen=%u, glName=%u}: %s (offset=%llu, size=%llu, " - "storage=%u bytes)", - call, record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, - fault, static_cast(offset), - static_cast(size), stored->Desc.Width); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " %s {slot=%u, gen=%u, glName=%u}: %s (offset=%llu, size=%llu, " + "storage=%u bytes)", + call, record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, fault, + static_cast(offset), static_cast(size), stored->Desc.Width); return false; } PinNoLiveHostWrites(*stored, record.Res, call); @@ -1419,7 +1431,7 @@ namespace MobileGL::MG_Pipe { // Their staged bytes still belong to the server before SEG_STAGE retires. Keep the // existing backend hooks authoritative when present, and use this fallback otherwise. void AdoptTextureWithoutBackendHook(const MGPipeResourceRecord& stored, const MGPSubData& upload, - const void* bytes, const MGPSubRegion* regions) { + const void* bytes, const MGPSubRegion* regions) { if (!bytes || upload.Blob.Size == 0) return; auto& store = MG_Record::ServerStagedTexture(); const auto& desc = stored.Desc; @@ -1433,13 +1445,12 @@ namespace MobileGL::MG_Pipe { // A client record's run: on monolith's record arm it is the frontend level shadow, which // an aliasing store keeps by address (P13 W4). if (upload.RegionCount == 0) { - store.Adopt(key, target, upload.Level, extent, bytes, - static_cast(upload.Blob.Size), /*frontendShadow=*/true); + store.Adopt(key, target, upload.Level, extent, bytes, static_cast(upload.Blob.Size), + /*frontendShadow=*/true); return; } - store.AdoptRun(key, target, upload.Level, extent, - MG_Record::StagedTextureRunImageOffset(upload, regions), bytes, - static_cast(upload.Blob.Size), /*frontendShadow=*/true); + store.AdoptRun(key, target, upload.Level, extent, MG_Record::StagedTextureRunImageOffset(upload, regions), + bytes, static_cast(upload.Blob.Size), /*frontendShadow=*/true); } void DefineTextureWithoutBackendHook(const MGPResourceDesc& desc, const MGPRespecifiedLevel* level) { @@ -1459,26 +1470,46 @@ namespace MobileGL::MG_Pipe { const auto defineChain = [&](TextureUploadTarget target) { for (Uint32 mip = 0; mip < desc.Levels; ++mip) define(static_cast(target), static_cast(mip), - MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, - desc.Height, desc.Depth, mip)); + MG_Record::StagedTextureMipExtent(desc.Target, desc.Width, desc.Height, desc.Depth, mip)); }; switch (static_cast(desc.Target)) { - case MGPipeResourceTarget::Tex1D: defineChain(TextureUploadTarget::Texture1D); break; - case MGPipeResourceTarget::Tex2D: defineChain(TextureUploadTarget::Texture2D); break; - case MGPipeResourceTarget::Tex3D: defineChain(TextureUploadTarget::Texture3D); break; - case MGPipeResourceTarget::Tex1DArray: defineChain(TextureUploadTarget::Texture1DArray); break; - case MGPipeResourceTarget::Tex2DArray: defineChain(TextureUploadTarget::Texture2DArray); break; + case MGPipeResourceTarget::Tex1D: + defineChain(TextureUploadTarget::Texture1D); + break; + case MGPipeResourceTarget::Tex2D: + defineChain(TextureUploadTarget::Texture2D); + break; + case MGPipeResourceTarget::Tex3D: + defineChain(TextureUploadTarget::Texture3D); + break; + case MGPipeResourceTarget::Tex1DArray: + defineChain(TextureUploadTarget::Texture1DArray); + break; + case MGPipeResourceTarget::Tex2DArray: + defineChain(TextureUploadTarget::Texture2DArray); + break; case MGPipeResourceTarget::TexCube: for (Uint32 face = static_cast(TextureUploadTarget::CubeMapPositiveX); face <= static_cast(TextureUploadTarget::CubeMapNegativeZ); ++face) defineChain(static_cast(face)); break; - case MGPipeResourceTarget::TexCubeArray: defineChain(TextureUploadTarget::CubeMapArray); break; - case MGPipeResourceTarget::Tex2DMS: defineChain(TextureUploadTarget::Texture2DMultisample); break; - case MGPipeResourceTarget::Tex2DMSArray: defineChain(TextureUploadTarget::Texture2DMultisampleArray); break; - case MGPipeResourceTarget::TexRect: defineChain(TextureUploadTarget::TextureRectangle); break; - case MGPipeResourceTarget::TexBuffer: defineChain(TextureUploadTarget::TextureBuffer); break; - default: break; + case MGPipeResourceTarget::TexCubeArray: + defineChain(TextureUploadTarget::CubeMapArray); + break; + case MGPipeResourceTarget::Tex2DMS: + defineChain(TextureUploadTarget::Texture2DMultisample); + break; + case MGPipeResourceTarget::Tex2DMSArray: + defineChain(TextureUploadTarget::Texture2DMultisampleArray); + break; + case MGPipeResourceTarget::TexRect: + defineChain(TextureUploadTarget::TextureRectangle); + break; + case MGPipeResourceTarget::TexBuffer: + defineChain(TextureUploadTarget::TextureBuffer); + break; + default: + break; } } @@ -1505,22 +1536,22 @@ namespace MobileGL::MG_Pipe { fault = "a texture upload carries no bytes"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_subdata {slot=%u, gen=%u, glName=%u}: %s (target=%u, " - "level=%u, box=%d,%d,%d %ux%ux%u, regions=%u)", - record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, fault, - record.Target, record.Level, record.UnionBox.X, record.UnionBox.Y, - record.UnionBox.Z, record.UnionBox.W, record.UnionBox.H, - record.UnionBox.D, record.RegionCount); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_subdata {slot=%u, gen=%u, glName=%u}: %s (target=%u, " + "level=%u, box=%d,%d,%d %ux%ux%u, regions=%u)", + record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, fault, record.Target, record.Level, + record.UnionBox.X, record.UnionBox.Y, record.UnionBox.Z, record.UnionBox.W, record.UnionBox.H, + record.UnionBox.D, record.RegionCount); return false; } if (!AccumulatePendingUpload(*stored, record, regions)) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_subdata {slot=%u, gen=%u, glName=%u}: the resource names " - "more distinct (upload target, level) pairs than any texture can " - "have (%u)", - record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, - kMGPipeMaxPendingUploads); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_subdata {slot=%u, gen=%u, glName=%u}: the resource names " + "more distinct (upload target, level) pairs than any texture can " + "have (%u)", + record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, kMGPipeMaxPendingUploads); return false; } // The serial moves for the buffer half's reason: the twin stamps its own synced @@ -1555,9 +1586,8 @@ namespace MobileGL::MG_Pipe { // checked; the destination is start + i and the field is only the client's label, as // with MGPVertexBuffer::BindingIndex. template - Bool ApplyUnitWindow(const char* call, Uint32 start, Uint32 count, Uint64 contentHash, - const Entry* tail, ArrayT& destination, Uint32& destinationStart, - Uint32& destinationCount) { + Bool ApplyUnitWindow(const char* call, Uint32 start, Uint32 count, Uint64 contentHash, const Entry* tail, + ArrayT& destination, Uint32& destinationStart, Uint32& destinationCount) { const Uint64 capacity = destination.size(); const Uint64 end = Uint64{start} + Uint64{count}; const char* fault = nullptr; @@ -1567,11 +1597,12 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty set carries no entries"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " %s {start=%u, count=%u, hash=%llu}: %s (the applier holds %llu " - "entries)", - call, start, count, static_cast(contentHash), - fault, static_cast(capacity)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " %s {start=%u, count=%u, hash=%llu}: %s (the applier holds %llu " + "entries)", + call, start, count, static_cast(contentHash), fault, + static_cast(capacity)); return false; } for (Uint32 i = 0; i < count; ++i) { @@ -1632,8 +1663,7 @@ namespace MobileGL::MG_Pipe { // skips in BOTH directions round-trips perfectly, which is why the unit suite reads the // members back explicitly; what THIS adds is that it runs over every real program the // verify lane links rather than over one hand-built instance. - void PinProgramArchiveRoundTrip(const MGPProgramDesc& desc, - const MG_State::GLState::LinkArtifacts& link, + void PinProgramArchiveRoundTrip(const MGPProgramDesc& desc, const MG_State::GLState::LinkArtifacts& link, const MG_State::GLState::SpirvArtifacts& spirv) { Vector encoded; MG_State::GLState::EncodeProgramArtifacts(link, spirv, encoded); @@ -1643,8 +1673,8 @@ namespace MobileGL::MG_Pipe { const char* fault = nullptr; if (encoded.empty()) { fault = "the encoder produced no bytes for a program that has artefacts"; - } else if (!MG_State::GLState::DecodeProgramArtifacts(encoded.data(), encoded.size(), - decodedLink, decodedSpirv)) { + } else if (!MG_State::GLState::DecodeProgramArtifacts(encoded.data(), encoded.size(), decodedLink, + decodedSpirv)) { fault = "the archive this client would put on the wire does not decode"; } else { MG_State::GLState::EncodeProgramArtifacts(decodedLink, decodedSpirv, reencoded); @@ -1658,12 +1688,12 @@ namespace MobileGL::MG_Pipe { } } if (fault == nullptr) return; - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeVerifyDiffer") - " program-archive create_shader_state {slot=%u, gen=%u}: %s (%llu bytes " - "encoded, %llu re-encoded)", - desc.Cso.Slot, desc.Cso.Gen, fault, - static_cast(encoded.size()), - static_cast(reencoded.size())); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeVerifyDiffer") " program-archive create_shader_state {slot=%u, gen=%u}: %s (%llu bytes " + "encoded, %llu re-encoded)", + desc.Cso.Slot, desc.Cso.Gen, fault, static_cast(encoded.size()), + static_cast(reencoded.size())); } #endif } // namespace @@ -1673,12 +1703,22 @@ namespace MobileGL::MG_Pipe { // P5c (rv, CONTRACT-P5C.md §5.3): the three texture shutters' server-side answers. See // MGPipeApplierState::TextureShutterSerial / ContextSerial for the bump rule. - Uint64 MGPipeApplierTextureShutterSerial() { return MGPipeApplier().TextureShutterSerial; } - Uint64 MGPipeApplierContextSerial() { return MGPipeApplier().ContextSerial; } - void MGPipeApplierNoteTextureStateMoved() { ++MGPipeApplier().TextureShutterSerial; } + Uint64 MGPipeApplierTextureShutterSerial() { + return MGPipeApplier().TextureShutterSerial; + } + Uint64 MGPipeApplierContextSerial() { + return MGPipeApplier().ContextSerial; + } + void MGPipeApplierNoteTextureStateMoved() { + ++MGPipeApplier().TextureShutterSerial; + } - void MGPipeSetResourceOps(const MGPipeResourceOps* ops) { g_resourceOps = ops; } - const MGPipeResourceOps* MGPipeGetResourceOps() { return g_resourceOps; } + void MGPipeSetResourceOps(const MGPipeResourceOps* ops) { + g_resourceOps = ops; + } + const MGPipeResourceOps* MGPipeGetResourceOps() { + return g_resourceOps; + } namespace { // True (and counted in MGPipeApplierState::RefusedNoConsumer) when nothing in this @@ -1693,8 +1733,9 @@ namespace MobileGL::MG_Pipe { #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { const auto* session = MG_Remote::Server::ServerSession::Active(); - if (session && session->CallMaskIsSet() && MG_Remote::MGCapsServerConsumes( - session->CallMask(), kMGPipeSubsystemTextureResources)) return false; + if (session && session->CallMaskIsSet() && + MG_Remote::MGCapsServerConsumes(session->CallMask(), kMGPipeSubsystemTextureResources)) + return false; } #endif if (g_resourceOps != nullptr) return false; @@ -1920,8 +1961,7 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplyCreateRenderState(const MGPRenderStateDesc& desc, const void* chunkBytes) { - MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, - "create_render_state named the reserved slot 0"); + MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, "create_render_state named the reserved slot 0"); MGPipeRenderStateCsoRecord* recordAt = RecordAt(MGPipeApplier().RenderStateCsos, desc.Cso.Slot, kMGPipeMaxRenderStateCsoSlots); if (recordAt == nullptr) { @@ -1931,9 +1971,10 @@ namespace MobileGL::MG_Pipe { "create_render_state {slot=%u, gen=%u} is outside the record table bound (%u)", desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxRenderStateCsoSlots); #else - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_render_state {slot=%u, gen=%u} is outside the record table bound (%u)", - desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxRenderStateCsoSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") " create_render_state {slot=%u, gen=%u} is outside " + "the record table bound (%u)", + desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxRenderStateCsoSlots); #endif return; } @@ -1951,22 +1992,22 @@ namespace MobileGL::MG_Pipe { // inherit the unnamed chunks from. record.PipelineBytes = {}; if ((desc.ChunkMask & kAllPipelineChunks) != kAllPipelineChunks) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeIncompleteCso") - " create_render_state {slot=%u, gen=%u} with no BaseCso named chunks " - "0x%x, not the whole pipeline half 0x%x; the rest is zeroed", - desc.Cso.Slot, desc.Cso.Gen, desc.ChunkMask, - static_cast(kAllPipelineChunks)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeIncompleteCso") " create_render_state {slot=%u, gen=%u} with no BaseCso named chunks " + "0x%x, not the whole pipeline half 0x%x; the rest is zeroed", + desc.Cso.Slot, desc.Cso.Gen, desc.ChunkMask, static_cast(kAllPipelineChunks)); } } else { const MGPipeRenderStateCsoRecord* base = FindCso(desc.BaseCso); - record.PipelineBytes = - base != nullptr ? base->PipelineBytes : Array{}; + record.PipelineBytes = base != nullptr ? base->PipelineBytes : Array{}; if (base == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeDeadBaseCso") - " create_render_state {slot=%u, gen=%u} named a dead BaseCso " - "{slot=%u, gen=%u}; the delta chunks land on zeroed bytes, not on " - "the recycled slot's previous occupant", - desc.Cso.Slot, desc.Cso.Gen, desc.BaseCso.Slot, desc.BaseCso.Gen); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeDeadBaseCso") " create_render_state {slot=%u, gen=%u} named a dead BaseCso " + "{slot=%u, gen=%u}; the delta chunks land on zeroed bytes, not on " + "the recycled slot's previous occupant", + desc.Cso.Slot, desc.Cso.Gen, desc.BaseCso.Slot, desc.BaseCso.Gen); } } @@ -1983,13 +2024,12 @@ namespace MobileGL::MG_Pipe { void MGPipeApplyBindRenderState(const MGPBindRenderState& bind) { const MGPipeRenderStateCsoRecord* record = FindCso(bind.Cso); - MOBILEGL_ASSERT(record != nullptr, "bind_render_state named a dead CSO {slot=%u, gen=%u}", - bind.Cso.Slot, bind.Cso.Gen); + MOBILEGL_ASSERT(record != nullptr, "bind_render_state named a dead CSO {slot=%u, gen=%u}", bind.Cso.Slot, + bind.Cso.Gen); if (record == nullptr) return; PipeInputs& inputs = gPipeInputs; - MGPipeScatterPipelineBytes(record->PipelineBytes.data(), - MGPipeApplyAccess::RenderState(inputs)); + MGPipeScatterPipelineBytes(record->PipelineBytes.data(), MGPipeApplyAccess::RenderState(inputs)); MGPipeApplyAccess::SetRenderStateVersions(inputs, bind.Version, bind.PipelineVersion); MGPipeApplier().BoundRenderStateCso = bind.Cso; // A bind scatters the WHOLE pipeline half - the record is always a complete one, @@ -2070,19 +2110,20 @@ namespace MobileGL::MG_Pipe { std::memcmp(&working.PatchDefaultInnerLevel, &inner, sizeof(inner)) == 0; if (!agrees) { ++MGPipeApplier().PatchCarrierDivergences; - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipePatchCarriersDiffer") - " set_patch_state says vertices=%u outer=(%g,%g,%g,%g) inner=(%g,%g); " - "chunk P0 delivered vertices=%u outer=(%g,%g,%g,%g) inner=(%g,%g)", - patch.Vertices, static_cast(outer.x()), - static_cast(outer.y()), static_cast(outer.z()), - static_cast(outer.w()), static_cast(inner.x()), - static_cast(inner.y()), working.PatchVertices, - static_cast(working.PatchDefaultOuterLevel.x()), - static_cast(working.PatchDefaultOuterLevel.y()), - static_cast(working.PatchDefaultOuterLevel.z()), - static_cast(working.PatchDefaultOuterLevel.w()), - static_cast(working.PatchDefaultInnerLevel.x()), - static_cast(working.PatchDefaultInnerLevel.y())); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipePatchCarriersDiffer") " set_patch_state says vertices=%u outer=(%g,%g,%g,%g) " + "inner=(%g,%g); " + "chunk P0 delivered vertices=%u outer=(%g,%g,%g,%g) inner=(%g,%g)", + patch.Vertices, static_cast(outer.x()), static_cast(outer.y()), + static_cast(outer.z()), static_cast(outer.w()), static_cast(inner.x()), + static_cast(inner.y()), working.PatchVertices, + static_cast(working.PatchDefaultOuterLevel.x()), + static_cast(working.PatchDefaultOuterLevel.y()), + static_cast(working.PatchDefaultOuterLevel.z()), + static_cast(working.PatchDefaultOuterLevel.w()), + static_cast(working.PatchDefaultInnerLevel.x()), + static_cast(working.PatchDefaultInnerLevel.y())); } } @@ -2093,8 +2134,7 @@ namespace MobileGL::MG_Pipe { working.PatchDefaultInnerLevel); } - void MGPipeApplySetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, - const MGPAttribValue* tail) { + void MGPipeApplySetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, const MGPAttribValue* tail) { PipeInputs& inputs = gPipeInputs; PipeInputs::CurrentVertexAttributeValue* slots = MGPipeApplyAccess::VertexAttribDefaults(inputs); // The loop walks the MASK's 32 bits, not the slot array, and every named bit consumes @@ -2138,9 +2178,10 @@ namespace MobileGL::MG_Pipe { fault = "Count does not match the attributes Mask names"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("PipeAttribTailMalformed") - " set_vertex_attrib_defaults: %s (Mask=0x%x, Count=%u, consumed=%u)", - fault, hdr.Mask, hdr.Count, consumed); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "PipeAttribTailMalformed") " set_vertex_attrib_defaults: %s (Mask=0x%x, Count=%u, consumed=%u)", + fault, hdr.Mask, hdr.Count, consumed); } } @@ -2163,8 +2204,7 @@ namespace MobileGL::MG_Pipe { MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceCreate.BufferRange\"} - " "only a texture-buffer resource may carry BufOffset/BufSize"); } - MOBILEGL_ASSERT(desc.Resource.Slot >= kMGPipeFirstAllocatableSlot, - "resource_create named the reserved slot 0"); + MOBILEGL_ASSERT(desc.Resource.Slot >= kMGPipeFirstAllocatableSlot, "resource_create named the reserved slot 0"); if (desc.Resource.Slot < kMGPipeFirstAllocatableSlot) return false; // P4a: THE TABLE IS CHOSEN BY THE DESCRIPTOR'S TARGET, and getting that wrong is the one @@ -2172,10 +2212,11 @@ namespace MobileGL::MG_Pipe { // Texture and a live Renderbuffer at the same time, in three independent slot spaces. Vector* table = ResourceTableForTarget(desc.Target); if (table == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_create {slot=%u, gen=%u, glName=%u}: the descriptor names no " - "resource target (%u)", - desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, desc.Target); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_create {slot=%u, gen=%u, glName=%u}: the descriptor names no " + "resource target (%u)", + desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, desc.Target); return false; } @@ -2187,11 +2228,11 @@ namespace MobileGL::MG_Pipe { // from the handle and nothing else survives. MGPipeResourceRecord* record = RecordAt(*table, desc.Resource.Slot, kMGPipeMaxResourceSlots); if (record == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_create {slot=%u, gen=%u, glName=%u}: the slot is outside the " - "record table's bound (%u)", - desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, - kMGPipeMaxResourceSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_create {slot=%u, gen=%u, glName=%u}: the slot is outside the " + "record table's bound (%u)", + desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, kMGPipeMaxResourceSlots); return false; } // P4a's belt, and only over the P4a half of this call: the BUFFER row is P3a's and its @@ -2237,10 +2278,11 @@ namespace MobileGL::MG_Pipe { const MGPRespecifiedLevel* level) { Vector* table = ResourceTableForTarget(desc.Target); if (table == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_respecify {slot=%u, gen=%u, glName=%u}: the descriptor names " - "no resource target (%u)", - desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, desc.Target); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_respecify {slot=%u, gen=%u, glName=%u}: the descriptor names " + "no resource target (%u)", + desc.Resource.Slot, desc.Resource.Gen, desc.GlNameForDiag, desc.Target); return false; } // P4a's belt, for MGPipeApplyResourceCreate's reason and in front of the resolution @@ -2253,15 +2295,13 @@ namespace MobileGL::MG_Pipe { if (record == nullptr) return false; PinNoLiveHostWrites(*record, desc.Resource, "resource_respecify"); PinRespecifyScopeCoversItsDeclaration(desc, level, desc.Resource, "resource_respecify"); - if (level != nullptr && - (desc.Target == kMGPipeResourceTargetBuffer || - desc.Target == static_cast(MGPipeResourceTarget::Renderbuffer) || - desc.Target == static_cast(MGPipeResourceTarget::TexBuffer))) { + if (level != nullptr && (desc.Target == kMGPipeResourceTargetBuffer || + desc.Target == static_cast(MGPipeResourceTarget::Renderbuffer) || + desc.Target == static_cast(MGPipeResourceTarget::TexBuffer))) { MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.Target\"} - " "this resource target cannot name a texture mip level"); } - if (!MGPipeRespecifyIsWholeResource(desc) && - !MGPipeRespecifiedExtentCarrierIsCanonical(desc)) { + if (!MGPipeRespecifyIsWholeResource(desc) && !MGPipeRespecifiedExtentCarrierIsCanonical(desc)) { MGP_TRIP_WIRE_REPORT("MGPipe: Fatal{ProtocolCorruption, \"ResourceRespecify.ExtentCarrier\"} - " "a named image level's depth word must fit in 32 bits (no high BufSize bits)"); } @@ -2356,8 +2396,7 @@ namespace MobileGL::MG_Pipe { return true; } - Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes, - const MGPSubRegion* regions) { + Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes, const MGPSubRegion* regions) { // ONE CALL, TWO HALVES, and the branch is one comparison. For a buffer the applier // stores NOTHING per record - the contents are the backend's, and the range is the // backend's to land - so its whole job is the gate and the serial. For a texture there @@ -2384,13 +2423,13 @@ namespace MobileGL::MG_Pipe { // BOTH HALVES GO IN THE LINE. The packed field alone leaves a reader doing the // arithmetic the encoding exists to stop anyone doing by hand, and the upload half // is what says whether the emitter packed the pair the wrong way round. - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_subdata {slot=%u, gen=%u}: the record's resource target names " - "no texture to upload into (target=%u, resource target=%u, upload " - "target=%u)", - record.Res.Slot, record.Res.Gen, record.Target, - static_cast(resourceTarget), - static_cast(MGPipeSubDataUploadTargetOf(record.Target))); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_subdata {slot=%u, gen=%u}: the record's resource target names " + "no texture to upload into (target=%u, resource target=%u, upload " + "target=%u)", + record.Res.Slot, record.Res.Gen, record.Target, static_cast(resourceTarget), + static_cast(MGPipeSubDataUploadTargetOf(record.Target))); return false; } // P4a's belt, AFTER the target fault above and before the upload is accumulated: this @@ -2437,9 +2476,9 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty flush carries no bytes"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_flush_range {slot=%u, gen=%u, glName=%u}: %s " - "(offset=%llu, size=%llu, storage=%u bytes)", + MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_flush_range {slot=%u, gen=%u, glName=%u}: %s " + "(offset=%llu, size=%llu, storage=%u bytes)", record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, fault, static_cast(record.Offset), static_cast(record.Size), stored->Desc.Width); @@ -2463,9 +2502,9 @@ namespace MobileGL::MG_Pipe { const char* fault = BufferRangeFault(record.Offset, record.Size, stored->Desc.Width); if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_readback {slot=%u, gen=%u, glName=%u}: %s " - "(offset=%llu, size=%llu, storage=%u bytes)", + MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_readback {slot=%u, gen=%u, glName=%u}: %s " + "(offset=%llu, size=%llu, storage=%u bytes)", record.Res.Slot, record.Res.Gen, stored->Desc.GlNameForDiag, fault, static_cast(record.Offset), static_cast(record.Size), stored->Desc.Width); @@ -2501,10 +2540,11 @@ namespace MobileGL::MG_Pipe { // record of a live object of another kind that happens to hold the same slot. Vector* table = ResourceTableForKind(handle.Kind); if (table == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " resource_destroy {slot=%u, gen=%u}: the handle names no resource kind " - "(%u)", - handle.Handle.Slot, handle.Handle.Gen, handle.Kind); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " resource_destroy {slot=%u, gen=%u}: the handle names no resource kind " + "(%u)", + handle.Handle.Slot, handle.Handle.Gen, handle.Kind); return; } MGPipeResourceRecord* record = ResolveResourceIn(*table, "resource_destroy", handle.Handle); @@ -2538,8 +2578,9 @@ namespace MobileGL::MG_Pipe { if (static_cast(handle.Kind) == MGPipeKind::Texture) { if (g_resourceOps != nullptr && g_resourceOps->TextureDestroy != nullptr) g_resourceOps->TextureDestroy(handle.Handle); - else { MG_Record::ServerStagedTexture().Drop( - MG_Record::StagedTextureStore::KeyForHandle(handle.Handle)); } + else { + MG_Record::ServerStagedTexture().Drop(MG_Record::StagedTextureStore::KeyForHandle(handle.Handle)); + } } if (static_cast(handle.Kind) != MGPipeKind::Buffer) return; if (g_resourceOps != nullptr && g_resourceOps->Destroy != nullptr) { @@ -2578,8 +2619,7 @@ namespace MobileGL::MG_Pipe { // trip as a mint. // // The frontend already tolerates a decline in all three places, and has since P3a. - if (MG_Config::Transport != MG_Config::TransportMode::Monolith && - MG_Remote::Client::AdoptTierIsEmulate()) { + if (MG_Config::Transport != MG_Config::TransportMode::Monolith && MG_Remote::Client::AdoptTierIsEmulate()) { return nullptr; } #endif @@ -2611,10 +2651,11 @@ namespace MobileGL::MG_Pipe { // ResourceTableForKind Fatal exists to stop. Acting on another kind's storage is the // corrupt-record verdict, not the dropped-call one, so it moves no refusal counter. if (static_cast(handle.Kind) != MGPipeKind::Buffer) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " unmap_persistent {slot=%u, gen=%u}: the persistent donation is the " - "buffer family's and the handle names another kind (%u)", - handle.Handle.Slot, handle.Handle.Gen, handle.Kind); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " unmap_persistent {slot=%u, gen=%u}: the persistent donation is the " + "buffer family's and the handle names another kind (%u)", + handle.Handle.Slot, handle.Handle.Gen, handle.Kind); return; } MGPipeResourceRecord* record = ResolveResource("unmap_persistent", handle.Handle); @@ -2632,10 +2673,11 @@ namespace MobileGL::MG_Pipe { Bool MGPipeApplyAdoptExternal(const MGPHandleOnly& handle, void* ahb, Uint64 size) { // The same kind verdict unmap_persistent gives: a store is the buffer family's. if (static_cast(handle.Kind) != MGPipeKind::Buffer) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " adopt_external {slot=%u, gen=%u}: a T0 store is the buffer family's " - "and the handle names another kind (%u)", - handle.Handle.Slot, handle.Handle.Gen, handle.Kind); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " adopt_external {slot=%u, gen=%u}: a T0 store is the buffer family's " + "and the handle names another kind (%u)", + handle.Handle.Slot, handle.Handle.Gen, handle.Kind); return false; } MGPipeResourceRecord* record = ResolveResource("adopt_external", handle.Handle); @@ -2655,10 +2697,11 @@ namespace MobileGL::MG_Pipe { void* MGPipeApplyDonateInProcess(const MGPHandleOnly& handle) { if (static_cast(handle.Kind) != MGPipeKind::Buffer) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " donate_in_process {slot=%u, gen=%u}: a T0 store is the buffer family's " - "and the handle names another kind (%u)", - handle.Handle.Slot, handle.Handle.Gen, handle.Kind); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " donate_in_process {slot=%u, gen=%u}: a T0 store is the buffer family's " + "and the handle names another kind (%u)", + handle.Handle.Slot, handle.Handle.Gen, handle.Kind); return nullptr; } MGPipeResourceRecord* record = ResolveResource("donate_in_process", handle.Handle); @@ -2706,22 +2749,23 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty blob carries no bytes"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_vertex_elements {slot=%u, gen=%u}: %s (attributes=%u, " - "bindingPoints=%u, that describes %llu bytes, blob declares %llu)", - desc.Cso.Slot, desc.Cso.Gen, fault, desc.AttributeCount, - desc.BindingPointCount, static_cast(declared), - static_cast(desc.Blob.Size)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_vertex_elements {slot=%u, gen=%u}: %s (attributes=%u, " + "bindingPoints=%u, that describes %llu bytes, blob declares %llu)", + desc.Cso.Slot, desc.Cso.Gen, fault, desc.AttributeCount, desc.BindingPointCount, + static_cast(declared), static_cast(desc.Blob.Size)); return; } MGPipeVertexElementsRecord* recordAt = RecordAt(MGPipeApplier().VertexElementsCsos, desc.Cso.Slot, kMGPipeMaxVertexElementsSlots); if (recordAt == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_vertex_elements {slot=%u, gen=%u}: the slot is outside the record " - "table's bound (%u)", - desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxVertexElementsSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_vertex_elements {slot=%u, gen=%u}: the slot is outside the record " + "table's bound (%u)", + desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxVertexElementsSlots); return; } MGPipeVertexElementsRecord& record = *recordAt; @@ -2783,7 +2827,8 @@ namespace MobileGL::MG_Pipe { auto& applier = MGPipeApplier(); if (record->StoredBuffersValid) { const MGPVertexBuffers& header = record->StoredBuffersHeader; - for (Uint32 i = 0; i < header.Count; ++i) applier.VertexBuffers[header.Start + i] = record->StoredBuffers[i]; + for (Uint32 i = 0; i < header.Count; ++i) + applier.VertexBuffers[header.Start + i] = record->StoredBuffers[i]; applier.VertexBufferStart = header.Start; applier.VertexBufferCount = header.Count; applier.VertexFetchBaseInstance = header.BaseInstance; @@ -2831,11 +2876,11 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty set carries no entries"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " set_vertex_buffers {start=%u, count=%u, hash=%llu}: %s (the applier holds " - "%u entries)", - hdr.Start, hdr.Count, static_cast(hdr.ContentHash), - fault, kMGPipeMaxVertexAttribs); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " set_vertex_buffers {start=%u, count=%u, hash=%llu}: %s (the applier holds " + "%u entries)", + hdr.Start, hdr.Count, static_cast(hdr.ContentHash), fault, kMGPipeMaxVertexAttribs); return; } @@ -2869,7 +2914,8 @@ namespace MobileGL::MG_Pipe { bound->StoredBuffersValid = hdr.Start == 0 && hdr.Count <= kMGPipeVaoStoredVertexBuffers; if (bound->StoredBuffersValid) { bound->StoredBuffersHeader = hdr; - for (Uint32 i = 0; i < hdr.Count; ++i) bound->StoredBuffers[i] = tail[i]; + for (Uint32 i = 0; i < hdr.Count; ++i) + bound->StoredBuffers[i] = tail[i]; } } } @@ -2951,17 +2997,16 @@ namespace MobileGL::MG_Pipe { // clamped to it, and a truncated record arriving here as a plausible index is exactly // what the framebuffer subsystem's cap refusal exists to prevent upstream. for (Uint32 i = 0; fault == nullptr && i < kMGPipeMaxColorAttachments; ++i) { - if (state.DrawBuffers[i] < -1 || - state.DrawBuffers[i] >= static_cast(kMGPipeMaxColorAttachments)) { + if (state.DrawBuffers[i] < -1 || state.DrawBuffers[i] >= static_cast(kMGPipeMaxColorAttachments)) { fault = "a draw-buffer entry names a colour attachment outside the record's own array"; } } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " set_framebuffer_state {slot=%u, gen=%u, target=%u}: %s (the record " - "carries %u colour attachments)", - state.Fbo.Slot, state.Fbo.Gen, state.Target, fault, - kMGPipeMaxColorAttachments); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " set_framebuffer_state {slot=%u, gen=%u, target=%u}: %s (the record " + "carries %u colour attachments)", + state.Fbo.Slot, state.Fbo.Gen, state.Target, fault, kMGPipeMaxColorAttachments); return; } @@ -3050,8 +3095,7 @@ namespace MobileGL::MG_Pipe { // ================================================================================ void MGPipeApplyCreateSamplerState(const MGPSamplerDesc& desc, const SamplerParameters* parameters) { - MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, - "create_sampler_state named the reserved slot 0"); + MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, "create_sampler_state named the reserved slot 0"); if (desc.Cso.Slot < kMGPipeFirstAllocatableSlot) return; // THE ONE BLOB RULE, on the family's own blob: a non-zero Parameters.Size must be @@ -3067,22 +3111,23 @@ namespace MobileGL::MG_Pipe { fault = "the record declares no parameters and carries none"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_sampler_state {slot=%u, gen=%u}: %s (one SamplerParameters is " - "%llu bytes, the blob declares %llu)", - desc.Cso.Slot, desc.Cso.Gen, fault, - static_cast(sizeof(SamplerParameters)), - static_cast(desc.Parameters.Size)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_sampler_state {slot=%u, gen=%u}: %s (one SamplerParameters is " + "%llu bytes, the blob declares %llu)", + desc.Cso.Slot, desc.Cso.Gen, fault, static_cast(sizeof(SamplerParameters)), + static_cast(desc.Parameters.Size)); return; } MGPipeSamplerCsoRecord* recordAt = RecordAt(MGPipeApplier().SamplerCsos, desc.Cso.Slot, kMGPipeMaxSamplerCsoSlots); if (recordAt == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_sampler_state {slot=%u, gen=%u}: the slot is outside the record " - "table's bound (%u)", - desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxSamplerCsoSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_sampler_state {slot=%u, gen=%u}: the slot is outside the record " + "table's bound (%u)", + desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxSamplerCsoSlots); return; } // P4a's belt, after the blob rule and the slot bound and before the record moves. @@ -3111,8 +3156,8 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplyDeleteSamplerState(const MGPHandleOnly& handle) { - MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::SamplerCso), - "delete_sampler_state on kind %u", handle.Kind); + MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::SamplerCso), "delete_sampler_state on kind %u", + handle.Kind); // A SAMPLER CSO IS ONE VALUE FOR THE WHOLE SESSION, and its delete drops it from every // share group that holds it. The client's cache is content-addressed and process-wide: // one handle names one canonical value, published into each group that uses it (a @@ -3152,17 +3197,17 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplyCreateSamplerView(const MGPSamplerView& view) { - MOBILEGL_ASSERT(view.Cso.Slot >= kMGPipeFirstAllocatableSlot, - "create_sampler_view named the reserved slot 0"); + MOBILEGL_ASSERT(view.Cso.Slot >= kMGPipeFirstAllocatableSlot, "create_sampler_view named the reserved slot 0"); if (view.Cso.Slot < kMGPipeFirstAllocatableSlot) return; MGPipeSamplerViewRecord* recordAt = RecordAt(MGPipeApplier().SamplerViewCsos, view.Cso.Slot, kMGPipeMaxSamplerViewSlots); if (recordAt == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_sampler_view {slot=%u, gen=%u}: the slot is outside the record " - "table's bound (%u)", - view.Cso.Slot, view.Cso.Gen, kMGPipeMaxSamplerViewSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_sampler_view {slot=%u, gen=%u}: the slot is outside the record " + "table's bound (%u)", + view.Cso.Slot, view.Cso.Gen, kMGPipeMaxSamplerViewSlots); return; } // P4a's belt, after the slot bound and before the record moves. @@ -3233,10 +3278,11 @@ namespace MobileGL::MG_Pipe { // pushes it onto the TEXTURE with glTexParameter*, so this handle is the only thing // that says which values those are. if (MGPipeHandleIsNull(params.BuiltinSampler)) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " set_texture_params {slot=%u, gen=%u, glName=%u}: the record names no " - "built-in sampler CSO, and every texture object owns one", - params.Res.Slot, params.Res.Gen, record->Desc.GlNameForDiag); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " set_texture_params {slot=%u, gen=%u, glName=%u}: the record names no " + "built-in sampler CSO, and every texture object owns one", + params.Res.Slot, params.Res.Gen, record->Desc.GlNameForDiag); return false; } // AND THE CSO IT NAMES IS NOT RESOLVED. The sampler subsystem is its own bit and may be @@ -3326,21 +3372,21 @@ namespace MobileGL::MG_Pipe { // latches only its set-hash suppressor. void MGPipeApplySetShaderBuffers(const MGPShaderBuffers& hdr, const MGPBufferRange* tail) { if (hdr.Class >= kMGPipeShaderBufferClassCount) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " SetShaderBuffers.Class - class %u names no binding-point " - "array; the three are Uniform=0, ShaderStorage=1, " - "AtomicCounter=2", + MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " SetShaderBuffers.Class - class %u names no binding-point " + "array; the three are Uniform=0, ShaderStorage=1, " + "AtomicCounter=2", hdr.Class); return; } const Uint64 end = Uint64{hdr.Start} + Uint64{hdr.Count}; if (end > kMGPipeMaxBufferBindingPoints || (hdr.Count != 0 && tail == nullptr)) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " SetShaderBuffers.Count {class=%u, start=%u, count=%u}: the " - "window runs past the %u binding points the wire carries, or a " - "non-empty set carries no ranges", - hdr.Class, hdr.Start, hdr.Count, - static_cast(kMGPipeMaxBufferBindingPoints)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " SetShaderBuffers.Count {class=%u, start=%u, count=%u}: the " + "window runs past the %u binding points the wire carries, or a " + "non-empty set carries no ranges", + hdr.Class, hdr.Start, hdr.Count, static_cast(kMGPipeMaxBufferBindingPoints)); return; } // THE WINDOW IS THE BOUND AND ENTRIES OUTSIDE IT ARE NOT CLEARED, exactly as the three @@ -3348,7 +3394,8 @@ namespace MobileGL::MG_Pipe { // a binding at or above Count means "nothing bound" to every reader (the frontend // array's default says the same thing, which is what makes the two pictures agree). auto& destination = MGPipeApplier().BoundShaderBuffers[hdr.Class]; - for (Uint32 i = 0; i < hdr.Count; ++i) destination[hdr.Start + i] = tail[i]; + for (Uint32 i = 0; i < hdr.Count; ++i) + destination[hdr.Start + i] = tail[i]; MGPipeApplier().ShaderBufferStart[hdr.Class] = hdr.Start; MGPipeApplier().ShaderBufferCount[hdr.Class] = hdr.Count; for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) { @@ -3376,21 +3423,26 @@ namespace MobileGL::MG_Pipe { // validation a record ever gets is the one the applier does itself, which is the rule // every sibling in this file already keeps. const char* fault = nullptr; - if (hdr.BlockBindingCount > kMGPipeMaxProgramBlockBindings) fault = "BlockBindingCount"; - else if (hdr.SamplerUnitCount > kMGPipeMaxProgramSamplerUnits) fault = "SamplerUnitCount"; - else if (hdr.StorageOverrideCount > kMGPipeMaxProgramStorageOverrides) fault = "StorageOverrideCount"; - else if (hdr.BlockBindingCount != 0 && blockBindings == nullptr) fault = "BlockBindings"; - else if (hdr.SamplerUnitCount != 0 && samplerUnits == nullptr) fault = "SamplerUnits"; - else if (hdr.StorageOverrideCount != 0 && - (storageOverrides == nullptr || storageOverrideNames == nullptr)) { + if (hdr.BlockBindingCount > kMGPipeMaxProgramBlockBindings) + fault = "BlockBindingCount"; + else if (hdr.SamplerUnitCount > kMGPipeMaxProgramSamplerUnits) + fault = "SamplerUnitCount"; + else if (hdr.StorageOverrideCount > kMGPipeMaxProgramStorageOverrides) + fault = "StorageOverrideCount"; + else if (hdr.BlockBindingCount != 0 && blockBindings == nullptr) + fault = "BlockBindings"; + else if (hdr.SamplerUnitCount != 0 && samplerUnits == nullptr) + fault = "SamplerUnits"; + else if (hdr.StorageOverrideCount != 0 && (storageOverrides == nullptr || storageOverrideNames == nullptr)) { fault = "StorageOverrides"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " set_program_bindings {slot=%u, gen=%u}: %s is out of range or " - "its tail is absent (%u/%u/%u declared)", - hdr.Cso.Slot, hdr.Cso.Gen, fault, hdr.BlockBindingCount, - hdr.SamplerUnitCount, hdr.StorageOverrideCount); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " set_program_bindings {slot=%u, gen=%u}: %s is out of range or " + "its tail is absent (%u/%u/%u declared)", + hdr.Cso.Slot, hdr.Cso.Gen, fault, hdr.BlockBindingCount, hdr.SamplerUnitCount, + hdr.StorageOverrideCount); return; } // P4a's belt, for MGPipeApplyBindShaderState's reason: with no consumer this applier @@ -3476,7 +3528,9 @@ namespace MobileGL::MG_Pipe { thread_local Bool g_currentRecordBarrieredByEscalation = false; } // namespace - Bool MGPipeApplierCurrentRecordIsBarriered() { return g_currentRecordBarriered; } + Bool MGPipeApplierCurrentRecordIsBarriered() { + return g_currentRecordBarriered; + } void MGPipeApplierSetCurrentRecordBarriered(Bool barriered) { g_currentRecordBarriered = barriered; @@ -3499,12 +3553,10 @@ namespace MobileGL::MG_Pipe { // binding signature, image formats, patch parameters) arrive through other records. // ================================================================================ - void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc, - const MG_State::GLState::LinkArtifacts* link, + void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc, const MG_State::GLState::LinkArtifacts* link, const MG_State::GLState::SpirvArtifacts* spirv, SharedPtr archive) { - MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, - "create_shader_state named the reserved slot 0"); + MOBILEGL_ASSERT(desc.Cso.Slot >= kMGPipeFirstAllocatableSlot, "create_shader_state named the reserved slot 0"); if (desc.Cso.Slot < kMGPipeFirstAllocatableSlot) return; // THE ONE BLOB RULE, over seven blob refs at once. All seven are declared with Size 0 in @@ -3522,9 +3574,9 @@ namespace MobileGL::MG_Pipe { fault = "the default uniform block is larger than any program may declare"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_shader_state {slot=%u, gen=%u}: %s (stages=0x%x, " - "globalUboSize=%u, bound %u)", + MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_shader_state {slot=%u, gen=%u}: %s (stages=0x%x, " + "globalUboSize=%u, bound %u)", desc.Cso.Slot, desc.Cso.Gen, fault, desc.StageMask, desc.GlobalUboSize, kMGPipeMaxGlobalConstantsBytes); return; @@ -3532,10 +3584,11 @@ namespace MobileGL::MG_Pipe { MGPipeShaderCsoRecord* recordAt = ShaderCsoRecordAt(desc.Cso.Slot); if (recordAt == nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " create_shader_state {slot=%u, gen=%u}: the slot is outside the record " - "table's bound (%u)", - desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxShaderCsoSlots); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " create_shader_state {slot=%u, gen=%u}: the slot is outside the record " + "table's bound (%u)", + desc.Cso.Slot, desc.Cso.Gen, kMGPipeMaxShaderCsoSlots); return; } @@ -3603,8 +3656,8 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplyBindShaderState(const MGPHandleOnly& handle) { - MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), - "bind_shader_state on kind %u", handle.Kind); + MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), "bind_shader_state on kind %u", + handle.Kind); // P4a's belt, and first for set_texture_params' reason: with no consumer this applier // holds no shader CSO record, so the resolution below would report the designed state // as RefusedObjectCalls. The null-handle unbind is behind it too - a binding this @@ -3625,8 +3678,8 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplyDeleteShaderState(const MGPHandleOnly& handle) { - MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), - "delete_shader_state on kind %u", handle.Kind); + MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), "delete_shader_state on kind %u", + handle.Kind); // A COMPOSITE'S SLOT HAS TWO INDEPENDENT RELEASE PATHS - the pipeline cache's eviction // and the composite program's own destructor - and both arrive here through one client // helper. The second is a refusal: the record is already gone, so it is counted and @@ -3664,8 +3717,8 @@ namespace MobileGL::MG_Pipe { // draw resolves GetProgramForDraw and a dispatch resolves GetProgramForDispatch, and a // pipeline object flattened into a composite is the first of those and never the second. void MGPipeApplySetDrawProgram(const MGPHandleOnly& handle) { - MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), - "set_draw_program on kind %u", handle.Kind); + MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), "set_draw_program on kind %u", + handle.Kind); if (NoP4aConsumer()) return; // P4a's belt, for MGPipeApplyBindShaderState's reason. if (MGPipeHandleIsNull(handle.Handle)) { MGPipeApplier().DrawProgram = kMGPipeNullHandle; @@ -3679,8 +3732,8 @@ namespace MobileGL::MG_Pipe { } void MGPipeApplySetDispatchProgram(const MGPHandleOnly& handle) { - MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), - "set_dispatch_program on kind %u", handle.Kind); + MOBILEGL_ASSERT(handle.Kind == static_cast(MGPipeKind::ShaderCso), "set_dispatch_program on kind %u", + handle.Kind); if (NoP4aConsumer()) return; // P4a's belt, for MGPipeApplyBindShaderState's reason. if (MGPipeHandleIsNull(handle.Handle)) { MGPipeApplier().DispatchProgram = kMGPipeNullHandle; @@ -3732,11 +3785,12 @@ namespace MobileGL::MG_Pipe { fault = "a non-empty block carries no bytes"; } if (fault != nullptr) { - MGP_TRIP_WIRE_REPORT("MGPipe: " MGP_TRIP_WIRE_TAG("ProtocolCorruption") - " set_global_constants {slot=%u, gen=%u}: %s (version=%u, the program " - "declares %u bytes, the blob declares %llu)", - record.ShaderCso.Slot, record.ShaderCso.Gen, fault, record.Version, size, - static_cast(record.Blob.Size)); + MGP_TRIP_WIRE_REPORT( + "MGPipe: " MGP_TRIP_WIRE_TAG( + "ProtocolCorruption") " set_global_constants {slot=%u, gen=%u}: %s (version=%u, the program " + "declares %u bytes, the blob declares %llu)", + record.ShaderCso.Slot, record.ShaderCso.Gen, fault, record.Version, size, + static_cast(record.Blob.Size)); return; } @@ -3765,11 +3819,15 @@ namespace MobileGL::MG_Pipe { thread_local Bool tl_serverOriginatedWrite = false; } // namespace - Bool MGPipeApplyingServerOriginatedWrite() { return tl_serverOriginatedWrite; } + Bool MGPipeApplyingServerOriginatedWrite() { + return tl_serverOriginatedWrite; + } MGPipeServerOriginatedWriteScope::MGPipeServerOriginatedWriteScope() : m_previous(tl_serverOriginatedWrite) { tl_serverOriginatedWrite = true; } - MGPipeServerOriginatedWriteScope::~MGPipeServerOriginatedWriteScope() { tl_serverOriginatedWrite = m_previous; } + MGPipeServerOriginatedWriteScope::~MGPipeServerOriginatedWriteScope() { + tl_serverOriginatedWrite = m_previous; + } void MGPipeUnmigratedEmulation(const char* name) { #if MOBILEGL_BUILD_DISAGGREGATED diff --git a/MobileGL/MG_Pipe/PipeApply.h b/MobileGL/MG_Pipe/PipeApply.h index cf5fcae7..84d3ef91 100644 --- a/MobileGL/MG_Pipe/PipeApply.h +++ b/MobileGL/MG_Pipe/PipeApply.h @@ -88,8 +88,7 @@ namespace MobileGL::MG_Pipe { // shadow leaves them null and keeps the pre-tx shape. void (*TextureSubData)(MGPipeHandle res, const MGPSubData& record, const void* bytes, const MGPSubRegion* regions); - void (*TextureRespecify)(MGPipeHandle res, const MGPResourceDesc& desc, - const MGPRespecifiedLevel* level); + void (*TextureRespecify)(MGPipeHandle res, const MGPResourceDesc& desc, const MGPRespecifiedLevel* level); void (*TextureDestroy)(MGPipeHandle res); // P11 B2 (T0, docs/archive/contracts/CONTRACT-P11.md B2): the two members a backend that can take a // client's AHardwareBuffer as a store registers. Both null = this backend has no T0 and @@ -374,8 +373,8 @@ namespace MobileGL::MG_Pipe { // GlobalConstants: a relink replaces the archive these index into, so a surviving tail // would name block indices and uniform locations of a program that no longer exists. // The client re-emits set_program_bindings before the create that describes the relink. - Vector BlockBindings; // dense, GL uniform-block index order - Vector SamplerUnits; // sparse, ascending Location + Vector BlockBindings; // dense, GL uniform-block index order + Vector SamplerUnits; // sparse, ascending Location Vector StorageOverrides; // name-keyed by design // The client's commutative hash of the override MAP, so the twin's "must I rebuild the // storage-block bindings" clause is one Uint64 compare and the name tail is touched only @@ -541,8 +540,8 @@ namespace MobileGL::MG_Pipe { // go red for the reason it exists (ROADMAP.md), and only a poison or verify build // aborts: the shipped push build counts and logs, so these counters are how a unit // case sees the wire fire in EVERY build rather than in one. - Uint32 PatchCarrierComparisons = 0; // cumulative, armed set_patch_state calls only - Uint32 PatchCarrierDivergences = 0; // cumulative + Uint32 PatchCarrierComparisons = 0; // cumulative, armed set_patch_state calls only + Uint32 PatchCarrierDivergences = 0; // cumulative // Every call this applier REFUSED because it named a record this applier does not // have: an unknown slot, a slot that is not live, or a generation that has moved on @@ -692,8 +691,7 @@ namespace MobileGL::MG_Pipe { // Start is 0 by contract, Count is the touched high-water mark, and a binding at or // above Count means "nothing bound" - which is what the frontend array's default says // too, so the server does not have to clear entries outside the window to be right. - Array - BoundShaderBuffers[kMGPipeShaderBufferClassCount]{}; + Array BoundShaderBuffers[kMGPipeShaderBufferClassCount]{}; Uint32 ShaderBufferStart[kMGPipeShaderBufferClassCount] = {0, 0, 0}; Uint32 ShaderBufferCount[kMGPipeShaderBufferClassCount] = {0, 0, 0}; // Which entries of classes 1 and 2 the shader may WRITE through. It is what replaces @@ -706,8 +704,7 @@ namespace MobileGL::MG_Pipe { // point 32 or above read as read-only here while the record said nothing was wrong. // Always through MGPipeShaderBufferMaskHas / ...Set (MGPipeTypes.h), which is the same // arithmetic the payload's own mask goes through - one table, both sides. - Uint32 ShaderBufferWritableMask[kMGPipeShaderBufferClassCount] - [kMGPipeShaderBufferWritableMaskWords] = {}; + Uint32 ShaderBufferWritableMask[kMGPipeShaderBufferClassCount][kMGPipeShaderBufferWritableMaskWords] = {}; // ONE serial for all three classes, ++ on every applied record and ADVANCED (never // zeroed) by MGPipeApplierReset, for VertexBuffersSerial's reason - and the emitter's // latch resets with it, or the first emission after a make-current is suppressed as @@ -775,7 +772,6 @@ namespace MobileGL::MG_Pipe { // context's applier first would be asking a harder question than the one it needs // answered. The integrator resolved the two packages' duplicate here (ID-103). - // Every verb's writer calls this FIRST and then sets its own fields, so no field ever // outlives the verb that wrote it and a reader can treat non-null as "this verb's". void ClearVerbHandles() { @@ -791,7 +787,6 @@ namespace MobileGL::MG_Pipe { VerbIndirectParameterBuffer = kMGPipeNullHandle; VerbDispatchIndirectBuffer = kMGPipeNullHandle; } - }; // P14: where a new applier's working-state serials start. Each applier (one per served context) @@ -1085,9 +1080,8 @@ namespace MobileGL::MG_Pipe { Uint32 Depth = 0; }; - inline MGPRespecifiedLevel MGPipeMakeRespecifiedLevel(Uint16 uploadTarget, Uint16 level, - Uint32 width = 0, Uint32 height = 0, - Uint32 depth = 0) { + inline MGPRespecifiedLevel MGPipeMakeRespecifiedLevel(Uint16 uploadTarget, Uint16 level, Uint32 width = 0, + Uint32 height = 0, Uint32 depth = 0) { MGPRespecifiedLevel value{}; value.UploadTarget = uploadTarget; value.Level = level; @@ -1153,8 +1147,7 @@ namespace MobileGL::MG_Pipe { Bool m_previous; }; - Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes, - const MGPSubRegion* regions = nullptr); + Bool MGPipeApplyResourceSubData(const MGPSubData& record, const void* bytes, const MGPSubRegion* regions = nullptr); // buffer_subdata_resident: same shape; `bytes` is the application's staging store and is // valid for the duration of the call only. The op-table entry may be null. void MGPipeApplyBufferSubDataResident(const MGPSubData& record, const void* bytes); @@ -1302,8 +1295,7 @@ namespace MobileGL::MG_Pipe { // codec. The verify build round-trips the artefacts through the codec before storing, and a // mismatch is Fatal{PipeVerifyDiffer, "program-archive"}. `archive` has no default so that a // caller holding one cannot silently omit it. - void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc, - const MG_State::GLState::LinkArtifacts* link, + void MGPipeApplyCreateShaderState(const MGPProgramDesc& desc, const MG_State::GLState::LinkArtifacts* link, const MG_State::GLState::SpirvArtifacts* spirv, SharedPtr archive); void MGPipeApplyBindShaderState(const MGPHandleOnly& handle); diff --git a/MobileGL/MG_Pipe/PipeFatalFamily.h b/MobileGL/MG_Pipe/PipeFatalFamily.h index e979c31c..8c09506f 100644 --- a/MobileGL/MG_Pipe/PipeFatalFamily.h +++ b/MobileGL/MG_Pipe/PipeFatalFamily.h @@ -35,8 +35,9 @@ namespace MobileGL::MG_Pipe { // in that string, which is what keeps the two from diverging. inline const char* FatalFamilyName(MGFatalFamily family) { switch (family) { -#define X(Family, WireCode, Why) \ - case MGFatalFamily::Family: return #Family; +#define X(Family, WireCode, Why) \ + case MGFatalFamily::Family: \ + return #Family; MGL_FATAL_FAMILY_LIST(X) #undef X } diff --git a/MobileGL/MG_Pipe/PipeMutation.h b/MobileGL/MG_Pipe/PipeMutation.h index ea71bfd4..f195b1ba 100644 --- a/MobileGL/MG_Pipe/PipeMutation.h +++ b/MobileGL/MG_Pipe/PipeMutation.h @@ -85,7 +85,7 @@ namespace MobileGL::MG_State::GLState { class FramebufferObject; class SamplerObject; class ProgramObject; -} +} // namespace MobileGL::MG_State::GLState namespace MobileGL::MG_Pipe { // (Features.PipePush & kMGPipeSubsystemResources) != 0 && MGPipeGetResourceOps() != nullptr. @@ -224,8 +224,8 @@ namespace MobileGL::MG_Pipe { void MGPipeEmitResourceCreate(MG_State::GLState::BufferObject& buffer); void MGPipeEmitResourceRespecify(MG_State::GLState::BufferObject& buffer); void MGPipeEmitResourceSubData(MG_State::GLState::BufferObject& buffer, SizeT offset, SizeT size); - void MGPipeEmitBufferSubDataResident(MG_State::GLState::BufferObject& buffer, SizeT offset, - const void* bytes, SizeT size); + void MGPipeEmitBufferSubDataResident(MG_State::GLState::BufferObject& buffer, SizeT offset, const void* bytes, + SizeT size); void MGPipeEmitResourceFlushRange(MG_State::GLState::BufferObject& buffer, SizeT offset, SizeT size, Uint32 accessFlags); void MGPipeEmitResourceReadback(MG_State::GLState::BufferObject& buffer); @@ -323,8 +323,7 @@ namespace MobileGL::MG_Pipe { LevelsFrom = 2, }; void MGPipeEmitTextureResourceRespecify(MG_State::GLState::ITextureObject& texture, - MGPipeTextureRespecifyScope scope, Uint32 uploadTarget, - Uint32 level); + MGPipeTextureRespecifyScope scope, Uint32 uploadTarget, Uint32 level); void MGPipeEmitTextureParams(MG_State::GLState::ITextureObject& texture); // The DRAIN LIST's append, on a level's FIRST dirty mark, keyed on the STORAGE OWNER from // day one (D-D4: a view and its owner already share one dirty state, so an upload through @@ -359,7 +358,6 @@ namespace MobileGL::MG_Pipe { // the validate point's image walk (which notes it as well, D-A4's letter). void MGPipeNoteTextureImageBound(MG_State::GLState::ITextureObject& texture); - // ---- sampler CSOs and sampler views: MG_Impl/Pipe/SamplerEmit.h, package C ---- // // Entry points MGPipeSamplerEmitter must provide, returning void: diff --git a/MobileGL/MG_Pipe/PipeRoute.cpp b/MobileGL/MG_Pipe/PipeRoute.cpp index 1efd20e6..c964eb4d 100644 --- a/MobileGL/MG_Pipe/PipeRoute.cpp +++ b/MobileGL/MG_Pipe/PipeRoute.cpp @@ -17,7 +17,6 @@ #include "PipeRoute.h" - #include #include #include @@ -35,9 +34,9 @@ namespace MobileGL::MG_Pipe { // --------------------------------------------------------------------------------- struct ReplyMailbox { - Uint64 Slot = 0; // the MGPReplySlot::Id this answer belongs to; 0 = empty - Int32 Status = 0; // 0 OK, 1 DECLINED, 2 ERROR (Transport::ReplyStatus) - Uint64 Value = 0; // the Bool, or the pointer, the row answered with + Uint64 Slot = 0; // the MGPReplySlot::Id this answer belongs to; 0 = empty + Int32 Status = 0; // 0 OK, 1 DECLINED, 2 ERROR (Transport::ReplyStatus) + Uint64 Value = 0; // the Bool, or the pointer, the row answered with Uint64 NextTicket = 0; Uint64 Taken = 0; Uint64 Declined = 0; @@ -58,9 +57,15 @@ namespace MobileGL::MG_Pipe { } // namespace - const MGPipeScreen& MGPipeMonolithScreen() { return g_monolithScreen; } - const MGPipeContext& MGPipeMonolithContext() { return g_monolithContext; } - const MGPipeRouteEscapes& MGPipeMonolithEscapes() { return g_monolithEscapes; } + const MGPipeScreen& MGPipeMonolithScreen() { + return g_monolithScreen; + } + const MGPipeContext& MGPipeMonolithContext() { + return g_monolithContext; + } + const MGPipeRouteEscapes& MGPipeMonolithEscapes() { + return g_monolithEscapes; + } MGPReplySlot MGPipeMintReplySlot() { // 1-based, for ReplySlot.h's reason: "Seq is 1-based; 0 means 'no record'". A zero id @@ -79,8 +84,7 @@ namespace MobileGL::MG_Pipe { "unread. The mailbox is one deep because R-1's verb barrier makes the " "in-flight depth one; two outstanding answers means the barrier is not " "holding", - static_cast(slot.Id), - static_cast(g_reply.Slot)); + static_cast(slot.Id), static_cast(g_reply.Slot)); std::abort(); } g_reply.Slot = slot.Id; @@ -101,8 +105,7 @@ namespace MobileGL::MG_Pipe { if (g_reply.Slot != slot.Id) { MGLOG_F("MGPipe: Fatal{ReplyMismatched, \"%s\"} - waiting on slot %llu, the " "mailbox holds slot %llu", - row, static_cast(slot.Id), - static_cast(g_reply.Slot)); + row, static_cast(slot.Id), static_cast(g_reply.Slot)); std::abort(); } const Uint64 value = g_reply.Value; @@ -144,12 +147,22 @@ namespace MobileGL::MG_Pipe { return reinterpret_cast(static_cast(value)); } - Uint64 MGPipeRepliesTaken() { return g_reply.Taken; } - Uint64 MGPipeRepliesDeclined() { return g_reply.Declined; } + Uint64 MGPipeRepliesTaken() { + return g_reply.Taken; + } + Uint64 MGPipeRepliesDeclined() { + return g_reply.Declined; + } - MGPipeRouteArm MGPipeInstalledArm() { return g_arm; } - void MGPipeNoteInstalledArm(MGPipeRouteArm arm) { g_arm = arm; } - Bool MGPipeTablesAreInstalled() { return g_arm != MGPipeRouteArm::kNone; } + MGPipeRouteArm MGPipeInstalledArm() { + return g_arm; + } + void MGPipeNoteInstalledArm(MGPipeRouteArm arm) { + g_arm = arm; + } + Bool MGPipeTablesAreInstalled() { + return g_arm != MGPipeRouteArm::kNone; + } // --------------------------------------------------------------------------------- // The thirty-eight monolith adapters @@ -162,17 +175,19 @@ namespace MobileGL::MG_Pipe { namespace { -#define MGP_MONO_PLAIN(Name, Payload) \ - void Mono_##Name(const Payload* payload) { MGPipeApply##Name(*payload); } - -#define MGP_MONO_BLOB(Name, Payload) \ - void Mono_##Name(const Payload* payload, const void* blobBytes, Uint64) { \ - MGPipeApply##Name(*payload, blobBytes); \ +#define MGP_MONO_PLAIN(Name, Payload) \ + void Mono_##Name(const Payload* payload) { \ + MGPipeApply##Name(*payload); \ } -#define MGP_MONO_TAIL(Name, Payload, TailType) \ - void Mono_##Name(const Payload* payload, const void* varTail, Uint32) { \ - MGPipeApply##Name(*payload, static_cast(varTail)); \ +#define MGP_MONO_BLOB(Name, Payload) \ + void Mono_##Name(const Payload* payload, const void* blobBytes, Uint64) { \ + MGPipeApply##Name(*payload, blobBytes); \ + } + +#define MGP_MONO_TAIL(Name, Payload, TailType) \ + void Mono_##Name(const Payload* payload, const void* varTail, Uint32) { \ + MGPipeApply##Name(*payload, static_cast(varTail)); \ } // -- screen ------------------------------------------------------------------- @@ -232,15 +247,16 @@ namespace MobileGL::MG_Pipe { // row 17, including its padding trap - the bytes staged must be the bytes a later // memcmp compares). void Mono_CreateSamplerState(const MGPSamplerDesc* payload, const void* blobBytes, Uint64) { - MGPipeApplyCreateSamplerState(*payload, - static_cast(blobBytes)); + MGPipeApplyCreateSamplerState(*payload, static_cast(blobBytes)); } // set_residual_value_state: RETIRED (P13 W3c). Nothing emits it any more; the row stays // installed so the table is total, and reaching it is a named death. void Mono_SetResidualValueState(const MGPResidualValueState*, const void*, Uint64) { MGLOG_F("MGPipe: Fatal{ResidualBlockSize} - set_residual_value_state reached the monolith " - "route, but %s", "the residual value block was deleted in P13 W3c (op 46 is a retired row; the verify comparator owns the capability cross-check)"); + "route, but %s", + "the residual value block was deleted in P13 W3c (op 46 is a retired row; the verify comparator " + "owns the capability cross-check)"); std::abort(); } @@ -266,10 +282,10 @@ namespace MobileGL::MG_Pipe { MGPipePostReply(*reply, accepted ? 0 : 1, accepted ? 1u : 0u); } - void Mono_ResourceSubData(const MGPSubData* payload, const void* blobBytes, Uint64, - const void* varTail, Uint32, MGPReplySlot* reply) { - const Bool accepted = MGPipeApplyResourceSubData( - *payload, blobBytes, static_cast(varTail)); + void Mono_ResourceSubData(const MGPSubData* payload, const void* blobBytes, Uint64, const void* varTail, Uint32, + MGPReplySlot* reply) { + const Bool accepted = + MGPipeApplyResourceSubData(*payload, blobBytes, static_cast(varTail)); MGPipePostReply(*reply, accepted ? 0 : 1, accepted ? 1u : 0u); } @@ -284,15 +300,13 @@ namespace MobileGL::MG_Pipe { MGPipeApplyResourceFlushRange(*record, bytes); } - void* Mono_Escape_MapPersistent(const MGPHandleOnly* handle, Uint64 size, - const void* seedBytes) { + void* Mono_Escape_MapPersistent(const MGPHandleOnly* handle, Uint64 size, const void* seedBytes) { return MGPipeApplyMapPersistent(*handle, size, seedBytes); } - void Mono_Escape_CreateShaderState(const MGPProgramDesc* desc, - const MG_State::GLState::LinkArtifacts* link, - const MG_State::GLState::SpirvArtifacts* spirv, - const Uint32* linkedStages, Uint32 linkedStageCount) { + void Mono_Escape_CreateShaderState(const MGPProgramDesc* desc, const MG_State::GLState::LinkArtifacts* link, + const MG_State::GLState::SpirvArtifacts* spirv, const Uint32* linkedStages, + Uint32 linkedStageCount) { // The stage list is the CLIENT ARM's framing input and this arm has no use for it: // the monolith twin reads GetLinkedShaderStages() off the frontend object it is // handed. Named and discarded rather than left out of the signature, so the two @@ -306,7 +320,8 @@ namespace MobileGL::MG_Pipe { auto archive = MakeShared(); archive->Link = *link; archive->Spirv = *spirv; - if (linkedStages != nullptr) archive->LinkedStages.assign(linkedStages, linkedStages + linkedStageCount); + if (linkedStages != nullptr) + archive->LinkedStages.assign(linkedStages, linkedStages + linkedStageCount); MGPipeApplyCreateShaderState(*desc, link, spirv, std::move(archive)); return; } @@ -329,8 +344,7 @@ namespace MobileGL::MG_Pipe { const MGPProgramSamplerUnit* samplerUnits, const MGPProgramStorageOverride* storageOverrides, const char* const* storageOverrideNames) { - MGPipeApplySetProgramBindings(*hdr, blockBindings, samplerUnits, storageOverrides, - storageOverrideNames); + MGPipeApplySetProgramBindings(*hdr, blockBindings, samplerUnits, storageOverrides, storageOverrideNames); } } // namespace @@ -389,4 +403,3 @@ namespace MobileGL::MG_Pipe { } } // namespace MobileGL::MG_Pipe - diff --git a/MobileGL/MG_Pipe/PipeRoute.h b/MobileGL/MG_Pipe/PipeRoute.h index 75e407a4..1091c59d 100644 --- a/MobileGL/MG_Pipe/PipeRoute.h +++ b/MobileGL/MG_Pipe/PipeRoute.h @@ -18,7 +18,6 @@ #pragma once #include - #include "MGPipe.h" #include "PipeApply.h" // P12: MGPipeHandleIsPublished, the latch the respecify predicate below reads. @@ -72,10 +71,9 @@ namespace MobileGL::MG_Pipe { // the stage of each lives in `ProgramObject::m_linkedShaderSnapshot`, which is GL-thread // state and not an artifact. The client arm frames it in front of the archive; the // monolith arm ignores it, because its twin reads the snapshot directly. - void (*CreateShaderState)(const MGPProgramDesc* desc, - const MG_State::GLState::LinkArtifacts* link, - const MG_State::GLState::SpirvArtifacts* spirv, - const Uint32* linkedStages, Uint32 linkedStageCount); + void (*CreateShaderState)(const MGPProgramDesc* desc, const MG_State::GLState::LinkArtifacts* link, + const MG_State::GLState::SpirvArtifacts* spirv, const Uint32* linkedStages, + Uint32 linkedStageCount); void (*SetProgramBindings)(const MGPProgramBindings* hdr, const Int32* blockBindings, const MGPProgramSamplerUnit* samplerUnits, const MGPProgramStorageOverride* storageOverrides, @@ -124,7 +122,11 @@ namespace MobileGL::MG_Pipe { // Which arm is installed, so a case can assert the arm it thinks it is testing rather than // trusting `MOBILEGL_TRANSPORT`. `MG_Config::Transport` says what was ASKED for; this says // what the table actually does. - enum class MGPipeRouteArm : Uint8 { kNone, kMonolith, kClientWire }; + enum class MGPipeRouteArm : Uint8 { + kNone, + kMonolith, + kClientWire + }; MGPipeRouteArm MGPipeInstalledArm(); void MGPipeNoteInstalledArm(MGPipeRouteArm arm); @@ -151,8 +153,7 @@ namespace MobileGL::MG_Pipe { // latch clear, so later records keep waiting for the real answer. inline Bool MGPipeResourceRespecifyWantsItsReply(const MGPResourceDesc& desc) { if (MGPipeInstalledArm() != MGPipeRouteArm::kClientWire) return true; - if (static_cast(desc.Target) == MGPipeResourceTarget::Buffer) - return false; + if (static_cast(desc.Target) == MGPipeResourceTarget::Buffer) return false; return !MGPipeHandleIsPublished(MGPipeKind::Texture, desc.Resource); } inline Bool MGPipeRouteResourceRespecify(const MGPResourceDesc& desc, const void* initialBytes, @@ -162,8 +163,7 @@ namespace MobileGL::MG_Pipe { inline void MGPipeRouteResourceDestroy(const MGPHandleOnly& handle) { MGP_ResourceDestroy(&handle); } - inline void* MGPipeRouteMapPersistent(const MGPHandleOnly& handle, Uint64 size, - const void* seedBytes) { + inline void* MGPipeRouteMapPersistent(const MGPHandleOnly& handle, Uint64 size, const void* seedBytes) { return gMGPipeRouteEscapes.MapPersistent(&handle, size, seedBytes); } inline void MGPipeRouteUnmapPersistent(const MGPHandleOnly& handle) { @@ -190,8 +190,7 @@ namespace MobileGL::MG_Pipe { return record.Target != kMGPipeResourceTargetBuffer; } - inline Bool MGPipeRouteResourceSubData(const MGPSubData& record, const void* bytes, - Uint64 byteCount, + inline Bool MGPipeRouteResourceSubData(const MGPSubData& record, const void* bytes, Uint64 byteCount, const MGPSubRegion* regions = nullptr) { // THE SLOT IS STILL MINTED AND STILL TAKEN, even for the buffer half. The row keeps // kReplySlot on the wire, the server still posts, and the mailbox's "every minted slot @@ -206,8 +205,7 @@ namespace MobileGL::MG_Pipe { if (MGPipeInstalledArm() == MGPipeRouteArm::kClientWire) return accepted; return MGPipeSubDataWantsItsReply(record) ? accepted : true; } - inline void MGPipeRouteBufferSubDataResident(const MGPSubData& record, const void* bytes, - Uint64 byteCount) { + inline void MGPipeRouteBufferSubDataResident(const MGPSubData& record, const void* bytes, Uint64 byteCount) { MGP_BufferSubDataResident(&record, bytes, byteCount); } inline void MGPipeRouteResourceFlushRange(const MGPFlushRange& record, const void* bytes) { @@ -223,8 +221,7 @@ namespace MobileGL::MG_Pipe { } // ---- render state ------------------------------------------------------------------ - inline void MGPipeRouteCreateRenderState(const MGPRenderStateDesc& desc, - const void* chunkBytes) { + inline void MGPipeRouteCreateRenderState(const MGPRenderStateDesc& desc, const void* chunkBytes) { MGP_CreateRenderState(&desc, chunkBytes, desc.Blob.Size); } inline void MGPipeRouteBindRenderState(const MGPBindRenderState& bind) { @@ -239,7 +236,9 @@ namespace MobileGL::MG_Pipe { inline void MGPipeRouteSetPixelPackState(const MGPPixelPackState& pack) { MGP_SetPixelPackState(&pack); } - inline void MGPipeRouteSetPatchState(const MGPPatchState& patch) { MGP_SetPatchState(&patch); } + inline void MGPipeRouteSetPatchState(const MGPPatchState& patch) { + MGP_SetPatchState(&patch); + } // P5c (rv, CONTRACT-P5C.md §5.3): emitted ONLY with an active transport (PipeFill.cpp // gates on it); under monolith the row is never produced and the fields keep coming // through the residual fill (G1). The row IS an ordinary routed set_* otherwise - the @@ -248,14 +247,12 @@ namespace MobileGL::MG_Pipe { inline void MGPipeRouteSetContextValues(const MGPContextValues& values) { MGP_SetContextValues(&values); } - inline void MGPipeRouteSetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, - const MGPAttribValue* tail) { + inline void MGPipeRouteSetVertexAttribDefaults(const MGPVertexAttribDefaults& hdr, const MGPAttribValue* tail) { MGP_SetVertexAttribDefaults(&hdr, tail, hdr.Count); } // ---- vertex input ------------------------------------------------------------------ - inline void MGPipeRouteCreateVertexElements(const MGPVertexElements& desc, - const void* blobBytes) { + inline void MGPipeRouteCreateVertexElements(const MGPVertexElements& desc, const void* blobBytes) { MGP_CreateVertexElements(&desc, blobBytes, desc.Blob.Size); } inline void MGPipeRouteBindVertexElements(const MGPHandleOnly& handle) { @@ -264,8 +261,7 @@ namespace MobileGL::MG_Pipe { inline void MGPipeRouteDeleteVertexElements(const MGPHandleOnly& handle) { MGP_DeleteVertexElements(&handle); } - inline void MGPipeRouteSetVertexBuffers(const MGPVertexBuffers& hdr, - const MGPVertexBuffer* tail) { + inline void MGPipeRouteSetVertexBuffers(const MGPVertexBuffers& hdr, const MGPVertexBuffer* tail) { MGP_SetVertexBuffers(&hdr, tail, hdr.Count); } inline void MGPipeRouteSetIndexBuffer(const MGPIndexBuffer& record) { @@ -278,8 +274,7 @@ namespace MobileGL::MG_Pipe { } // The parameters row declares Size 0 today and table 1 row 17 puts it under rule A, so // the length is stated here once, from the type the applier stores by value. - inline void MGPipeRouteCreateSamplerState(const MGPSamplerDesc& desc, - const SamplerParameters* parameters) { + inline void MGPipeRouteCreateSamplerState(const MGPSamplerDesc& desc, const SamplerParameters* parameters) { MGP_CreateSamplerState(&desc, parameters, sizeof(SamplerParameters)); } inline void MGPipeRouteDeleteSamplerState(const MGPHandleOnly& handle) { @@ -319,10 +314,9 @@ namespace MobileGL::MG_Pipe { } // ---- programs ---------------------------------------------------------------------- - inline void MGPipeRouteCreateShaderState(const MGPProgramDesc& desc, - const MG_State::GLState::LinkArtifacts* link, - const MG_State::GLState::SpirvArtifacts* spirv, - const Uint32* linkedStages, Uint32 linkedStageCount) { + inline void MGPipeRouteCreateShaderState(const MGPProgramDesc& desc, const MG_State::GLState::LinkArtifacts* link, + const MG_State::GLState::SpirvArtifacts* spirv, const Uint32* linkedStages, + Uint32 linkedStageCount) { gMGPipeRouteEscapes.CreateShaderState(&desc, link, spirv, linkedStages, linkedStageCount); } inline void MGPipeRouteBindShaderState(const MGPHandleOnly& handle) { @@ -337,16 +331,14 @@ namespace MobileGL::MG_Pipe { inline void MGPipeRouteSetDispatchProgram(const MGPHandleOnly& handle) { MGP_SetDispatchProgram(&handle); } - inline void MGPipeRouteSetGlobalConstants(const MGPGlobalConstants& record, const void* bytes, - Uint64 byteCount) { + inline void MGPipeRouteSetGlobalConstants(const MGPGlobalConstants& record, const void* bytes, Uint64 byteCount) { MGP_SetGlobalConstants(&record, bytes, byteCount); } // P5e (pg), opcode 80. The fifth escape; see MGPipeRouteEscapes above for why this row has // no generated shape. `storageOverrideNames` is index-aligned with `storageOverrides` and // is what the applier copies into the record - the MGHostSpan inside each override element // describes where the name lives on the WIRE and is resolved by the decoder, never here. - inline void MGPipeRouteSetProgramBindings(const MGPProgramBindings& hdr, - const Int32* blockBindings, + inline void MGPipeRouteSetProgramBindings(const MGPProgramBindings& hdr, const Int32* blockBindings, const MGPProgramSamplerUnit* samplerUnits, const MGPProgramStorageOverride* storageOverrides, const char* const* storageOverrideNames) { @@ -355,4 +347,3 @@ namespace MobileGL::MG_Pipe { } } // namespace MobileGL::MG_Pipe - diff --git a/MobileGL/MG_Pipe/PipeSessionFail.h b/MobileGL/MG_Pipe/PipeSessionFail.h index 17d377e0..15f13799 100644 --- a/MobileGL/MG_Pipe/PipeSessionFail.h +++ b/MobileGL/MG_Pipe/PipeSessionFail.h @@ -158,7 +158,9 @@ namespace MobileGL::MG_Pipe { bool MGPipeDebugSessionLossDue(unsigned clientPid); #else // P13 W5: the device-loss knobs end a SESSION; a library without a transport has none to end. - inline bool MGPipeDebugDeviceLossDue() { return false; } + inline bool MGPipeDebugDeviceLossDue() { + return false; + } #endif } // namespace MobileGL::MG_Pipe diff --git a/MobileGL/MG_Pipe/PipeVerbSink.h b/MobileGL/MG_Pipe/PipeVerbSink.h index 794a54c1..d1e79070 100644 --- a/MobileGL/MG_Pipe/PipeVerbSink.h +++ b/MobileGL/MG_Pipe/PipeVerbSink.h @@ -105,9 +105,7 @@ namespace MobileGL::MG_Pipe { (void)replies; return false; } - virtual Bool OnGetTextureImage(const MGPReadbackInfo&, Uint64, MGPipeReplySink*) { - return false; - } + virtual Bool OnGetTextureImage(const MGPReadbackInfo&, Uint64, MGPipeReplySink*) { return false; } // P9 (CONTRACT-P9.md §1): the same two reads with a pack buffer as the destination. No // seq and no sink - nothing is answered; the rows land in `Dst` on this side. virtual Bool OnReadPixelsToBuffer(const MGPReadbackToBuffer&) { return false; } @@ -118,8 +116,7 @@ namespace MobileGL::MG_Pipe { // two is non-null. The span is VALIDATED (all four R-2 arms, the segment-range one // included) and names a SEG_STAGE run the client staged; the sink resolves it through // MGPipeHostBytes and never holds the pointer past its return (rule C). - virtual Bool OnDrawVbo(const MGPDrawInfo& info, const MGPDrawRange* ranges, - const MGHostSpan* userIndices, + virtual Bool OnDrawVbo(const MGPDrawInfo& info, const MGPDrawRange* ranges, const MGHostSpan* userIndices, const MGPDrawIndirect* indirect) { (void)info; (void)ranges; @@ -151,8 +148,7 @@ namespace MobileGL::MG_Pipe { } // `name` is the NUL-terminated block name the decoder copied out of the record's // SEG_STAGE blob; valid for the call only. - virtual Bool OnSetStorageBlockBinding(const MGPStorageBlockBinding& binding, - const char* name) { + virtual Bool OnSetStorageBlockBinding(const MGPStorageBlockBinding& binding, const char* name) { (void)binding; (void)name; return false; @@ -225,8 +221,7 @@ namespace MobileGL::MG_Pipe { // ---- shared images, opcode 85 (docs/archive/notes/anland/plan-ahb-dmabuf.md). // `seq` is the record's reply-slot id, which an Import's descriptor names on the aux // socket. True fills `reply`; false is a DECLINED answer. - virtual Bool OnSharedImage(const MGPSharedImageOp& op, Uint64 seq, - MGPSharedImageReply& reply) { + virtual Bool OnSharedImage(const MGPSharedImageOp& op, Uint64 seq, MGPSharedImageReply& reply) { (void)op; (void)seq; (void)reply; diff --git a/MobileGL/MG_Pipe/PipeWireLayout.h b/MobileGL/MG_Pipe/PipeWireLayout.h index 98005cc1..4aed72a8 100644 --- a/MobileGL/MG_Pipe/PipeWireLayout.h +++ b/MobileGL/MG_Pipe/PipeWireLayout.h @@ -39,8 +39,8 @@ namespace MobileGL::MG_Pipe { } return hash; } - inline constexpr Uint64 kMGPipeWireMemberLayoutDigest = WireMemberLayoutDigest( - kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember)); + inline constexpr Uint64 kMGPipeWireMemberLayoutDigest = + WireMemberLayoutDigest(kMGPipeWireLayoutMembers, sizeof(kMGPipeWireLayoutMembers) / sizeof(WireLayoutMember)); // Bump when a fixed-layout pipe carrier keeps its bytes but changes their meaning. The // mutable-level extent carrier reuses BufOffset/BufSize only on named image respecifies. // 2 (P14): bind_vertex_elements re-applies the array's kept vertex-buffer and index @@ -62,7 +62,8 @@ namespace MobileGL::MG_Pipe { constexpr Uint64 WireRenderStateDigest() { Uint64 hash = 1469598103934665603ull; WireMixNumber(hash, kMGPipeRenderStateChunkTableVersion); - for (SizeT boundary : kMGPipeRenderStateChunkBoundaries) WireMixNumber(hash, boundary); + for (SizeT boundary : kMGPipeRenderStateChunkBoundaries) + WireMixNumber(hash, boundary); return hash; } } // namespace MobileGL::MG_Pipe diff --git a/MobileGL/MG_Pipe/VerbRecordOps.h b/MobileGL/MG_Pipe/VerbRecordOps.h index f8afca81..4b11b059 100644 --- a/MobileGL/MG_Pipe/VerbRecordOps.h +++ b/MobileGL/MG_Pipe/VerbRecordOps.h @@ -138,7 +138,8 @@ namespace MobileGL::MG_Pipe { constexpr auto MakeTable() { Array(MGPipeVerb::kVerbCount)> table{}; - for (const auto& row : kRows) table[static_cast(row.Verb)] = row.Op; + for (const auto& row : kRows) + table[static_cast(row.Verb)] = row.Op; return table; } inline constexpr auto kTable = MakeTable(); diff --git a/MobileGL/MG_Remote/CapsCodec.cpp b/MobileGL/MG_Remote/CapsCodec.cpp index 28d86678..4c192c0b 100644 --- a/MobileGL/MG_Remote/CapsCodec.cpp +++ b/MobileGL/MG_Remote/CapsCodec.cpp @@ -144,9 +144,7 @@ namespace MobileGL::MG_Remote { } // How many elements of `elementBytes` could still possibly be there. The gate a // length-prefixed array is held to before it reserves anything. - Uint64 RoomFor(Uint64 elementBytes) const { - return elementBytes == 0 ? 0 : m_left / elementBytes; - } + Uint64 RoomFor(Uint64 elementBytes) const { return elementBytes == 0 ? 0 : m_left / elementBytes; } Bool Ok() const { return m_ok; } Uint64 Left() const { return m_left; } // Trailing bytes the format does not account for are a REFUSAL: they mean the @@ -244,8 +242,8 @@ namespace MobileGL::MG_Remote { return false; } if (version != kFormatCapabilitiesCodecVersion) { - MGLOG_E("MG_Remote caps: format-capability blob is version %u, this build reads %u", - version, kFormatCapabilitiesCodecVersion); + MGLOG_E("MG_Remote caps: format-capability blob is version %u, this build reads %u", version, + kFormatCapabilitiesCodecVersion); return false; } if (targets != MG_Backend::kFormatCapabilityTargetCount || @@ -253,8 +251,7 @@ namespace MobileGL::MG_Remote { // Not a corrupt stream: a peer whose TextureTarget or TextureInternalFormat enum // is a different size. Reading it anyway shears every cell onto a neighbouring // format, which is exactly the failure the ABI fingerprint exists to make loud. - MGLOG_E("MG_Remote caps: format-capability blob is %ux%u, this build is %llux%llu", - targets, formats, + MGLOG_E("MG_Remote caps: format-capability blob is %ux%u, this build is %llux%llu", targets, formats, static_cast(MG_Backend::kFormatCapabilityTargetCount), static_cast(MG_Backend::kFormatCapabilityFormatCount)); return false; @@ -278,8 +275,7 @@ namespace MobileGL::MG_Remote { return false; } table[index / MG_Backend::kFormatCapabilityFormatCount] - [index % MG_Backend::kFormatCapabilityFormatCount] = - FormatCapabilityFlags(raw); + [index % MG_Backend::kFormatCapabilityFormatCount] = FormatCapabilityFlags(raw); } return true; }; @@ -460,9 +456,8 @@ namespace MobileGL::MG_Remote { inputs.ProgramArtifactsSchema = MG_State::GLState::ProgramArtifactsSchemaFingerprint(); inputs.OpCount = static_cast(MG_Pipe::MGPWireOp::kOpCount); #if MOBILEGL_BUILD_DISAGGREGATED - inputs.ControlSchemaRevision = - (static_cast(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) | - MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision; + inputs.ControlSchemaRevision = (static_cast(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) | + MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision; #else // The pull build has no respecify extent carrier; preserve its pre-P7 wire fingerprint. inputs.ControlSchemaRevision = MOBILEGL_PROTOCOL_CONTROL_REVISION; @@ -479,8 +474,14 @@ namespace MobileGL::MG_Remote { return fingerprint; } // Compatibility spelling for existing wire tests and helper peers. - Uint64 CapsAbiFingerprint() { return WireFingerprint(); } - const char* BuildFingerprint() { return MOBILEGL_BUILD_STAMP_VALUE; } - Bool BuildFingerprintPresent() { return MOBILEGL_BUILD_STAMP_PRESENT != 0; } + Uint64 CapsAbiFingerprint() { + return WireFingerprint(); + } + const char* BuildFingerprint() { + return MOBILEGL_BUILD_STAMP_VALUE; + } + Bool BuildFingerprintPresent() { + return MOBILEGL_BUILD_STAMP_PRESENT != 0; + } } // namespace MobileGL::MG_Remote diff --git a/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp b/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp index 804698bb..e879d23f 100644 --- a/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp +++ b/MobileGL/MG_Remote/Client/AdoptTierChoice.cpp @@ -34,16 +34,16 @@ namespace MobileGL::MG_Remote::Client { // `dl` (CONTRACT-P6 5.2): the family word every death here carries. if (tier <= 1) { SessionFail(MGFatalFamily::UnimplementedAdoptTier, - "MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u " - "reached a map_persistent that no handshake settled; T0 and T1 are not " - "implemented and only T2 (emulate) runs.", - static_cast(tier), static_cast(tier)); + "MGPipe: Fatal{UnimplementedAdoptTier, \"T%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u " + "reached a map_persistent that no handshake settled; T0 and T1 are not " + "implemented and only T2 (emulate) runs.", + static_cast(tier), static_cast(tier)); } else { SessionFail(MGFatalFamily::UnimplementedAdoptTier, - "MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is " - "not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the " - "P5 default).", - static_cast(tier), static_cast(tier)); + "MGPipe: Fatal{UnimplementedAdoptTier, \"%u\"} - MOBILEGL_IPC_ADOPT_TIER=%u is " + "not an adoption tier; the only values are 0 and 1 (P11) and 2 (emulate, the " + "P5 default).", + static_cast(tier), static_cast(tier)); } } } // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp index dae3fbb7..ba78701f 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp @@ -25,7 +25,7 @@ // library but never constructs one. namespace MobileGL::MG_Backend { extern UniquePtr& pActiveBackendObject; -} +} // namespace MobileGL::MG_Backend namespace MobileGL::MG_Remote::Client { @@ -134,7 +134,9 @@ namespace MobileGL::MG_Remote::Client { return true; } - Bool BackendObject_Remote::InitPbufferSurface(EGLint, EGLint) { return true; } + Bool BackendObject_Remote::InitPbufferSurface(EGLint, EGLint) { + return true; + } const RendererInfo& BackendObject_Remote::GetRendererInfo() const { // TRAP 1: a reference, so the storage is the mirror's and not a temporary's. @@ -192,8 +194,7 @@ namespace MobileGL::MG_Remote::Client { return MG_Backend::BackendObject::InitializeEGLDisplay(dpy, major, minor); } - Bool BackendObject_Remote::CreateEGLWindowSurface(EGLSurface surface, - const MG_Backend::WindowHandle& handle) { + Bool BackendObject_Remote::CreateEGLWindowSurface(EGLSurface surface, const MG_Backend::WindowHandle& handle) { // The handle first: the server's backend has to know which window it is about to make // a surface for, and ServerSetWindowHandle is the only way to tell it. WaitForApplyBeforeEglForwarder("CreateEGLWindowSurface"); @@ -237,7 +238,8 @@ namespace MobileGL::MG_Remote::Client { if (!reply.ok) { MGLOG_E("MG_Remote client: the server could not resize the server-owned window surface to %ux%u " "(channel rc=%d, refusal %s)", - width, height, static_cast(reply.transport), Server::SurfaceRefusalCodeName(reply.refusal)); + width, height, static_cast(reply.transport), + Server::SurfaceRefusalCodeName(reply.refusal)); return false; } NoteServerOwnedRequest(surface, width, height); @@ -384,8 +386,7 @@ namespace MobileGL::MG_Remote::Client { return true; } - Bool BackendObject_Remote::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, - EGLContext ctx) { + Bool BackendObject_Remote::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { // FORWARD FIRST, THEN RUN THE BASE. The server has to own the context before the base // class latches "the surface is initialised" and calls InitCapabilities, because // InitCapabilities' answer comes from a snapshot the server can only publish once its @@ -492,7 +493,8 @@ namespace MobileGL::MG_Remote::Client { const std::lock_guard lock(m_eglStateMutex); ResetEGLRuntimeState(); m_homedSurfaces.clear(); - for (const auto& [id, references] : m_sessionSharedImages) m_lostSessionSharedImages.insert(id); + for (const auto& [id, references] : m_sessionSharedImages) + m_lostSessionSharedImages.insert(id); m_sessionSharedImages.clear(); // The display the application initialized is initialized on the new server too: its // surfaces and contexts are created against it from here on. @@ -634,7 +636,8 @@ namespace MobileGL::MG_Remote::Client { op.Damage[MG_Pipe::kMGPImportPlane1Pitch] = static_cast(layout.Pitch[1]); op.Damage[MG_Pipe::kMGPImportYuvColorSpace] = static_cast(layout.ColorSpace); op.Damage[MG_Pipe::kMGPImportYuvRange] = static_cast(layout.Range); - op.Damage[MG_Pipe::kMGPImportYuvSiting] = static_cast((layout.SitingX & 0xffffu) | (layout.SitingY << 16)); + op.Damage[MG_Pipe::kMGPImportYuvSiting] = + static_cast((layout.SitingX & 0xffffu) | (layout.SitingY << 16)); op.Damage[MG_Pipe::kMGPImportModifierLo] = static_cast(static_cast(layout.Modifier)); op.Damage[MG_Pipe::kMGPImportModifierHi] = static_cast(static_cast(layout.Modifier >> 32)); op.Damage[MG_Pipe::kMGPImportHasModifier] = layout.HasModifier ? 1 : 0; diff --git a/MobileGL/MG_Remote/Client/CapsMirror.cpp b/MobileGL/MG_Remote/Client/CapsMirror.cpp index cb3c3bc4..810fa2fe 100644 --- a/MobileGL/MG_Remote/Client/CapsMirror.cpp +++ b/MobileGL/MG_Remote/Client/CapsMirror.cpp @@ -18,7 +18,7 @@ #include "../CapsCodec.h" #include "../FatalFunnel.h" -#include // MG_Config::SplitTransportRequestedByConfig +#include // MG_Config::SplitTransportRequestedByConfig #include // MGPipeServerArm, D1c's role predicate #include #include @@ -69,8 +69,7 @@ namespace MobileGL::MG_Remote::Client { // re-derived here from this process's switch, on every adoption. void ReconcileClientCompileExtensions(RendererInfo& renderer) { Vector& extensions = renderer.RendererGLInfo.Extensions; - const auto found = - std::find(extensions.begin(), extensions.end(), E_GL_KHR_parallel_shader_compile); + const auto found = std::find(extensions.begin(), extensions.end(), E_GL_KHR_parallel_shader_compile); const Bool advertised = found != extensions.end(); const Bool compilesAsync = MG_Util::Async::AsyncShaderCompileEnabled(); if (advertised == compilesAsync) return; @@ -86,8 +85,7 @@ namespace MobileGL::MG_Remote::Client { } // namespace void CapsMirror::Adopt(const MG_Pipe::MGPCaps& caps, const MG_Backend::FormatCapabilityCache& formats, - const RendererInfo& renderer, const String& apiVersion, - BackendType backend) { + const RendererInfo& renderer, const String& apiVersion, BackendType backend) { m_caps = caps; // The two blobrefs inside MGPCaps name SEG_STAGE runs that belong to the SERVER's // encoder and are meaningless on this side; the decoded objects beside them are the @@ -123,10 +121,16 @@ namespace MobileGL::MG_Remote::Client { if (g_capsAdoptedHook != nullptr) g_capsAdoptedHook(); } - void SetCapsAdoptedHook(CapsAdoptedHook hook) { g_capsAdoptedHook = hook; } + void SetCapsAdoptedHook(CapsAdoptedHook hook) { + g_capsAdoptedHook = hook; + } - Bool CapsMirror::Valid() const { return m_generation != 0; } - Uint64 CapsMirror::Generation() const { return m_generation; } + Bool CapsMirror::Valid() const { + return m_generation != 0; + } + Uint64 CapsMirror::Generation() const { + return m_generation; + } const RendererInfo& CapsMirror::Renderer() const { WarnPlaceholder("Renderer", m_generation); @@ -153,7 +157,9 @@ namespace MobileGL::MG_Remote::Client { return m_backend; } - Uint64 CapsMirror::CallMask() const { return m_caps.CallMask; } + Uint64 CapsMirror::CallMask() const { + return m_caps.CallMask; + } Bool CapsMirror::HasCap(MG_Pipe::MGPCapBit bit) const { return (m_caps.CallMask & static_cast(bit)) != 0; @@ -178,8 +184,7 @@ namespace MobileGL::MG_Remote::Client { MGLOG_W("MG_Remote client: the server does not consume MGPipe subsystem 0x%llx - " "this family emits NOTHING and the legacy pull path runs for it " "(R-8). callMask=0x%llx, caps generation %llu", - static_cast(subsystemBit), - static_cast(m_caps.CallMask), + static_cast(subsystemBit), static_cast(m_caps.CallMask), static_cast(m_generation)); } } @@ -209,8 +214,7 @@ namespace MobileGL::MG_Remote::Client { // The wait is real only when somebody else is fetching the snapshot. ClientSession's // hook answers false when no bring-up is in flight and when the CALLING thread is the // one performing it - both of which mean nothing can land while this call blocks. - if (g_firstSnapshotWait != nullptr && g_firstSnapshotWait(kFirstSnapshotWaitMs) && - m_generation != 0) { + if (g_firstSnapshotWait != nullptr && g_firstSnapshotWait(kFirstSnapshotWaitMs) && m_generation != 0) { return; } // NOT MGLOG_W AND CARRY ON. The whole defect this closes is that carrying on was @@ -226,11 +230,12 @@ namespace MobileGL::MG_Remote::Client { "pull path on the client to take over. The session must be started - and its " "first CapsSnapshot adopted - before any frontend object of this family is " "born; MobileGL::Initialize does that before MG_State::Init()", - CapsSubsystemName(subsystemBit), - static_cast(subsystemBit)); + CapsSubsystemName(subsystemBit), static_cast(subsystemBit)); } - void SetCapsFirstSnapshotWait(CapsFirstSnapshotWait wait) { g_firstSnapshotWait = wait; } + void SetCapsFirstSnapshotWait(CapsFirstSnapshotWait wait) { + g_firstSnapshotWait = wait; + } Uint64 PublishedCapsGeneration() { return g_publishedGeneration.load(::std::memory_order_acquire); @@ -238,22 +243,34 @@ namespace MobileGL::MG_Remote::Client { const char* CapsSubsystemName(Uint64 subsystemBit) { switch (subsystemBit) { - case MG_Pipe::kMGPipeSubsystemResources: return "Resources"; - case MG_Pipe::kMGPipeSubsystemVertexInput: return "VertexInput"; - case MG_Pipe::kMGPipeSubsystemFramebuffer: return "Framebuffer"; - case MG_Pipe::kMGPipeSubsystemTextureResources: return "TextureResources"; - case MG_Pipe::kMGPipeSubsystemSamplers: return "Samplers"; - case MG_Pipe::kMGPipeSubsystemPrograms: return "Programs"; - case MG_Pipe::kMGPipeSubsystemBufferBindings: return "BufferBindings"; - default: break; + case MG_Pipe::kMGPipeSubsystemResources: + return "Resources"; + case MG_Pipe::kMGPipeSubsystemVertexInput: + return "VertexInput"; + case MG_Pipe::kMGPipeSubsystemFramebuffer: + return "Framebuffer"; + case MG_Pipe::kMGPipeSubsystemTextureResources: + return "TextureResources"; + case MG_Pipe::kMGPipeSubsystemSamplers: + return "Samplers"; + case MG_Pipe::kMGPipeSubsystemPrograms: + return "Programs"; + case MG_Pipe::kMGPipeSubsystemBufferBindings: + return "BufferBindings"; + default: + break; } // A bit with no word yet, or a mask of several. Named by its value rather than by a // guess: G14 lets the list grow, never lets a wrong word into a Fatal. return "unnamed-subsystem"; } - Uint64 ConsumerRefusals() { return g_consumerRefusals; } - Uint64 LastRefusedSubsystem() { return g_lastRefusedSubsystem; } + Uint64 ConsumerRefusals() { + return g_consumerRefusals; + } + Uint64 LastRefusedSubsystem() { + return g_lastRefusedSubsystem; + } void ResetConsumerRefusalsForTest() { g_consumerRefusals = 0; g_lastRefusedSubsystem = 0; diff --git a/MobileGL/MG_Remote/Client/CapsMirror.h b/MobileGL/MG_Remote/Client/CapsMirror.h index 5372dcbd..b4237bdc 100644 --- a/MobileGL/MG_Remote/Client/CapsMirror.h +++ b/MobileGL/MG_Remote/Client/CapsMirror.h @@ -28,8 +28,7 @@ namespace MobileGL::MG_Remote::Client { // Replaces the whole mirror and bumps Generation(). Called once per CapsSnapshot, // including the re-sends that mean "invalidate" (R-12). void Adopt(const MG_Pipe::MGPCaps& caps, const MG_Backend::FormatCapabilityCache& formats, - const RendererInfo& renderer, const String& apiVersion, - BackendType backend); + const RendererInfo& renderer, const String& apiVersion, BackendType backend); // False until the first snapshot. The placeholder answers below are still safe to // read - that is the point - but a caller that can wait should. diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index fe14c116..1b291f0e 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -201,8 +201,7 @@ namespace MobileGL::MG_Remote::Client { Bool m_outer = false; }; - MobileGLResult ReceiveEnvelope(Transport::ITransport& transport, std::vector& out, - Uint32 timeoutMs) { + MobileGLResult ReceiveEnvelope(Transport::ITransport& transport, std::vector& out, Uint32 timeoutMs) { Uint64 size = 0; MobileGLMutableByteSpan empty{nullptr, 0}; const MobileGLResult probe = transport.ReceiveFrame(empty, &size, timeoutMs); @@ -339,15 +338,15 @@ namespace MobileGL::MG_Remote::Client { Uint32 DrainEventRing(Transport::EventRingConsumer& events); Transport::SessionWait WaitForAppliedBudget(Transport::SessionProducer& producer, - Transport::EventRingConsumer& events, - Uint64 seq, Uint64 remainingMs) { + Transport::EventRingConsumer& events, Uint64 seq, + Uint64 remainingMs) { // The transport takes Uint32 milliseconds with UINT32_MAX meaning forever. // Keep even the largest GL timeout finite, using bounded chunks and preserving // the doorbell's immediate shutdown result. Avoid a giant chrono deadline too. constexpr Uint32 kMaxFiniteWaitMs = Transport::kWaitForever - 1; for (;;) { - const Uint32 chunkMs = remainingMs > kMaxFiniteWaitMs - ? kMaxFiniteWaitMs : static_cast(remainingMs); + const Uint32 chunkMs = + remainingMs > kMaxFiniteWaitMs ? kMaxFiniteWaitMs : static_cast(remainingMs); // P5e (ra, CONTRACT-P5E §2.6): THE PARK BREAKS ON TWO THINGS. A server that // ran out of SEG_EVENT stops producing and therefore stops applying, so a // waiter that could only be woken by appliedSeq would wait for a watermark @@ -358,8 +357,7 @@ namespace MobileGL::MG_Remote::Client { const auto wait = producer.WaitForAppliedOrEventBacklog(seq, chunkMs); if (wait == Transport::SessionWait::Reached) { const auto* progress = producer.Progress(); - if (progress == nullptr || - Transport::Watermark::Reached(progress->appliedSeq, seq)) { + if (progress == nullptr || Transport::Watermark::Reached(progress->appliedSeq, seq)) { return wait; } DrainEventRing(events); @@ -375,11 +373,16 @@ namespace MobileGL::MG_Remote::Client { const char* TransportModeName(MG_Config::TransportMode mode) { switch (mode) { - case MG_Config::TransportMode::Monolith: return "monolith"; - case MG_Config::TransportMode::InProcess: return "inproc"; - case MG_Config::TransportMode::Spawn: return "spawn"; - case MG_Config::TransportMode::UnixSocket: return "unix:"; - case MG_Config::TransportMode::NamedPipe: return "pipe:"; + case MG_Config::TransportMode::Monolith: + return "monolith"; + case MG_Config::TransportMode::InProcess: + return "inproc"; + case MG_Config::TransportMode::Spawn: + return "spawn"; + case MG_Config::TransportMode::UnixSocket: + return "unix:"; + case MG_Config::TransportMode::NamedPipe: + return "pipe:"; } return "?"; } @@ -395,10 +398,8 @@ namespace MobileGL::MG_Remote::Client { auto& defaultFBOInfo = MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo; if (!defaultFBOInfo) return; const auto format = static_cast(info.InternalFormat); - auto* colorTex = - static_cast(defaultFBOInfo->colorAttachment.get()); - auto* depthTex = - static_cast(defaultFBOInfo->depthAttachment.get()); + auto* colorTex = static_cast(defaultFBOInfo->colorAttachment.get()); + auto* depthTex = static_cast(defaultFBOInfo->depthAttachment.get()); auto* stencilTex = static_cast(defaultFBOInfo->stencilAttachment.get()); if (info.Width != 0 && info.Height != 0) { @@ -408,8 +409,7 @@ namespace MobileGL::MG_Remote::Client { // FramebufferObject::CheckCompleteness requires every attachment to agree. const Int extentWidth = static_cast(info.Width); const Int extentHeight = static_cast(info.Height); - const SizeT attachmentByteSize = - static_cast(info.Width) * static_cast(info.Height) * 4; + const SizeT attachmentByteSize = static_cast(info.Width) * static_cast(info.Height) * 4; if (colorTex != nullptr) { colorTex->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{extentWidth, extentHeight, 1}, attachmentByteSize}); @@ -464,20 +464,21 @@ namespace MobileGL::MG_Remote::Client { Bool corrupt = false; while (events.Pop(view, &corrupt)) { if (corrupt) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"event-ring\"} - the reverse " - "channel's record stream is corrupt"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"event-ring\"} - the reverse " + "channel's record stream is corrupt"); } switch (view.kind) { case Transport::kEventBufferWriteback: { if (view.payloadSize < sizeof(Transport::EventBufferWritebackHead)) break; - const auto* head = - static_cast(view.payload); + const auto* head = static_cast(view.payload); const void* bytes = static_cast(view.payload) + sizeof(*head); if (view.payloadSize - sizeof(*head) < head->Size) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"buffer-writeback\"} - the " - "head declares %llu inline bytes and the record carries %llu", - static_cast(head->Size), - static_cast(view.payloadSize - sizeof(*head))); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"buffer-writeback\"} - the " + "head declares %llu inline bytes and the record carries %llu", + static_cast(head->Size), + static_cast(view.payloadSize - sizeof(*head))); } // The blobref names SEG_EVENT and the IN-SEGMENT offset of those inline // bytes - never a host address (R-2's rule B), which is the whole reason @@ -488,37 +489,34 @@ namespace MobileGL::MG_Remote::Client { blob.Offset = events.OffsetInSegment(bytes); blob.Size = head->Size; MG_Pipe::MGPipeClientOnBufferWritebackAt( - MG_Pipe::MGPipeHandle{head->Resource.Slot, head->Resource.Gen}, - head->Offset, blob, head->ProducedAtSeq); + MG_Pipe::MGPipeHandle{head->Resource.Slot, head->Resource.Gen}, head->Offset, blob, + head->ProducedAtSeq); ++delivered; break; } case Transport::kEventGpuWritten: { if (view.payloadSize < sizeof(Transport::EventGpuWrittenHead)) break; - const auto* head = - static_cast(view.payload); + const auto* head = static_cast(view.payload); const Uint64 tail = view.payloadSize - sizeof(*head); if (tail / sizeof(Transport::EventRange) < head->RangeCount) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"gpu-written\"} - RangeCount " - "%u does not fit the record's %llu tail bytes", - static_cast(head->RangeCount), - static_cast(tail)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"gpu-written\"} - RangeCount " + "%u does not fit the record's %llu tail bytes", + static_cast(head->RangeCount), static_cast(tail)); } // EventRange and MGPRange are the same two Uint64s (EventRing.h asserts it next // to EventRange), so the tail is handed over as-is rather than copied into // a second array a later reader could get out of step with. const auto* ranges = reinterpret_cast( static_cast(view.payload) + sizeof(*head)); - MG_Pipe::MGPipeClientOnGpuWritten( - MG_Pipe::MGPipeHandle{head->Resource.Slot, head->Resource.Gen}, - static_cast(head->RangeCount), ranges); + MG_Pipe::MGPipeClientOnGpuWritten(MG_Pipe::MGPipeHandle{head->Resource.Slot, head->Resource.Gen}, + static_cast(head->RangeCount), ranges); ++delivered; break; } case Transport::kEventSurfaceChanged: { if (view.payloadSize < sizeof(Transport::EventSurfaceChangedHead)) break; - const auto* head = - static_cast(view.payload); + const auto* head = static_cast(view.payload); MG_Pipe::MGPSurfaceInfo info{}; info.Width = head->Width; info.Height = head->Height; @@ -535,22 +533,20 @@ namespace MobileGL::MG_Remote::Client { } case Transport::kEventGlError: { if (view.payloadSize < sizeof(Transport::EventGlErrorHead)) break; - const auto* head = - static_cast(view.payload); + const auto* head = static_cast(view.payload); const char* message = - reinterpret_cast(static_cast(view.payload) + - sizeof(*head)); + reinterpret_cast(static_cast(view.payload) + sizeof(*head)); const Uint64 tail = view.payloadSize - sizeof(*head); // MessageBytes == 0 is the corrupt shape, never "no message": the NUL // travels (CONTRACT-P5C §1, rule A's twin), so a legal record carries at // least one byte and that byte terminates the string. if (head->MessageBytes == 0 || tail < head->MessageBytes || message[head->MessageBytes - 1] != '\0') { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"kEventGlError\"} - " - "MessageBytes %u against a %llu-byte tail, or the terminating " - "NUL is missing", - static_cast(head->MessageBytes), - static_cast(tail)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"kEventGlError\"} - " + "MessageBytes %u against a %llu-byte tail, or the terminating " + "NUL is missing", + static_cast(head->MessageBytes), static_cast(tail)); } // The error queue is CLIENT state, written here on the GL thread. The // observation point is the next drain after the post - P9 owns the @@ -558,8 +554,7 @@ namespace MobileGL::MG_Remote::Client { if (MG_State::pGLContext != nullptr) { MG_State::pGLContext->RecordError( static_cast(head->Code), - MakeUnique( - String(message, static_cast(head->MessageBytes - 1)))); + MakeUnique(String(message, static_cast(head->MessageBytes - 1)))); } else { MGLOG_E_ONCE("MG_Remote client: a kEventGlError (code %u) arrived with " "no live context and is dropped", @@ -571,12 +566,12 @@ namespace MobileGL::MG_Remote::Client { case Transport::kEventFenceSignaled: { // P10 (CONTRACT-P10.md §1): remembered for every later poll of that fence. if (view.payloadSize < sizeof(Transport::EventFenceSignaledHead)) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"kEventFenceSignaled\"} - " - "a %llu-byte record is shorter than the fence handle it carries", - static_cast(view.payloadSize)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"kEventFenceSignaled\"} - " + "a %llu-byte record is shorter than the fence handle it carries", + static_cast(view.payloadSize)); } - const auto* head = - static_cast(view.payload); + const auto* head = static_cast(view.payload); NoteFenceSignaledByServer(MG_Pipe::MGPipeHandle{head->Fence.Slot, head->Fence.Gen}); ++delivered; break; @@ -610,8 +605,7 @@ namespace MobileGL::MG_Remote::Client { // rather than an ABI skew - which is why it is a refusal and not FatalAbiMismatch. MGLOG_E("MG_Remote client: CapsSnapshot carries %llu dynamic bytes, this build's " "struct is %llu - the snapshot is refused whole", - static_cast(dynamicBytes == nullptr ? 0 - : dynamicBytes->size()), + static_cast(dynamicBytes == nullptr ? 0 : dynamicBytes->size()), static_cast(sizeof(caps.Dynamic))); return false; } @@ -649,8 +643,7 @@ namespace MobileGL::MG_Remote::Client { static_cast(rawBackend)); return false; } - CapsMirrorInstance().Adopt(caps, formats, renderer, apiVersion, - static_cast(rawBackend)); + CapsMirrorInstance().Adopt(caps, formats, renderer, apiVersion, static_cast(rawBackend)); return true; } @@ -659,7 +652,9 @@ namespace MobileGL::MG_Remote::Client { // Null, not a Fatal: MG_Backend::Init() asks whether a session exists before it decides to // install the remote backend object, and that question has a legitimate "no" - it is the // monolith answer. Every call that PRESUMES a session aborts instead. - ClientSession* ClientSession::Active() { return g_active; } + ClientSession* ClientSession::Active() { + return g_active; + } Bool ClientSession::DeviceLost() { if (tl_wireMute != 0) return true; @@ -699,12 +694,22 @@ namespace MobileGL::MG_Remote::Client { return session != nullptr && session->m_deviceLost.load(std::memory_order_acquire); } - ClientSession::ScopedWireMute::ScopedWireMute() { ++tl_wireMute; } - ClientSession::ScopedWireMute::~ScopedWireMute() { --tl_wireMute; } - ClientSession::ScopedCurrentSessionWork::ScopedCurrentSessionWork() { ++tl_currentSessionWork; } - ClientSession::ScopedCurrentSessionWork::~ScopedCurrentSessionWork() { --tl_currentSessionWork; } + ClientSession::ScopedWireMute::ScopedWireMute() { + ++tl_wireMute; + } + ClientSession::ScopedWireMute::~ScopedWireMute() { + --tl_wireMute; + } + ClientSession::ScopedCurrentSessionWork::ScopedCurrentSessionWork() { + ++tl_currentSessionWork; + } + ClientSession::ScopedCurrentSessionWork::~ScopedCurrentSessionWork() { + --tl_currentSessionWork; + } - Uint64 ClientSession::SessionsStarted() { return g_remoteSessionsStarted.load(std::memory_order_acquire); } + Uint64 ClientSession::SessionsStarted() { + return g_remoteSessionsStarted.load(std::memory_order_acquire); + } void ClientSession::LatchDeviceLost(const char* why) { // exchange, so the log line is written EXACTLY once however many waits notice the same @@ -778,7 +783,8 @@ namespace MobileGL::MG_Remote::Client { return MOBILEGL_ERR_TRANSPORT_CLOSED; } const Uint32 lostServerPid = m_peerServerPid; - for (auto& [key, store] : m_t0Stores) LostSessionT0Stores().push_back(store); + for (auto& [key, store] : m_t0Stores) + LostSessionT0Stores().push_back(store); m_t0Stores.clear(); Stop(); const MobileGLResult started = StartSpawned(); @@ -826,8 +832,7 @@ namespace MobileGL::MG_Remote::Client { // split bring-up above MG_State::Init(). if (tl_bringingUpHere) return false; if (g_bringUpsInFlight.load(::std::memory_order_acquire) == 0) return false; - const auto deadline = - std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); while (std::chrono::steady_clock::now() < deadline) { if (PublishedCapsGeneration() != 0) return true; if (g_bringUpsInFlight.load(::std::memory_order_acquire) == 0) break; @@ -843,7 +848,8 @@ namespace MobileGL::MG_Remote::Client { ClientSession::ClientSession() { m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer); - m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn(); + m_cmd = &m_link->CommandsOut(); + m_events = &m_link->EventsIn(); // F1: installed from the constructor rather than from Start, because the question it // answers is asked by frontend object births that may precede any Start at all - and // the honest answer to those is the Fatal, not a silent false. @@ -856,7 +862,9 @@ namespace MobileGL::MG_Remote::Client { } } - Bool ClientSession::Started() const { return m_started; } + Bool ClientSession::Started() const { + return m_started; + } MobileGLResult ClientSession::Start(MG_Config::TransportMode mode, const String& endpoint) { // F1: the bring-up window opens HERE and not at FinishStartup, because under inproc the @@ -888,9 +896,8 @@ namespace MobileGL::MG_Remote::Client { return StartOverTransportPair(std::move(clientEnd), std::move(serverEnd)); } - MobileGLResult ClientSession::StartOverTransportPair( - std::unique_ptr clientEnd, - std::unique_ptr serverEnd) { + MobileGLResult ClientSession::StartOverTransportPair(std::unique_ptr clientEnd, + std::unique_ptr serverEnd) { const BringUpScope bringUp; // F1, and see Start() for why it opens this early. if (m_started) { return MOBILEGL_ERR_INVALID_ARGUMENT; @@ -913,21 +920,20 @@ namespace MobileGL::MG_Remote::Client { ::flatbuffers::FlatBufferBuilder builder(512); auto stamp = builder.CreateString(BuildFingerprint()); // P11 B2: the adopt-tier ask rides the proposal (LinkTerms.adoptTier, append-only). - auto terms = ::MobileGL::Wire::CreateLinkTerms( - builder, dataPlane, ::MobileGL::Wire::WireForm::StructImage, 0, 0, 0, 0, - static_cast(Transport::AdoptTierAskFor( - dataPlane == ::MobileGL::Wire::DataPlane::Stream))); + auto terms = ::MobileGL::Wire::CreateLinkTerms(builder, dataPlane, ::MobileGL::Wire::WireForm::StructImage, + 0, 0, 0, 0, + static_cast(Transport::AdoptTierAskFor( + dataPlane == ::MobileGL::Wire::DataPlane::Stream))); const char* tokenText = std::getenv("MOBILEGL_IPC_TOKEN"); auto token = builder.CreateString(tokenText == nullptr ? "" : tokenText); - auto hello = ::MobileGL::Wire::CreateHello( - builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp, - static_cast(MG_Config::ActiveBackendType), SelfProcessId(), 0, - fingerprint, fingerprint, terms, token, dial); - auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); + auto hello = + ::MobileGL::Wire::CreateHello(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp, + static_cast(MG_Config::ActiveBackendType), SelfProcessId(), 0, + fingerprint, fingerprint, terms, token, dial); + auto root = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); - const MobileGLResult sent = m_transport->SendFrame( - MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); + const MobileGLResult sent = + m_transport->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); if (sent != MOBILEGL_OK) { Stop(); return sent; @@ -968,14 +974,13 @@ namespace MobileGL::MG_Remote::Client { return MOBILEGL_ERR_PROTOCOL_MISMATCH; } const ::MobileGL::Wire::Welcome* welcome = envelope->msg_as_Welcome(); - const char* theirStamp = welcome->buildFingerprint() == nullptr - ? nullptr - : welcome->buildFingerprint()->c_str(); + const char* theirStamp = + welcome->buildFingerprint() == nullptr ? nullptr : welcome->buildFingerprint()->c_str(); // 4.3: the pid the SERVER stated, taken before any check can abort, so a Fatal line // can name it too. m_peerServerPid = static_cast(welcome->serverPid()); - const MobileGLResult compatible = ValidatePeerHandshake(*m_transport, - welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial); + const MobileGLResult compatible = ValidatePeerHandshake( + *m_transport, welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial); if (compatible != MOBILEGL_OK) { Stop(); return compatible; @@ -984,20 +989,20 @@ namespace MobileGL::MG_Remote::Client { if (terms == nullptr || terms->dataPlane() != dataPlane || terms->wireForm() != ::MobileGL::Wire::WireForm::StructImage || terms->cmdWindowBytes() < Transport::kMinRingCapacity || - (terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 || - terms->stageWindowBytes() == 0 || terms->maxReplyBytes() == 0 || + (terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 || terms->stageWindowBytes() == 0 || + terms->maxReplyBytes() == 0 || terms->maxReplyBytes() > 0xFFFFFFFFull - sizeof(Transport::ReplySlotHeader) || terms->eventWindowBytes() < Transport::kMinRingCapacity || (terms->eventWindowBytes() & (terms->eventWindowBytes() - 1)) != 0) { - const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "invalid server link terms"); + const auto result = + RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, "invalid server link terms"); Stop(); return result; } if (welcome->backendType() != static_cast(MG_Config::ActiveBackendType)) { - const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::Backend, - "server backend differs from request", static_cast(MG_Config::ActiveBackendType), - welcome->backendType()); + const auto result = RefuseHandshake( + *m_transport, ::MobileGL::Wire::RefuseCode::Backend, "server backend differs from request", + static_cast(MG_Config::ActiveBackendType), welcome->backendType()); Stop(); return result; } @@ -1009,7 +1014,8 @@ namespace MobileGL::MG_Remote::Client { terms->dataPlane() == ::MobileGL::Wire::DataPlane::Stream, Transport::AdoptTierSide::Client); if (terms->adoptTier() != adoptAsk) { const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "the server's adoptTier echo is not this client's ask", adoptAsk, terms->adoptTier()); + "the server's adoptTier echo is not this client's ask", adoptAsk, + terms->adoptTier()); Stop(); return result; } @@ -1023,20 +1029,17 @@ namespace MobileGL::MG_Remote::Client { // sides agree about the geometry at all - which is the assertion that would // otherwise first run in P6, on the day it is expensive to be wrong. using Slot = Transport::SessionSegmentSlot; - const auto agrees = [&](const ::MobileGL::Wire::SegmentRef* ref, Slot slot, - const char* name) { + const auto agrees = [&](const ::MobileGL::Wire::SegmentRef* ref, Slot slot, const char* name) { if (ref == nullptr || ref->sizeBytes() != server.Shm().AnnouncedSize(slot)) { MGLOG_E("MG_Remote client: Welcome's %s SegmentRef announces %llu bytes, the " "server mapped %llu", - name, - static_cast(ref == nullptr ? 0 : ref->sizeBytes()), + name, static_cast(ref == nullptr ? 0 : ref->sizeBytes()), static_cast(server.Shm().AnnouncedSize(slot))); return false; } return true; }; - if (!agrees(welcome->cmdRing(), Slot::Cmd, "cmd") || - !agrees(welcome->stageRing(), Slot::Stage, "stage") || + if (!agrees(welcome->cmdRing(), Slot::Cmd, "cmd") || !agrees(welcome->stageRing(), Slot::Stage, "stage") || !agrees(welcome->replyPool(), Slot::Reply, "reply") || !agrees(welcome->eventRing(), Slot::Event, "event")) { Stop(); @@ -1047,15 +1050,13 @@ namespace MobileGL::MG_Remote::Client { // ---- 5. attach to the four segments. Under `inproc` this is the SAME mapping booked // under the client role; under `spawn` it becomes ShmSegment::Adopt of the fds the // server passed by SCM_RIGHTS, which is why nothing below this line knows which it was. - const MobileGLResult attached = - m_link->Memory().AttachInProcess(server.Shm(), Transport::MemoryRole::Client); + const MobileGLResult attached = m_link->Memory().AttachInProcess(server.Shm(), Transport::MemoryRole::Client); if (attached != MOBILEGL_OK) { Stop(); return attached; } - return FinishStartup(&m_clientTransport->PeerDoorbell(), - &m_clientTransport->SelfDoorbell(), + return FinishStartup(&m_clientTransport->PeerDoorbell(), &m_clientTransport->SelfDoorbell(), /*startApplyThreadHere=*/true); } @@ -1120,10 +1121,9 @@ namespace MobileGL::MG_Remote::Client { const bool tcp = configured.compare(0, 6, "tcp://") == 0; const bool unixSocket = configured.compare(0, 5, "unix:") == 0; const std::string data(MG_Config::Ipc.Data.c_str()); - if ((!tcp && !unixSocket) || - (data != "auto" && data != (tcp ? "stream" : "shm"))) { - MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} unsupported control/data pair %s/%s", - configured.c_str(), data.c_str()); + if ((!tcp && !unixSocket) || (data != "auto" && data != (tcp ? "stream" : "shm"))) { + MGLOG_E("MG_Remote: Refuse{ProtocolMismatch} unsupported control/data pair %s/%s", configured.c_str(), + data.c_str()); return MOBILEGL_ERR_UNSUPPORTED; } m_connectDial = true; @@ -1136,9 +1136,8 @@ namespace MobileGL::MG_Remote::Client { m_dataEndpoint = tcp ? endpoint : std::string(); const auto started = StartOverSocket(std::move(socket)); if (started == MOBILEGL_OK) { - MGLOG_I("MG_Remote client: control=%s data=%s server=%s pid=%u dial=connect", - tcp ? "tcp" : "unix", tcp ? "stream" : "shm", - tcp ? endpoint.c_str() + 6 : endpoint.c_str(), m_peerServerPid); + MGLOG_I("MG_Remote client: control=%s data=%s server=%s pid=%u dial=connect", tcp ? "tcp" : "unix", + tcp ? "stream" : "shm", tcp ? endpoint.c_str() + 6 : endpoint.c_str(), m_peerServerPid); } return started; } @@ -1157,13 +1156,11 @@ namespace MobileGL::MG_Remote::Client { // refused by name 20 s later. The abstract namespace needs no directory, // no mode bits and no cleanup after a crash, and its reach - the network // namespace - is exactly one app's processes. - const std::string endpoint = std::string("@mgl-") + - std::to_string(static_cast(::getpid())) + "-" + + const std::string endpoint = std::string("@mgl-") + std::to_string(static_cast(::getpid())) + "-" + std::to_string(reinterpret_cast(this)); const MobileGLResult launched = - Server::LaunchServer(std::string(MG_Config::Ipc.ServerPath.c_str()), endpoint, - &m_spawned); + Server::LaunchServer(std::string(MG_Config::Ipc.ServerPath.c_str()), endpoint, &m_spawned); if (launched != MOBILEGL_OK) { return launched; } @@ -1197,8 +1194,7 @@ namespace MobileGL::MG_Remote::Client { return started; } - MobileGLResult ClientSession::StartOverSocket( - std::unique_ptr transport) { + MobileGLResult ClientSession::StartOverSocket(std::unique_ptr transport) { const BringUpScope bringUp; // F1, and see Start() for why it opens this early. if (m_started) { return MOBILEGL_ERR_INVALID_ARGUMENT; @@ -1215,27 +1211,26 @@ namespace MobileGL::MG_Remote::Client { // comparing it catches a stale MOBILEGL_IPC_SERVER_PATH (CONTRACT-P6 4.1). const auto dial = m_connectDial ? ::MobileGL::Wire::DialMode::Connect : ::MobileGL::Wire::DialMode::Fork; const auto dataPlane = m_socketTransport->IsTcp() ? ::MobileGL::Wire::DataPlane::Stream - : ::MobileGL::Wire::DataPlane::SharedSegments; + : ::MobileGL::Wire::DataPlane::SharedSegments; const Uint64 fingerprint = WireFingerprint(); { ::flatbuffers::FlatBufferBuilder builder(512); auto stamp = builder.CreateString(BuildFingerprint()); // P11 B2: the adopt-tier ask rides the proposal (LinkTerms.adoptTier, append-only). - auto terms = ::MobileGL::Wire::CreateLinkTerms( - builder, dataPlane, ::MobileGL::Wire::WireForm::StructImage, 0, 0, 0, 0, - static_cast(Transport::AdoptTierAskFor( - dataPlane == ::MobileGL::Wire::DataPlane::Stream))); + auto terms = ::MobileGL::Wire::CreateLinkTerms(builder, dataPlane, ::MobileGL::Wire::WireForm::StructImage, + 0, 0, 0, 0, + static_cast(Transport::AdoptTierAskFor( + dataPlane == ::MobileGL::Wire::DataPlane::Stream))); const char* tokenText = std::getenv("MOBILEGL_IPC_TOKEN"); auto token = builder.CreateString(tokenText == nullptr ? "" : tokenText); - auto hello = ::MobileGL::Wire::CreateHello( - builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp, - static_cast(MG_Config::ActiveBackendType), SelfProcessId(), 0, - fingerprint, fingerprint, terms, token, dial); - auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); + auto hello = + ::MobileGL::Wire::CreateHello(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp, + static_cast(MG_Config::ActiveBackendType), SelfProcessId(), 0, + fingerprint, fingerprint, terms, token, dial); + auto root = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); - const MobileGLResult sent = m_transport->SendFrame( - MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); + const MobileGLResult sent = + m_transport->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); if (sent != MOBILEGL_OK) { Stop(); return sent; @@ -1254,8 +1249,7 @@ namespace MobileGL::MG_Remote::Client { const Uint32 replySlotCount = Transport::kDefaultReplySlotCount; { std::vector frame; - const MobileGLResult received = - ReceiveEnvelope(*m_transport, frame, ColdHandshakeBudgetMs()); + const MobileGLResult received = ReceiveEnvelope(*m_transport, frame, ColdHandshakeBudgetMs()); if (received != MOBILEGL_OK) { MGLOG_E("MG_Remote client: no Welcome from the spawned server within %u ms (rc=%d)", ColdHandshakeBudgetMs(), static_cast(received)); @@ -1275,14 +1269,13 @@ namespace MobileGL::MG_Remote::Client { return MOBILEGL_ERR_PROTOCOL_MISMATCH; } const ::MobileGL::Wire::Welcome* welcome = envelope->msg_as_Welcome(); - const char* theirStamp = welcome->buildFingerprint() == nullptr - ? nullptr - : welcome->buildFingerprint()->c_str(); + const char* theirStamp = + welcome->buildFingerprint() == nullptr ? nullptr : welcome->buildFingerprint()->c_str(); // 4.3: the pid the SERVER stated, taken before any check can abort, so a Fatal line // can name it too. m_peerServerPid = static_cast(welcome->serverPid()); - const MobileGLResult compatible = ValidatePeerHandshake(*m_transport, - welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial); + const MobileGLResult compatible = ValidatePeerHandshake( + *m_transport, welcome->abiMajor(), welcome->abiMinor(), welcome->wireFingerprint(), theirStamp, dial); if (compatible != MOBILEGL_OK) { Stop(); return compatible; @@ -1291,20 +1284,20 @@ namespace MobileGL::MG_Remote::Client { if (terms == nullptr || terms->dataPlane() != dataPlane || terms->wireForm() != ::MobileGL::Wire::WireForm::StructImage || terms->cmdWindowBytes() < Transport::kMinRingCapacity || - (terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 || - terms->stageWindowBytes() == 0 || terms->maxReplyBytes() == 0 || + (terms->cmdWindowBytes() & (terms->cmdWindowBytes() - 1)) != 0 || terms->stageWindowBytes() == 0 || + terms->maxReplyBytes() == 0 || terms->maxReplyBytes() > 0xFFFFFFFFull - sizeof(Transport::ReplySlotHeader) || terms->eventWindowBytes() < Transport::kMinRingCapacity || (terms->eventWindowBytes() & (terms->eventWindowBytes() - 1)) != 0) { - const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "invalid server link terms"); + const auto result = + RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, "invalid server link terms"); Stop(); return result; } if (welcome->backendType() != static_cast(MG_Config::ActiveBackendType)) { - const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::Backend, - "server backend differs from request", static_cast(MG_Config::ActiveBackendType), - welcome->backendType()); + const auto result = RefuseHandshake( + *m_transport, ::MobileGL::Wire::RefuseCode::Backend, "server backend differs from request", + static_cast(MG_Config::ActiveBackendType), welcome->backendType()); Stop(); return result; } @@ -1316,7 +1309,8 @@ namespace MobileGL::MG_Remote::Client { Transport::SettleAdoptTierAtHandshake(stream, Transport::AdoptTierSide::Client); if (terms->adoptTier() != adoptAsk) { const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "the server's adoptTier echo is not this client's ask", adoptAsk, terms->adoptTier()); + "the server's adoptTier echo is not this client's ask", adoptAsk, + terms->adoptTier()); Stop(); return result; } @@ -1330,9 +1324,9 @@ namespace MobileGL::MG_Remote::Client { // data connection this client could open; refused rather than guessed at. const auto* nonce = welcome->dataNonce(); if (nonce == nullptr || nonce->size() != Transport::kDataNonceBytes || m_dataEndpoint.empty()) { - const auto result = RefuseHandshake(*m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, - "stream Welcome carries no data nonce", Transport::kDataNonceBytes, - nonce == nullptr ? 0 : nonce->size()); + const auto result = RefuseHandshake( + *m_transport, ::MobileGL::Wire::RefuseCode::LinkTerms, "stream Welcome carries no data nonce", + Transport::kDataNonceBytes, nonce == nullptr ? 0 : nonce->size()); Stop(); return result; } @@ -1341,15 +1335,13 @@ namespace MobileGL::MG_Remote::Client { negotiatedSizes.CmdRingBytes = terms->cmdWindowBytes(); negotiatedSizes.StageBytes = terms->stageWindowBytes(); negotiatedSizes.EventRingBytes = terms->eventWindowBytes(); - negotiatedSizes.ReplyBytes = (terms->maxReplyBytes() + sizeof(Transport::ReplySlotHeader)) * - negotiatedSizes.ReplySlotCount; + negotiatedSizes.ReplyBytes = + (terms->maxReplyBytes() + sizeof(Transport::ReplySlotHeader)) * negotiatedSizes.ReplySlotCount; const ::MobileGL::Wire::SegmentRef* refs[4] = {welcome->cmdRing(), welcome->stageRing(), - welcome->replyPool(), - welcome->eventRing()}; + welcome->replyPool(), welcome->eventRing()}; for (int index = 0; index < 4; ++index) { if (refs[index] == nullptr || refs[index]->sizeBytes() == 0) { - MGLOG_E("MG_Remote client: Welcome SegmentRef %d is missing or zero-sized", - index); + MGLOG_E("MG_Remote client: Welcome SegmentRef %d is missing or zero-sized", index); Stop(); return MOBILEGL_ERR_PROTOCOL_MISMATCH; } @@ -1362,13 +1354,16 @@ namespace MobileGL::MG_Remote::Client { const auto opened = Transport::SocketTransport::ConnectDataConnection( m_dataEndpoint, kSpawnConnectTimeoutMs, MobileGLByteSpan{dataBind.data(), dataBind.size()}, &dataFd); if (opened != MOBILEGL_OK) { - MGLOG_E("MG_Remote client: could not open the data connection to %s (rc=%d)", - m_dataEndpoint.c_str(), static_cast(opened)); + MGLOG_E("MG_Remote client: could not open the data connection to %s (rc=%d)", m_dataEndpoint.c_str(), + static_cast(opened)); Stop(); return opened; } const auto attached = AttachStreamLink(dataFd, negotiatedSizes); - if (attached != MOBILEGL_OK) { Stop(); return attached; } + if (attached != MOBILEGL_OK) { + Stop(); + return attached; + } m_controlInbox = std::make_unique(*m_transport); Server::ServerLoopInstance().SetRemoteControlSink(&RemoteControlSinkThunk, this); return FinishStartup(&m_link->ConsumerBell(), &m_link->ProducerBell(), false); @@ -1404,18 +1399,17 @@ namespace MobileGL::MG_Remote::Client { int fd = -1; std::vector scratch(Transport::FdPassing::kMaxSidebandBytes); MobileGLMutableByteSpan span{scratch.data(), scratch.size()}; - const MobileGLResult got = - m_transport->ReceiveFd(&fd, span, &sidebandSize, kHandshakeTimeoutMs); + const MobileGLResult got = m_transport->ReceiveFd(&fd, span, &sidebandSize, kHandshakeTimeoutMs); if (got != MOBILEGL_OK || fd < 0) { - MGLOG_E("MG_Remote client: segment descriptor %d did not arrive (rc=%d)", - received, static_cast(got)); + MGLOG_E("MG_Remote client: segment descriptor %d did not arrive (rc=%d)", received, + static_cast(got)); closeAll(); Stop(); return got == MOBILEGL_OK ? MOBILEGL_ERR_PROTOCOL_MISMATCH : got; } if (sidebandSize != sizeof(Sideband)) { - MGLOG_E("MG_Remote client: segment offer %d carried %llu sideband bytes", - received, static_cast(sidebandSize)); + MGLOG_E("MG_Remote client: segment offer %d carried %llu sideband bytes", received, + static_cast(sidebandSize)); ::close(fd); closeAll(); Stop(); @@ -1438,8 +1432,8 @@ namespace MobileGL::MG_Remote::Client { // ---- adopt and map. The same Adopt + Map every inproc run has // exercised since P5; only the origin of the descriptor is new. - const MobileGLResult adopted = m_link->Memory().AdoptFromDescriptors( - fds, announced, replySlotCount, Transport::MemoryRole::Client); + const MobileGLResult adopted = + m_link->Memory().AdoptFromDescriptors(fds, announced, replySlotCount, Transport::MemoryRole::Client); if (adopted != MOBILEGL_OK) { closeAll(); Stop(); @@ -1486,8 +1480,7 @@ namespace MobileGL::MG_Remote::Client { return FinishStartup(m_socketPeerBell.get(), m_socketSelfBell.get(), false); } - MobileGLResult ClientSession::RemoteControlSinkThunk(void* user, - Server::SurfaceControlFrame& frame) { + MobileGLResult ClientSession::RemoteControlSinkThunk(void* user, Server::SurfaceControlFrame& frame) { return static_cast(user)->RunRemoteSurfaceControlFrame(frame); } @@ -1515,7 +1508,8 @@ namespace MobileGL::MG_Remote::Client { const auto seq = ++m_logFlushSeq; flatbuffers::FlatBufferBuilder builder(64); auto flush = ::MobileGL::Wire::CreateLogFlush(builder, seq, false); - auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::LogFlush, flush.Union()); + auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::LogFlush, flush.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); if (m_transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) { LatchDeviceLost("control stream closed during LogFlush"); @@ -1529,13 +1523,16 @@ namespace MobileGL::MG_Remote::Client { return; } if (received != MOBILEGL_OK) - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} no acknowledgement rc=%d", received); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, LogFlush} no acknowledgement rc=%d", received); const auto* reply = ParseEnvelope(bytes); if (!reply) - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} malformed acknowledgement"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, LogFlush} malformed acknowledgement"); if (const auto* ack = reply->msg_as_LogFlush()) { if (!ack->ack() || ack->seq() != seq) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, LogFlush} reply sequence mismatch"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, LogFlush} reply sequence mismatch"); } return; } @@ -1561,8 +1558,8 @@ namespace MobileGL::MG_Remote::Client { // live one: three of the twelve forwarders ride the frame channel // without a wire kind (CONTRACT-P6 §2), and a spawn client that // reaches one has found a gap rather than a transport error. - MGLOG_E("MG_Remote client: surface op kind %d cannot cross the wire (%s)", - static_cast(frame.kind), MG_Remote::SurfaceWireErrorName(encoded)); + MGLOG_E("MG_Remote client: surface op kind %d cannot cross the wire (%s)", static_cast(frame.kind), + MG_Remote::SurfaceWireErrorName(encoded)); return MOBILEGL_ERR_UNSUPPORTED; } @@ -1579,12 +1576,12 @@ namespace MobileGL::MG_Remote::Client { if (m_started && m_link && !m_link->Capabilities().PublishIsDelivery) { const auto submitted = m_producer.LastPublishedSeq(); m_producer.PublishAndNotify(submitted); - if (WaitForAppliedBudget(m_producer, *m_events, submitted, budgetMs) != - Transport::SessionWait::Reached) return MOBILEGL_ERR_TRANSPORT_CLOSED; + if (WaitForAppliedBudget(m_producer, *m_events, submitted, budgetMs) != Transport::SessionWait::Reached) + return MOBILEGL_ERR_TRANSPORT_CLOSED; DrainEventRing(*m_events); } - const MobileGLResult sent = m_transport->SendFrame( - MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); + const MobileGLResult sent = + m_transport->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); if (sent != MOBILEGL_OK) { return sent; } @@ -1611,13 +1608,14 @@ namespace MobileGL::MG_Remote::Client { std::vector reply; Uint32 waitMs = budgetMs; if (progressReports != 0) { - const auto spentMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - sentAt).count(); - waitMs = spentMs >= ceilingMs ? 0u : std::min(budgetMs, ceilingMs - static_cast(spentMs)); + const auto spentMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - sentAt) + .count(); + waitMs = + spentMs >= ceilingMs ? 0u : std::min(budgetMs, ceilingMs - static_cast(spentMs)); } // A zero wait is the ceiling, reached: answered exactly as an expired budget is. - const MobileGLResult received = - waitMs == 0 ? MOBILEGL_ERR_TIMEOUT : ReceiveControlFrame(reply, waitMs); + const MobileGLResult received = waitMs == 0 ? MOBILEGL_ERR_TIMEOUT : ReceiveControlFrame(reply, waitMs); if (received != MOBILEGL_OK) { // `dl` (D5b): THE EXPIRY IS NOT FATAL AND IT IS NOT ONE ANSWER. The transport // already distinguishes the two states this wait can end in, and the contract @@ -1650,8 +1648,8 @@ namespace MobileGL::MG_Remote::Client { "open. This is NOT device loss (CONTRACT-P6 5.4): a frozen or merely " "slow peer produces no hangup, and only a hangup may arm the latch. " "If this repeats, the server is wedged rather than gone.", - Server::SurfaceControlOpName(frame.kind), - static_cast(frame.seq), budgetMs, + Server::SurfaceControlOpName(frame.kind), static_cast(frame.seq), + budgetMs, coldStart ? "the cold-start budget, MOBILEGL_IPC_COLD_START_MS: the " "server may still have been bringing its backend up" : "the steady bound, MOBILEGL_IPC_CONTROL_TIMEOUT_MS"); @@ -1667,8 +1665,7 @@ namespace MobileGL::MG_Remote::Client { else MGLOG_E("MG_Remote client: the server had reported progress on seq %llu " "%u time(s), the last at %u ms, and then stopped", - static_cast(frame.seq), progressReports, - lastProgressMs); + static_cast(frame.seq), progressReports, lastProgressMs); } MGLOG_E("MG_Remote client: no SurfaceReply for seq %llu within %u ms (rc=%d)", static_cast(frame.seq), waitMs == 0 ? ceilingMs : budgetMs, @@ -1710,9 +1707,8 @@ namespace MobileGL::MG_Remote::Client { MGLOG_I("MG_Remote client: the server is still running %s seq %llu (%u ms after " "it was posted to its apply thread); the reply budget of %u ms restarts " "on each progress report, up to %u ms", - Server::SurfaceControlOpName(frame.kind), - static_cast(frame.seq), progress->elapsedMs(), budgetMs, - ceilingMs); + Server::SurfaceControlOpName(frame.kind), static_cast(frame.seq), + progress->elapsedMs(), budgetMs, ceilingMs); } ++progressReports; lastProgressMs = progress->elapsedMs(); @@ -1727,7 +1723,8 @@ namespace MobileGL::MG_Remote::Client { const char* fam = fatal->family() ? fatal->family()->c_str() : ""; const char* msg = fatal->message() ? fatal->message()->c_str() : ""; MGLOG_E("MG_Remote client: the server published a SessionFault before dying - " - "Fatal family %s: %s", fam, msg); + "Fatal family %s: %s", + fam, msg); } LatchDeviceLost(FaultReason(envelope, "the server published a SessionFault and then aborted").c_str()); frame.ok = false; @@ -1749,14 +1746,12 @@ namespace MobileGL::MG_Remote::Client { // multi-second GPU stall. Drop it and keep waiting for ours. MGLOG_W("MG_Remote client: dropping the late SurfaceReply for seq %llu (an op that " "timed out); still waiting for seq %llu", - static_cast(wire->seq()), - static_cast(frame.seq)); + static_cast(wire->seq()), static_cast(frame.seq)); continue; } if (wire->seq() != frame.seq) { MGLOG_E("MG_Remote client: SurfaceReply names seq %llu, we asked for %llu", - static_cast(wire->seq()), - static_cast(frame.seq)); + static_cast(wire->seq()), static_cast(frame.seq)); return MOBILEGL_ERR_PROTOCOL_MISMATCH; } MG_Remote::DecodeWireSurfaceReply(*wire, &frame); @@ -1782,8 +1777,7 @@ namespace MobileGL::MG_Remote::Client { // transport question: under inproc the client is what starts the server // role, because there is no server-side main to do it. Under spawn there // IS one, and it started the apply thread before it ever sent Welcome. - MobileGLResult ClientSession::FinishStartup(Transport::Doorbell* peerBell, - Transport::Doorbell* selfBell, + MobileGLResult ClientSession::FinishStartup(Transport::Doorbell* peerBell, Transport::Doorbell* selfBell, bool startApplyThreadHere) { // F1: every startup path - inproc, spawn, unix, tcp - reaches step 7 through here, so // this is the one place that has to declare "a first snapshot is being fetched, on @@ -1804,10 +1798,10 @@ namespace MobileGL::MG_Remote::Client { // PeerDoorbell() is the bell the SERVER parks on and this side rings; SelfDoorbell() is // this side's own. Which is which is the session's knowledge, not the transport's. m_link->BindDoorbells(peerBell, selfBell); - peerBell = &m_link->ConsumerBell(); selfBell = &m_link->ProducerBell(); + peerBell = &m_link->ConsumerBell(); + selfBell = &m_link->ProducerBell(); m_producer.Attach(*m_link, SpinUsFromConfig()); - // P5e (ra, §2.6): the drain's other half. A server parked on a full SEG_EVENT is woken // by the drain that emptied it, not by the next thing this client happens to publish. m_events->SetServerDoorbell(peerBell); @@ -1838,14 +1832,17 @@ namespace MobileGL::MG_Remote::Client { m_encoder.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); m_encoder.SetStageRetirementDoorbell(m_producer.SelfDoorbell()); m_encoder.SetStageWaitTimeoutMs(kBarrierTimeoutMs); - m_encoder.SetCancellationState(&m_deviceLost, +[](void* self) { - auto& session = *static_cast(self); - auto* bell = session.m_producer.SelfDoorbell(); - if (bell && bell->PeerHungUp()) { - session.LatchDeviceLost("staging observed a peer that had hung up"); - } - return session.m_deviceLost.load(std::memory_order_acquire) || (bell && bell->Dead()); - }, this); + m_encoder.SetCancellationState( + &m_deviceLost, + +[](void* self) { + auto& session = *static_cast(self); + auto* bell = session.m_producer.SelfDoorbell(); + if (bell && bell->PeerHungUp()) { + session.LatchDeviceLost("staging observed a peer that had hung up"); + } + return session.m_deviceLost.load(std::memory_order_acquire) || (bell && bell->Dead()); + }, + this); // P5e (ra, §2.6): the stage bell is a client wait and every client wait drains. The // encoder owns the park; the session owns SEG_EVENT, so the session lends it a drain. @@ -1868,8 +1865,7 @@ namespace MobileGL::MG_Remote::Client { } const auto* envelope = ParseEnvelope(initialCaps); if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::CapsSnapshot || - envelope->msg_as_CapsSnapshot() == nullptr || - !AdoptCapsSnapshot(envelope->msg_as_CapsSnapshot())) { + envelope->msg_as_CapsSnapshot() == nullptr || !AdoptCapsSnapshot(envelope->msg_as_CapsSnapshot())) { MGLOG_E("MG_Remote client: Refuse{InitialCapsSnapshot} first post-Welcome control " "frame is not a valid capabilities snapshot; the session was not started"); Stop(); @@ -1973,7 +1969,8 @@ namespace MobileGL::MG_Remote::Client { // gets right, and only then do the transports go. m_producer.Detach(); m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer); - m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn(); + m_cmd = &m_link->CommandsOut(); + m_events = &m_link->EventsIn(); Server::ServerSessionInstance().Close(); m_clientTransport.reset(); m_serverTransport.reset(); @@ -2016,16 +2013,21 @@ namespace MobileGL::MG_Remote::Client { if (m_socketTransport && !m_deviceLost.load(std::memory_order_acquire) && m_socketTransport->ShutdownSend() == MOBILEGL_OK) { bool closed = false; - if (m_controlInbox) closed = m_controlInbox->WaitClosed(kDrainTimeoutMs); + if (m_controlInbox) + closed = m_controlInbox->WaitClosed(kDrainTimeoutMs); else { const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(kDrainTimeoutMs); for (;;) { const auto left = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + deadline - std::chrono::steady_clock::now()) + .count(); if (left <= 0) break; std::vector finalFrame; const auto result = ReceiveControlFrame(finalFrame, static_cast(left)); - if (result == MOBILEGL_ERR_TRANSPORT_CLOSED) { closed = true; break; } + if (result == MOBILEGL_ERR_TRANSPORT_CLOSED) { + closed = true; + break; + } if (result != MOBILEGL_OK) break; } } @@ -2053,7 +2055,8 @@ namespace MobileGL::MG_Remote::Client { *m_events = Transport::EventRingConsumer(); *m_cmd = Transport::RingProducer(); m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ClientProducer); - m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn(); + m_cmd = &m_link->CommandsOut(); + m_events = &m_link->EventsIn(); m_link->Memory().Close(); Server::ServerSessionInstance().Close(); m_clientTransport.reset(); @@ -2100,19 +2103,21 @@ namespace MobileGL::MG_Remote::Client { } int exitCode = 0; if (Server::ReapServer(m_spawned, 3000, &exitCode) != MOBILEGL_OK) { - MGLOG_W("MG_Remote client: the spawned server (pid %d) did not exit within 3 s", - m_spawned.pid); + MGLOG_W("MG_Remote client: the spawned server (pid %d) did not exit within 3 s", m_spawned.pid); } else if (exitCode != 0) { MGLOG_W("MG_Remote client: the spawned server exited %d", exitCode); } } Server::ServerLoopInstance().SetRemoteControlSink(nullptr, nullptr); - } - Wire::PipeWireEncoder& ClientSession::Encoder() { return m_encoder; } + Wire::PipeWireEncoder& ClientSession::Encoder() { + return m_encoder; + } - CapsMirror& ClientSession::Caps() { return CapsMirrorInstance(); } + CapsMirror& ClientSession::Caps() { + return CapsMirrorInstance(); + } // PACKAGE c1's. The barrier's wait and the reply's wait are ONE wait (R-3/R-5), which is // what makes a blocking ReadPixels, MapPersistent's decline and the four Bool acceptances @@ -2125,31 +2130,31 @@ namespace MobileGL::MG_Remote::Client { // question locally, which is the c0f/c0g defect P4a paid two contract corrections for; and // "always accept" is ID-39's 66 lost DirectVulkan uploads with a wire in between. Uint64 ClientSession::EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* varTail, Uint64 varTailBytes, void* replyOut, - Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut) { + const void* varTail, Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, + Int32* statusOut, Uint64* replySizeOut) { // One tail is the two-tail form with one entry (P5b d1). Nothing is duplicated: the // barrier policy below has exactly one body, and the encoder's one-tail EncodeRecord is // itself defined as the tails form with tailCount <= 1. const Wire::WireTail tail{varTail, varTailBytes}; return EmitAndWaitTails(op, payload, payloadBytes, varTail != nullptr ? &tail : nullptr, - varTail != nullptr ? 1u : 0u, replyOut, replyBytes, statusOut, - replySizeOut); + varTail != nullptr ? 1u : 0u, replyOut, replyBytes, statusOut, replySizeOut); } - Uint64 ClientSession::EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, - Uint64 payloadBytes, const Wire::WireTail* tails, - Uint32 tailCount, void* replyOut, Uint64 replyBytes, - Int32* statusOut, Uint64* replySizeOut, - Bool wantReply, Bool willReadReply) { + Uint64 ClientSession::EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, + const Wire::WireTail* tails, Uint32 tailCount, void* replyOut, + Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut, Bool wantReply, + Bool willReadReply) { Uint64 varTailBytes = 0; - for (Uint32 i = 0; i < tailCount; ++i) varTailBytes += tails[i].Size; + for (Uint32 i = 0; i < tailCount; ++i) + varTailBytes += tails[i].Size; if (statusOut != nullptr) *statusOut = Wire::ReplySink::kStatusError; if (replySizeOut != nullptr) *replySizeOut = 0; if (!m_started) { - SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - EmitAndWait on a session that has " - "not started. There is no fall-through: a record that could not be emitted " - "is a verb that did not happen", - Wire::WireOpName(op)); + SessionFail(MGFatalFamily::NoClientSession, + "MGPipe: Fatal{NoClientSession, \"%s\"} - EmitAndWait on a session that has " + "not started. There is no fall-through: a record that could not be emitted " + "is a verb that did not happen", + Wire::WireOpName(op)); } // R-1's INVARIANT, AS A RUNTIME CHECK RATHER THAN A SENTENCE - but only while the @@ -2163,10 +2168,11 @@ namespace MobileGL::MG_Remote::Client { // the fill quiescent during that apply). With the batch off, the original check // stands as written. if (MG_Config::Ipc.BatchWaits == 0 && ApplyThreadIsInsideApplier()) { - SessionFail(MGFatalFamily::BarrierViolation, "MGPipe: Fatal{BarrierViolation, \"%s\"} - the apply thread is inside the " - "applier while the GL thread is emitting. R-1 makes at most one of them " - "runnable, which is what keeps one process-wide gPipeInputs legal", - Wire::WireOpName(op)); + SessionFail(MGFatalFamily::BarrierViolation, + "MGPipe: Fatal{BarrierViolation, \"%s\"} - the apply thread is inside the " + "applier while the GL thread is emitting. R-1 makes at most one of them " + "runnable, which is what keeps one process-wide gPipeInputs legal", + Wire::WireOpName(op)); } const auto declineCancelled = [&] { @@ -2182,16 +2188,18 @@ namespace MobileGL::MG_Remote::Client { // question, and a Fatal rather than an assert because both mistakes are silent: one // blocks on an answer nobody will send, the other discards an answer that never existed. if (wantReply && !willReadReply) { - SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"%s\"} - the caller asked to" - " block on this record's answer AND declared that nobody will read it; the server" - " would skip the answer this call is about to wait for", - Wire::WireOpName(op)); + SessionFail(MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"%s\"} - the caller asked to" + " block on this record's answer AND declared that nobody will read it; the server" + " would skip the answer this call is about to wait for", + Wire::WireOpName(op)); } if (!willReadReply && !rowCarriesReplySlot) { - SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"%s\"} - the caller declared this" - " record's answer unread, but the row carries no reply slot: there was no answer to" - " skip, so the declaration is a mistake about which row this is", - Wire::WireOpName(op)); + SessionFail(MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"%s\"} - the caller declared this" + " record's answer unread, but the row carries no reply slot: there was no answer to" + " skip, so the declaration is a mistake about which row this is", + Wire::WireOpName(op)); } const Bool skipAnswer = rowCarriesReplySlot && !willReadReply; Uint64 seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount, skipAnswer); @@ -2212,10 +2220,11 @@ namespace MobileGL::MG_Remote::Client { return declineCancelled(); } if (wait != Transport::SessionWait::Reached) { - SessionFail(MGFatalFamily::RetirementWaitFailed, "MGPipe: Fatal{RetirementWaitFailed, \"SEG_CMD\"} - %s needs %llu " - "reclaimable bytes; the retirement wait ended on %s", - Wire::WireOpName(op), static_cast(needed), - wait == Transport::SessionWait::ShutDown ? "shutdown" : "timeout"); + SessionFail(MGFatalFamily::RetirementWaitFailed, + "MGPipe: Fatal{RetirementWaitFailed, \"SEG_CMD\"} - %s needs %llu " + "reclaimable bytes; the retirement wait ended on %s", + Wire::WireOpName(op), static_cast(needed), + wait == Transport::SessionWait::ShutDown ? "shutdown" : "timeout"); } // P5e (ra, §2.6): EVERY WAIT EXIT DRAINS. The cmd-space wait is one of the two // safety nets under run-ahead (the stage bell is the other), and a client parked @@ -2226,8 +2235,10 @@ namespace MobileGL::MG_Remote::Client { seq = m_encoder.EncodeRecord(op, payload, payloadBytes, tails, tailCount); if (seq == Wire::kInvalidSeq) { if (DeviceLost() || m_encoder.Cancelled()) return declineCancelled(); - SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s refused after sufficient " - "space retired", Wire::WireOpName(op)); + SessionFail(MGFatalFamily::RingOverrun, + "MGPipe: Fatal{RingOverrun, \"SEG_CMD\"} - %s refused after sufficient " + "space retired", + Wire::WireOpName(op)); } } @@ -2236,8 +2247,7 @@ namespace MobileGL::MG_Remote::Client { // wakeup. // The op goes with the tally (P12): a total cannot say whether the waits are uploads, // creates or parameter sets, and those three have different fixes. - const auto replyMetricStart = - Transport::LinkMetricsBeginReply(ownsReplySlot, static_cast(op)); + const auto replyMetricStart = Transport::LinkMetricsBeginReply(ownsReplySlot, static_cast(op)); // P12: the residency clock AND the declaration, both placed HERE - after EncodeRecord has // an ordinal and BEFORE the publish, which is the only window in which the declaration is // guaranteed to precede its own answer. A call that declined before encoding emits nothing @@ -2247,8 +2257,7 @@ namespace MobileGL::MG_Remote::Client { // the create window emits without waiting and reads afterwards, so its records must be // declared even though this call does not read them. Links that cannot retain ignore it, // and the window does not defer on them (see ClientSession::LinkRetainsReplies). - if (rowCarriesReplySlot && willReadReply && m_link != nullptr) - m_link->DeclareReplyRead(seq); + if (rowCarriesReplySlot && willReadReply && m_link != nullptr) m_link->DeclareReplyRead(seq); // P11 B2: T0's Offer is queued HERE - the seq exists and the record is not yet visible, // so the server's inbox finds the descriptor already queued when it decodes the record // (Server/AdoptInbox.h). One-shot: AdoptPersistentT0 sets it for its own record only. @@ -2325,8 +2334,7 @@ namespace MobileGL::MG_Remote::Client { if (!ownsReplySlot && !RunAheadArmed() && MG_Config::Ipc.BatchWaits != 0 && op != MG_Pipe::MGPWireOp::GenerateMipmap) { const MG_Pipe::MGPipeCallClass callClass = MG_Pipe::MGPipeCallClassFor(op); - if (callClass == MG_Pipe::kCtxState || callClass == MG_Pipe::kCtxCso || - callClass == MG_Pipe::kCtxObject) { + if (callClass == MG_Pipe::kCtxState || callClass == MG_Pipe::kCtxCso || callClass == MG_Pipe::kCtxObject) { return seq; } } @@ -2343,16 +2351,14 @@ namespace MobileGL::MG_Remote::Client { if (MG_Config::Ipc.WaitStats && RunAheadArmed()) ++g_waitCensus.recordWaits[static_cast(op)]; const BarrierWaitScope waiting; const Uint64 waitBudgetMs = AppliedWaitBudgetMs(op, payload); - const Transport::SessionWait wait = - WaitForAppliedBudget(m_producer, *m_events, seq, waitBudgetMs); + const Transport::SessionWait wait = WaitForAppliedBudget(m_producer, *m_events, seq, waitBudgetMs); if (wait == Transport::SessionWait::ShutDown) { // `dl`: SHUTDOWN IS TWO DIFFERENT EVENTS AND ONLY ONE IS A DEVICE LOSS. Stop() kills // this same bell to wake a parked applier, so an orderly exit arrives here too - and // latching on Dead() alone would arm the device-lost flag on every clean teardown. // PeerHungUp() is true only when a descriptor whose far end ONLY THE SERVER held // reported hangup, which nothing this side can cause. - if (const Transport::Doorbell* bell = m_producer.SelfDoorbell(); - bell != nullptr && bell->PeerHungUp()) { + if (const Transport::Doorbell* bell = m_producer.SelfDoorbell(); bell != nullptr && bell->PeerHungUp()) { LatchDeviceLost("the barrier woke on a peer that had hung up"); } // The doorbell died: the server went away. The only thing that returns from a @@ -2376,11 +2382,12 @@ namespace MobileGL::MG_Remote::Client { return seq; } if (wait != Transport::SessionWait::Reached) { - SessionFail(MGFatalFamily::BarrierTimeout, "MGPipe: Fatal{BarrierTimeout, \"%s\"} - appliedSeq did not reach %llu within " - "%llu ms. A bounded wait is deliberate: a wedged CI job and a lost record look " - "identical from outside, and only one of them is a bug worth finding", - Wire::WireOpName(op), static_cast(seq), - static_cast(waitBudgetMs)); + SessionFail(MGFatalFamily::BarrierTimeout, + "MGPipe: Fatal{BarrierTimeout, \"%s\"} - appliedSeq did not reach %llu within " + "%llu ms. A bounded wait is deliberate: a wedged CI job and a lost record look " + "identical from outside, and only one of them is a bug worth finding", + Wire::WireOpName(op), static_cast(seq), + static_cast(waitBudgetMs)); } Transport::LinkMetricsReplyApplied(replyMetricStart); @@ -2409,10 +2416,11 @@ namespace MobileGL::MG_Remote::Client { Uint64 replySize = 0; Int32 status = Wire::ReplySink::kStatusError; if (!ReadReply(seq, replyOut, replyBytes, &status, &replySize)) { - SessionFail(MGFatalFamily::ReplyMissing, "MGPipe: Fatal{ReplyMissing, \"%s\"} - seq %llu carries kReplySlot and the " - "server applied it, but its slot does not stamp that seq. The stamp is what " - "makes a wrong-slot read detectable rather than plausible (R-3)", - Wire::WireOpName(op), static_cast(seq)); + SessionFail(MGFatalFamily::ReplyMissing, + "MGPipe: Fatal{ReplyMissing, \"%s\"} - seq %llu carries kReplySlot and the " + "server applied it, but its slot does not stamp that seq. The stamp is what " + "makes a wrong-slot read detectable rather than plausible (R-3)", + Wire::WireOpName(op), static_cast(seq)); } if (statusOut != nullptr) *statusOut = status; if (replySizeOut != nullptr) *replySizeOut = replySize; @@ -2425,33 +2433,41 @@ namespace MobileGL::MG_Remote::Client { // (R-5). A client that treated it as a failure would re-create ID-39 from the other // side. if (replyOut != nullptr && replySize > replyBytes) { - SessionFail(MGFatalFamily::ReplyTooLarge, "MGPipe: Fatal{ReplyTooLarge, \"%s\"} - the answer is %llu bytes and the " - "caller offered %llu. P5 does not chunk a reply", - Wire::WireOpName(op), static_cast(replySize), - static_cast(replyBytes)); + SessionFail(MGFatalFamily::ReplyTooLarge, + "MGPipe: Fatal{ReplyTooLarge, \"%s\"} - the answer is %llu bytes and the " + "caller offered %llu. P5 does not chunk a reply", + Wire::WireOpName(op), static_cast(replySize), + static_cast(replyBytes)); } return seq; } - Bool ClientSession::BarrierArmed() const { return m_barrierArmed; } + Bool ClientSession::BarrierArmed() const { + return m_barrierArmed; + } // ---- P5e (ra): the run-ahead latch and the waits it leaves behind --------------------- - Bool ClientSession::RunAheadArmed() const { return m_runAheadArmed; } + Bool ClientSession::RunAheadArmed() const { + return m_runAheadArmed; + } // THE PRODUCER'S NUMBER, NOT A SECOND ONE. SessionProducer records it inside // PublishAndNotify, which is the only place a record becomes published at all; a copy kept // here would be a second thing to forget to write on a path that publishes without waiting // - which, after P5e, is most of them. - Uint64 ClientSession::LastPublishedSeq() const { return m_producer.LastPublishedSeq(); } - Uint64 ClientSession::PresentCreditWaits() const { return m_presentCreditWaits; } + Uint64 ClientSession::LastPublishedSeq() const { + return m_producer.LastPublishedSeq(); + } + Uint64 ClientSession::PresentCreditWaits() const { + return m_presentCreditWaits; + } void ClientSession::LatchRunAheadFromCaps() { #if MOBILEGL_BUILD_DISAGGREGATED // THE CONJUNCTION, IN THE CONTRACT'S OWN ORDER (§1). The caps read is last because it // is the only one that costs a mirror load, and because it is the only one whose answer // can change: the other two are process configuration. - const Bool armed = m_barrierArmed && MG_Config::Ipc.RunAhead != 0 && - Caps().HasCap(MG_Pipe::kCapRunAheadApply); + const Bool armed = m_barrierArmed && MG_Config::Ipc.RunAhead != 0 && Caps().HasCap(MG_Pipe::kCapRunAheadApply); if (!m_runAheadLatched) { m_runAheadLatched = true; m_runAheadArmed = armed; @@ -2511,9 +2527,9 @@ namespace MobileGL::MG_Remote::Client { const auto now = std::chrono::steady_clock::now(); const auto remaining = std::chrono::duration_cast(deadline - now).count(); const Transport::SessionWait wait = now >= deadline - ? Transport::SessionWait::TimedOut - : m_producer.WaitForAppliedOrEventBacklog( - target, static_cast(std::max(remaining, 1))); + ? Transport::SessionWait::TimedOut + : m_producer.WaitForAppliedOrEventBacklog( + target, static_cast(std::max(remaining, 1))); if (wait == Transport::SessionWait::ShutDown) { // Only a peer that hung up is a device loss; Stop() reaches this path too. if (const Transport::Doorbell* bell = m_producer.SelfDoorbell(); @@ -2522,15 +2538,17 @@ namespace MobileGL::MG_Remote::Client { } if (!DeviceLost()) MGLOG_E("MG_Remote client: the forced wait for %s woke on a dead doorbell; the " - "server is gone", why); + "server is gone", + why); return; } if (wait != Transport::SessionWait::Reached) { - SessionFail(MGFatalFamily::BarrierTimeout, "MGPipe: Fatal{BarrierTimeout, \"%s\"} - a forced run-ahead wait did not " - "reach appliedSeq %llu within %u ms. The forced waits of CONTRACT-P5E §2.5 " - "are the points at which the client must be in step again, so a wedge here " - "is a wedge in the queue and not a slow frame", - why, static_cast(target), kBarrierTimeoutMs); + SessionFail(MGFatalFamily::BarrierTimeout, + "MGPipe: Fatal{BarrierTimeout, \"%s\"} - a forced run-ahead wait did not " + "reach appliedSeq %llu within %u ms. The forced waits of CONTRACT-P5E §2.5 " + "are the points at which the client must be in step again, so a wedge here " + "is a wedge in the queue and not a slow frame", + why, static_cast(target), kBarrierTimeoutMs); } // Drain even when only the event backlog woke us: the server may need that // space before it can advance appliedSeq. SurfaceChanged callbacks and pin @@ -2619,35 +2637,34 @@ namespace MobileGL::MG_Remote::Client { wait = m_producer.WaitForPresentAckOrEventBacklog(awaited, kBarrierTimeoutMs); if (wait != Transport::SessionWait::Reached) break; const auto* progress = m_producer.Progress(); - if (progress == nullptr || - Transport::Watermark::Reached(progress->presentAckSerial, awaited)) { + if (progress == nullptr || Transport::Watermark::Reached(progress->presentAckSerial, awaited)) { break; } DrainEventRing(*m_events); } if (wait == Transport::SessionWait::TimedOut) { - SessionFail(MGFatalFamily::PresentCreditTimeout, "MGPipe: Fatal{PresentCreditTimeout} - present %llu waited %u ms for " - "swap %llu to come back with a credit of %u. The credit, never the " - "ring's bytes, is what paces a run-ahead client (CONTRACT-P5E §2.4), " - "so a credit that never returns is a server that stopped swapping", - static_cast(serial), kBarrierTimeoutMs, - static_cast(awaited), credit); + SessionFail(MGFatalFamily::PresentCreditTimeout, + "MGPipe: Fatal{PresentCreditTimeout} - present %llu waited %u ms for " + "swap %llu to come back with a credit of %u. The credit, never the " + "ring's bytes, is what paces a run-ahead client (CONTRACT-P5E §2.4), " + "so a credit that never returns is a server that stopped swapping", + static_cast(serial), kBarrierTimeoutMs, + static_cast(awaited), credit); } // ShutDown is teardown and returns: the present did not happen, the same answer // the barrier gives on a dead doorbell. if (wait == Transport::SessionWait::Reached) DrainEventRing(*m_events); - const double creditMs = - static_cast(std::chrono::duration_cast( - std::chrono::steady_clock::now() - paceStarted).count()) / 1e6; + const double creditMs = static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - paceStarted) + .count()) / + 1e6; const auto io = Transport::LinkMetricsTakeSendStats(); // P15: MGLOG_D - an INFO line per frame on the GL thread cost a format and a // logd write every present on the shipped build. MGLOG_D("P65ClientPace present=%llu credit_wait_ms=%.1f | io: %llu B in %llu sendmsg " "send_ms=%.1f total_ms=%.1f", - static_cast(serial), creditMs, - static_cast(io.bytes), - static_cast(io.calls), - static_cast(io.nsInRecv) / 1e6, + static_cast(serial), creditMs, static_cast(io.bytes), + static_cast(io.calls), static_cast(io.nsInRecv) / 1e6, static_cast(io.nsTotal) / 1e6); } m_presentsSent = serial; @@ -2665,14 +2682,15 @@ namespace MobileGL::MG_Remote::Client { // apply thread inside the applier" is a question the GL thread asks about a DIFFERENT // thread in the shared-block control. Under role split each thread instead observes only // its own diagnostic; the client writes a different block and waits through appliedSeq. - Bool ClientSession::InBarrierWait() { return g_inBarrierWait; } + Bool ClientSession::InBarrierWait() { + return g_inBarrierWait; + } Bool ClientSession::ApplyThreadIsInsideApplier() { if (MG_Pipe::MGPipeRoleSplitRehearsalActive()) return g_roleInsideApplier; return g_applyThreadInsideApplier.load(std::memory_order_acquire); } - void ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt(const char* surface, - Bool isBarrieredFill) { + void ClientSession::RefusePipeInputsTouchWhileApplierOwnsIt(const char* surface, Bool isBarrieredFill) { // THE CHAIN IS A CONJUNCTION, SO ITS ORDER IS FREE - AND IT IS ORDERED CHEAPEST-FIRST // (P5d round 3, package D). Every line below is a pure predicate whose only effect is // "no violation, go home"; the Fatal fires on the AND of all of them, so reordering @@ -2710,15 +2728,16 @@ namespace MobileGL::MG_Remote::Client { // P15: with two blocks the GL thread's fill writes the client block, which the // applier never reads (the dual-block census is empty), so there is nothing to race. if (isBarrieredFill && MG_Pipe::MGPipeBlocksAreDistinct()) return; - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched " - "gPipeInputs (%s) on a RUN-AHEAD session %s. With the client running ahead " - "the block is the server's to read for as long as ANY record is in flight " - "(CONTRACT-P5E §3), so the only legal GL-thread write is a barriered fill " - "that has first taken §2.5's forced wait (QuiesceApplierBeforeFill)", - surface, - isBarrieredFill ? "in a barriered fill that did not first wait for the " - "applier to catch up" - : "outside a barriered fill"); + SessionFail(MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched " + "gPipeInputs (%s) on a RUN-AHEAD session %s. With the client running ahead " + "the block is the server's to read for as long as ANY record is in flight " + "(CONTRACT-P5E §3), so the only legal GL-thread write is a barriered fill " + "that has first taken §2.5's forced wait (QuiesceApplierBeforeFill)", + surface, + isBarrieredFill ? "in a barriered fill that did not first wait for the " + "applier to catch up" + : "outside a barriered fill"); } // MOBILEGL_IPC_BATCH_WAITS=1 makes "the fill runs while the apply thread applies an // earlier value record" the INTENDED shape: the fill writes only fields no record @@ -2728,7 +2747,7 @@ namespace MobileGL::MG_Remote::Client { // it stands only while the batch is off. if (MG_Config::Ipc.BatchWaits != 0) return; if (Server::ServerLoop::OnApplyThread()) return; // the applier owns the block inside a verb - if (!ClientSessionInstance().Started()) return; // the bring-up window pre-dates the roles + if (!ClientSessionInstance().Started()) return; // the bring-up window pre-dates the roles // MOBILEGL_IPC_VERB_BARRIER=0 is R-1's NEGATIVE CONTROL: the single-writer rule is off // by the operator's own hand there, and EmitAndWait's Fatal{BarrierViolation} owns the // red. Firing here instead would pre-empt the control's evidence line. @@ -2738,12 +2757,13 @@ namespace MobileGL::MG_Remote::Client { // access in a shared library, and it must stay thread-local (see InBarrierWait's note // above - a second GL thread would otherwise read the first one's wait as its own). if (InBarrierWait()) return; - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched gPipeInputs " - "(%s) while the apply thread was inside the applier and this thread was not in a " - "barrier wait. R-1's barrier is the only thing that makes one process-wide " - "gPipeInputs legal (CONTRACT-P5 table 3); a touch in this window races the " - "applier's own reads of it", - surface); + SessionFail(MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"gPipeInputs\"} - the GL thread touched gPipeInputs " + "(%s) while the apply thread was inside the applier and this thread was not in a " + "barrier wait. R-1's barrier is the only thing that makes one process-wide " + "gPipeInputs legal (CONTRACT-P5 table 3); a touch in this window races the " + "applier's own reads of it", + surface); } void ClientSession::NoteApplyThreadEnteredApplier() { g_roleInsideApplier = true; @@ -2819,27 +2839,33 @@ namespace MobileGL::MG_Remote::Client { void ClientSession::AttachDataLink(std::unique_ptr link) { m_link = std::move(link); - m_cmd = &m_link->CommandsOut(); m_events = &m_link->EventsIn(); + m_cmd = &m_link->CommandsOut(); + m_events = &m_link->EventsIn(); m_producer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); m_encoder.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); m_events->SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); } - Transport::SessionProducer& ClientSession::Producer() { return m_producer; } + Transport::SessionProducer& ClientSession::Producer() { + return m_producer; + } Transport::SessionWait ClientSession::WaitForApplied(Uint64 seq, Uint32 timeoutMs) { return m_producer.WaitForApplied(seq, timeoutMs); } - Uint64 ClientSession::ReplyPostings() const { return m_replyPostings; } + Uint64 ClientSession::ReplyPostings() const { + return m_replyPostings; + } Bool ClientSession::LinkRetainsReplies() const { return m_link != nullptr && m_link->Capabilities().RetainsReplies; } - Bool ClientSession::ReadReply(Uint64 seq, void* outBytes, Uint64 outCapacity, Int32* outStatus, - Uint64* outSize) { - const void* bytes = nullptr; Uint64 size = 0; Int32 status = 0; + Bool ClientSession::ReadReply(Uint64 seq, void* outBytes, Uint64 outCapacity, Int32* outStatus, Uint64* outSize) { + const void* bytes = nullptr; + Uint64 size = 0; + Int32 status = 0; if (m_link->ReadReply(seq, &status, &bytes, &size) != MOBILEGL_OK) return false; if (outStatus) *outStatus = status; if (outSize) *outSize = size; @@ -2848,22 +2874,28 @@ namespace MobileGL::MG_Remote::Client { return true; } - Uint32 ClientSession::MaxReplyBytes() const { return static_cast(m_link->Capabilities().MaxReplyBytes); } + Uint32 ClientSession::MaxReplyBytes() const { + return static_cast(m_link->Capabilities().MaxReplyBytes); + } - Bool ClientSession::ReplyCanHold(Uint64 bytes) const { return bytes <= MaxReplyBytes(); } + Bool ClientSession::ReplyCanHold(Uint64 bytes) const { + return bytes <= MaxReplyBytes(); + } // ID-47. Forwarded verbatim so that the message, the boundary and the abort are the pool's // and are pinned once, in SessionTest, rather than re-derived per caller. - void ClientSession::RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, - Uint32 type, Uint64 bytes) const { + void ClientSession::RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, Uint32 type, + Uint64 bytes) const { if (!ReplyCanHold(bytes)) { SessionFail(MGFatalFamily::ReplyTooLarge, - "MGPipe: Fatal{ReplyTooLarge, \"ReadPixels %ux%u 0x%04X/0x%04X %llu > %u\"}", - width, height, format, type, static_cast(bytes), MaxReplyBytes()); + "MGPipe: Fatal{ReplyTooLarge, \"ReadPixels %ux%u 0x%04X/0x%04X %llu > %u\"}", width, height, + format, type, static_cast(bytes), MaxReplyBytes()); } } - Transport::EventRingConsumer& ClientSession::Events() { return *m_events; } + Transport::EventRingConsumer& ClientSession::Events() { + return *m_events; + } Uint32 ClientSession::DrainPublishedEvents() { // Not gated on m_started on purpose: PumpControlPlane's own pre-start call during @@ -2894,17 +2926,29 @@ namespace MobileGL::MG_Remote::Client { g_serverOwnedSurfaces.end(); } - Uint64 ClientSession::EventRingCapacityBytes() const { return m_link->Memory().EventRingCapacity(); } + Uint64 ClientSession::EventRingCapacityBytes() const { + return m_link->Memory().EventRingCapacity(); + } - Uint64 ClientSession::StageCapacityBytes() const { return m_link->Memory().StageBytes(); } + Uint64 ClientSession::StageCapacityBytes() const { + return m_link->Memory().StageBytes(); + } - Wire::SegmentTable& ClientSession::Segments() { return m_segments; } + Wire::SegmentTable& ClientSession::Segments() { + return m_segments; + } - Transport::RingControl* ClientSession::Control() { return m_link->Memory().CmdControl(); } + Transport::RingControl* ClientSession::Control() { + return m_link->Memory().CmdControl(); + } - Transport::SessionSegments& ClientSession::Shm() { return m_link->Memory(); } + Transport::SessionSegments& ClientSession::Shm() { + return m_link->Memory(); + } - Transport::ITransport* ClientSession::Control_Plane() { return m_transport; } + Transport::ITransport* ClientSession::Control_Plane() { + return m_transport; + } // ---- P11 B2: T0 (docs/archive/contracts/CONTRACT-P11.md B2) -------------------------------------------- @@ -2925,8 +2969,7 @@ namespace MobileGL::MG_Remote::Client { if (!m_adoptT0Asked) return false; if (m_adoptT0Decision >= 0) return m_adoptT0Decision == 1; const Bool peerIsThisProcess = Transport::PeerIsThisProcess(m_transport); - if (Caps().HasCap(MG_Pipe::kCapAdoptT0) && peerIsThisProcess && - !Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) { + if (Caps().HasCap(MG_Pipe::kCapAdoptT0) && peerIsThisProcess && !Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) { // A server in this process offered the AHardwareBuffer arm. The stores are this // process's memory already; an in-process session never allocates one. m_adoptT0Decision = 0; @@ -2960,8 +3003,7 @@ namespace MobileGL::MG_Remote::Client { return m_adoptT0Decision == 1; } - void* ClientSession::AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, - const void* seed) { + void* ClientSession::AdoptPersistentT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed) { using namespace Transport::AdoptT0; if (Caps().HasCap(MG_Pipe::kCapAdoptInProcess)) return AdoptPersistentInProcess(handle, size, seed); // Everything that can fail runs BEFORE the record is encoded, so the record never waits on @@ -3016,9 +3058,8 @@ namespace MobileGL::MG_Remote::Client { return; } #endif - shared = m_transport != nullptr - ? m_transport->ShareFd(hop, MobileGLByteSpan{&offer, sizeof(offer)}) - : MOBILEGL_ERR_NOT_INITIALIZED; + shared = m_transport != nullptr ? m_transport->ShareFd(hop, MobileGLByteSpan{&offer, sizeof(offer)}) + : MOBILEGL_ERR_NOT_INITIALIZED; }; Int32 status = Wire::ReplySink::kStatusError; EmitAndWait(MG_Pipe::MGPWireOp::MapPersistent, &handle, sizeof(handle), nullptr, 0, nullptr, 0, &status); @@ -3074,8 +3115,7 @@ namespace MobileGL::MG_Remote::Client { // already share, as monolith's donation is. The server has drained the GPU's use of the store // before answering and applies nothing of this client's until this call returns, so the // shadow is copied in here, by the side that owns it. - void* ClientSession::AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, - const void* seed) { + void* ClientSession::AdoptPersistentInProcess(const MG_Pipe::MGPHandleOnly& handle, Uint64 size, const void* seed) { Uint64 base = 0; Uint64 returned = 0; Int32 status = Wire::ReplySink::kStatusError; @@ -3162,8 +3202,9 @@ namespace MobileGL::MG_Remote::Client { return true; } // An earlier record's descriptor whose answer was never taken: not this one. - MGLOG_W("MG_Remote client: an aux descriptor for record %llu arrived while record %llu's was awaited; closed", - static_cast(offer.Seq), static_cast(seq)); + MGLOG_W( + "MG_Remote client: an aux descriptor for record %llu arrived while record %llu's was awaited; closed", + static_cast(offer.Seq), static_cast(seq)); ::close(fd); } #endif @@ -3181,15 +3222,16 @@ namespace MobileGL::MG_Remote::Client { if (m_adoptT0Asked || m_t0Fallbacks != 0) { // A default (unset-knob) session that did not run T0 says its totals at MGLOG_D. const bool quiet = m_adoptT0Quiet && m_adoptT0Decision != 1; - const char* ran = - m_adoptT0Decision == 1 ? "ran T0" : m_adoptT0Decision == 0 || m_t0Fallbacks ? "fell back to T2" : "never decided"; -#define MGL_T0_CLIENT_TOTALS(LOG) \ - LOG("MG_Remote client: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ - "fallbacks %llu, released %llu, held at teardown %zu", \ - ran, m_adoptT0Quiet ? " (default)" : "", static_cast(m_t0Imported), \ - static_cast(m_t0Bytes), static_cast(m_t0Declined), \ - static_cast(m_t0Fallbacks), static_cast(m_t0Released), \ - m_t0Stores.size()) + const char* ran = m_adoptT0Decision == 1 ? "ran T0" + : m_adoptT0Decision == 0 || m_t0Fallbacks ? "fell back to T2" + : "never decided"; +#define MGL_T0_CLIENT_TOTALS(LOG) \ + LOG("MG_Remote client: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ + "fallbacks %llu, released %llu, held at teardown %zu", \ + ran, m_adoptT0Quiet ? " (default)" : "", static_cast(m_t0Imported), \ + static_cast(m_t0Bytes), static_cast(m_t0Declined), \ + static_cast(m_t0Fallbacks), static_cast(m_t0Released), \ + m_t0Stores.size()) if (quiet) { MGL_T0_CLIENT_TOTALS(MGLOG_D); } else { @@ -3199,7 +3241,8 @@ namespace MobileGL::MG_Remote::Client { (void)ran; } if (releaseStores) { - for (auto& [key, store] : m_t0Stores) Transport::AdoptT0::ReleaseHeld(store); + for (auto& [key, store] : m_t0Stores) + Transport::AdoptT0::ReleaseHeld(store); } m_t0Stores.clear(); m_adoptT0Asked = false; @@ -3234,10 +3277,8 @@ namespace MobileGL::MG_Remote::Client { "R-10's proof obligation, R-9's producer readings, P5d round 3's client wait " "pair and P5e's present credit, published from the session that produced them", static_cast(maxRecord), m_encoder.MaxRecordOpName(), - static_cast(cap), - static_cast(permille / 10), - static_cast(permille % 10), - static_cast(cap * 2), + static_cast(cap), static_cast(permille / 10), + static_cast(permille % 10), static_cast(cap * 2), static_cast(m_encoder.CmdBytesWritten()), static_cast(m_encoder.CmdWraps()), static_cast(m_encoder.CmdWrapPads()), diff --git a/MobileGL/MG_Remote/Client/ClientSession.h b/MobileGL/MG_Remote/Client/ClientSession.h index 7996804d..204d38ce 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.h +++ b/MobileGL/MG_Remote/Client/ClientSession.h @@ -20,7 +20,7 @@ #include "../Transport/ControlInbox.h" #include -#include // the device-lost latch is read off the GL thread +#include // the device-lost latch is read off the GL thread #include #include @@ -47,7 +47,7 @@ namespace MobileGL::MG_Remote::Transport { class InProcessTransport; -} +} // namespace MobileGL::MG_Remote::Transport namespace MobileGL::MG_Remote::Client { @@ -165,8 +165,7 @@ namespace MobileGL::MG_Remote::Client { static MobileGLResult RemoteControlSinkThunk(void* user, Server::SurfaceControlFrame& frame); MobileGLResult RunRemoteSurfaceControlFrame(Server::SurfaceControlFrame& frame); - MobileGLResult FinishStartup(Transport::Doorbell* peerBell, - Transport::Doorbell* selfBell, + MobileGLResult FinishStartup(Transport::Doorbell* peerBell, Transport::Doorbell* selfBell, bool startApplyThreadHere); // `transport` is this process's end of a connection to an ALREADY @@ -206,9 +205,9 @@ namespace MobileGL::MG_Remote::Client { // // Waiting is spin(MOBILEGL_IPC_SPIN_US) then park, through Doorbell::Wait, with // producerParked set before blocking - the shape Doorbell::Wait already implements. - Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* varTail, Uint64 varTailBytes, void* replyOut, - Uint64 replyBytes, Int32* statusOut, Uint64* replySizeOut = nullptr); + Uint64 EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const void* varTail, + Uint64 varTailBytes, void* replyOut, Uint64 replyBytes, Int32* statusOut, + Uint64* replySizeOut = nullptr); // The same call for a record that carries TWO tails (P5b d1: draw_vbo's user-index span // or its MGPDrawIndirect block behind the range array). The one-tail form above is this @@ -225,9 +224,8 @@ namespace MobileGL::MG_Remote::Client { // skip an answer a caller uses: R-5 still forbids re-deriving one locally, and every // other kReplySlot row passes true (the default) and waits. Uint64 EmitAndWaitTails(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const Wire::WireTail* tails, Uint32 tailCount, void* replyOut, - Uint64 replyBytes, Int32* statusOut, - Uint64* replySizeOut = nullptr, Bool wantReply = true, + const Wire::WireTail* tails, Uint32 tailCount, void* replyOut, Uint64 replyBytes, + Int32* statusOut, Uint64* replySizeOut = nullptr, Bool wantReply = true, Bool willReadReply = true); // ---- P11 B2: T0 (docs/archive/contracts/CONTRACT-P11.md B2) ------------------------------------ @@ -381,8 +379,7 @@ namespace MobileGL::MG_Remote::Client { // barriered fill is always legal. Under lockstep it checks only with // MOBILEGL_IPC_BATCH_WAITS=0 and an armed barrier, and fires when the applier is inside // and this thread is not in a barrier wait. Never fires on the apply thread. - static void RefusePipeInputsTouchWhileApplierOwnsIt(const char* surface, - Bool isBarrieredFill = false); + static void RefusePipeInputsTouchWhileApplierOwnsIt(const char* surface, Bool isBarrieredFill = false); // ---- c1's additions --------------------------------------------------------------- @@ -455,8 +452,7 @@ namespace MobileGL::MG_Remote::Client { // The reply slot for `seq`, addressed seq % slots with the seq stamped back into the // header for self-check (R-3). `outStatus` is 0 OK / 1 DECLINED / 2 ERROR, and // DECLINED IS A REAL ANSWER - MapPersistent's nullptr and the four Bool acceptances. - Bool ReadReply(Uint64 seq, void* outBytes, Uint64 outCapacity, Int32* outStatus, - Uint64* outSize); + Bool ReadReply(Uint64 seq, void* outBytes, Uint64 outCapacity, Int32* outStatus, Uint64* outSize); // What one answer may carry. No single answer is ever bigger than this: a ReadPixels // whose whole read is bigger is split by the emitter into row bands (or, for a row // wider than a reply, single-row pieces) that each fit (g5-readback, EmitTables.h @@ -473,8 +469,7 @@ namespace MobileGL::MG_Remote::Client { // pixel fits a reply, once for that one pixel instead of emitting; the server's Post // keeps its own refusal as the last line of defence, but that one fires on the apply // thread with the record already on the wire, where all the client sees is a hang. - void RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, Uint32 type, - Uint64 bytes) const; + void RequireReadPixelsReplyFits(Uint32 width, Uint32 height, Uint32 format, Uint32 type, Uint64 bytes) const; // The reverse channel's reading end: OnBufferWriteback / OnGpuWritten / // OnSurfaceChanged. Drained by the GL thread between verbs. diff --git a/MobileGL/MG_Remote/Client/EmitTables.cpp b/MobileGL/MG_Remote/Client/EmitTables.cpp index e6d4765d..f143195a 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.cpp +++ b/MobileGL/MG_Remote/Client/EmitTables.cpp @@ -97,10 +97,11 @@ namespace MobileGL::MG_Remote::Client { if (session == nullptr) { // @Ph-declined (ID-P7-1): returns ClientSession& and runs in the CLIENT - no // session to hand back, no peer bytes, and the latch is a server-session idea. - SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is " - "installed but no ClientSession is active. A slot may not fall through " - "to a driver this role does not have", - slot); + SessionFail(MGFatalFamily::NoClientSession, + "MGPipe: Fatal{NoClientSession, \"%s\"} - the remote emit table is " + "installed but no ClientSession is active. A slot may not fall through " + "to a driver this role does not have", + slot); } return *session; } @@ -186,8 +187,7 @@ namespace MobileGL::MG_Remote::Client { g_publishedMaxRecordBytes = encoder.MaxRecordBytesSeen(); MGLOG_I("MGPipe: wire ledger: new maximum record - maxrec=%llu " "maxrecop=%s cap=%llu (R-10's proof obligation; blobs are cut, a record is not)", - static_cast(g_publishedMaxRecordBytes), - encoder.MaxRecordOpName(), + static_cast(g_publishedMaxRecordBytes), encoder.MaxRecordOpName(), static_cast(encoder.MaxRecordBytesCap())); } } @@ -208,8 +208,7 @@ namespace MobileGL::MG_Remote::Client { // travels exactly as it did - the present row's own barrier is the pacing there. record.FrameSerial = session.AcquirePresentCredit(); record.DamageCount = session.TakePendingPresentDamage(record.Damage); - session.EmitAndWait(MG_Pipe::MGPWireOp::Present, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::Present, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); // R-12's invalidation edge, drained at the one boundary every target crosses. A // second CapsSnapshot IS the invalidation; nothing else on the client can see that @@ -278,12 +277,13 @@ namespace MobileGL::MG_Remote::Client { RemoteFenceProxy& FenceProxy(MG_Backend::BackendSyncHandle proxy) { const auto it = g_fenceProxies.find(proxy); - if (it == g_fenceProxies.end()) - Wire::WireProtocolFatal("Fence.proxy", "unknown client fence proxy"); + if (it == g_fenceProxies.end()) Wire::WireProtocolFatal("Fence.proxy", "unknown client fence proxy"); return *it->second; } - MG_Pipe::MGPipeHandle FenceHandle(MG_Backend::BackendSyncHandle proxy) { return FenceProxy(proxy).Handle; } + MG_Pipe::MGPipeHandle FenceHandle(MG_Backend::BackendSyncHandle proxy) { + return FenceProxy(proxy).Handle; + } // ---- P10 (CONTRACT-P10.md §1): WHAT THE SERVER HAS SAID IS SIGNALED ----------------- // @@ -332,7 +332,11 @@ namespace MobileGL::MG_Remote::Client { // eventually true, and the escalation is the bound that holds even if one is late. A call // that may wait (a non-zero timeout) is never answered "not yet" here - it crosses and // waits on the server, as the GL call asks. - enum class FencePollAnswer { Signaled, NotYet, Cross }; + enum class FencePollAnswer { + Signaled, + NotYet, + Cross + }; FencePollAnswer AnswerFencePollLocally(ClientSession& session, RemoteFenceProxy& fence, Bool mayWait) { if (!FencePollsAnswerLocally()) return FencePollAnswer::Cross; session.PollEntry(); @@ -358,20 +362,17 @@ namespace MobileGL::MG_Remote::Client { auto proxy = MakeUnique(); proxy->Handle = MG_Pipe::MGPipeSlots().Allocate(MG_Pipe::MGPipeKind::Fence); const MG_Pipe::MGPHandleOnly desc{proxy->Handle, static_cast(MG_Pipe::MGPipeKind::Fence), 0}; - session.EmitAndWait(MG_Pipe::MGPWireOp::FenceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::FenceCreate, &desc, sizeof(desc), nullptr, 0, nullptr, 0, nullptr); auto* local = proxy.get(); g_fenceProxies.emplace(local, std::move(proxy)); return local; } - Uint32 ReadFenceReply(ClientSession& session, MG_Pipe::MGPWireOp op, - const void* payload, Uint64 bytes) { + Uint32 ReadFenceReply(ClientSession& session, MG_Pipe::MGPWireOp op, const void* payload, Uint64 bytes) { Uint32 result = 0; Int32 status = Wire::ReplySink::kStatusError; Uint64 replyBytes = 0; - session.EmitAndWait(op, payload, bytes, nullptr, 0, &result, sizeof(result), - &status, &replyBytes); + session.EmitAndWait(op, payload, bytes, nullptr, 0, &result, sizeof(result), &status, &replyBytes); // A declined reply is an answer, not a corruption: the server declines these rows when // it cannot answer them, and the client reports a verb that did not happen (the // device-lost latch, a doorbell that died during teardown) as declined. It gets the @@ -382,8 +383,7 @@ namespace MobileGL::MG_Remote::Client { "happen (a lost device, or a teardown); answering the no-op the " "frontend uses for a fence it cannot wait on", Wire::WireOpName(op)); - return op == MG_Pipe::MGPWireOp::FenceWait ? static_cast(GL_ALREADY_SIGNALED) - : 1u; + return op == MG_Pipe::MGPWireOp::FenceWait ? static_cast(GL_ALREADY_SIGNALED) : 1u; } // ERROR AND A SHORT OK ARE STILL THE PROTOCOL CORRUPTIONS THEY ALWAYS WERE: a // transport fault is not the server saying no, and an OK answer that arrived empty is @@ -399,9 +399,12 @@ namespace MobileGL::MG_Remote::Client { RemoteFenceProxy& fence = FenceProxy(proxy); if (fence.FromEndedSession()) return GL_ALREADY_SIGNALED; switch (AnswerFencePollLocally(session, fence, /*mayWait=*/timeout != 0)) { - case FencePollAnswer::Signaled: return GL_ALREADY_SIGNALED; - case FencePollAnswer::NotYet: return GL_TIMEOUT_EXPIRED; - case FencePollAnswer::Cross: break; + case FencePollAnswer::Signaled: + return GL_ALREADY_SIGNALED; + case FencePollAnswer::NotYet: + return GL_TIMEOUT_EXPIRED; + case FencePollAnswer::Cross: + break; } g_fenceRoundTrips.fetch_add(1, std::memory_order_relaxed); const MG_Pipe::MGPFenceWait request{fence.Handle, timeout, flags, 0}; @@ -416,9 +419,12 @@ namespace MobileGL::MG_Remote::Client { RemoteFenceProxy& fence = FenceProxy(proxy); if (fence.FromEndedSession()) return true; switch (AnswerFencePollLocally(session, fence, /*mayWait=*/false)) { - case FencePollAnswer::Signaled: return true; - case FencePollAnswer::NotYet: return false; - case FencePollAnswer::Cross: break; + case FencePollAnswer::Signaled: + return true; + case FencePollAnswer::NotYet: + return false; + case FencePollAnswer::Cross: + break; } g_fenceRoundTrips.fetch_add(1, std::memory_order_relaxed); const MG_Pipe::MGPHandleOnly desc{fence.Handle, static_cast(MG_Pipe::MGPipeKind::Fence), 0}; @@ -433,8 +439,8 @@ namespace MobileGL::MG_Remote::Client { const std::lock_guard lock(g_fenceMutex); if (FenceProxy(proxy).FromEndedSession()) return; const MG_Pipe::MGPFenceWait request{FenceHandle(proxy), timeout, flags, 0}; - session.EmitAndWait(MG_Pipe::MGPWireOp::FenceWaitServer, &request, sizeof(request), - nullptr, 0, nullptr, 0, nullptr); + session.EmitAndWait(MG_Pipe::MGPWireOp::FenceWaitServer, &request, sizeof(request), nullptr, 0, nullptr, 0, + nullptr); } void EmitDeleteSync(MG_Backend::BackendSyncHandle proxy) { @@ -446,8 +452,8 @@ namespace MobileGL::MG_Remote::Client { if (auto* session = ClientSession::Active(); session != nullptr && session->Started() && !FenceProxy(proxy).FromEndedSession()) { const MG_Pipe::MGPHandleOnly desc{handle, static_cast(MG_Pipe::MGPipeKind::Fence), 0}; - session->EmitAndWait(MG_Pipe::MGPWireOp::FenceDestroy, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, nullptr); + session->EmitAndWait(MG_Pipe::MGPWireOp::FenceDestroy, &desc, sizeof(desc), nullptr, 0, nullptr, 0, + nullptr); } MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Fence, handle); ForgetFence(handle); @@ -505,16 +511,18 @@ namespace MobileGL::MG_Remote::Client { // The union, for the places that want every class-C row at once (the definitions and // the assignments). A package never edits THIS; it edits its own list above. -#define MGR_UNMIGRATED_GL_SLOTS(X) \ - MGR_UNMIGRATED_D1_SLOTS(X) \ - MGR_UNMIGRATED_I1_SLOTS(X) \ - MGR_UNMIGRATED_T2_SLOTS(X) \ - MGR_UNMIGRATED_F1_SLOTS(X) \ +#define MGR_UNMIGRATED_GL_SLOTS(X) \ + MGR_UNMIGRATED_D1_SLOTS(X) \ + MGR_UNMIGRATED_I1_SLOTS(X) \ + MGR_UNMIGRATED_T2_SLOTS(X) \ + MGR_UNMIGRATED_F1_SLOTS(X) \ MGR_UNMIGRATED_TAIL_SLOTS(X) #define MGR_UNMIGRATED_GL_VALUE_SLOTS(X) MGR_UNMIGRATED_TAIL_VALUE_SLOTS(X) -#define MGR_DEFINE_UNMIGRATED(Name, Ret, Sig) \ - Ret Name##_Unmigrated Sig { UnmigratedVerbFatal(#Name); } +#define MGR_DEFINE_UNMIGRATED(Name, Ret, Sig) \ + Ret Name##_Unmigrated Sig { \ + UnmigratedVerbFatal(#Name); \ + } MGR_UNMIGRATED_GL_SLOTS(MGR_DEFINE_UNMIGRATED) MGR_UNMIGRATED_GL_VALUE_SLOTS(MGR_DEFINE_UNMIGRATED) @@ -556,9 +564,8 @@ namespace MobileGL::MG_Remote::Client { constexpr Uint32 kEmittedSlotsTail = 4; constexpr Uint32 kEmittedSlotsSync = 5; constexpr Uint32 kEmittedSlotsQueries = 11; - constexpr Uint32 kEmittedSlots = - kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + kEmittedSlotsF1 + - kEmittedSlotsTail + kEmittedSlotsSync + kEmittedSlotsQueries; + constexpr Uint32 kEmittedSlots = kEmittedSlotsP5 + kEmittedSlotsD1 + kEmittedSlotsI1 + kEmittedSlotsT2 + + kEmittedSlotsF1 + kEmittedSlotsTail + kEmittedSlotsSync + kEmittedSlotsQueries; constexpr Uint32 kLocallyAnsweredSlots = 2; // GetIntegeri_v, IsTimerQuerySupported // EACH PACKAGE'S OWNERSHIP, PINNED. A package that flips a slot removes one row and @@ -613,8 +620,7 @@ namespace MobileGL::MG_Remote::Client { // reader is GL_Query.cpp:221. It does NOT ride inside MGPCaps::Dynamic - it is a // member of GLFunctionsTable, which is exactly the thing a split client never // receives - so it is answered from kCapCpuXfbPrimitiveAccounting. - table.GL.PrefersCpuXfbPrimitiveAccounting = - CapsMirrorInstance().PrefersCpuXfbPrimitiveAccounting(); + table.GL.PrefersCpuXfbPrimitiveAccounting = CapsMirrorInstance().PrefersCpuXfbPrimitiveAccounting(); // ---- class B. P13 W5: every slot a VerbChannel emitter serves (draws, clears, // blits, readbacks, compute, copies, image binds, XFB, mipmaps) is MG_Record's @@ -681,8 +687,14 @@ namespace MobileGL::MG_Remote::Client { return table; } - Uint32 ImplementedVerbCount() { return kEmittedSlots; } - Uint32 LocallyAnsweredSlotCount() { return kLocallyAnsweredSlots; } - Uint32 UnmigratedSlotCount() { return kUnmigratedSlots; } + Uint32 ImplementedVerbCount() { + return kEmittedSlots; + } + Uint32 LocallyAnsweredSlotCount() { + return kLocallyAnsweredSlots; + } + Uint32 UnmigratedSlotCount() { + return kUnmigratedSlots; + } } // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/EmitTables.h b/MobileGL/MG_Remote/Client/EmitTables.h index 4501fcec..af78cfc1 100644 --- a/MobileGL/MG_Remote/Client/EmitTables.h +++ b/MobileGL/MG_Remote/Client/EmitTables.h @@ -47,27 +47,27 @@ namespace MobileGL::MG_Remote::Client { // planners are MG_Record's (MG_Impl/Pipe/Verb/VerbPort.h) - the record arm runs them in a // library without a transport. Re-exported here under the names the remote table's users // and its unit cases already spell. - using MG_Record::UnmigratedVerbFatal; + using MG_Record::DroppedClearEmissions; + using MG_Record::DroppedDrawEmissions; + using MG_Record::ForEachReadbackBand; using MG_Record::InstallMonolithVerbPort; using MG_Record::MonolithVerbPortInstalled; - using MG_Record::SetDropClearEmissionForNegativeControl; - using MG_Record::DroppedClearEmissions; - using MG_Record::SetDropDrawEmissionForNegativeControl; - using MG_Record::DroppedDrawEmissions; - using MG_Record::TightReadbackByteCount; - using MG_Record::ScatterTightReadbackIntoPackState; + using MG_Record::PlanDrawIndirect; + using MG_Record::PlanDrawInfo; + using MG_Record::PlanDrawRange; + using MG_Record::PlanReadbackBands; using MG_Record::ReadbackBand; using MG_Record::ReadbackBandPlan; - using MG_Record::PlanReadbackBands; - using MG_Record::ForEachReadbackBand; - using MG_Record::ScatterReadbackBandIntoPackState; using MG_Record::ReadbackPackStateIsTightForTest; using MG_Record::ReadbackReplyIsComplete; using MG_Record::RemoteDrawBindings; using MG_Record::RemoteIndexSizeFor; - using MG_Record::PlanDrawInfo; - using MG_Record::PlanDrawRange; - using MG_Record::PlanDrawIndirect; + using MG_Record::ScatterReadbackBandIntoPackState; + using MG_Record::ScatterTightReadbackIntoPackState; + using MG_Record::SetDropClearEmissionForNegativeControl; + using MG_Record::SetDropDrawEmissionForNegativeControl; + using MG_Record::TightReadbackByteCount; + using MG_Record::UnmigratedVerbFatal; // How many of the 71 slots have a real emitter. Reported at bring-up and asserted by the // gate: a table that silently loses an emitter should not be able to look the same as one diff --git a/MobileGL/MG_Remote/Client/GpuWritePending.cpp b/MobileGL/MG_Remote/Client/GpuWritePending.cpp index f5d51cfd..4fbc618e 100644 --- a/MobileGL/MG_Remote/Client/GpuWritePending.cpp +++ b/MobileGL/MG_Remote/Client/GpuWritePending.cpp @@ -104,17 +104,19 @@ namespace MobileGL::MG_Remote::Client { // one outcome the reverse channel exists to prevent: stale bytes that look like a // successful read. It is the reply form's ReadbackDeclined, arriving one call later. if (ClientSession::DeviceLost()) { - SessionFail(MGFatalFamily::ReadbackDeclined, "MGPipe: Fatal{ReadbackDeclined, \"buffer-writeback\"} - buffer %u " - "still owes a GPU write (a pack-buffer read or a shader store) and the device is " - "lost, so its readback was declined; its shadow holds the bytes from before the " - "write and is not returned as the GPU's", - buffer.GetExternalIndex()); + SessionFail(MGFatalFamily::ReadbackDeclined, + "MGPipe: Fatal{ReadbackDeclined, \"buffer-writeback\"} - buffer %u " + "still owes a GPU write (a pack-buffer read or a shader store) and the device is " + "lost, so its readback was declined; its shadow holds the bytes from before the " + "write and is not returned as the GPU's", + buffer.GetExternalIndex()); } - SessionFail(MGFatalFamily::UnimplementedWritebackWait, "MGPipe: Fatal{UnimplementedWritebackWait} - a ClientSession is active and buffer %u " - "still has an outstanding GPU write after its readback was emitted. The wait is " - "ClientSession::EmitAndWait's (R-3: the reply slot id IS the record seq); P5 package " - "b1 landed the third state and s1/c1 own the wait itself.", - buffer.GetExternalIndex()); + SessionFail(MGFatalFamily::UnimplementedWritebackWait, + "MGPipe: Fatal{UnimplementedWritebackWait} - a ClientSession is active and buffer %u " + "still has an outstanding GPU write after its readback was emitted. The wait is " + "ClientSession::EmitAndWait's (R-3: the reply slot id IS the record seq); P5 package " + "b1 landed the third state and s1/c1 own the wait itself.", + buffer.GetExternalIndex()); } } // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/GpuWritePending.h b/MobileGL/MG_Remote/Client/GpuWritePending.h index 4895121d..db457b5b 100644 --- a/MobileGL/MG_Remote/Client/GpuWritePending.h +++ b/MobileGL/MG_Remote/Client/GpuWritePending.h @@ -24,7 +24,6 @@ namespace MobileGL::MG_State::GLState { namespace MobileGL::MG_Remote::Client { - // ---- SyncGpuWrites' third state (CONTRACT-P5.md section 3) ----------------------- // Blocks until this buffer's OnBufferWriteback has landed. With no session - a build-split diff --git a/MobileGL/MG_Remote/Client/ServerProbe.cpp b/MobileGL/MG_Remote/Client/ServerProbe.cpp index 02ca469f..5377b64f 100644 --- a/MobileGL/MG_Remote/Client/ServerProbe.cpp +++ b/MobileGL/MG_Remote/Client/ServerProbe.cpp @@ -88,8 +88,9 @@ namespace MobileGL::MG_Remote::Client { if (!g_declineLogged.exchange(true)) { ServerProbeTarget target; (void)ConfiguredProbeTarget(&target); - const auto tookUs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - started).count(); + const auto tookUs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - started) + .count(); MGLOG_I("MG_Remote client: no MobileGL server answers at \"%s\" (probed in %lld us); declining, so " "the system's next GL implementation serves this process", target.endpoint.c_str(), static_cast(tookUs)); diff --git a/MobileGL/MG_Remote/Client/ServerProbe.h b/MobileGL/MG_Remote/Client/ServerProbe.h index 8f65985d..0fbb74fc 100644 --- a/MobileGL/MG_Remote/Client/ServerProbe.h +++ b/MobileGL/MG_Remote/Client/ServerProbe.h @@ -30,8 +30,8 @@ namespace MobileGL::MG_Remote::Client { }; struct ServerProbeTarget { - std::string endpoint; // SocketTransport form: a path, "@name", or "tcp://host:port" - Uint32 timeoutMs = 0; // 0: probing disabled + std::string endpoint; // SocketTransport form: a path, "@name", or "tcp://host:port" + Uint32 timeoutMs = 0; // 0: probing disabled }; // What the configuration says to probe; false when it names nothing probeable (NotApplicable). diff --git a/MobileGL/MG_Remote/Client/SlotCaps.h b/MobileGL/MG_Remote/Client/SlotCaps.h index 971bb3d5..fc018321 100644 --- a/MobileGL/MG_Remote/Client/SlotCaps.h +++ b/MobileGL/MG_Remote/Client/SlotCaps.h @@ -25,13 +25,13 @@ #include #include -#define MGL_BACKEND_SLOT_CAP(Slot, CapBit) \ - (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ - ? ::MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap(CapBit) \ +#define MGL_BACKEND_SLOT_CAP(Slot, CapBit) \ + (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ + ? ::MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap(CapBit) \ : ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) -#define MGL_BACKEND_SLOT_LOCAL(Slot) \ - (::MobileGL::MG_Config::Transport == ::MobileGL::MG_Config::TransportMode::Monolith && \ +#define MGL_BACKEND_SLOT_LOCAL(Slot) \ + (::MobileGL::MG_Config::Transport == ::MobileGL::MG_Config::TransportMode::Monolith && \ ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) // The POINTER-valued forms, for the several sites shaped `if (const auto f = TABLE.GL.Slot)`. @@ -41,27 +41,23 @@ // GL_Getter.cpp's GetInteger64v by -150 bytes and GetIntegerv by +2, which is two "resized" // symbols and a red G1. In a pull build these two expand to the slot expression ITSELF, so the // init-statement survives verbatim and the pull build's code generation cannot move. -#define MGL_BACKEND_SLOT_PTR_CAP(Slot, CapBit) \ - (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ - ? (::MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap(CapBit) \ - ? ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot \ - : nullptr) \ +#define MGL_BACKEND_SLOT_PTR_CAP(Slot, CapBit) \ + (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ + ? (::MobileGL::MG_Remote::Client::CapsMirrorInstance().HasCap(CapBit) \ + ? ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot \ + : nullptr) \ : ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) -#define MGL_BACKEND_SLOT_PTR_LOCAL(Slot) \ - (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ - ? nullptr \ +#define MGL_BACKEND_SLOT_PTR_LOCAL(Slot) \ + (::MobileGL::MG_Config::Transport != ::MobileGL::MG_Config::TransportMode::Monolith \ + ? nullptr \ : ::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) #else -#define MGL_BACKEND_SLOT_CAP(Slot, CapBit) \ - (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) -#define MGL_BACKEND_SLOT_LOCAL(Slot) \ - (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) -#define MGL_BACKEND_SLOT_PTR_CAP(Slot, CapBit) \ - (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) -#define MGL_BACKEND_SLOT_PTR_LOCAL(Slot) \ - (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) +#define MGL_BACKEND_SLOT_CAP(Slot, CapBit) (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) +#define MGL_BACKEND_SLOT_LOCAL(Slot) (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot != nullptr) +#define MGL_BACKEND_SLOT_PTR_CAP(Slot, CapBit) (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) +#define MGL_BACKEND_SLOT_PTR_LOCAL(Slot) (::MobileGL::MG_Backend::gBackendFunctionsTable.GL.Slot) #endif diff --git a/MobileGL/MG_Remote/Client/WireTables.cpp b/MobileGL/MG_Remote/Client/WireTables.cpp index 7248691c..5f4a69a8 100644 --- a/MobileGL/MG_Remote/Client/WireTables.cpp +++ b/MobileGL/MG_Remote/Client/WireTables.cpp @@ -61,7 +61,9 @@ namespace MobileGL::MG_Remote::Client { // thread an emitter runs the server's code directly, as the decoder does. Under spawn the // answer is constant per process. Declared in WireTables.h because PipeFill.cpp and // EGLImpl.cpp need it too; kept out of line so that header includes no server header. - Bool RunsAsTheServerRole() { return Server::ServerLoop::OnApplyThread(); } + Bool RunsAsTheServerRole() { + return Server::ServerLoop::OnApplyThread(); + } namespace { @@ -92,10 +94,11 @@ namespace MobileGL::MG_Remote::Client { if (session == nullptr) { // @Ph-declined (ID-P7-1): returns ClientSession& and runs in the CLIENT - no // session to hand back, no peer bytes, and the latch is a server-session idea. - SessionFail(MGFatalFamily::NoClientSession, "MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are " - "installed but no ClientSession is active. A row may not fall through to " - "a driver this role does not have", - row); + SessionFail(MGFatalFamily::NoClientSession, + "MGPipe: Fatal{NoClientSession, \"%s\"} - the client wire tables are " + "installed but no ClientSession is active. A row may not fall through to " + "a driver this role does not have", + row); } return *session; } @@ -104,14 +107,14 @@ namespace MobileGL::MG_Remote::Client { // record declared no blob" and "the record declared an empty blob" must not be spelled // the same way on a wire), so a row whose decoder calls RequireDeclaredBlob or // ResolveOrFatal is checked HERE, on the producing side, where the row has a name. - MG_Pipe::MGPBlobRef StageRequired(ClientSession& session, const char* row, - const void* bytes, Uint64 count) { + MG_Pipe::MGPBlobRef StageRequired(ClientSession& session, const char* row, const void* bytes, Uint64 count) { if (bytes == nullptr || count == 0) { - SessionFail(MGFatalFamily::BlobMissing, "MGPipe: Fatal{BlobMissing, \"%s\"} - the row's decoder requires a " - "declared blob and the call site handed over %llu bytes at %p. Under " - "monolith the companion pointer carries them; under split they have to " - "be staged, and there is nothing to stage", - row, static_cast(count), bytes); + SessionFail(MGFatalFamily::BlobMissing, + "MGPipe: Fatal{BlobMissing, \"%s\"} - the row's decoder requires a " + "declared blob and the call site handed over %llu bytes at %p. Under " + "monolith the companion pointer carries them; under split they have to " + "be staged, and there is nothing to stage", + row, static_cast(count), bytes); } return session.Encoder().StageBytes(bytes, count); } @@ -128,41 +131,40 @@ namespace MobileGL::MG_Remote::Client { // The three regular shapes // --------------------------------------------------------------------------------- -#define MGP_WIRE_PLAIN(Name, Payload, Table) \ - void Wire_##Name(const MG_Pipe::Payload* payload) { \ - if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolith##Table().Name(payload); return; } \ - ClientSession& session = RequireSession(#Name); \ - session.EmitAndWait(MGPWireOp::Name, payload, sizeof(*payload), nullptr, 0, nullptr, 0, \ - nullptr); \ - ++g_emitted; \ +#define MGP_WIRE_PLAIN(Name, Payload, Table) \ + void Wire_##Name(const MG_Pipe::Payload* payload) { \ + if (RunsAsTheServerRole()) { \ + MG_Pipe::MGPipeMonolith##Table().Name(payload); \ + return; \ + } \ + ClientSession& session = RequireSession(#Name); \ + session.EmitAndWait(MGPWireOp::Name, payload, sizeof(*payload), nullptr, 0, nullptr, 0, nullptr); \ + ++g_emitted; \ } -#define MGP_WIRE_BLOB(Name, Payload, BlobMember) \ - void Wire_##Name(const MG_Pipe::Payload* payload, const void* blobBytes, \ - Uint64 blobByteCount) { \ - if (RunsAsTheServerRole()) { \ - MG_Pipe::MGPipeMonolithContext().Name(payload, blobBytes, blobByteCount); \ - return; \ - } \ - ClientSession& session = RequireSession(#Name); \ - MG_Pipe::Payload record = *payload; \ - record.BlobMember = StageRequired(session, #Name, blobBytes, blobByteCount); \ - session.EmitAndWait(MGPWireOp::Name, &record, sizeof(record), nullptr, 0, nullptr, 0, \ - nullptr); \ - ++g_emitted; \ +#define MGP_WIRE_BLOB(Name, Payload, BlobMember) \ + void Wire_##Name(const MG_Pipe::Payload* payload, const void* blobBytes, Uint64 blobByteCount) { \ + if (RunsAsTheServerRole()) { \ + MG_Pipe::MGPipeMonolithContext().Name(payload, blobBytes, blobByteCount); \ + return; \ + } \ + ClientSession& session = RequireSession(#Name); \ + MG_Pipe::Payload record = *payload; \ + record.BlobMember = StageRequired(session, #Name, blobBytes, blobByteCount); \ + session.EmitAndWait(MGPWireOp::Name, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); \ + ++g_emitted; \ } -#define MGP_WIRE_TAIL(Name, Payload, TailType) \ - void Wire_##Name(const MG_Pipe::Payload* payload, const void* varTail, Uint32 varTailCount) { \ - if (RunsAsTheServerRole()) { \ - MG_Pipe::MGPipeMonolithContext().Name(payload, varTail, varTailCount); \ - return; \ - } \ - ClientSession& session = RequireSession(#Name); \ - session.EmitAndWait(MGPWireOp::Name, payload, sizeof(*payload), varTail, \ - static_cast(varTailCount) * sizeof(MG_Pipe::TailType), \ - nullptr, 0, nullptr); \ - ++g_emitted; \ +#define MGP_WIRE_TAIL(Name, Payload, TailType) \ + void Wire_##Name(const MG_Pipe::Payload* payload, const void* varTail, Uint32 varTailCount) { \ + if (RunsAsTheServerRole()) { \ + MG_Pipe::MGPipeMonolithContext().Name(payload, varTail, varTailCount); \ + return; \ + } \ + ClientSession& session = RequireSession(#Name); \ + session.EmitAndWait(MGPWireOp::Name, payload, sizeof(*payload), varTail, \ + static_cast(varTailCount) * sizeof(MG_Pipe::TailType), nullptr, 0, nullptr); \ + ++g_emitted; \ } // -- screen ------------------------------------------------------------------- @@ -170,10 +172,12 @@ namespace MobileGL::MG_Remote::Client { // store is released by this client after the record is out (the server's import holds its // own reference until its fence for the store's last use; CONTRACT-P11.md B2). void Wire_ResourceDestroy(const MG_Pipe::MGPHandleOnly* payload) { - if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolithScreen().ResourceDestroy(payload); return; } + if (RunsAsTheServerRole()) { + MG_Pipe::MGPipeMonolithScreen().ResourceDestroy(payload); + return; + } ClientSession& session = RequireSession("ResourceDestroy"); - session.EmitAndWait(MGPWireOp::ResourceDestroy, payload, sizeof(*payload), nullptr, 0, nullptr, 0, - nullptr); + session.EmitAndWait(MGPWireOp::ResourceDestroy, payload, sizeof(*payload), nullptr, 0, nullptr, 0, nullptr); ++g_emitted; if (static_cast(payload->Kind) == MG_Pipe::MGPipeKind::Buffer) session.ReleaseT0Store(payload->Handle); @@ -248,8 +252,7 @@ namespace MobileGL::MG_Remote::Client { ClientSession& session = RequireSession("SetDynamicState"); MG_Pipe::MGPDynamicState record = *payload; record.Blob = StageOptional(session, blobBytes, blobByteCount); - session.EmitAndWait(MGPWireOp::SetDynamicState, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MGPWireOp::SetDynamicState, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); ++g_emitted; } @@ -258,7 +261,9 @@ namespace MobileGL::MG_Remote::Client { void Wire_SetResidualValueState(const MG_Pipe::MGPResidualValueState*, const void*, Uint64) { SessionFail(MGFatalFamily::ResidualBlockSize, "MGPipe: Fatal{ResidualBlockSize} - set_residual_value_state reached the wire table, " - "but %s", "the residual value block was deleted in P13 W3c (op 46 is a retired row; the verify comparator owns the capability cross-check)"); + "but %s", + "the residual value block was deleted in P13 W3c (op 46 is a retired row; the verify " + "comparator owns the capability cross-check)"); } // resource_readback. kReplySlot, and the answer is COMPLETION only: the bytes travel @@ -267,12 +272,14 @@ namespace MobileGL::MG_Remote::Client { // row 22). So the reply buffer is deliberately {nullptr, 0} and the wait is what makes // the writeback already drained by the time this returns. void Wire_ResourceReadback(const MG_Pipe::MGPReadback* payload, MG_Pipe::MGPReplySlot* reply) { - if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolithContext().ResourceReadback(payload, reply); return; } + if (RunsAsTheServerRole()) { + MG_Pipe::MGPipeMonolithContext().ResourceReadback(payload, reply); + return; + } ClientSession& session = RequireSession("ResourceReadback"); Int32 status = 0; - const Uint64 seq = session.EmitAndWait(MGPWireOp::ResourceReadback, payload, - sizeof(*payload), nullptr, 0, nullptr, 0, - &status); + const Uint64 seq = session.EmitAndWait(MGPWireOp::ResourceReadback, payload, sizeof(*payload), nullptr, 0, + nullptr, 0, &status); // Cancellation has no wire ordinal; retain the caller's nonzero local ticket. if (seq != Wire::kInvalidSeq) reply->Id = seq; MG_Pipe::MGPipePostReply(*reply, status, 0); @@ -313,14 +320,12 @@ namespace MobileGL::MG_Remote::Client { // constant having drifted apart. 1 (and anything below it) is the pre-window shape. Uint32 CreateWindowEffective() { if (MG_Config::Ipc.CreateWindow <= 1) return 1; - return MG_Config::Ipc.CreateWindow < kCreateWindowMax ? MG_Config::Ipc.CreateWindow - : kCreateWindowMax; + return MG_Config::Ipc.CreateWindow < kCreateWindowMax ? MG_Config::Ipc.CreateWindow : kCreateWindowMax; } struct PendingCreate { Uint64 Seq = 0; MG_Pipe::MGPipeKind Kind = MG_Pipe::MGPipeKind::None; MG_Pipe::MGPipeHandle Handle{}; - }; PendingCreate g_createWindow[kCreateWindowMax]; Uint32 g_createWindowHead = 0; @@ -342,8 +347,8 @@ namespace MobileGL::MG_Remote::Client { // out to be LATENCY-bound, not throughput-bound (the server applies all 103,097 records in // 1.95 s of a 9.64 s frame, and the client's 1,161 blocking takes at ~4.9 ms are 5.7 s of // it) - so the way to make it faster is fewer blocks, not a faster apply. - Uint64 g_blockNoWindow = 0, g_blockSuspectsFull = 0, g_blockSuspectObject = 0, - g_blockWindowStillFull = 0, g_blockTotal = 0; + Uint64 g_blockNoWindow = 0, g_blockSuspectsFull = 0, g_blockSuspectObject = 0, g_blockWindowStillFull = 0, + g_blockTotal = 0; void NoteCreateBlocked(Uint64& reason) { ++reason; ++g_blockTotal; @@ -352,8 +357,7 @@ namespace MobileGL::MG_Remote::Client { if (g_blockTotal % 256 == 0) { MGLOG_I("P12 create blocks: total=%llu no-window=%llu suspects-full=%llu " "suspect-object=%llu window-still-full=%llu", - static_cast(g_blockTotal), - static_cast(g_blockNoWindow), + static_cast(g_blockTotal), static_cast(g_blockNoWindow), static_cast(g_blockSuspectsFull), static_cast(g_blockSuspectObject), static_cast(g_blockWindowStillFull)); @@ -386,8 +390,7 @@ namespace MobileGL::MG_Remote::Client { } void ClearCreateSuspect(MG_Pipe::MGPipeHandle handle) { for (SizeT i = 0; i < g_createSuspects.size(); ++i) { - if (g_createSuspects[i].Slot != handle.Slot || g_createSuspects[i].Gen != handle.Gen) - continue; + if (g_createSuspects[i].Slot != handle.Slot || g_createSuspects[i].Gen != handle.Gen) continue; for (SizeT j = i + 1; j < g_createSuspects.size(); ++j) g_createSuspects[j - 1] = g_createSuspects[j]; g_createSuspects.pop_back(); @@ -399,9 +402,12 @@ namespace MobileGL::MG_Remote::Client { // rather than defaulted, so a target added later cannot silently borrow the texture latch. MG_Pipe::MGPipeKind CreateRecordKind(const MG_Pipe::MGPResourceDesc& desc) { switch (static_cast(desc.Target)) { - case MG_Pipe::MGPipeResourceTarget::Buffer: return MG_Pipe::MGPipeKind::Buffer; - case MG_Pipe::MGPipeResourceTarget::Renderbuffer: return MG_Pipe::MGPipeKind::Renderbuffer; - default: return MG_Pipe::MGPipeKind::Texture; + case MG_Pipe::MGPipeResourceTarget::Buffer: + return MG_Pipe::MGPipeKind::Buffer; + case MG_Pipe::MGPipeResourceTarget::Renderbuffer: + return MG_Pipe::MGPipeKind::Renderbuffer; + default: + return MG_Pipe::MGPipeKind::Texture; } } @@ -420,11 +426,10 @@ namespace MobileGL::MG_Remote::Client { // blocking take only: the poll is not a round trip, and it is the blocking take // that is the thing MOBILEGL_IPC_CREATE_WINDOW divides (4,536 -> creates/window). const Uint64 metricStart = - blocking ? Transport::LinkMetricsBeginReply( - true, static_cast(MGPWireOp::ResourceCreate)) - : 0; - const Transport::SessionWait wait = - session.WaitForApplied(head.Seq, blocking ? 120000u : 0u); + blocking + ? Transport::LinkMetricsBeginReply(true, static_cast(MGPWireOp::ResourceCreate)) + : 0; + const Transport::SessionWait wait = session.WaitForApplied(head.Seq, blocking ? 120000u : 0u); if (blocking) Transport::LinkMetricsReplyApplied(metricStart); if (wait != Transport::SessionWait::Reached) return; Int32 status = Wire::ReplySink::kStatusError; @@ -451,9 +456,10 @@ namespace MobileGL::MG_Remote::Client { // ERROR IS NOT A DECLINE (M4 / R-5), and a deferred answer does not change that: // the same named Fatal the blocking take raises. if (status == Wire::ReplySink::kStatusError) { - SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"resource_create\"} - the row answered " - "ERROR, which is not an acceptance answer; folding it into accepted or " - "refused would make a transport fault look like a resource decision"); + SessionFail(MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"resource_create\"} - the row answered " + "ERROR, which is not an acceptance answer; folding it into accepted or " + "refused would make a transport fault look like a resource decision"); } if (status == Wire::ReplySink::kStatusDeclined) { MG_Pipe::MGPipeNoteHandleUnpublished(head.Kind, head.Handle); @@ -483,9 +489,8 @@ namespace MobileGL::MG_Remote::Client { void EmitCreateBlocking(ClientSession& session, const MG_Pipe::MGPResourceDesc* payload, MG_Pipe::MGPReplySlot* reply) { Int32 status = Wire::ReplySink::kStatusError; - const Uint64 seq = session.EmitAndWait(MGPWireOp::ResourceCreate, payload, - sizeof(*payload), nullptr, 0, nullptr, 0, - &status); + const Uint64 seq = session.EmitAndWait(MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, + nullptr, 0, &status); // Cancellation has no wire ordinal; retain the caller's nonzero local ticket. if (seq != Wire::kInvalidSeq) reply->Id = seq; MG_Pipe::MGPipePostReply(*reply, status, status == 0 ? 1u : 0u); @@ -498,7 +503,10 @@ namespace MobileGL::MG_Remote::Client { // emissions, so a budget counted here fires too late. The window defers only where the // link retains answers, and the store bounds it (docs/Performance/ipc-create-window.md). void Wire_ResourceCreate(const MG_Pipe::MGPResourceDesc* payload, MG_Pipe::MGPReplySlot* reply) { - if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolithScreen().ResourceCreate(payload, reply); return; } + if (RunsAsTheServerRole()) { + MG_Pipe::MGPipeMonolithScreen().ResourceCreate(payload, reply); + return; + } ClientSession& session = RequireSession("ResourceCreate"); // THE WINDOW MAY ONLY DEFER WHERE THE LINK RETAINS ANSWERS (P12). On one that does // not - the shared pool, whose slot is seq % slotCount and is reused - the answer the @@ -519,14 +527,13 @@ namespace MobileGL::MG_Remote::Client { // defers the answer and reads it later; this is the arm without one (the shared pool). if (window <= 1 && session.RunAheadArmed() && !CreateIsSuspect(payload->Resource)) { Int32 status = Wire::ReplySink::kStatusError; - const Uint64 seq = session.EmitAndWaitTails( - MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, nullptr, 0, - &status, nullptr, /*wantReply=*/false, /*willReadReply=*/false); + const Uint64 seq = + session.EmitAndWaitTails(MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, nullptr, + 0, &status, nullptr, /*wantReply=*/false, /*willReadReply=*/false); if (seq != Wire::kInvalidSeq) reply->Id = seq; - MG_Pipe::MGPipePostReply(*reply, - seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined - : Wire::ReplySink::kStatusOk, - seq == Wire::kInvalidSeq ? 0u : 1u); + MG_Pipe::MGPipePostReply( + *reply, seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined : Wire::ReplySink::kStatusOk, + seq == Wire::kInvalidSeq ? 0u : 1u); ++g_emitted; if (seq == Wire::kInvalidSeq) ++g_declined; return; @@ -543,8 +550,7 @@ namespace MobileGL::MG_Remote::Client { } else if (CreateIsSuspect(payload->Resource)) { NoteCreateBlocked(g_blockSuspectObject); } - if (window <= 1 || g_createSuspects.size() >= kCreateSuspectMax || - CreateIsSuspect(payload->Resource)) { + if (window <= 1 || g_createSuspects.size() >= kCreateSuspectMax || CreateIsSuspect(payload->Resource)) { // THE WINDOW RETIRES HERE, AND IT MAY BE HOLDING ANSWERS (P12). This branch is // taken when the window is off, when the suspect list is full, or for a refused // object - and in the last two cases entries already emitted are still in flight. @@ -579,9 +585,9 @@ namespace MobileGL::MG_Remote::Client { // not wait for its answer, and the drain above reads it later. A P12 no-reply bit // keyed on wantReply would tell the server to skip the very answer this row comes // back for (docs/.../p12/CREATE-WINDOW-MEASURED.md section 7). - const Uint64 seq = session.EmitAndWaitTails( - MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, nullptr, 0, - &status, nullptr, /*wantReply=*/false, /*willReadReply=*/true); + const Uint64 seq = + session.EmitAndWaitTails(MGPWireOp::ResourceCreate, payload, sizeof(*payload), nullptr, 0, nullptr, 0, + &status, nullptr, /*wantReply=*/false, /*willReadReply=*/true); if (seq == Wire::kInvalidSeq) { // Cancelled: there is no ordinal to read an answer from, and the caller must NOT // latch an object the record never described. @@ -605,21 +611,22 @@ namespace MobileGL::MG_Remote::Client { // waits, so a refusal reaches the caller as a refusal. EmitTextureParams pays the // confirming create at the texture's first use (HealUnpublishedRecord). void Wire_SetTextureParams(const MG_Pipe::MGPTextureParams* payload, MG_Pipe::MGPReplySlot* reply) { - if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolithContext().SetTextureParams(payload, reply); return; } + if (RunsAsTheServerRole()) { + MG_Pipe::MGPipeMonolithContext().SetTextureParams(payload, reply); + return; + } ClientSession& session = RequireSession("SetTextureParams"); - const Bool confirmed = MG_Pipe::MGPipeHandleIsPublished(MG_Pipe::MGPipeKind::Texture, - payload->Res); + const Bool confirmed = MG_Pipe::MGPipeHandleIsPublished(MG_Pipe::MGPipeKind::Texture, payload->Res); Int32 status = Wire::ReplySink::kStatusError; Uint64 seq = Wire::kInvalidSeq; if (confirmed) { - seq = session.EmitAndWaitTails(MGPWireOp::SetTextureParams, payload, sizeof(*payload), - nullptr, 0, nullptr, 0, &status, nullptr, + seq = session.EmitAndWaitTails(MGPWireOp::SetTextureParams, payload, sizeof(*payload), nullptr, 0, + nullptr, 0, &status, nullptr, /*wantReply=*/false, /*willReadReply=*/false); - status = seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined - : Wire::ReplySink::kStatusOk; + status = seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined : Wire::ReplySink::kStatusOk; } else { - seq = session.EmitAndWait(MGPWireOp::SetTextureParams, payload, sizeof(*payload), - nullptr, 0, nullptr, 0, &status); + seq = session.EmitAndWait(MGPWireOp::SetTextureParams, payload, sizeof(*payload), nullptr, 0, nullptr, + 0, &status); } // Cancellation has no wire ordinal; retain the caller's nonzero local ticket. if (seq != Wire::kInvalidSeq) reply->Id = seq; @@ -628,12 +635,11 @@ namespace MobileGL::MG_Remote::Client { if (status == 1) ++g_declined; } - void Wire_ResourceSubData(const MG_Pipe::MGPSubData* payload, const void* blobBytes, - Uint64 blobByteCount, const void* varTail, Uint32 varTailCount, - MG_Pipe::MGPReplySlot* reply) { + void Wire_ResourceSubData(const MG_Pipe::MGPSubData* payload, const void* blobBytes, Uint64 blobByteCount, + const void* varTail, Uint32 varTailCount, MG_Pipe::MGPReplySlot* reply) { if (RunsAsTheServerRole()) { - MG_Pipe::MGPipeMonolithContext().ResourceSubData(payload, blobBytes, blobByteCount, - varTail, varTailCount, reply); + MG_Pipe::MGPipeMonolithContext().ResourceSubData(payload, blobBytes, blobByteCount, varTail, + varTailCount, reply); return; } ClientSession& session = RequireSession("ResourceSubData"); @@ -655,12 +661,10 @@ namespace MobileGL::MG_Remote::Client { // may never take this path - R-5 has not moved. const Bool wantReply = MG_Pipe::MGPipeSubDataWantsItsReply(record); Int32 status = 0; - const Wire::WireTail tail{varTail, static_cast(varTailCount) * - sizeof(MG_Pipe::MGPSubRegion)}; + const Wire::WireTail tail{varTail, static_cast(varTailCount) * sizeof(MG_Pipe::MGPSubRegion)}; const Uint64 seq = session.EmitAndWaitTails( - MGPWireOp::ResourceSubData, &record, sizeof(record), - varTail != nullptr ? &tail : nullptr, varTail != nullptr ? 1u : 0u, nullptr, 0, - &status, nullptr, wantReply, + MGPWireOp::ResourceSubData, &record, sizeof(record), varTail != nullptr ? &tail : nullptr, + varTail != nullptr ? 1u : 0u, nullptr, 0, &status, nullptr, wantReply, // ONE ANSWER TO BOTH QUESTIONS, and on this row that is not a shortcut: the // wire arm's MGPipeSubDataWantsItsReply is false for the whole texture half // (PipeRoute.h), the caller discards the Bool either way, and the record's own @@ -671,7 +675,8 @@ namespace MobileGL::MG_Remote::Client { if (seq != Wire::kInvalidSeq) reply->Id = seq; const Int32 postedStatus = (seq == Wire::kInvalidSeq || status == Wire::ReplySink::kStatusDeclined) ? Wire::ReplySink::kStatusDeclined - : wantReply ? status : Wire::ReplySink::kStatusOk; + : wantReply ? status + : Wire::ReplySink::kStatusOk; MG_Pipe::MGPipePostReply(*reply, postedStatus, postedStatus == Wire::ReplySink::kStatusOk ? 1u : 0u); if (seq != Wire::kInvalidSeq) ++g_emitted; if (postedStatus == Wire::ReplySink::kStatusDeclined) ++g_declined; @@ -680,15 +685,14 @@ namespace MobileGL::MG_Remote::Client { void Wire_BufferSubDataResident(const MG_Pipe::MGPSubData* payload, const void* blobBytes, Uint64 blobByteCount) { if (RunsAsTheServerRole()) { - MG_Pipe::MGPipeMonolithContext().BufferSubDataResident(payload, blobBytes, - blobByteCount); + MG_Pipe::MGPipeMonolithContext().BufferSubDataResident(payload, blobBytes, blobByteCount); return; } ClientSession& session = RequireSession("BufferSubDataResident"); MG_Pipe::MGPSubData record = *payload; record.Blob = StageOptional(session, blobBytes, blobByteCount); - session.EmitAndWait(MGPWireOp::BufferSubDataResident, &record, sizeof(record), nullptr, - 0, nullptr, 0, nullptr); + session.EmitAndWait(MGPWireOp::BufferSubDataResident, &record, sizeof(record), nullptr, 0, nullptr, 0, + nullptr); ++g_emitted; } @@ -701,19 +705,19 @@ namespace MobileGL::MG_Remote::Client { // (MGPipeForEachSubDataRecordRange) lives there. What this emitter owes is the REFUSAL: // a non-null pointer arriving here means the call site was not converted, and silently // ignoring it would lose exactly the bytes R-13.3 promised would follow. - Bool Wire_Escape_ResourceRespecify(const MG_Pipe::MGPResourceDesc* desc, - const void* initialBytes, + Bool Wire_Escape_ResourceRespecify(const MG_Pipe::MGPResourceDesc* desc, const void* initialBytes, const MG_Pipe::MGPRespecifiedLevel* level) { if (RunsAsTheServerRole()) { return MG_Pipe::MGPipeMonolithEscapes().ResourceRespecify(desc, initialBytes, level); } ClientSession& session = RequireSession("ResourceRespecify"); if (initialBytes != nullptr) { - SessionFail(MGFatalFamily::UncarriedInitialBytes, "MGPipe: Fatal{UncarriedInitialBytes, \"resource_respecify\"} - a call " - "site handed initial content to a split respecify. R-13.3 rules that " - "initialBytes never crosses and that the content follows as " - "resource_subdata; a caller that still passes it has bytes nothing will " - "carry"); + SessionFail(MGFatalFamily::UncarriedInitialBytes, + "MGPipe: Fatal{UncarriedInitialBytes, \"resource_respecify\"} - a call " + "site handed initial content to a split respecify. R-13.3 rules that " + "initialBytes never crosses and that the content follows as " + "resource_subdata; a caller that still passes it has bytes nothing will " + "carry"); } // Scope and exact mutable mip extent travel together through the descriptor helper. // It temporarily reuses BufOffset/BufSize on non-buffer image targets; the server @@ -721,8 +725,8 @@ namespace MobileGL::MG_Remote::Client { MG_Pipe::MGPResourceDesc record = *desc; if (level != nullptr) { #if MOBILEGL_BUILD_DISAGGREGATED - MG_Pipe::MGPipeSetRespecifiedLevel(record, level->UploadTarget, level->Level, - level->Width, level->Height, level->Depth); + MG_Pipe::MGPipeSetRespecifiedLevel(record, level->UploadTarget, level->Level, level->Width, + level->Height, level->Depth); #else MG_Pipe::MGPipeSetRespecifiedLevel(record, level->UploadTarget, level->Level); #endif @@ -738,14 +742,13 @@ namespace MobileGL::MG_Remote::Client { const Bool wantsReply = MG_Pipe::MGPipeResourceRespecifyWantsItsReply(record); Int32 status = Wire::ReplySink::kStatusError; if (!wantsReply) { - const Uint64 seq = session.EmitAndWaitTails( - MGPWireOp::ResourceRespecify, &record, sizeof(record), nullptr, 0, nullptr, 0, - &status, nullptr, /*wantReply=*/false, /*willReadReply=*/false); - status = seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined - : Wire::ReplySink::kStatusOk; + const Uint64 seq = + session.EmitAndWaitTails(MGPWireOp::ResourceRespecify, &record, sizeof(record), nullptr, 0, nullptr, + 0, &status, nullptr, /*wantReply=*/false, /*willReadReply=*/false); + status = seq == Wire::kInvalidSeq ? Wire::ReplySink::kStatusDeclined : Wire::ReplySink::kStatusOk; } else { - (void)session.EmitAndWait(MGPWireOp::ResourceRespecify, &record, sizeof(record), - nullptr, 0, nullptr, 0, &status); + (void)session.EmitAndWait(MGPWireOp::ResourceRespecify, &record, sizeof(record), nullptr, 0, nullptr, 0, + &status); } ++g_emitted; // P11 B2: A RESPECIFIED BUFFER'S T0 STORE IS DONE FOR THIS CLIENT. The frontend has @@ -761,9 +764,10 @@ namespace MobileGL::MG_Remote::Client { // - the generated acceptance rows through MGPipeTakeReplyBool, and now these two // escapes - answers ERROR with the same named Fatal. if (status == 2) { - SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"resource_respecify\"} - the row answered " - "ERROR, which is not an acceptance answer; folding it into accepted or " - "refused would make a transport fault look like a resource decision"); + SessionFail(MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"resource_respecify\"} - the row answered " + "ERROR, which is not an acceptance answer; folding it into accepted or " + "refused would make a transport fault look like a resource decision"); } if (status == 1) ++g_declined; return status == 0; @@ -773,16 +777,15 @@ namespace MobileGL::MG_Remote::Client { // {range, AccessFlags} control record and the bytes of exactly that range arrive ahead // of it as resource_subdata. Same refusal as above, for the same reason: a blobref here // would be "a second, forgeable way to say the same thing". - void Wire_Escape_ResourceFlushRange(const MG_Pipe::MGPFlushRange* record, - const void* bytes) { + void Wire_Escape_ResourceFlushRange(const MG_Pipe::MGPFlushRange* record, const void* bytes) { if (RunsAsTheServerRole()) { MG_Pipe::MGPipeMonolithEscapes().ResourceFlushRange(record, bytes); return; } ClientSession& session = RequireSession("ResourceFlushRange"); (void)bytes; // ruled uncarried; the emitter in PipeFill.cpp sends the range first - session.EmitAndWait(MGPWireOp::ResourceFlushRange, record, sizeof(*record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MGPWireOp::ResourceFlushRange, record, sizeof(*record), nullptr, 0, nullptr, 0, + nullptr); ++g_emitted; } @@ -791,8 +794,7 @@ namespace MobileGL::MG_Remote::Client { // MapPersistentRoundtrips counter is defined as "one per storage definition in both // modes" and a client that answered locally would zero it. `size` and `seedBytes` have // no carrier (MGPHandleOnly is {Handle, Kind}) and need none: nothing is minted. - void* Wire_Escape_MapPersistent(const MG_Pipe::MGPHandleOnly* handle, Uint64 size, - const void* seedBytes) { + void* Wire_Escape_MapPersistent(const MG_Pipe::MGPHandleOnly* handle, Uint64 size, const void* seedBytes) { if (RunsAsTheServerRole()) { return MG_Pipe::MGPipeMonolithEscapes().MapPersistent(handle, size, seedBytes); } @@ -810,27 +812,28 @@ namespace MobileGL::MG_Remote::Client { (void)size; (void)seedBytes; Int32 status = 0; - session.EmitAndWait(MGPWireOp::MapPersistent, handle, sizeof(*handle), nullptr, 0, - nullptr, 0, &status); + session.EmitAndWait(MGPWireOp::MapPersistent, handle, sizeof(*handle), nullptr, 0, nullptr, 0, &status); ++g_emitted; // ERROR IS NOT A DECLINE (M4 / codex 5 / R-5), the same rule the respecify escape and // the generated acceptance rows obey: status 2 is a transport fault, and returning // nullptr for it would make it indistinguishable from R-6's legitimate decline. if (status == 2) { - SessionFail(MGFatalFamily::ReplyError, "MGPipe: Fatal{ReplyError, \"map_persistent\"} - the row answered ERROR, " - "which is not an acceptance answer; a transport fault is not a resource " - "decision and may not be folded into the decline R-6 predicts"); + SessionFail(MGFatalFamily::ReplyError, + "MGPipe: Fatal{ReplyError, \"map_persistent\"} - the row answered ERROR, " + "which is not an acceptance answer; a transport fault is not a resource " + "decision and may not be folded into the decline R-6 predicts"); } if (status == 1) ++g_declined; // NOT "always nullptr": the answer is READ. R-6 says the server declines, and the // day it stops declining this returns what it actually said rather than what the // ruling predicted. if (status == 0) { - SessionFail(MGFatalFamily::UnexpectedMapAccept, "MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server " - "accepted a persistent map under split. R-6 makes the split answer a " - "constant decline because there is no way to hand a host pointer across " - "a process boundary in P5; a pointer arriving here is one this client " - "cannot dereference"); + SessionFail(MGFatalFamily::UnexpectedMapAccept, + "MGPipe: Fatal{UnexpectedMapAccept, \"map_persistent\"} - the server " + "accepted a persistent map under split. R-6 makes the split answer a " + "constant decline because there is no way to hand a host pointer across " + "a process boundary in P5; a pointer arriving here is one this client " + "cannot dereference"); } return nullptr; } @@ -843,20 +846,20 @@ namespace MobileGL::MG_Remote::Client { // Spirv[i] is Fatal on the far side rather than ignored. void Wire_Escape_CreateShaderState(const MG_Pipe::MGPProgramDesc* desc, const MG_State::GLState::LinkArtifacts* link, - const MG_State::GLState::SpirvArtifacts* spirv, - const Uint32* linkedStages, Uint32 linkedStageCount) { + const MG_State::GLState::SpirvArtifacts* spirv, const Uint32* linkedStages, + Uint32 linkedStageCount) { if (RunsAsTheServerRole()) { - MG_Pipe::MGPipeMonolithEscapes().CreateShaderState(desc, link, spirv, linkedStages, - linkedStageCount); + MG_Pipe::MGPipeMonolithEscapes().CreateShaderState(desc, link, spirv, linkedStages, linkedStageCount); return; } ClientSession& session = RequireSession("CreateShaderState"); if (link == nullptr || spirv == nullptr) { - SessionFail(MGFatalFamily::ArtefactsMissing, "MGPipe: Fatal{ArtefactsMissing, \"create_shader_state\"} - the record's " - "two typed companions are null. Under monolith the applier reads the " - "modules out of spirv->generatedSpirv; under split there is nothing to " - "serialise, and emitting the record anyway would create a CSO with no " - "code"); + SessionFail(MGFatalFamily::ArtefactsMissing, + "MGPipe: Fatal{ArtefactsMissing, \"create_shader_state\"} - the record's " + "two typed companions are null. Under monolith the applier reads the " + "modules out of spirv->generatedSpirv; under split there is nothing to " + "serialise, and emitting the record anyway would create a CSO with no " + "code"); } // EncodeProgramArchive APPENDS and never fails - everything it walks is owned // plain data - so an empty archive means the two structs themselves were empty, @@ -876,14 +879,15 @@ namespace MobileGL::MG_Remote::Client { static_cast(archive.size())); } if (archive.empty()) { - SessionFail(MGFatalFamily::ArchiveEmpty, "MGPipe: Fatal{ArchiveEmpty, \"create_shader_state\"} - the program's " - "artefacts serialised to nothing"); + SessionFail(MGFatalFamily::ArchiveEmpty, + "MGPipe: Fatal{ArchiveEmpty, \"create_shader_state\"} - the program's " + "artefacts serialised to nothing"); } MG_Pipe::MGPProgramDesc record = *desc; - for (Uint32 i = 0; i < 6; ++i) record.Spirv[i] = MG_Pipe::MGPBlobRef{}; + for (Uint32 i = 0; i < 6; ++i) + record.Spirv[i] = MG_Pipe::MGPBlobRef{}; record.Reflection = session.Encoder().StageBytes(archive.data(), archive.size()); - session.EmitAndWait(MGPWireOp::CreateShaderState, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + session.EmitAndWait(MGPWireOp::CreateShaderState, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); ++g_emitted; } @@ -896,14 +900,12 @@ namespace MobileGL::MG_Remote::Client { // already is. Staged rather than packed into one run with offsets, because the honesty // pass the decoder runs is per span and a packed run would have to be re-split there // against arithmetic nothing on the wire declares. - void Wire_Escape_SetProgramBindings(const MG_Pipe::MGPProgramBindings* hdr, - const Int32* blockBindings, + void Wire_Escape_SetProgramBindings(const MG_Pipe::MGPProgramBindings* hdr, const Int32* blockBindings, const MG_Pipe::MGPProgramSamplerUnit* samplerUnits, const MG_Pipe::MGPProgramStorageOverride* storageOverrides, const char* const* storageOverrideNames) { if (RunsAsTheServerRole()) { - MG_Pipe::MGPipeMonolithEscapes().SetProgramBindings(hdr, blockBindings, samplerUnits, - storageOverrides, + MG_Pipe::MGPipeMonolithEscapes().SetProgramBindings(hdr, blockBindings, samplerUnits, storageOverrides, storageOverrideNames); return; } @@ -938,17 +940,15 @@ namespace MobileGL::MG_Remote::Client { const Wire::WireTail tails[3] = { {blockBindings, static_cast(record.BlockBindingCount) * sizeof(Int32)}, - {samplerUnits, static_cast(record.SamplerUnitCount) * - sizeof(MG_Pipe::MGPProgramSamplerUnit)}, + {samplerUnits, static_cast(record.SamplerUnitCount) * sizeof(MG_Pipe::MGPProgramSamplerUnit)}, {overrides.empty() ? nullptr : overrides.data(), - static_cast(record.StorageOverrideCount) * - sizeof(MG_Pipe::MGPProgramStorageOverride)}, + static_cast(record.StorageOverrideCount) * sizeof(MG_Pipe::MGPProgramStorageOverride)}, }; // ALWAYS THREE, even when a count is 0 - the decoder derives the third tail's // offset from the first two, so declaring fewer would put the same bytes somewhere // else (PipeWireCodec.cpp's layout arm says this from the other side). - session.EmitAndWaitTails(MGPWireOp::SetProgramBindings, &record, sizeof(record), tails, 3, - nullptr, 0, nullptr); + session.EmitAndWaitTails(MGPWireOp::SetProgramBindings, &record, sizeof(record), tails, 3, nullptr, 0, + nullptr); ++g_emitted; } @@ -990,13 +990,12 @@ namespace MobileGL::MG_Remote::Client { ContextFrameEmit SendCreateContextFrame(Uint64 clientContextToken, Uint64 shareGroupToken, Uint32 flags) { if (!ContextFramesCanCross()) return ContextFrameEmit::NoWire; return Server::ServerCreateEGLContext(clientContextToken, shareGroupToken, flags) ? ContextFrameEmit::Sent - : ContextFrameEmit::Refused; + : ContextFrameEmit::Refused; } ContextFrameEmit SendDestroyContextFrame(Uint64 clientContextToken) { if (!ContextFramesCanCross()) return ContextFrameEmit::NoWire; - return Server::ServerDestroyEGLContext(clientContextToken) ? ContextFrameEmit::Sent - : ContextFrameEmit::Refused; + return Server::ServerDestroyEGLContext(clientContextToken) ? ContextFrameEmit::Sent : ContextFrameEmit::Refused; } // P14 S1: bind_context. The same four windows as EmitApplierResetRecord below answer false, @@ -1010,8 +1009,8 @@ namespace MobileGL::MG_Remote::Client { if (g_clientTablesUninstalled.load(std::memory_order_acquire)) return false; MG_Pipe::MGPBindContext record{}; record.ClientContextToken = clientContextToken; - const Uint64 seq = session->EmitAndWait(MGPWireOp::BindContext, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + const Uint64 seq = + session->EmitAndWait(MGPWireOp::BindContext, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); ++g_emitted; // A DECLINED binding moved nothing on the server (a lost session, or a thread whose // context is from an ended one), so the caller must not believe the session is bound to @@ -1031,8 +1030,8 @@ namespace MobileGL::MG_Remote::Client { if (g_clientTablesUninstalled.load(std::memory_order_acquire)) return false; MG_Pipe::MGPApplierReset record{}; record.ContextSerial = g_applierResetContextSerial; - const Uint64 seq = session->EmitAndWait(MGPWireOp::ApplierReset, &record, sizeof(record), nullptr, 0, - nullptr, 0, nullptr); + const Uint64 seq = + session->EmitAndWait(MGPWireOp::ApplierReset, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); // THE SERIAL COUNTS RESETS THE SERVER WAS SENT, not resets this client primed: a declined // record (a lost session, a thread on an ended session's context) never reached the // server's count, and moving ours past it would latch the next session that hears one. @@ -1059,8 +1058,7 @@ namespace MobileGL::MG_Remote::Client { // can never disagree about who supplies the eight fields. Bool ContextValuesWireLive() { ClientSession* session = ClientSession::Active(); - return session != nullptr && session->Started() && - !g_clientTablesUninstalled.load(std::memory_order_acquire); + return session != nullptr && session->Started() && !g_clientTablesUninstalled.load(std::memory_order_acquire); } ObjectDeathEmit EmitObjectDeathRecord(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId) { @@ -1098,29 +1096,29 @@ namespace MobileGL::MG_Remote::Client { // The WAIT is the only property the blocking mailbox hop provided and the only one P5c // keeps (§5.2 step 3): it orders the death against in-flight verbs that name the // handle, so the server's twin cannot be released underneath a verb that is using it. - session->EmitAndWait(MGPWireOp::ObjectDeath, &record, sizeof(record), nullptr, 0, nullptr, - 0, nullptr); + session->EmitAndWait(MGPWireOp::ObjectDeath, &record, sizeof(record), nullptr, 0, nullptr, 0, nullptr); ++g_emitted; return ObjectDeathEmit::Emitted; } void RequireClientTablesInstalled(const char* row) { if (g_clientTablesUninstalled.load(std::memory_order_acquire)) { - SessionFail(MGFatalFamily::ClientTablesUninstalled, "MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - the client tables are " - "being torn down; refusing before session or ring access", row); + SessionFail(MGFatalFamily::ClientTablesUninstalled, + "MGPipe: Fatal{ClientTablesUninstalled, \"%s\"} - the client tables are " + "being torn down; refusing before session or ring access", + row); } } namespace { - void WireStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, - MG_Pipe::MGPipeHandle /*handle*/) { + void WireStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, MG_Pipe::MGPipeHandle /*handle*/) { (void)EmitObjectDeathRecord(kind, lifetimeId); } const MG_State::GLState::StateObjectDeathOps kClientStateObjectDeathOps = { .OnDestroyed = &WireStateObjectDestroyed, }; - } + } // namespace void InstallClientWireTables() { using namespace MG_Pipe; @@ -1218,8 +1216,12 @@ namespace MobileGL::MG_Remote::Client { g_clientTablesUninstalled.store(false, std::memory_order_release); } - Uint64 ClientWireRecordsEmitted() { return g_emitted; } - Uint64 ClientWireRecordsDeclined() { return g_declined; } + Uint64 ClientWireRecordsEmitted() { + return g_emitted; + } + Uint64 ClientWireRecordsDeclined() { + return g_declined; + } // ---- P12: the CREATE WINDOW, as numbers a test can read --------------------------------- // @@ -1230,21 +1232,33 @@ namespace MobileGL::MG_Remote::Client { // ceiling - it would REPLACE the oldest confirmation and leave the count where it was. Read it // against the effective ceiling and the array size, which are the two numbers the guard itself // is written in terms of, and not against a literal repeated in the test. - Uint32 ClientCreateWindowPending() { return g_createWindowCount; } + Uint32 ClientCreateWindowPending() { + return g_createWindowCount; + } // Taken + Pending is every create the window ever accepted: the answers it has read back and // the ones still outstanding. A create the window accepted and then LOST would break that // sum, which is the failure a provisional accept makes possible and a count cannot show. - Uint64 ClientCreateWindowTaken() { return g_createWindowTaken; } + Uint64 ClientCreateWindowTaken() { + return g_createWindowTaken; + } // P12: RetainedOutstanding / SkippedUnwanted / UnansweredWanted live on the LINK, where the // store is (StreamLink::RetainedOutstanding and friends). They are what says whether the // declared-answer store is bounded by the CLIENT (SkippedUnwanted grows, outstanding does // not) rather than by the server's answer rate - which is the property the capacity rests on. - Uint32 ClientCreateWindowEffective() { return CreateWindowEffective(); } - Uint32 ClientCreateWindowArraySize() { return kCreateWindowMax; } + Uint32 ClientCreateWindowEffective() { + return CreateWindowEffective(); + } + Uint32 ClientCreateWindowArraySize() { + return kCreateWindowMax; + } // Objects whose create came back DECLINED: out of the window, next create blocking, and back // in the moment a blocking create lands. - Uint32 ClientCreateWindowSuspects() { return static_cast(g_createSuspects.size()); } - Uint64 ClientCreateWindowRefusals() { return g_createRefusals; } + Uint32 ClientCreateWindowSuspects() { + return static_cast(g_createSuspects.size()); + } + Uint64 ClientCreateWindowRefusals() { + return g_createRefusals; + } } // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/Client/WireTables.h b/MobileGL/MG_Remote/Client/WireTables.h index 19a19ea5..d91ab078 100644 --- a/MobileGL/MG_Remote/Client/WireTables.h +++ b/MobileGL/MG_Remote/Client/WireTables.h @@ -121,7 +121,11 @@ namespace MobileGL::MG_Remote::Client { // (before ClientSession::Start, or on the server role's own thread) means keep the context on // the client's books alone. Refused is a server answer: eglCreateContext then returns // EGL_NO_CONTEXT, because the server would refuse that context's first bind_context. - enum class ContextFrameEmit : Uint8 { Sent, NoWire, Refused }; + enum class ContextFrameEmit : Uint8 { + Sent, + NoWire, + Refused + }; ContextFrameEmit SendCreateContextFrame(Uint64 clientContextToken, Uint64 shareGroupToken, Uint32 flags); // A destroy has the same three answers; the client's EGL book is dropped either way (that is // the caller's own state machine), and what a refusal changes is only what the server holds. @@ -153,7 +157,11 @@ namespace MobileGL::MG_Remote::Client { // third is NOT folded into the second: a server role with no wire at all - a ServerLoop // fixture's shape, never a real split's - still has a server loop the caller delivers to, // where a handle that never existed has nothing to deliver. - enum class ObjectDeathEmit : Uint8 { Emitted, NoHandle, NoSession }; + enum class ObjectDeathEmit : Uint8 { + Emitted, + NoHandle, + NoSession + }; ObjectDeathEmit EmitObjectDeathRecord(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId); } // namespace MobileGL::MG_Remote::Client diff --git a/MobileGL/MG_Remote/FatalFamily.h b/MobileGL/MG_Remote/FatalFamily.h index 73d9e1fd..c24ac934 100644 --- a/MobileGL/MG_Remote/FatalFamily.h +++ b/MobileGL/MG_Remote/FatalFamily.h @@ -31,8 +31,9 @@ namespace MobileGL::MG_Remote { // that exists has a row, and a family that does not exist is not a value of the enum. inline ::MobileGL::Wire::FatalCode FatalCodeForFamily(MGFatalFamily family) { switch (family) { -#define X(Family, WireCode, Why) \ - case MGFatalFamily::Family: return ::MobileGL::Wire::FatalCode::WireCode; +#define X(Family, WireCode, Why) \ + case MGFatalFamily::Family: \ + return ::MobileGL::Wire::FatalCode::WireCode; MGL_FATAL_FAMILY_LIST(X) #undef X } diff --git a/MobileGL/MG_Remote/FatalFunnel.cpp b/MobileGL/MG_Remote/FatalFunnel.cpp index 7cacd3c2..286f8383 100644 --- a/MobileGL/MG_Remote/FatalFunnel.cpp +++ b/MobileGL/MG_Remote/FatalFunnel.cpp @@ -8,7 +8,7 @@ #include -#include "../Includes.h" // MGLOG_F and the umbrella; a .cpp may pull it, a Transport/ header may not +#include "../Includes.h" // MGLOG_F and the umbrella; a .cpp may pull it, a Transport/ header may not #include // the seam MG_Backend's renderer dies through #include "Client/ClientSession.h" #include "Protocol/generated/protocol_generated.h" @@ -67,8 +67,7 @@ namespace MobileGL::MG_Remote { return MGFatalFamily::ProtocolCorruption; } - [[noreturn]] void PipeSessionFailAdapter(MG_Pipe::MGPipeFatalFamily family, - const char* line) { + [[noreturn]] void PipeSessionFailAdapter(MG_Pipe::MGPipeFatalFamily family, const char* line) { SessionFail(MapPipeFamily(family), "%s", line); } @@ -94,8 +93,7 @@ namespace MobileGL::MG_Remote { void FormatFaultLine(char (&line)[512], const char* fmt, va_list args) { const int written = std::vsnprintf(line, sizeof(line), fmt, args); if (written < 0) { - std::snprintf(line, sizeof(line), - "MG_Remote: unformattable Fatal diagnostic (format=%s)", fmt); + std::snprintf(line, sizeof(line), "MG_Remote: unformattable Fatal diagnostic (format=%s)", fmt); } } @@ -138,7 +136,9 @@ namespace MobileGL::MG_Remote { std::unordered_map g_domains; int g_nextDomain = 1; - LatchState& Latch() { return *t_latch; } + LatchState& Latch() { + return *t_latch; + } } // namespace int FatalLatchDomainOpen() { @@ -177,7 +177,9 @@ namespace MobileGL::MG_Remote { return previous->id; } - void ArmSessionLatch() { Latch().armed.store(true, std::memory_order_release); } + void ArmSessionLatch() { + Latch().armed.store(true, std::memory_order_release); + } void ResetSessionLatch() { LatchState& latch = Latch(); @@ -189,9 +191,13 @@ namespace MobileGL::MG_Remote { latch.latched.store(false, std::memory_order_release); } - bool SessionLatchArmed() { return Latch().armed.load(std::memory_order_acquire); } + bool SessionLatchArmed() { + return Latch().armed.load(std::memory_order_acquire); + } - bool SessionLatched() { return Latch().latched.load(std::memory_order_acquire); } + bool SessionLatched() { + return Latch().latched.load(std::memory_order_acquire); + } MGFatalFamily SessionLatchedFamily() { LatchState& latch = Latch(); @@ -199,9 +205,13 @@ namespace MobileGL::MG_Remote { return latch.latchedFamily; } - const char* SessionLatchedLine() { return Latch().latchedLine; } + const char* SessionLatchedLine() { + return Latch().latchedLine; + } - std::uint64_t SessionLatchCount() { return Latch().count.load(std::memory_order_relaxed); } + std::uint64_t SessionLatchCount() { + return Latch().count.load(std::memory_order_relaxed); + } bool SessionLatch(MGFatalFamily family, const char* fmt, ...) { char line[512]; @@ -249,8 +259,7 @@ namespace MobileGL::MG_Remote { FatalFamilyName(family), kSessionLatchedExitCode); // Before the peer hears of it: what the observer records must already hold when the client // reacts (a lost compositor re-creates its window surface on a fresh session at once). - if (const SessionLatchObserver observer = g_sessionLatchObserver.load(std::memory_order_acquire)) - observer(); + if (const SessionLatchObserver observer = g_sessionLatchObserver.load(std::memory_order_acquire)) observer(); PublishSessionFault(family, line); return false; } @@ -270,41 +279,40 @@ namespace MobileGL::MG_Remote { } namespace { - void PublishSessionFault(MGFatalFamily family, const char* line) { - // A SessionFault frame to the peer, BEFORE the abort, so the other side's log names the - // family instead of reading a bare EOF (5.2). Best-effort by nature: this thread is about - // to die, so a send that blocks or fails changes nothing it could have changed anyway. - // - // WHICHEVER ROLE THIS PROCESS IS. Under spawn only one session is active per process - - // the client has ClientSession, the server has ServerSession - so publishing on whatever - // control plane exists reaches the far side. Under inproc both exist and the frame goes - // to an in-process transport the aborting process will never read; harmless, and not - // worth a special case that would then be the untested one. - // - // `code` is the coarse seven-value projection; `family` carries the full word; `message` - // is the same line just logged. seq/op are not threaded through the 90 call sites and the - // message already names the op where it matters, so the frame carries what it can name - // honestly rather than a field that is always zero. - Transport::ITransport* control = nullptr; - if (Client::ClientSession* client = Client::ClientSession::Active()) { - control = client->Control_Plane(); - } - if (control == nullptr) { - if (Server::ServerSession* server = Server::ServerSession::Active()) { - control = server->Control_Plane(); + void PublishSessionFault(MGFatalFamily family, const char* line) { + // A SessionFault frame to the peer, BEFORE the abort, so the other side's log names the + // family instead of reading a bare EOF (5.2). Best-effort by nature: this thread is about + // to die, so a send that blocks or fails changes nothing it could have changed anyway. + // + // WHICHEVER ROLE THIS PROCESS IS. Under spawn only one session is active per process - + // the client has ClientSession, the server has ServerSession - so publishing on whatever + // control plane exists reaches the far side. Under inproc both exist and the frame goes + // to an in-process transport the aborting process will never read; harmless, and not + // worth a special case that would then be the untested one. + // + // `code` is the coarse seven-value projection; `family` carries the full word; `message` + // is the same line just logged. seq/op are not threaded through the 90 call sites and the + // message already names the op where it matters, so the frame carries what it can name + // honestly rather than a field that is always zero. + Transport::ITransport* control = nullptr; + if (Client::ClientSession* client = Client::ClientSession::Active()) { + control = client->Control_Plane(); + } + if (control == nullptr) { + if (Server::ServerSession* server = Server::ServerSession::Active()) { + control = server->Control_Plane(); + } + } + if (control != nullptr) { + ::flatbuffers::FlatBufferBuilder builder(256); + const auto fatal = ::MobileGL::Wire::CreateFatalDirect(builder, FatalCodeForFamily(family), line, + FatalFamilyName(family)); + const auto root = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Fatal, fatal.Union()); + ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); + (void)control->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); } } - if (control != nullptr) { - ::flatbuffers::FlatBufferBuilder builder(256); - const auto fatal = ::MobileGL::Wire::CreateFatalDirect( - builder, FatalCodeForFamily(family), line, FatalFamilyName(family)); - const auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Fatal, fatal.Union()); - ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); - (void)control->SendFrame( - MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); - } - } } // namespace // P13 W5: THE RECORD ARM'S DEATHS GO THROUGH THIS FUNNEL WHEREVER MG_Remote IS LINKED. @@ -320,8 +328,7 @@ namespace MobileGL::MG_Remote { return SessionLatch(family, "%s", line); } [[maybe_unused]] const bool g_recordFailHooksRegistered = [] { - MG_Pipe::MGPipeInstallRecordFailHooks(&RecordFailThroughFunnel, &RecordLatchThroughFunnel, - &SessionLatched); + MG_Pipe::MGPipeInstallRecordFailHooks(&RecordFailThroughFunnel, &RecordLatchThroughFunnel, &SessionLatched); return true; }(); } // namespace diff --git a/MobileGL/MG_Remote/Handshake.h b/MobileGL/MG_Remote/Handshake.h index 76101d44..05b1b7f2 100644 --- a/MobileGL/MG_Remote/Handshake.h +++ b/MobileGL/MG_Remote/Handshake.h @@ -20,18 +20,16 @@ #include namespace MobileGL::MG_Remote { - inline MobileGLResult RefuseHandshake(Transport::ITransport& transport, - ::MobileGL::Wire::RefuseCode code, - const char* detail, Uint64 expected = 0, - Uint64 actual = 0, const char* peerValue = nullptr) { - MGLOG_E("MGPipe: Refuse{%s} %s expected=%llu actual=%llu peer=%s", - ::MobileGL::Wire::EnumNameRefuseCode(code), detail, - static_cast(expected), static_cast(actual), + inline MobileGLResult RefuseHandshake(Transport::ITransport& transport, ::MobileGL::Wire::RefuseCode code, + const char* detail, Uint64 expected = 0, Uint64 actual = 0, + const char* peerValue = nullptr) { + MGLOG_E("MGPipe: Refuse{%s} %s expected=%llu actual=%llu peer=%s", ::MobileGL::Wire::EnumNameRefuseCode(code), + detail, static_cast(expected), static_cast(actual), peerValue == nullptr ? "" : peerValue); ::flatbuffers::FlatBufferBuilder builder(256); auto refusal = ::MobileGL::Wire::CreateRefuseDirect(builder, code, detail, expected, actual, peerValue); - auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Refuse, - refusal.Union()); + auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Refuse, refusal.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()}); return MOBILEGL_ERR_PROTOCOL_MISMATCH; @@ -65,32 +63,31 @@ namespace MobileGL::MG_Remote { const char* bytes = presented == nullptr ? "" : presented->c_str(); const std::size_t size = presented == nullptr ? 0u : presented->size(); if (Transport::ConstantTimeTokenMatch(expected, bytes, size)) return MOBILEGL_OK; - return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::Authentication, - "token mismatch"); + return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::Authentication, "token mismatch"); } - inline MobileGLResult ValidatePeerHandshake(Transport::ITransport& transport, - Uint32 major, Uint32 minor, Uint64 fingerprint, - const char* stamp, ::MobileGL::Wire::DialMode dial) { + inline MobileGLResult ValidatePeerHandshake(Transport::ITransport& transport, Uint32 major, Uint32 minor, + Uint64 fingerprint, const char* stamp, + ::MobileGL::Wire::DialMode dial) { using ::MobileGL::Wire::RefuseCode; if (major != MOBILEGL_PROTOCOL_ABI_MAJOR || minor != MOBILEGL_PROTOCOL_ABI_MINOR) return RefuseHandshake(transport, RefuseCode::ProtocolVersion, "protocol version", - MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR), - MOBILEGL_ABI_VERSION(major, minor)); + MOBILEGL_ABI_VERSION(MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR), + MOBILEGL_ABI_VERSION(major, minor)); if (fingerprint != WireFingerprint()) - return RefuseHandshake(transport, RefuseCode::WireFingerprint, "wire layout", - WireFingerprint(), fingerprint); + return RefuseHandshake(transport, RefuseCode::WireFingerprint, "wire layout", WireFingerprint(), + fingerprint); const bool sameBuild = BuildFingerprintPresent() && stamp != nullptr && stamp[0] != '\0' && std::strcmp(stamp, BuildFingerprint()) == 0; const char* required = std::getenv("MOBILEGL_IPC_REQUIRE_SAME_BUILD"); - const bool requireSame = dial == ::MobileGL::Wire::DialMode::Fork || - (required != nullptr && std::strcmp(required, "1") == 0); + const bool requireSame = + dial == ::MobileGL::Wire::DialMode::Fork || (required != nullptr && std::strcmp(required, "1") == 0); if (!sameBuild && requireSame) return RefuseHandshake(transport, RefuseCode::BuildFingerprint, "build identity", 0, 0, stamp == nullptr ? "" : stamp); if (!sameBuild && dial == ::MobileGL::Wire::DialMode::Connect) - MGLOG_W("MGPipe: compatible wire with different build: local=%s peer=%s", - BuildFingerprint(), stamp == nullptr ? "" : stamp); + MGLOG_W("MGPipe: compatible wire with different build: local=%s peer=%s", BuildFingerprint(), + stamp == nullptr ? "" : stamp); return MOBILEGL_OK; } @@ -103,11 +100,10 @@ namespace MobileGL::MG_Remote { inline std::vector EncodeDataBind(const std::uint8_t* nonce, std::size_t size) { ::flatbuffers::FlatBufferBuilder builder(64); auto bind = ::MobileGL::Wire::CreateDataBind(builder, builder.CreateVector(nonce, size)); - auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::DataBind, - bind.Union()); + auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::DataBind, bind.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); - return std::vector(builder.GetBufferPointer(), - builder.GetBufferPointer() + builder.GetSize()); + return std::vector(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); } // True when `frame` is a verifiable CtrlEnvelope carrying a DataBind whose nonce is exactly diff --git a/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.cpp b/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.cpp index b99e33a9..bcacc2ab 100644 --- a/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.cpp +++ b/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.cpp @@ -28,37 +28,37 @@ namespace MobileGL::MG_Remote { // renumbers either side fails HERE, at compile time, instead of silently mistranslating // op identities between two processes that both still announce the same ABI major. static_assert(static_cast(SurfaceControlOp::InitializeDisplay) == - static_cast(::MobileGL::Wire::SurfaceOpKind::InitializeDisplay)); + static_cast(::MobileGL::Wire::SurfaceOpKind::InitializeDisplay)); static_assert(static_cast(SurfaceControlOp::CreateWindowSurface) == - static_cast(::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface)); + static_cast(::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface)); static_assert(static_cast(SurfaceControlOp::CreatePbufferSurface) == - static_cast(::MobileGL::Wire::SurfaceOpKind::CreatePbufferSurface)); + static_cast(::MobileGL::Wire::SurfaceOpKind::CreatePbufferSurface)); static_assert(static_cast(SurfaceControlOp::ResizeWindowSurface) == - static_cast(::MobileGL::Wire::SurfaceOpKind::ResizeWindowSurface)); + static_cast(::MobileGL::Wire::SurfaceOpKind::ResizeWindowSurface)); static_assert(static_cast(SurfaceControlOp::ReleaseSurface) == - static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseSurface)); + static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseSurface)); static_assert(static_cast(SurfaceControlOp::MakeCurrent) == - static_cast(::MobileGL::Wire::SurfaceOpKind::MakeCurrent)); + static_cast(::MobileGL::Wire::SurfaceOpKind::MakeCurrent)); static_assert(static_cast(SurfaceControlOp::ReleaseCurrent) == - static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseCurrent)); + static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseCurrent)); // fc's three schema additions, append-only (protocol.fbs's own rule). static_assert(static_cast(SurfaceControlOp::SetSwapInterval) == - static_cast(::MobileGL::Wire::SurfaceOpKind::SetSwapInterval)); + static_cast(::MobileGL::Wire::SurfaceOpKind::SetSwapInterval)); static_assert(static_cast(SurfaceControlOp::ReleaseResources) == - static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseResources)); + static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseResources)); static_assert(static_cast(SurfaceControlOp::SetWindowHandle) == - static_cast(::MobileGL::Wire::SurfaceOpKind::SetWindowHandle)); + static_cast(::MobileGL::Wire::SurfaceOpKind::SetWindowHandle)); // cp's one schema addition, on the same append-only terms. static_assert(static_cast(SurfaceControlOp::InitCapabilities) == - static_cast(::MobileGL::Wire::SurfaceOpKind::InitCapabilities)); + static_cast(::MobileGL::Wire::SurfaceOpKind::InitCapabilities)); // P14 S1's two appends, on the same append-only terms. The VALUES are pinned because they // are wire ABI, and pinned SEPARATELY from the identity cast beside them: the cast would // stay green if both enums moved together to 20/21, and a peer built before the move // would then read the two ops as two others. static_assert(static_cast(SurfaceControlOp::CreateContext) == - static_cast(::MobileGL::Wire::SurfaceOpKind::CreateContext)); + static_cast(::MobileGL::Wire::SurfaceOpKind::CreateContext)); static_assert(static_cast(SurfaceControlOp::DestroyContext) == - static_cast(::MobileGL::Wire::SurfaceOpKind::DestroyContext)); + static_cast(::MobileGL::Wire::SurfaceOpKind::DestroyContext)); static_assert(static_cast(::MobileGL::Wire::SurfaceOpKind::SetSwapInterval) == 8 && static_cast(::MobileGL::Wire::SurfaceOpKind::ReleaseResources) == 9 && static_cast(::MobileGL::Wire::SurfaceOpKind::SetWindowHandle) == 10 && @@ -95,15 +95,24 @@ namespace MobileGL::MG_Remote { const char* SurfaceWireErrorName(SurfaceWireError error) { switch (error) { - case SurfaceWireError::None: return "None"; - case SurfaceWireError::UnknownOpKind: return "UnknownOpKind"; - case SurfaceWireError::InprocOnlyOpOnTheWire: return "InprocOnlyOpOnTheWire"; - case SurfaceWireError::WindowKindNamesNoBackend: return "WindowKindNamesNoBackend"; - case SurfaceWireError::UnknownWindowKind: return "UnknownWindowKind"; - case SurfaceWireError::MetalLayerArrived: return "MetalLayerArrived"; - case SurfaceWireError::AndroidNativeWindowArrived: return "AndroidNativeWindowArrived"; - case SurfaceWireError::ServerOwnedTokenNotZero: return "ServerOwnedTokenNotZero"; - case SurfaceWireError::ServerOwnedOnSetWindowHandle: return "ServerOwnedOnSetWindowHandle"; + case SurfaceWireError::None: + return "None"; + case SurfaceWireError::UnknownOpKind: + return "UnknownOpKind"; + case SurfaceWireError::InprocOnlyOpOnTheWire: + return "InprocOnlyOpOnTheWire"; + case SurfaceWireError::WindowKindNamesNoBackend: + return "WindowKindNamesNoBackend"; + case SurfaceWireError::UnknownWindowKind: + return "UnknownWindowKind"; + case SurfaceWireError::MetalLayerArrived: + return "MetalLayerArrived"; + case SurfaceWireError::AndroidNativeWindowArrived: + return "AndroidNativeWindowArrived"; + case SurfaceWireError::ServerOwnedTokenNotZero: + return "ServerOwnedTokenNotZero"; + case SurfaceWireError::ServerOwnedOnSetWindowHandle: + return "ServerOwnedOnSetWindowHandle"; } return ""; } @@ -133,29 +142,50 @@ namespace MobileGL::MG_Remote { bool WireWindowKindForWindowBackend(MG_Backend::WindowBackend backend, ::MobileGL::Wire::WindowKind* out) { switch (backend) { - case MG_Backend::WindowBackend::Android: *out = ::MobileGL::Wire::WindowKind::AndroidNativeWindow; return true; - case MG_Backend::WindowBackend::X11: *out = ::MobileGL::Wire::WindowKind::X11; return true; - case MG_Backend::WindowBackend::MetalLayer: *out = ::MobileGL::Wire::WindowKind::MetalLayer; return true; - case MG_Backend::WindowBackend::Win32: *out = ::MobileGL::Wire::WindowKind::Win32Hwnd; return true; - case MG_Backend::WindowBackend::Unknown: *out = ::MobileGL::Wire::WindowKind::None; return true; - default: return false; + case MG_Backend::WindowBackend::Android: + *out = ::MobileGL::Wire::WindowKind::AndroidNativeWindow; + return true; + case MG_Backend::WindowBackend::X11: + *out = ::MobileGL::Wire::WindowKind::X11; + return true; + case MG_Backend::WindowBackend::MetalLayer: + *out = ::MobileGL::Wire::WindowKind::MetalLayer; + return true; + case MG_Backend::WindowBackend::Win32: + *out = ::MobileGL::Wire::WindowKind::Win32Hwnd; + return true; + case MG_Backend::WindowBackend::Unknown: + *out = ::MobileGL::Wire::WindowKind::None; + return true; + default: + return false; } } bool WindowBackendForWireWindowKind(::MobileGL::Wire::WindowKind kind, MG_Backend::WindowBackend* out) { switch (kind) { - case ::MobileGL::Wire::WindowKind::AndroidNativeWindow: *out = MG_Backend::WindowBackend::Android; return true; - case ::MobileGL::Wire::WindowKind::X11: *out = MG_Backend::WindowBackend::X11; return true; - case ::MobileGL::Wire::WindowKind::MetalLayer: *out = MG_Backend::WindowBackend::MetalLayer; return true; - case ::MobileGL::Wire::WindowKind::Win32Hwnd: *out = MG_Backend::WindowBackend::Win32; return true; - case ::MobileGL::Wire::WindowKind::None: *out = MG_Backend::WindowBackend::Unknown; return true; + case ::MobileGL::Wire::WindowKind::AndroidNativeWindow: + *out = MG_Backend::WindowBackend::Android; + return true; + case ::MobileGL::Wire::WindowKind::X11: + *out = MG_Backend::WindowBackend::X11; + return true; + case ::MobileGL::Wire::WindowKind::MetalLayer: + *out = MG_Backend::WindowBackend::MetalLayer; + return true; + case ::MobileGL::Wire::WindowKind::Win32Hwnd: + *out = MG_Backend::WindowBackend::Win32; + return true; + case ::MobileGL::Wire::WindowKind::None: + *out = MG_Backend::WindowBackend::Unknown; + return true; // Surfaceless and Pbuffer are surface SHAPES, not window backends: no answer. - default: return false; + default: + return false; } } - SurfaceWireError EncodeSurfaceOpFrame(const SurfaceControlFrame& frame, - flatbuffers::FlatBufferBuilder* builder) { + SurfaceWireError EncodeSurfaceOpFrame(const SurfaceControlFrame& frame, flatbuffers::FlatBufferBuilder* builder) { ::MobileGL::Wire::SurfaceOpKind kind; if (!WireKindForSurfaceControlOp(frame.kind, &kind)) { return frame.kind == SurfaceControlOp::None ? SurfaceWireError::UnknownOpKind @@ -177,16 +207,16 @@ namespace MobileGL::MG_Remote { } windowKind = ::MobileGL::Wire::WindowKind::ServerOwned; nativeToken = 0; - } else if (!WireWindowKindForWindowBackend( - static_cast(frame.windowBackend), &windowKind)) { + } else if (!WireWindowKindForWindowBackend(static_cast(frame.windowBackend), + &windowKind)) { return SurfaceWireError::UnknownWindowKind; } } const auto op = ::MobileGL::Wire::CreateSurfaceOp( - *builder, frame.seq, kind, frame.display, frame.surface, windowKind, nativeToken, - frame.width, frame.height, frame.swapInterval, frame.readSurface, frame.context, - frame.shareGroupToken, frame.contextFlags); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(*builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + *builder, frame.seq, kind, frame.display, frame.surface, windowKind, nativeToken, frame.width, frame.height, + frame.swapInterval, frame.readSurface, frame.context, frame.shareGroupToken, frame.contextFlags); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(*builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(*builder, envelope); return SurfaceWireError::None; } @@ -253,8 +283,7 @@ namespace MobileGL::MG_Remote { return SurfaceWireError::None; } - void EncodeSurfaceReplyFrame(const SurfaceControlFrame& frame, - flatbuffers::FlatBufferBuilder* builder) { + void EncodeSurfaceReplyFrame(const SurfaceControlFrame& frame, flatbuffers::FlatBufferBuilder* builder) { // P12: the reply's surface geometry and refusal (SurfaceReply.width/height/refusal). const auto reply = ::MobileGL::Wire::CreateSurfaceReply( *builder, frame.seq, frame.ok, frame.eglMajor, frame.eglMinor, /*defaultFb=*/0, frame.eventHead, @@ -308,33 +337,35 @@ namespace MobileGL::MG_Remote { // definition. A non-zero one is a value from the peer's window system trying to // cross (Rule G) - the peer's corrupt bytes, latched like the rows beside it. (void)SessionLatch(MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.nativeToken\"} - a wire %s named " - "WindowKind::ServerOwned with native token 0x%llx. The server-owned window is the " - "server's own and its token is always 0: no value from the client's window system " - "crosses (Rule G)", - ::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind()), - static_cast(op.nativeToken())); + "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.nativeToken\"} - a wire %s named " + "WindowKind::ServerOwned with native token 0x%llx. The server-owned window is the " + "server's own and its token is always 0: no value from the client's window system " + "crosses (Rule G)", + ::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind()), + static_cast(op.nativeToken())); } else if (error == SurfaceWireError::AndroidNativeWindowArrived) { (void)SessionLatch(MGFatalFamily::UnmigratedSurface, - "MGPipe: Fatal{UnmigratedSurface, \"AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server\"} - a wire " - "SurfaceOp (%s) named an ANativeWindow*, which is a pointer into the " - "CLIENT's process and means nothing here. A client-process window " - "handle can never cross processes: a windowed out-of-process client must " - "use MOBILEGL_IPC_SURFACE=server (the server owns the window); " - "otherwise it is pbuffer/surfaceless only", - ::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind())); + "MGPipe: Fatal{UnmigratedSurface, " + "\"AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server\"} - a wire " + "SurfaceOp (%s) named an ANativeWindow*, which is a pointer into the " + "CLIENT's process and means nothing here. A client-process window " + "handle can never cross processes: a windowed out-of-process client must " + "use MOBILEGL_IPC_SURFACE=server (the server owns the window); " + "otherwise it is pbuffer/surfaceless only", + ::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind())); } else if (error == SurfaceWireError::MetalLayerArrived) { - (void)SessionLatch(MGFatalFamily::UnmigratedSurface, - "MGPipe: Fatal{UnmigratedSurface, \"MetalLayer:use-MOBILEGL_IPC_SURFACE=server\"} - a wire " - "SurfaceOp named a CAMetalLayer* in the CLIENT's process. " - "A client-process window handle can never cross processes: use " - "MOBILEGL_IPC_SURFACE=server (the server owns the window)"); + (void)SessionLatch( + MGFatalFamily::UnmigratedSurface, + "MGPipe: Fatal{UnmigratedSurface, \"MetalLayer:use-MOBILEGL_IPC_SURFACE=server\"} - a wire " + "SurfaceOp named a CAMetalLayer* in the CLIENT's process. " + "A client-process window handle can never cross processes: use " + "MOBILEGL_IPC_SURFACE=server (the server owns the window)"); } else { (void)SessionLatch(MGFatalFamily::ProtocolCorruption, - "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp\"} - a wire surface op " - "failed validation: %s (wire kind %u, window kind %u)", - SurfaceWireErrorName(error), static_cast(op.kind()), - static_cast(op.windowKind())); + "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp\"} - a wire surface op " + "failed validation: %s (wire kind %u, window kind %u)", + SurfaceWireErrorName(error), static_cast(op.kind()), + static_cast(op.windowKind())); } if (replyOut != nullptr) { *replyOut = SurfaceControlFrame{}; diff --git a/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.h b/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.h index ab7dd9a2..3ef83448 100644 --- a/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.h +++ b/MobileGL/MG_Remote/Protocol/SurfaceOpCodec.h @@ -34,12 +34,13 @@ namespace MobileGL::MG_Remote { enum class SurfaceWireError { None = 0, - UnknownOpKind, // no SurfaceControlOp for the wire kind (or None on the wire) - InprocOnlyOpOnTheWire, // a kind that may ride the inproc channel but never the wire - WindowKindNamesNoBackend,// Surfaceless/Pbuffer where a window backend is required - UnknownWindowKind, // a WindowKind the mapping table does not know - MetalLayerArrived, // -> Fatal{UnmigratedSurface, "MetalLayer:use-MOBILEGL_IPC_SURFACE=server"} - AndroidNativeWindowArrived, // -> Fatal{UnmigratedSurface, "AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server"} + UnknownOpKind, // no SurfaceControlOp for the wire kind (or None on the wire) + InprocOnlyOpOnTheWire, // a kind that may ride the inproc channel but never the wire + WindowKindNamesNoBackend, // Surfaceless/Pbuffer where a window backend is required + UnknownWindowKind, // a WindowKind the mapping table does not know + MetalLayerArrived, // -> Fatal{UnmigratedSurface, "MetalLayer:use-MOBILEGL_IPC_SURFACE=server"} + AndroidNativeWindowArrived, // -> Fatal{UnmigratedSurface, + // "AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server"} // P12 (on-screen server window), D2. WindowKind::ServerOwned is legal on // CreateWindowSurface only, and only with nativeToken 0: ServerOwnedTokenNotZero, // -> Fatal{ProtocolCorruption, "SurfaceOp.nativeToken"}, latched @@ -70,8 +71,7 @@ namespace MobileGL::MG_Remote { SurfaceWireError DecodeWireSurfaceOp(const ::MobileGL::Wire::SurfaceOp& op, Server::SurfaceControlFrame* frame); // The reply direction, same shape (CtrlEnvelope{SurfaceReply}). - void EncodeSurfaceReplyFrame(const Server::SurfaceControlFrame& frame, - flatbuffers::FlatBufferBuilder* builder); + void EncodeSurfaceReplyFrame(const Server::SurfaceControlFrame& frame, flatbuffers::FlatBufferBuilder* builder); void DecodeWireSurfaceReply(const ::MobileGL::Wire::SurfaceReply& reply, Server::SurfaceControlFrame* frame); // The server-side wire entry: decode, then post the frame through ServerLoop's channel and diff --git a/MobileGL/MG_Remote/Protocol/mg_protocol_base.h b/MobileGL/MG_Remote/Protocol/mg_protocol_base.h index 693bf6a1..fc19cbc7 100644 --- a/MobileGL/MG_Remote/Protocol/mg_protocol_base.h +++ b/MobileGL/MG_Remote/Protocol/mg_protocol_base.h @@ -23,12 +23,13 @@ #include #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -// --------------------------------------------------------------------------- -// ABI versions -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // ABI versions + // --------------------------------------------------------------------------- #define MOBILEGL_PROTOCOL_ABI_MAJOR 1 #define MOBILEGL_PROTOCOL_ABI_MINOR 0 @@ -46,63 +47,63 @@ extern "C" { #define MOBILEGL_ABI_MAJOR_OF(version) ((uint32_t)(version) >> 16) #define MOBILEGL_ABI_MINOR_OF(version) ((uint32_t)(version) & 0xFFFFu) -// --------------------------------------------------------------------------- -// Ids -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Ids + // --------------------------------------------------------------------------- -typedef uint64_t MobileGLSessionId; // one client GL context flow -typedef uint64_t MobileGLRequestSeq; // matches a request to its reply -typedef uint32_t MobileGLSegmentId; // shm segment id within a connection + typedef uint64_t MobileGLSessionId; // one client GL context flow + typedef uint64_t MobileGLRequestSeq; // matches a request to its reply + typedef uint32_t MobileGLSegmentId; // shm segment id within a connection -// --------------------------------------------------------------------------- -// Spans / regions -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Spans / regions + // --------------------------------------------------------------------------- -// Borrowed, read-only byte span. The pointee is owned by the producing side -// and is only valid for the duration documented at the consuming call site. -typedef struct MobileGLByteSpan { - const void* data; - uint64_t size; -} MobileGLByteSpan; + // Borrowed, read-only byte span. The pointee is owned by the producing side + // and is only valid for the duration documented at the consuming call site. + typedef struct MobileGLByteSpan { + const void* data; + uint64_t size; + } MobileGLByteSpan; -typedef struct MobileGLMutableByteSpan { - void* data; - uint64_t size; -} MobileGLMutableByteSpan; + typedef struct MobileGLMutableByteSpan { + void* data; + uint64_t size; + } MobileGLMutableByteSpan; -// A byte range inside an already-established shm segment. Segments are -// announced out of band (the SegmentRef table on the control channel, with the -// fd itself passed by SCM_RIGHTS) and stay stable for their declared lifetime; -// offsets are segment-relative. -typedef struct MobileGLShmRegion { - MobileGLSegmentId segmentId; - uint32_t reserved; - uint64_t offset; - uint64_t size; -} MobileGLShmRegion; + // A byte range inside an already-established shm segment. Segments are + // announced out of band (the SegmentRef table on the control channel, with the + // fd itself passed by SCM_RIGHTS) and stay stable for their declared lifetime; + // offsets are segment-relative. + typedef struct MobileGLShmRegion { + MobileGLSegmentId segmentId; + uint32_t reserved; + uint64_t offset; + uint64_t size; + } MobileGLShmRegion; -// --------------------------------------------------------------------------- -// Result codes (structured errors across every contract boundary) -// --------------------------------------------------------------------------- + // --------------------------------------------------------------------------- + // Result codes (structured errors across every contract boundary) + // --------------------------------------------------------------------------- -typedef enum MobileGLResult { - MOBILEGL_OK = 0, - MOBILEGL_ERR_NOT_INITIALIZED = 1, - MOBILEGL_ERR_INVALID_ARGUMENT = 2, - MOBILEGL_ERR_UNSUPPORTED = 3, - MOBILEGL_ERR_OUT_OF_MEMORY = 4, - MOBILEGL_ERR_PROTOCOL_MISMATCH = 5, // ABI/wire major mismatch, bad framing - MOBILEGL_ERR_TRANSPORT_CLOSED = 6, // peer gone / EOF - MOBILEGL_ERR_TIMEOUT = 7, // nothing arrived within the deadline - MOBILEGL_ERR_SHM_EXHAUSTED = 8, - MOBILEGL_ERR_SESSION_UNKNOWN = 9, - MOBILEGL_ERR_HANDLE_UNKNOWN = 10, - // The caller's buffer is smaller than the pending message. The message is - // NOT consumed and the required size is reported back; see - // ITransport::ReceiveFrame. - MOBILEGL_ERR_BUFFER_TOO_SMALL = 11, - MOBILEGL_ERR_FORCE_U32 = 0x7FFFFFFF -} MobileGLResult; + typedef enum MobileGLResult { + MOBILEGL_OK = 0, + MOBILEGL_ERR_NOT_INITIALIZED = 1, + MOBILEGL_ERR_INVALID_ARGUMENT = 2, + MOBILEGL_ERR_UNSUPPORTED = 3, + MOBILEGL_ERR_OUT_OF_MEMORY = 4, + MOBILEGL_ERR_PROTOCOL_MISMATCH = 5, // ABI/wire major mismatch, bad framing + MOBILEGL_ERR_TRANSPORT_CLOSED = 6, // peer gone / EOF + MOBILEGL_ERR_TIMEOUT = 7, // nothing arrived within the deadline + MOBILEGL_ERR_SHM_EXHAUSTED = 8, + MOBILEGL_ERR_SESSION_UNKNOWN = 9, + MOBILEGL_ERR_HANDLE_UNKNOWN = 10, + // The caller's buffer is smaller than the pending message. The message is + // NOT consumed and the required size is reported back; see + // ITransport::ReceiveFrame. + MOBILEGL_ERR_BUFFER_TOO_SMALL = 11, + MOBILEGL_ERR_FORCE_U32 = 0x7FFFFFFF + } MobileGLResult; #ifdef __cplusplus } // extern "C" diff --git a/MobileGL/MG_Remote/Server/AdoptInbox.cpp b/MobileGL/MG_Remote/Server/AdoptInbox.cpp index 5d647c72..cdcff1e8 100644 --- a/MobileGL/MG_Remote/Server/AdoptInbox.cpp +++ b/MobileGL/MG_Remote/Server/AdoptInbox.cpp @@ -32,15 +32,18 @@ namespace MobileGL::MG_Remote::Server { } } // namespace - AdoptInbox::~AdoptInbox() { Clear(); } + AdoptInbox::~AdoptInbox() { + Clear(); + } void AdoptInbox::Clear() { - for (auto& [seq, kept] : m_kept) CloseFd(kept.fd); + for (auto& [seq, kept] : m_kept) + CloseFd(kept.fd); m_kept.clear(); } - AdoptInbox::Outcome AdoptInbox::Take(Uint64 seq, Uint32 timeoutMs, int* outFd, - Transport::AdoptT0::Offer* outOffer, std::string& why) { + AdoptInbox::Outcome AdoptInbox::Take(Uint64 seq, Uint32 timeoutMs, int* outFd, Transport::AdoptT0::Offer* outOffer, + std::string& why) { return TakeKind(Kind::T0, seq, timeoutMs, outFd, outOffer, why); } @@ -71,13 +74,15 @@ namespace MobileGL::MG_Remote::Server { const auto deadline = Clock::now() + std::chrono::milliseconds(timeoutMs); for (;;) { const auto now = Clock::now(); - const Uint32 left = now >= deadline ? 0u - : static_cast(std::chrono::duration_cast(deadline - now).count()); + const Uint32 left = + now >= deadline ? 0u + : static_cast( + std::chrono::duration_cast(deadline - now).count()); int fd = -1; Uint8 sideband[Transport::FdPassing::kMaxSidebandBytes] = {}; std::uint64_t sidebandSize = 0; - const MobileGLResult received = m_transport->ReceiveFd( - &fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &sidebandSize, left); + const MobileGLResult received = + m_transport->ReceiveFd(&fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &sidebandSize, left); if (received == MOBILEGL_ERR_TIMEOUT) { why = std::string(kind == Kind::T0 ? "no store" : "no shared-image descriptor") + " arrived on the aux socket within " + std::to_string(timeoutMs) + " ms of its record"; diff --git a/MobileGL/MG_Remote/Server/AdoptInbox.h b/MobileGL/MG_Remote/Server/AdoptInbox.h index cbd9d090..78e5dd8f 100644 --- a/MobileGL/MG_Remote/Server/AdoptInbox.h +++ b/MobileGL/MG_Remote/Server/AdoptInbox.h @@ -29,7 +29,11 @@ namespace MobileGL::MG_Remote::Server { class AdoptInbox { public: - enum class Outcome : Uint8 { Taken, TimedOut, Closed }; + enum class Outcome : Uint8 { + Taken, + TimedOut, + Closed + }; AdoptInbox() = default; ~AdoptInbox(); @@ -40,8 +44,7 @@ namespace MobileGL::MG_Remote::Server { // The Offer for record `seq` and its hop descriptor (owned by the caller on Taken). Waits // at most `timeoutMs` for it; `why` names what went wrong otherwise. - Outcome Take(Uint64 seq, Uint32 timeoutMs, int* outFd, Transport::AdoptT0::Offer* outOffer, - std::string& why); + Outcome Take(Uint64 seq, Uint32 timeoutMs, int* outFd, Transport::AdoptT0::Offer* outOffer, std::string& why); // A shared image's descriptor for record `seq` (an Import; sideband MGPSharedImageFdOffer). // Same matching and the same bound as Take; the two kinds share the aux socket and are kept @@ -56,7 +59,10 @@ namespace MobileGL::MG_Remote::Server { SizeT Kept() const { return m_kept.size(); } private: - enum class Kind : Uint8 { T0, SharedImage }; + enum class Kind : Uint8 { + T0, + SharedImage + }; struct KeptOffer { int fd = -1; Transport::AdoptT0::Offer offer{}; diff --git a/MobileGL/MG_Remote/Server/ApplyThreadPolicy.h b/MobileGL/MG_Remote/Server/ApplyThreadPolicy.h index 8f7b8900..81b16663 100644 --- a/MobileGL/MG_Remote/Server/ApplyThreadPolicy.h +++ b/MobileGL/MG_Remote/Server/ApplyThreadPolicy.h @@ -41,26 +41,32 @@ namespace MobileGL::MG_Remote::Server { struct ApplyThreadPolicy { Uint64 requestedMask = 0; // 0 = make no affinity call Uint32 spinUs = 0; - Bool recognised = true; // false: the affinity text was not `auto`, `off` or a number - const char* rule = ""; // what the log line names + Bool recognised = true; // false: the affinity text was not `auto`, `off` or a number + const char* rule = ""; // what the log line names }; inline const char* ApplyPeerName(ApplyPeer peer) { switch (peer) { - case ApplyPeer::InProcess: return "in-process"; - case ApplyPeer::Forked: return "forked"; - case ApplyPeer::Connected: return "connected"; + case ApplyPeer::InProcess: + return "in-process"; + case ApplyPeer::Forked: + return "forked"; + case ApplyPeer::Connected: + return "connected"; } return "unknown"; } inline Uint32 PopCount64(Uint64 mask) { Uint32 count = 0; - for (; mask != 0; mask &= mask - 1) ++count; + for (; mask != 0; mask &= mask - 1) + ++count; return count; } - inline Uint64 LowestBit64(Uint64 mask) { return mask & (~mask + 1); } + inline Uint64 LowestBit64(Uint64 mask) { + return mask & (~mask + 1); + } // Why ReservedApplyCore answered what it did - the log line names it. enum class ReservedCoreReason : Uint8 { diff --git a/MobileGL/MG_Remote/Server/DisplayServerJni.cpp b/MobileGL/MG_Remote/Server/DisplayServerJni.cpp index b9ca75e0..180d6561 100644 --- a/MobileGL/MG_Remote/Server/DisplayServerJni.cpp +++ b/MobileGL/MG_Remote/Server/DisplayServerJni.cpp @@ -95,7 +95,7 @@ namespace { } // namespace extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeInstallDisplay(JNIEnv* env, - jclass clazz) { + jclass clazz) { JavaVM* vm = nullptr; if (env->GetJavaVM(&vm) != JNI_OK) vm = nullptr; jmethodID onGeometryRequest = env->GetStaticMethodID(clazz, "onGeometryRequest", "(II)V"); @@ -117,7 +117,7 @@ extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivi } extern "C" JNIEXPORT jint JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeServe(JNIEnv* env, jclass, - jstring listen) { + jstring listen) { if (listen == nullptr) return mobilegl_server_serve_inprocess(nullptr); const char* endpoint = env->GetStringUTFChars(listen, nullptr); if (endpoint == nullptr) return 71; // OutOfMemoryError pending @@ -147,13 +147,13 @@ extern "C" JNIEXPORT jboolean JNICALL Java_top_mobilegl_plugin_MobileGLDisplayAc } extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeDetachWindow(JNIEnv* env, - jclass) { + jclass) { const ServerWindowDetach detached = ServerDisplayInstance().Detach(); return env->NewStringUTF(ServerWindowDetachName(detached)); } extern "C" JNIEXPORT void JNICALL Java_top_mobilegl_plugin_MobileGLDisplayActivity_nativeUninstallDisplay(JNIEnv* env, - jclass) { + jclass) { ServerDisplayInstance().Uninstall(); const std::lock_guard lock(g_upcallMutex); if (g_activityClass != nullptr) env->DeleteGlobalRef(g_activityClass); diff --git a/MobileGL/MG_Remote/Server/EmbeddedServer.cpp b/MobileGL/MG_Remote/Server/EmbeddedServer.cpp index 86a2af9f..9823e1b7 100644 --- a/MobileGL/MG_Remote/Server/EmbeddedServer.cpp +++ b/MobileGL/MG_Remote/Server/EmbeddedServer.cpp @@ -18,14 +18,18 @@ namespace { int DetachResult(ServerWindowDetach result) { switch (result) { - case ServerWindowDetach::NoWindow: return MOBILEGL_SERVER_WINDOW_NO_WINDOW; - case ServerWindowDetach::Released: return MOBILEGL_SERVER_WINDOW_RELEASED; - case ServerWindowDetach::ReleasedBySession: return MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION; - case ServerWindowDetach::TimedOut: return MOBILEGL_SERVER_WINDOW_TIMED_OUT; + case ServerWindowDetach::NoWindow: + return MOBILEGL_SERVER_WINDOW_NO_WINDOW; + case ServerWindowDetach::Released: + return MOBILEGL_SERVER_WINDOW_RELEASED; + case ServerWindowDetach::ReleasedBySession: + return MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION; + case ServerWindowDetach::TimedOut: + return MOBILEGL_SERVER_WINDOW_TIMED_OUT; } return MOBILEGL_SERVER_WINDOW_TIMED_OUT; } -} +} // namespace extern "C" int mobilegl_server_display_install(const struct mobilegl_server_display_hooks* hooks) { if (hooks == nullptr || hooks->acquire == nullptr || hooks->release == nullptr) @@ -45,8 +49,8 @@ extern "C" int mobilegl_server_display_install(const struct mobilegl_server_disp extern "C" int mobilegl_server_display_install_android(mobilegl_server_geometry_callback request_geometry, void* user) { #if defined(__ANDROID__) const auto androidHooks = AndroidNativeWindowHooks(request_geometry, user); - const mobilegl_server_display_hooks hooks{androidHooks.acquire, androidHooks.release, - androidHooks.requestGeometry, androidHooks.user}; + const mobilegl_server_display_hooks hooks{androidHooks.acquire, androidHooks.release, androidHooks.requestGeometry, + androidHooks.user}; return mobilegl_server_display_install(&hooks); #else (void)request_geometry; diff --git a/MobileGL/MG_Remote/Server/EmbeddedServer.h b/MobileGL/MG_Remote/Server/EmbeddedServer.h index 71b6bb79..636339e3 100644 --- a/MobileGL/MG_Remote/Server/EmbeddedServer.h +++ b/MobileGL/MG_Remote/Server/EmbeddedServer.h @@ -18,68 +18,69 @@ #endif #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -struct ANativeWindow; + struct ANativeWindow; -typedef void (*mobilegl_server_geometry_callback)(void* user, uint32_t width, uint32_t height); + typedef void (*mobilegl_server_geometry_callback)(void* user, uint32_t width, uint32_t height); -// Opaque-window hooks also let non-Android hosts embed the display without depending on -// MobileGL's C++ headers. Both reference callbacks are required. Geometry may be null. -struct mobilegl_server_display_hooks { - void (*acquire)(void* user, void* window); - void (*release)(void* user, void* window); - mobilegl_server_geometry_callback request_geometry; - void* user; -}; + // Opaque-window hooks also let non-Android hosts embed the display without depending on + // MobileGL's C++ headers. Both reference callbacks are required. Geometry may be null. + struct mobilegl_server_display_hooks { + void (*acquire)(void* user, void* window); + void (*release)(void* user, void* window); + mobilegl_server_geometry_callback request_geometry; + void* user; + }; -enum mobilegl_server_display_result { - MOBILEGL_SERVER_DISPLAY_OK = 0, - MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT = -1, - MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED = -2, - MOBILEGL_SERVER_DISPLAY_NOT_INSTALLED = -3, - MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM = -4 -}; + enum mobilegl_server_display_result { + MOBILEGL_SERVER_DISPLAY_OK = 0, + MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT = -1, + MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED = -2, + MOBILEGL_SERVER_DISPLAY_NOT_INSTALLED = -3, + MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM = -4 + }; -enum mobilegl_server_window_detach { - MOBILEGL_SERVER_WINDOW_NO_WINDOW = 0, - MOBILEGL_SERVER_WINDOW_RELEASED = 1, - MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION = 2, - MOBILEGL_SERVER_WINDOW_TIMED_OUT = 3 -}; + enum mobilegl_server_window_detach { + MOBILEGL_SERVER_WINDOW_NO_WINDOW = 0, + MOBILEGL_SERVER_WINDOW_RELEASED = 1, + MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION = 2, + MOBILEGL_SERVER_WINDOW_TIMED_OUT = 3 + }; -// Install once before serving; owner lifecycle calls must be serialized. A live display or -// an old lease awaiting release refuses a replacement, so its window always uses its original -// reference hooks. The geometry callback runs on the apply thread and must return promptly. -// A positive size requests fixed buffer dimensions; 0/0 requests the host's layout dimensions. -// Report the resulting dimensions with attach, for example from SurfaceHolder.surfaceChanged. -// Callback user data must live until uninstall has returned and any timed-out lease has ended. -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install(const struct mobilegl_server_display_hooks* hooks); -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install_android(mobilegl_server_geometry_callback request_geometry, - void* user); + // Install once before serving; owner lifecycle calls must be serialized. A live display or + // an old lease awaiting release refuses a replacement, so its window always uses its original + // reference hooks. The geometry callback runs on the apply thread and must return promptly. + // A positive size requests fixed buffer dimensions; 0/0 requests the host's layout dimensions. + // Report the resulting dimensions with attach, for example from SurfaceHolder.surfaceChanged. + // Callback user data must live until uninstall has returned and any timed-out lease has ended. + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install(const struct mobilegl_server_display_hooks* hooks); + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install_android( + mobilegl_server_geometry_callback request_geometry, void* user); -// Attach retains its own reference. Attaching the same window updates its dimensions without -// another retain. A different window detaches the previous one first (up to 3 seconds). -// Android attach uses ANativeWindow_getWidth/Height for zero dimensions; the caller may release -// its ANativeWindow_fromSurface reference immediately after this call. -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach(void* window, uint32_t width, uint32_t height); -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach_android(struct ANativeWindow* window, uint32_t width, - uint32_t height); + // Attach retains its own reference. Attaching the same window updates its dimensions without + // another retain. A different window detaches the previous one first (up to 3 seconds). + // Android attach uses ANativeWindow_getWidth/Height for zero dimensions; the caller may release + // its ANativeWindow_fromSurface reference immediately after this call. + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach(void* window, uint32_t width, uint32_t height); + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach_android(struct ANativeWindow* window, uint32_t width, + uint32_t height); -// Returns mobilegl_server_window_detach. A timeout never drops a reference while the backend -// is rendering: release happens when the apply thread ends its lease. Surface loss currently -// ends the live split session; a fresh client session can use a newly attached window. -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_detach(uint32_t timeout_ms); -MOBILEGL_SERVER_EXPORT int mobilegl_server_display_uninstall(uint32_t timeout_ms); + // Returns mobilegl_server_window_detach. A timeout never drops a reference while the backend + // is rendering: release happens when the apply thread ends its lease. Surface loss currently + // ends the live split session; a fresh client session can use a newly attached window. + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_detach(uint32_t timeout_ms); + MOBILEGL_SERVER_EXPORT int mobilegl_server_display_uninstall(uint32_t timeout_ms); -// Blocking serve on the caller's thread; stop is non-blocking and safe on the UI thread. -// Endpoint: filesystem path, @abstract AF_UNIX name (SCM_RIGHTS + shared-memory rings), or -// tcp://host:port. One client session per process; independent workers need distinct endpoints. -// Before loading libMobileGL set MOBILEGL_IPC_ROLE=server and MOBILEGL_IPC_DIAL=no, and unset -// MOBILEGL_TRANSPORT, MOBILEGL_IPC_SERVER_PATH, MOBILEGL_IPC_RING_MB, MOBILEGL_IPC_STAGE_MB. -MOBILEGL_SERVER_EXPORT int mobilegl_server_serve_inprocess(const char* endpoint); -MOBILEGL_SERVER_EXPORT void mobilegl_server_stop_inprocess(void); + // Blocking serve on the caller's thread; stop is non-blocking and safe on the UI thread. + // Endpoint: filesystem path, @abstract AF_UNIX name (SCM_RIGHTS + shared-memory rings), or + // tcp://host:port. One client session per process; independent workers need distinct endpoints. + // Before loading libMobileGL set MOBILEGL_IPC_ROLE=server and MOBILEGL_IPC_DIAL=no, and unset + // MOBILEGL_TRANSPORT, MOBILEGL_IPC_SERVER_PATH, MOBILEGL_IPC_RING_MB, MOBILEGL_IPC_STAGE_MB. + MOBILEGL_SERVER_EXPORT int mobilegl_server_serve_inprocess(const char* endpoint); + MOBILEGL_SERVER_EXPORT void mobilegl_server_stop_inprocess(void); #ifdef __cplusplus } diff --git a/MobileGL/MG_Remote/Server/InProcessServer.h b/MobileGL/MG_Remote/Server/InProcessServer.h index d39aa9c5..7be51585 100644 --- a/MobileGL/MG_Remote/Server/InProcessServer.h +++ b/MobileGL/MG_Remote/Server/InProcessServer.h @@ -17,21 +17,22 @@ #pragma once -extern "C" { +extern "C" +{ -// Serves `endpoint` on the CALLING thread until mobilegl_server_stop_inprocess is called and the -// live session (if any) has ended, then returns 0. The endpoint is tcp://host:port, or - for a -// same-device client - a unix @abstract name or filesystem path, whose control+data pair and -// SCM_RIGHTS shm segments make it the fastest on-device shape. Other returns, all logged by -// name: 64 / 65 (the environment preconditions above), 71 (empty endpoint), 72 (could not -// listen - EADDRINUSE is retried for up to ~5 s first, the previous server may still be dying), -// 73 (the listener failed), 74 (a server is already serving in this process). Re-callable after -// it returned. -__attribute__((visibility("default"))) int mobilegl_server_serve_inprocess(const char* endpoint); + // Serves `endpoint` on the CALLING thread until mobilegl_server_stop_inprocess is called and the + // live session (if any) has ended, then returns 0. The endpoint is tcp://host:port, or - for a + // same-device client - a unix @abstract name or filesystem path, whose control+data pair and + // SCM_RIGHTS shm segments make it the fastest on-device shape. Other returns, all logged by + // name: 64 / 65 (the environment preconditions above), 71 (empty endpoint), 72 (could not + // listen - EADDRINUSE is retried for up to ~5 s first, the previous server may still be dying), + // 73 (the listener failed), 74 (a server is already serving in this process). Re-callable after + // it returned. + __attribute__((visibility("default"))) int mobilegl_server_serve_inprocess(const char* endpoint); -// Asks the in-process server to stop and returns at once (it does not wait: call it from a UI -// thread). The live session ends within one control slice (~100 ms) - or when the op it is running -// returns (a ServerOwned wait for a window is interrupted too) - and then serve returns 0. -__attribute__((visibility("default"))) void mobilegl_server_stop_inprocess(void); + // Asks the in-process server to stop and returns at once (it does not wait: call it from a UI + // thread). The live session ends within one control slice (~100 ms) - or when the op it is running + // returns (a ServerOwned wait for a window is interrupted too) - and then serve returns 0. + __attribute__((visibility("default"))) void mobilegl_server_stop_inprocess(void); } // extern "C" diff --git a/MobileGL/MG_Remote/Server/PairAcceptor.cpp b/MobileGL/MG_Remote/Server/PairAcceptor.cpp index b4d9a38e..b30a59a5 100644 --- a/MobileGL/MG_Remote/Server/PairAcceptor.cpp +++ b/MobileGL/MG_Remote/Server/PairAcceptor.cpp @@ -70,7 +70,8 @@ namespace MobileGL::MG_Remote::Server { PairAcceptor::PairAcceptor(int listenFd, std::uint32_t budgetMs) : m_listener(listenFd), m_budgetMs(budgetMs) {} PairAcceptor::~PairAcceptor() { - for (auto& waiting : m_waiting) Drop(waiting, true, kPairStopped); + for (auto& waiting : m_waiting) + Drop(waiting, true, kPairStopped); for (const auto& pair : m_ready) { RefuseAndClose(pair.first, Protocol::RefuseCode::Busy, kPairStopped); ::close(pair.second); @@ -176,25 +177,25 @@ namespace MobileGL::MG_Remote::Server { void PairAcceptor::Read(Waiting& waiting) { switch (waiting.frame.Pump(waiting.fd)) { - case Step::NeedMore: - return; - case Step::Complete: - Judge(waiting); - return; - case Step::EndedEmpty: - // A readiness probe - a connect() and a close() - costs an accept and nothing else. - MGLOG_D("MG_Remote server: a connection closed before presenting a PairBind (a probe); dropped"); - ::close(waiting.fd); - waiting.fd = -1; - return; - case Step::NotAFrame: - RefuseAndClose(waiting.fd, Protocol::RefuseCode::MalformedHello, kFirstFrameNotAFrame); - waiting.fd = -1; - return; - case Step::EndedPartial: - RefuseAndClose(waiting.fd, Protocol::RefuseCode::MalformedHello, kFirstFrameTruncated); - waiting.fd = -1; - return; + case Step::NeedMore: + return; + case Step::Complete: + Judge(waiting); + return; + case Step::EndedEmpty: + // A readiness probe - a connect() and a close() - costs an accept and nothing else. + MGLOG_D("MG_Remote server: a connection closed before presenting a PairBind (a probe); dropped"); + ::close(waiting.fd); + waiting.fd = -1; + return; + case Step::NotAFrame: + RefuseAndClose(waiting.fd, Protocol::RefuseCode::MalformedHello, kFirstFrameNotAFrame); + waiting.fd = -1; + return; + case Step::EndedPartial: + RefuseAndClose(waiting.fd, Protocol::RefuseCode::MalformedHello, kFirstFrameTruncated); + waiting.fd = -1; + return; } } @@ -281,9 +282,9 @@ namespace MobileGL::MG_Remote::Server { } void PairAcceptor::Compact() { - m_waiting.erase(std::remove_if(m_waiting.begin(), m_waiting.end(), - [](const Waiting& waiting) { return waiting.fd < 0; }), - m_waiting.end()); + m_waiting.erase( + std::remove_if(m_waiting.begin(), m_waiting.end(), [](const Waiting& waiting) { return waiting.fd < 0; }), + m_waiting.end()); } } // namespace MobileGL::MG_Remote::Server diff --git a/MobileGL/MG_Remote/Server/PairAcceptor.h b/MobileGL/MG_Remote/Server/PairAcceptor.h index 3c1b6e71..8dd33b16 100644 --- a/MobileGL/MG_Remote/Server/PairAcceptor.h +++ b/MobileGL/MG_Remote/Server/PairAcceptor.h @@ -96,8 +96,8 @@ namespace MobileGL::MG_Remote::Server { int m_listener; std::uint32_t m_budgetMs; - std::vector m_waiting; // accept order, oldest first - std::deque> m_ready; // {control, aux}, not yet handed out + std::vector m_waiting; // accept order, oldest first + std::deque> m_ready; // {control, aux}, not yet handed out Clock::time_point m_acceptResumeAt{}; }; diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index 50bb5170..02176cd6 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -96,7 +96,9 @@ namespace MobileGL::MG_Remote::Server { ++m_posted; } - Uint32 ReplyPool::SlotBytes() const { return m_slotBytes; } + Uint32 ReplyPool::SlotBytes() const { + return m_slotBytes; + } // ----------------------------------------------------------------------------------- // ServerVerbSink - the five class-B verbs @@ -109,8 +111,7 @@ namespace MobileGL::MG_Remote::Server { ServerVerbSink::FenceEntry& ServerVerbSink::FindFence(MG_Pipe::MGPipeHandle handle) { const auto it = m_fences.find(handle.Slot); - if (handle.Slot == 0 || it == m_fences.end() || - !it->second.Live || it->second.Gen != handle.Gen) { + if (handle.Slot == 0 || it == m_fences.end() || !it->second.Live || it->second.Gen != handle.Gen) { Wire::WireProtocolFatal("Fence.handle", "missing, destroyed or stale fence handle"); } return it->second; @@ -163,8 +164,7 @@ namespace MobileGL::MG_Remote::Server { auto& entry = FindFence(desc.Handle); const auto* table = Table("FenceStatus"); if (table == nullptr) return false; - result = entry.Native == nullptr || table->GL.GetSyncStatus == nullptr || - table->GL.GetSyncStatus(entry.Native); + result = entry.Native == nullptr || table->GL.GetSyncStatus == nullptr || table->GL.GetSyncStatus(entry.Native); return true; } @@ -296,10 +296,11 @@ namespace MobileGL::MG_Remote::Server { if (present.FrameSerial == 0 && MGPipeServerPublishesRunAhead()) { // PH-1 (3): latches in an armed session child - no credit is returned for it, the // session closes and the peer's credit wait ends on the hang-up. - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"Present.FrameSerial\"} - a run-ahead " - "server was handed present serial 0. The client mints this 1-based and " - "waits on it for its credit (CONTRACT-P5E §2.4); returning a credit for " - "serial 0 would release a wait that is asking about frame N"); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"Present.FrameSerial\"} - a run-ahead " + "server was handed present serial 0. The client mints this 1-based and " + "waits on it for its credit (CONTRACT-P5E §2.4); returning a credit for " + "serial 0 would release a wait that is asking about frame N"); } m_lastPresentSerial = present.FrameSerial != 0 ? present.FrameSerial : m_presents; // §2.4's other half, and the reason ServerSession::ReturnPresentCredit has had no @@ -335,12 +336,13 @@ namespace MobileGL::MG_Remote::Server { // about how many make-current edges have crossed, which no backend answer can fix. // PH-1 (3): latches in an armed session child (the peer wrote the serial); dies unarmed. if (reset.ContextSerial != m_applierResetSerial) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"} - the " - "record carries %llu and this session has accepted %llu reset(s); the " - "serial is asserted against the session's own count, not dispatched on " - "(one context per session in P5c)", - static_cast(reset.ContextSerial), - static_cast(m_applierResetSerial)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"} - the " + "record carries %llu and this session has accepted %llu reset(s); the " + "serial is asserted against the session's own count, not dispatched on " + "(one context per session in P5c)", + static_cast(reset.ContextSerial), + static_cast(m_applierResetSerial)); } ++m_applierResetSerial; // THE WHOLE POINT OF THE RECORD: the reset runs HERE, on the apply thread, against @@ -357,7 +359,9 @@ namespace MobileGL::MG_Remote::Server { return *this; } - ServerVerbSink::OwnedFd::~OwnedFd() { Reset(-1); } + ServerVerbSink::OwnedFd::~OwnedFd() { + Reset(-1); + } void ServerVerbSink::OwnedFd::Reset(int next) { #if !defined(_WIN32) @@ -521,7 +525,8 @@ namespace MobileGL::MG_Remote::Server { case MG_Pipe::kMGPSharedImageQueryBufferAge: { MG_Backend::BackendObject* backend = ServerLoopInstance().Backend(); if (backend == nullptr) return false; - reply.BufferAge = backend->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0); + reply.BufferAge = + backend->QueryCurrentBufferAge((op.Format & MG_Pipe::kMGPBufferAgeDamageRegionFollows) != 0); return true; } case MG_Pipe::kMGPSharedImageSetDamageRegion: { @@ -596,11 +601,12 @@ namespace MobileGL::MG_Remote::Server { // PH-1 (3): the refusal latches in an armed session child (the peer wrote the token); // unarmed it dies, like every other contract fact about a peer. if (!session->BindContext(bind.ClientContextToken)) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"BindContext.ClientContextToken\"} - the " - "record binds token %llu, which this session never created; a bind_context " - "names a context CreateContext put in the session's table, and 0 (the " - "release) is the only token that needs no entry", - static_cast(bind.ClientContextToken)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"BindContext.ClientContextToken\"} - the " + "record binds token %llu, which this session never created; a bind_context " + "names a context CreateContext put in the session's table, and 0 (the " + "release) is the only token that needs no entry", + static_cast(bind.ClientContextToken)); } ++m_bindContexts; #if MOBILEGL_BUILD_DISAGGREGATED @@ -634,22 +640,24 @@ namespace MobileGL::MG_Remote::Server { // not a no-op. // PH-1 (3): both refusals latch in an armed session child; unarmed they die as before. if (MG_Pipe::MGPipeHandleIsNull(death.Handle)) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"} - a null " - "handle never crosses: the client emits nothing for an object its own " - "allocator cannot resolve (CONTRACT-P5C.md §5.2)"); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"} - a null " + "handle never crosses: the client emits nothing for an object its own " + "allocator cannot resolve (CONTRACT-P5C.md §5.2)"); } if (death.Kind >= static_cast(MG_Pipe::MGPipeKind::KindCount)) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Kind\"} - %u is not an " - "MGPipeKind", - static_cast(death.Kind)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ObjectDeath.Kind\"} - %u is not an " + "MGPipeKind", + static_cast(death.Kind)); } // The per-kind release, keyed by the handle the record carried. A false answer is // NOT a decline: the kind's own delete opcode may already have released the twin // (the idempotent second path every notice arm documents), and a kind this backend // does not twin (Buffer, whose death crosses as resource_destroy) legally resolves // to nothing. - MG_Backend::DirectGLES::ReleaseTwinsForWireObjectDeath( - death.Handle, static_cast(death.Kind)); + MG_Backend::DirectGLES::ReleaseTwinsForWireObjectDeath(death.Handle, + static_cast(death.Kind)); ++m_objectDeaths; return true; } @@ -663,19 +671,22 @@ namespace MobileGL::MG_Remote::Server { void PipeApplier::Attach(Transport::ILink* link, MG_Backend::BackendObject* backend) { if (!link || !link->Attached() || !m_segments) - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, PipeApplier::Attach missing link}"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, PipeApplier::Attach missing link}"); m_verbs.SetBackend(backend); m_verbs.SetMaxReplyBytes(link->Capabilities().MaxReplyBytes); m_decoder = Wire::PipeWireDecoder(link, m_segments, m_replies); m_decoder.SetVerbSink(&m_verbs); - MG_Pipe::MGPipeServerBlockNoteIdentity(); m_attached = true; + MG_Pipe::MGPipeServerBlockNoteIdentity(); + m_attached = true; } void PipeApplier::Attach(Transport::RingControl* control, MG_Backend::BackendObject* backend) { if (control == nullptr || m_segments == nullptr) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::Attach\"} - no control " - "page or no segment table; ServerSession::Accept builds both before the " - "apply thread starts"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::Attach\"} - no control " + "page or no segment table; ServerSession::Accept builds both before the " + "apply thread starts"); } m_verbs.SetBackend(backend); m_decoder = Wire::PipeWireDecoder(control, m_segments, m_replies); @@ -695,13 +706,16 @@ namespace MobileGL::MG_Remote::Server { m_attached = false; } - Bool PipeApplier::Attached() const { return m_attached; } + Bool PipeApplier::Attached() const { + return m_attached; + } Bool PipeApplier::ApplyOne(const Transport::RingRecordView& record) { if (!m_attached) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::ApplyOne before Attach\"} " - "- a record reached the applier with no decoder; the apply thread calls " - "Attach once before its first pop"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"PipeApplier::ApplyOne before Attach\"} " + "- a record reached the applier with no decoder; the apply thread calls " + "Attach once before its first pop"); } // R-1's INVARIANT, THE SERVER'S HALF (table 3's gPipeInputs row, c1-v1 8.1). The flag // is raised for the WHOLE of this function and not only around DecodeAndApply: the @@ -720,11 +734,10 @@ namespace MobileGL::MG_Remote::Server { // per-opcode tables, so in a session that latches faults a short or unknown record is // declined before anything is stamped. if (!m_decoder.AdmitOrDecline(record)) return false; - const Bool wireSaysBarriered = MG_Pipe::MGPipeBarriered( - static_cast(record.kind), record.payload, MG_Pipe::MGPipeApplier()); - MG_Pipe::MGPipeApplierSetCurrentRecordBarriered( - MGPipeApplierStampsBarriered(MG_Pipe::kMGPipeP5eClientWaitRuleLanded, - MGPipeServerPublishesRunAhead(), wireSaysBarriered)); + const Bool wireSaysBarriered = MG_Pipe::MGPipeBarriered(static_cast(record.kind), + record.payload, MG_Pipe::MGPipeApplier()); + MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(MGPipeApplierStampsBarriered( + MG_Pipe::kMGPipeP5eClientWaitRuleLanded, MGPipeServerPublishesRunAhead(), wireSaysBarriered)); // P5e (gl), ID-128: AND WHETHER IT WAS BARRIERED BY ESCALATION RATHER THAN BY ITS CLASS. // The predicate above is the static wait class plus two payload-derived escalations // (ID-83: an open transform-feedback span, a draw carrying client vertex arrays), so the @@ -734,8 +747,7 @@ namespace MobileGL::MG_Remote::Server { // client arrays (ID-82) owes, neither of which is this one. MG_Pipe::MGPipeApplierSetCurrentRecordBarrieredByEscalation( wireSaysBarriered && - MG_Pipe::MGPipeWaitClassFor(static_cast(record.kind)) == - MG_Pipe::kWaitNone); + MG_Pipe::MGPipeWaitClassFor(static_cast(record.kind)) == MG_Pipe::kWaitNone); // ORDER IS THE CONTRACT'S: stamp, then apply. The stamp is what makes any server-side // read of gPipeInputs legal at all (PipeApplier.h's block 1), so a record applied // before it aborts on the FIRST field inside SyncRenderState. @@ -764,9 +776,13 @@ namespace MobileGL::MG_Remote::Server { MG_Pipe::MGPipeServerStampVerbBoundary(verb); } - void PipeApplier::LeaveApplier() { MG_Pipe::MGPipeServerClearVerbBoundary(); } + void PipeApplier::LeaveApplier() { + MG_Pipe::MGPipeServerClearVerbBoundary(); + } - Uint64 PipeApplier::ResidualPullCount() const { return MG_Pipe::MGPipeResidualPullCount(); } + Uint64 PipeApplier::ResidualPullCount() const { + return MG_Pipe::MGPipeResidualPullCount(); + } // The decoder poisons EXACTLY the runs it resolved, from inside DecodeAndApply, once the // applier has returned - so this entry point is the manual one, for a caller that knows a @@ -783,8 +799,12 @@ namespace MobileGL::MG_Remote::Server { std::memset(const_cast(run), 0xDD, static_cast(size)); } - Uint64 PipeApplier::PoisonedStageBytes() const { return m_decoder.PoisonedStageBytes(); } + Uint64 PipeApplier::PoisonedStageBytes() const { + return m_decoder.PoisonedStageBytes(); + } - Uint64 PipeApplier::DecoderAppliedSeq() const { return m_decoder.AppliedSeq(); } + Uint64 PipeApplier::DecoderAppliedSeq() const { + return m_decoder.AppliedSeq(); + } } // namespace MobileGL::MG_Remote::Server diff --git a/MobileGL/MG_Remote/Server/PipeApplier.h b/MobileGL/MG_Remote/Server/PipeApplier.h index 3c6476a2..19dac709 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.h +++ b/MobileGL/MG_Remote/Server/PipeApplier.h @@ -47,8 +47,7 @@ namespace MobileGL::MG_Remote::Server { // record is barriered. A server without that bit has a lockstep client, and stamping its // kWaitNone draws unbarriered while that client is parked would make CountBarrierPull abort. // Pure so a unit test can pin "no run-ahead bit, never runs ahead" for any input. - constexpr Bool MGPipeApplierStampsBarriered(Bool clientWaitRuleLanded, - Bool serverPublishesRunAhead, + constexpr Bool MGPipeApplierStampsBarriered(Bool clientWaitRuleLanded, Bool serverPublishesRunAhead, Bool wireSaysBarriered) { return (clientWaitRuleLanded && serverPublishesRunAhead) ? wireSaysBarriered : true; } diff --git a/MobileGL/MG_Remote/Server/PreAuthGate.h b/MobileGL/MG_Remote/Server/PreAuthGate.h index 47aea203..1a66f885 100644 --- a/MobileGL/MG_Remote/Server/PreAuthGate.h +++ b/MobileGL/MG_Remote/Server/PreAuthGate.h @@ -70,7 +70,8 @@ namespace MobileGL::MG_Remote::Server { inline constexpr const char* kFirstFrameBadDataBind = "first frame is a DataBind without a 16-byte nonce"; inline constexpr const char* kPreAuthDeadline = "no authenticated first frame within the pre-auth deadline"; inline constexpr const char* kPreAuthFull = "too many connections are awaiting authentication"; - inline constexpr const char* kPreAuthDisplaced = "displaced from the pre-auth queue by a connection from another address"; + inline constexpr const char* kPreAuthDisplaced = + "displaced from the pre-auth queue by a connection from another address"; inline constexpr const char* kAuthBackoffRefusal = "too many failed authentications from this address; retry later"; struct PreAuthKnobs { @@ -118,7 +119,14 @@ namespace MobileGL::MG_Remote::Server { // verify", and a peer speaking some other schema deserves the specific answer. msg_as_Hello() // and msg_as_DataBind() are part of the guard, not consequences of it: flatbuffers verifies a // NULL union member as valid, so a 24-byte envelope whose tag says Hello carries no Hello. - enum class FirstFrameShape { NoIdentifier, Unverifiable, Hello, DataBind, BadDataBind, NotAHello }; + enum class FirstFrameShape { + NoIdentifier, + Unverifiable, + Hello, + DataBind, + BadDataBind, + NotAHello + }; inline FirstFrameShape ClassifyFirstFrame(const std::uint8_t* payload, std::size_t size) { if (payload == nullptr || size < 8 || !::MobileGL::Wire::CtrlEnvelopeBufferHasIdentifier(payload)) @@ -128,8 +136,7 @@ namespace MobileGL::MG_Remote::Server { const auto* envelope = ::MobileGL::Wire::GetCtrlEnvelope(payload); if (envelope->msg_type() == ::MobileGL::Wire::CtrlMsg::DataBind) { const auto* bind = envelope->msg_as_DataBind(); - return bind != nullptr && bind->nonce() != nullptr && - bind->nonce()->size() == Transport::kDataNonceBytes + return bind != nullptr && bind->nonce() != nullptr && bind->nonce()->size() == Transport::kDataNonceBytes ? FirstFrameShape::DataBind : FirstFrameShape::BadDataBind; } @@ -142,12 +149,17 @@ namespace MobileGL::MG_Remote::Server { // shapes that are not malformed. inline const char* FirstFrameRefusalDetail(FirstFrameShape shape) { switch (shape) { - case FirstFrameShape::NoIdentifier: return kFirstFrameNoIdentifier; - case FirstFrameShape::Unverifiable: return kFirstFrameUnverifiable; - case FirstFrameShape::BadDataBind: return kFirstFrameBadDataBind; - case FirstFrameShape::NotAHello: return kFirstFrameNotAHello; - case FirstFrameShape::Hello: - case FirstFrameShape::DataBind: return nullptr; + case FirstFrameShape::NoIdentifier: + return kFirstFrameNoIdentifier; + case FirstFrameShape::Unverifiable: + return kFirstFrameUnverifiable; + case FirstFrameShape::BadDataBind: + return kFirstFrameBadDataBind; + case FirstFrameShape::NotAHello: + return kFirstFrameNotAHello; + case FirstFrameShape::Hello: + case FirstFrameShape::DataBind: + return nullptr; } return kFirstFrameNotAHello; } @@ -166,7 +178,8 @@ namespace MobileGL::MG_Remote::Server { inline std::size_t ChoosePendingToDisplace(const std::vector& pending, const std::string& newcomer) { std::unordered_map held; std::size_t most = 0; - for (const auto& address : pending) most = std::max(most, ++held[address]); + for (const auto& address : pending) + most = std::max(most, ++held[address]); const auto own = held.find(newcomer); if (own != held.end() && own->second >= most) return pending.size(); for (std::size_t index = 0; index < pending.size(); ++index) @@ -238,7 +251,10 @@ namespace MobileGL::MG_Remote::Server { auto victim = m_peers.end(); for (auto it = m_peers.begin(); it != m_peers.end(); ++it) { const bool expired = it->second.until <= now; - if (victim == m_peers.end()) { victim = it; continue; } + if (victim == m_peers.end()) { + victim = it; + continue; + } const bool victimExpired = victim->second.until <= now; if (expired != victimExpired) { if (expired) victim = it; diff --git a/MobileGL/MG_Remote/Server/ServerDisplay.cpp b/MobileGL/MG_Remote/Server/ServerDisplay.cpp index aa62f30e..c5e67d69 100644 --- a/MobileGL/MG_Remote/Server/ServerDisplay.cpp +++ b/MobileGL/MG_Remote/Server/ServerDisplay.cpp @@ -32,22 +32,32 @@ namespace MobileGL::MG_Remote::Server { const char* ServerWindowAcquireName(ServerWindowAcquire result) { switch (result) { - case ServerWindowAcquire::Acquired: return "Acquired"; - case ServerWindowAcquire::NoDisplay: return "NoDisplay"; - case ServerWindowAcquire::NoWindow: return "NoWindow"; - case ServerWindowAcquire::Cancelled: return "Cancelled"; - case ServerWindowAcquire::Interrupted: return "Interrupted"; - case ServerWindowAcquire::LeasedElsewhere: return "LeasedElsewhere"; + case ServerWindowAcquire::Acquired: + return "Acquired"; + case ServerWindowAcquire::NoDisplay: + return "NoDisplay"; + case ServerWindowAcquire::NoWindow: + return "NoWindow"; + case ServerWindowAcquire::Cancelled: + return "Cancelled"; + case ServerWindowAcquire::Interrupted: + return "Interrupted"; + case ServerWindowAcquire::LeasedElsewhere: + return "LeasedElsewhere"; } return ""; } const char* ServerWindowDetachName(ServerWindowDetach result) { switch (result) { - case ServerWindowDetach::NoWindow: return "NoWindow"; - case ServerWindowDetach::Released: return "Released"; - case ServerWindowDetach::ReleasedBySession: return "ReleasedBySession"; - case ServerWindowDetach::TimedOut: return "TimedOut"; + case ServerWindowDetach::NoWindow: + return "NoWindow"; + case ServerWindowDetach::Released: + return "Released"; + case ServerWindowDetach::ReleasedBySession: + return "ReleasedBySession"; + case ServerWindowDetach::TimedOut: + return "TimedOut"; } return ""; } @@ -164,8 +174,8 @@ namespace MobileGL::MG_Remote::Server { m_lostRequested = true; if (m_onLost != nullptr) m_onLost(m_leaseHolder); } - const Bool released = m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), - [this] { return m_leaseHolder == nullptr; }); + const Bool released = + m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this] { return m_leaseHolder == nullptr; }); if (!released) { // NOT RELEASED UNDER IT. The holder may still reference the window (Magma keeps the // raw pointer until the swapchain is built), so the reference stays until it ends @@ -265,11 +275,10 @@ namespace MobileGL::MG_Remote::Server { m_lostRequested = false; m_leaseEnding = false; // A suspended holder that leased a window again waits for no other. - m_suspended.erase(std::remove_if(m_suspended.begin(), m_suspended.end(), - [holder](const SuspendedHolder& entry) { - return entry.holder == holder; - }), - m_suspended.end()); + m_suspended.erase( + std::remove_if(m_suspended.begin(), m_suspended.end(), + [holder](const SuspendedHolder& entry) { return entry.holder == holder; }), + m_suspended.end()); if (out != nullptr) { out->window = m_window; out->width = m_width; @@ -322,8 +331,7 @@ namespace MobileGL::MG_Remote::Server { void ServerDisplay::ReleaseAfterLease(const ServerDisplayHooks& hooks, const std::vector& releases) const { for (void* window : releases) { ReleaseOutsideLock(hooks, window); - MGLOG_I("MG_Remote server: server window %p released after its session let go (a late Detach)", - window); + MGLOG_I("MG_Remote server: server window %p released after its session let go (a late Detach)", window); } } diff --git a/MobileGL/MG_Remote/Server/ServerDisplay.h b/MobileGL/MG_Remote/Server/ServerDisplay.h index 043772a2..e9d2b1c1 100644 --- a/MobileGL/MG_Remote/Server/ServerDisplay.h +++ b/MobileGL/MG_Remote/Server/ServerDisplay.h @@ -48,7 +48,7 @@ namespace MobileGL::MG_Remote::Server { struct ServerWindowLease { void* window = nullptr; - Uint32 width = 0; // the window's extent when the lease was taken + Uint32 width = 0; // the window's extent when the lease was taken Uint32 height = 0; Uint64 generation = 0; // False when a size was requested and the window had not reached it within the geometry @@ -58,10 +58,10 @@ namespace MobileGL::MG_Remote::Server { enum class ServerWindowAcquire : Uint8 { Acquired, - NoDisplay, // this process owns no display (HasDisplay() is false) - NoWindow, // a display, but no window was attached within the wait - Cancelled, // the caller's cancel predicate said so - Interrupted, // Interrupt() (the in-process server is stopping) + NoDisplay, // this process owns no display (HasDisplay() is false) + NoWindow, // a display, but no window was attached within the wait + Cancelled, // the caller's cancel predicate said so + Interrupted, // Interrupt() (the in-process server is stopping) // P14 S2: NOT A BUG ANY MORE. Several sessions share one process and the display's window // is one window, so a second ON-SCREEN session asking for it is refused by name here // (ServerLoop logs the holder); offscreen sessions take no lease and are unaffected. A holder @@ -115,9 +115,8 @@ namespace MobileGL::MG_Remote::Server { // may lease again while it holds the lease (a surface re-created at a new size). `onResized`, when // given, is called whenever the leased window changes size while the lease lasts. ServerWindowAcquire AcquireFor(Uint32 width, Uint32 height, Uint32 timeoutMs, void* holder, - ServerWindowLostHook onLost, ServerWindowWaitCancel cancel, - void* cancelUser, ServerWindowLease* out, - ServerWindowResizedHook onResized = nullptr); + ServerWindowLostHook onLost, ServerWindowWaitCancel cancel, void* cancelUser, + ServerWindowLease* out, ServerWindowResizedHook onResized = nullptr); // THE WINDOW'S EXTENT AS THE PLATFORM LAST REPORTED IT (surfaceCreated / surfaceChanged): the one // source of truth for the size a session publishes to its client. False unless `holder` holds // the lease on an attached window. diff --git a/MobileGL/MG_Remote/Server/ServerEntry.cpp b/MobileGL/MG_Remote/Server/ServerEntry.cpp index 00b42792..a1e4fa4f 100644 --- a/MobileGL/MG_Remote/Server/ServerEntry.cpp +++ b/MobileGL/MG_Remote/Server/ServerEntry.cpp @@ -16,4 +16,6 @@ extern "C" int mobilegl_server_main(int argc, char** argv); -int main(int argc, char** argv) { return mobilegl_server_main(argc, argv); } +int main(int argc, char** argv) { + return mobilegl_server_main(argc, argv); +} diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index a5bc5d91..7b4b011e 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -148,7 +148,9 @@ namespace MobileGL::MG_Remote::Server { // resolves ServerLoopInstance to that session's loop): a session that latched is over, and // its lease on the server window, if it holds one, ends once its apply thread has torn the // backend down - which a fresh session of the same client waits for (MarkLeaseEnding). - void MarkLatchedSessionsLeaseEnding() { ServerDisplayInstance().MarkLeaseEnding(&ServerLoopInstance()); } + void MarkLatchedSessionsLeaseEnding() { + ServerDisplayInstance().MarkLeaseEnding(&ServerLoopInstance()); + } // The bounded join. InProcessTransportTest.cpp:344 uses five seconds for the same // reason: a lost wakeup must be a RED TEST and not a hung CI job. @@ -174,8 +176,7 @@ namespace MobileGL::MG_Remote::Server { Uint64 ReadCpuMaxFrequencyKHz(const Uint cpu) { char path[128]; - std::snprintf(path, sizeof(path), - "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", cpu); + std::snprintf(path, sizeof(path), "/sys/devices/system/cpu/cpu%u/cpufreq/cpuinfo_max_freq", cpu); std::FILE* file = std::fopen(path, "r"); if (file == nullptr) return 0; unsigned long long value = 0; @@ -199,7 +200,8 @@ namespace MobileGL::MG_Remote::Server { if (frequencies[cpu] == 0) return; } Uint64 peak = 0; - for (Uint cpu = 0; cpu < cpuCount; ++cpu) peak = std::max(peak, frequencies[cpu]); + for (Uint cpu = 0; cpu < cpuCount; ++cpu) + peak = std::max(peak, frequencies[cpu]); if (peak == 0) return; const Uint64 threshold = peak * kBigCoreFrequencyPercent / 100; for (Uint cpu = 0; cpu < cpuCount; ++cpu) { @@ -387,7 +389,9 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } - MG_Backend::BackendObject* ServerLoop::Backend() { return m_backend.get(); } + MG_Backend::BackendObject* ServerLoop::Backend() { + return m_backend.get(); + } MobileGLResult ServerLoop::InstallBackendForTesting(UniquePtr backend) { if (m_backend != nullptr || backend == nullptr || m_running.load(std::memory_order_acquire)) { @@ -463,20 +467,30 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } - Bool ServerLoop::Running() const { return m_running.load(std::memory_order_acquire); } + Bool ServerLoop::Running() const { + return m_running.load(std::memory_order_acquire); + } // OnApplyThread() is inline in the header now (P5d round 3, package D). Its whole body is // one relaxed load of Detail::g_applyThreadKey and one comparison against the caller's own // thread pointer; the two writes to that key are in ApplyThreadMain, below. - Uint64 ServerLoop::ResolvedAffinityMask() const { return m_affinityMask; } - Uint64 ServerLoop::DrainedRecords() const { return m_drained.load(std::memory_order_acquire); } - Uint64 ServerLoop::ParkCount() const { return m_parks.load(std::memory_order_acquire); } + Uint64 ServerLoop::ResolvedAffinityMask() const { + return m_affinityMask; + } + Uint64 ServerLoop::DrainedRecords() const { + return m_drained.load(std::memory_order_acquire); + } + Uint64 ServerLoop::ParkCount() const { + return m_parks.load(std::memory_order_acquire); + } Uint64 ServerLoop::ParkBlockCount() const { return m_parkBlocks.load(std::memory_order_relaxed); } - Uint64 ServerLoop::NativeBindCount() const { return m_nativeBinds.load(std::memory_order_acquire); } + Uint64 ServerLoop::NativeBindCount() const { + return m_nativeBinds.load(std::memory_order_acquire); + } Uint64 ServerLoop::ClientReleaseCount() const { return m_clientReleases.load(std::memory_order_acquire); } @@ -491,9 +505,9 @@ namespace MobileGL::MG_Remote::Server { // test in BackendObject_DirectGLES.cpp) is a ClientRelease the server records but does not // forward. Otherwise an identical (dpy, draw, read, ctx) already held is a RepeatNoOp, and // anything else is a real NativeBind. Pure: a unit case drives it with no EGL context. - EglBindAction ClassifyEglMakeCurrent(Bool haveCurrent, EGLDisplay curDpy, EGLSurface curDraw, - EGLSurface curRead, EGLContext curCtx, EGLDisplay dpy, - EGLSurface draw, EGLSurface read, EGLContext ctx) { + EglBindAction ClassifyEglMakeCurrent(Bool haveCurrent, EGLDisplay curDpy, EGLSurface curDraw, EGLSurface curRead, + EGLContext curCtx, EGLDisplay dpy, EGLSurface draw, EGLSurface read, + EGLContext ctx) { if (draw == EGL_NO_SURFACE && read == EGL_NO_SURFACE && ctx == EGL_NO_CONTEXT) { return EglBindAction::ClientRelease; } @@ -503,9 +517,8 @@ namespace MobileGL::MG_Remote::Server { return EglBindAction::NativeBind; } - ServerLoop::MakeCurrentOutcome ServerLoop::ApplyMakeCurrent(MG_Backend::BackendObject* backend, - EGLDisplay dpy, EGLSurface draw, - EGLSurface read, EGLContext ctx) { + ServerLoop::MakeCurrentOutcome ServerLoop::ApplyMakeCurrent(MG_Backend::BackendObject* backend, EGLDisplay dpy, + EGLSurface draw, EGLSurface read, EGLContext ctx) { // Apply thread only (RunSurfaceControlFrame's dispatch put us here), so // m_haveCurrentTuple/m_cur* need no // lock. The counters are atomic for the reader on the test thread. @@ -515,8 +528,8 @@ namespace MobileGL::MG_Remote::Server { // this thread). What S4 changed is one layer down: the tuple now names the (session, context) // registry's native context instead of the process's one. MakeCurrentOutcome outcome; - const EglBindAction action = ClassifyEglMakeCurrent(m_haveCurrentTuple, m_curDpy, m_curDraw, - m_curRead, m_curCtx, dpy, draw, read, ctx); + const EglBindAction action = + ClassifyEglMakeCurrent(m_haveCurrentTuple, m_curDpy, m_curDraw, m_curRead, m_curCtx, dpy, draw, read, ctx); switch (action) { case EglBindAction::ClientRelease: // Recorded, NOT forwarded (ID-54): the context stays current on this thread until @@ -694,9 +707,7 @@ namespace MobileGL::MG_Remote::Server { // would park this thread inside the post, and the ring test below already says "not now". ServerVerbSink& verbs = session.Applier().Verbs(); constexpr Uint32 kFenceIdlePollMs = 1; - const auto eventRingHasRoom = [signals] { - return signals.EventRingFull->load(std::memory_order_acquire) == 0; - }; + const auto eventRingHasRoom = [signals] { return signals.EventRingFull->load(std::memory_order_acquire) == 0; }; for (;;) { std::optional turn(std::in_place); @@ -779,8 +790,7 @@ namespace MobileGL::MG_Remote::Server { const Bool fencePending = verbs.HasUnreportedFences(); m_applyParked.store(true, std::memory_order_release); const bool woke = bell.Wait(*signals.ConsumerParked, ready, spinUs, - fencePending ? kFenceIdlePollMs : Transport::kWaitForever, - &m_parkBlocks); + fencePending ? kFenceIdlePollMs : Transport::kWaitForever, &m_parkBlocks); m_applyParked.store(false, std::memory_order_release); if (!woke) { // Wait returns false only on a dead bell or an expired deadline. A dead bell IS the @@ -994,14 +1004,15 @@ namespace MobileGL::MG_Remote::Server { // frame's reply half; the reply is then published back into the slot under the lock so // the poster's copy-out after m_controlDone sees it. const auto dispatchStart = std::chrono::steady_clock::now(); - const MobileGLResult result = ApplySurfaceControlFrame( - frame, m_controlProbeHook.load(std::memory_order_acquire), - m_controlProbeUser.load(std::memory_order_acquire)); + const MobileGLResult result = + ApplySurfaceControlFrame(frame, m_controlProbeHook.load(std::memory_order_acquire), + m_controlProbeUser.load(std::memory_order_acquire)); // p7/spawnhang: A SLOW DISPATCH NAMES ITSELF. The first surface creation / MakeCurrent is // where a lazy native bring-up runs, and on a cold CI runner it outran the client's whole // reply budget with nothing in this log to say so - the investigation had to infer it. - const auto dispatchMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - dispatchStart).count(); + const auto dispatchMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - dispatchStart) + .count(); if (dispatchMs >= static_cast(kSlowControlDispatchMs)) { MGLOG_W("MG_Remote server: %s seq %llu ran %lld ms on mgl-srv-apply (a lazy native " "bring-up runs inside the first surface creation or MakeCurrent)", @@ -1108,8 +1119,8 @@ namespace MobileGL::MG_Remote::Server { // report - used to be popped and applied unpolled, and a GUILTY context ran one more record // (ServerLoopEglLatchTest under CPU load). `polledHead` is the command ring's head as loaded // just BEFORE the last poll (the apply loop's own, handed in): every record below it was - // visible when that poll ran. A record at or past it re-loads the head and polls first. While the client stays ahead this is one - // poll per batch the drain catches up to, never more than one per record. + // visible when that poll ran. A record at or past it re-loads the head and polls first. While the client stays + // ahead this is one poll per batch the drain catches up to, never more than one per record. Transport::LinkSignals signals{}; if (auto* link = session.DataLink()) signals = link->Signals(); for (;;) { @@ -1141,10 +1152,10 @@ namespace MobileGL::MG_Remote::Server { if (timed) { const auto applyStarted = std::chrono::steady_clock::now(); applier.ApplyOne(record); - sFrameApplyNs += static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - applyStarted) - .count()); + sFrameApplyNs += + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - applyStarted) + .count()); } else { applier.ApplyOne(record); } @@ -1154,8 +1165,7 @@ namespace MobileGL::MG_Remote::Server { if (record.kind == static_cast(MG_Pipe::MGPWireOp::Present)) { const auto now = std::chrono::steady_clock::now(); const auto wallNs = static_cast( - std::chrono::duration_cast(now - sFrameWallStart) - .count()); + std::chrono::duration_cast(now - sFrameWallStart).count()); const double applyMs = static_cast(sFrameApplyNs) / 1e6; const double wallMs = static_cast(wallNs) / 1e6; // AND THE STAGE NOTHING ELSE COVERS: the io thread's socket reads, which @@ -1170,16 +1180,14 @@ namespace MobileGL::MG_Remote::Server { "outside_ms=%.1f | read: %llu B in %llu recv(%llu reads) " "recv_ms=%.1f total_ms=%.1f (%.0f%% blocked, %.1f MB/s)", static_cast(++sFrameIndex), - static_cast(sFrameRecords), applyMs, wallMs, - wallMs - applyMs, + static_cast(sFrameRecords), applyMs, wallMs, wallMs - applyMs, static_cast(read.bytes), static_cast(read.calls), static_cast(read.reads), - static_cast(read.nsInRecv) / 1e6, - static_cast(read.nsTotal) / 1e6, - read.nsTotal ? 100.0 * static_cast(read.nsInRecv) / - static_cast(read.nsTotal) - : 0.0, + static_cast(read.nsInRecv) / 1e6, static_cast(read.nsTotal) / 1e6, + read.nsTotal + ? 100.0 * static_cast(read.nsInRecv) / static_cast(read.nsTotal) + : 0.0, read.nsTotal ? static_cast(read.bytes) / (static_cast(read.nsTotal) / 1e9) / 1e6 : 0.0); @@ -1210,9 +1218,10 @@ namespace MobileGL::MG_Remote::Server { // for ever; skipping desynchronises seq, and seq IS the reply-slot id. PH-1 (3): // the header is the peer's bytes, so an armed session child latches here and // stops reading the ring - neither a retry nor a skip. - (void)SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SEG_CMD record header\"} - the " - "consumer refused a record header at applied seq %llu", - static_cast(consumer.AppliedSeq())); + (void)SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"SEG_CMD record header\"} - the " + "consumer refused a record header at applied seq %llu", + static_cast(consumer.AppliedSeq())); break; } if (!popped) break; @@ -1242,12 +1251,13 @@ namespace MobileGL::MG_Remote::Server { if (applier.DecoderAppliedSeq() != consumer.AppliedSeq()) { // PH-1 (3): latched, and nothing is retired against a watermark this side no // longer believes. - (void)SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"appliedSeq batched\"} - the " - "session's watermark is %llu and the decoder applied %llu records. P5 " - "forbids batching appliedSeq (R-9): the verb barrier's waiter reads it, " - "and a watermark ahead of the decoder promises work that has not run", - static_cast(consumer.AppliedSeq()), - static_cast(applier.DecoderAppliedSeq())); + (void)SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"appliedSeq batched\"} - the " + "session's watermark is %llu and the decoder applied %llu records. P5 " + "forbids batching appliedSeq (R-9): the verb barrier's waiter reads it, " + "and a watermark ahead of the decoder promises work that has not run", + static_cast(consumer.AppliedSeq()), + static_cast(applier.DecoderAppliedSeq())); return applied; } // RETIRE. MANDATORY, not optional: RingProducer::FreeBytes() reclaims against @@ -1311,12 +1321,13 @@ namespace MobileGL::MG_Remote::Server { // render correctly with the context on the wrong thread - the one outcome R-1 // exists to make impossible. Fatal by name, never a fallback; compare // CreateBackend's default: arm, which also refuses rather than substitutes. - SessionFail(MGFatalFamily::ApplyThreadNotRunning, "MGPipe: Fatal{ApplyThreadNotRunning, \"EGL on the app thread\"} - a " - "control frame reached RunSurfaceControlFrame with the server's backend " - "built but no mgl-srv-apply thread running (ClientSession::Start failed " - "after ServerLoop::CreateBackend). Dispatching it inline would make the " - "context current on the APP thread - the split lane's whole premise. " - "Refusing by name rather than falling back to monolith"); + SessionFail(MGFatalFamily::ApplyThreadNotRunning, + "MGPipe: Fatal{ApplyThreadNotRunning, \"EGL on the app thread\"} - a " + "control frame reached RunSurfaceControlFrame with the server's backend " + "built but no mgl-srv-apply thread running (ClientSession::Start failed " + "after ServerLoop::CreateBackend). Dispatching it inline would make the " + "context current on the APP thread - the split lane's whole premise. " + "Refusing by name rather than falling back to monolith"); } // Backend already gone (post-Stop teardown, or the pre-Init window): the dispatch's // null-backend arm would answer NOT_INITIALIZED anyway, so that is the honest result @@ -1361,11 +1372,11 @@ namespace MobileGL::MG_Remote::Server { const ControlProgressSink sink = m_progressSink; void* const user = m_progressUser; if (!running || sink == nullptr) continue; - const auto elapsedMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - postedAt).count(); + const auto elapsedMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - postedAt) + .count(); lock.unlock(); - sink(user, frame.kind, frame.seq, - static_cast(std::min(elapsedMs, 0xFFFFFFFFll))); + sink(user, frame.kind, frame.seq, static_cast(std::min(elapsedMs, 0xFFFFFFFFll))); lock.lock(); } frame = m_controlFrame; // the reply half, written by the dispatch @@ -1405,7 +1416,9 @@ namespace MobileGL::MG_Remote::Server { m_exitCv.notify_all(); } - void ServerLoop::AbandonQueuedRecords() { m_abandonQueue.store(true, std::memory_order_release); } + void ServerLoop::AbandonQueuedRecords() { + m_abandonQueue.store(true, std::memory_order_release); + } void ServerLoop::Stop() { if (!m_thread.joinable()) { @@ -1445,8 +1458,7 @@ namespace MobileGL::MG_Remote::Server { Bool exited = false; { std::unique_lock lock(m_exitMutex); - exited = m_exitCv.wait_for(lock, std::chrono::milliseconds(kJoinTimeoutMs), - [this] { return m_exited; }); + exited = m_exitCv.wait_for(lock, std::chrono::milliseconds(kJoinTimeoutMs), [this] { return m_exited; }); } if (!exited && !m_applyParked.load(std::memory_order_acquire)) { // BUSY, NOT LOST (kBusyJoinTimeoutMs): the thread is applying, most likely blocked on @@ -1466,15 +1478,16 @@ namespace MobileGL::MG_Remote::Server { // run on into the client's teardown, reading rings the client is about to unmap - // a use-after-free whose only symptom is an intermittent crash somewhere else. // Aborting here is red, immediate, and names the cause. - SessionFail(MGFatalFamily::ApplyThreadJoinTimeout, "MGPipe: Fatal{ApplyThreadJoinTimeout} - mgl-srv-apply did not exit within " - "%u ms of Stop(). Stop() published m_stopRequested (in the park predicate) " - "BEFORE it rang, so a plain Notify should already have un-parked the thread; " - "Doorbell::Kill() (Doorbell.h, CondVarDoorbell::Kill; table 3 step 2) is " - "the belt to that " - "Notify's braces. If the thread is still parked after both, the wakeup was " - "lost, not slow. Aborting rather than detaching: a detached apply thread still " - "owns the context and would read rings the client is about to unmap", - kJoinTimeoutMs); + SessionFail(MGFatalFamily::ApplyThreadJoinTimeout, + "MGPipe: Fatal{ApplyThreadJoinTimeout} - mgl-srv-apply did not exit within " + "%u ms of Stop(). Stop() published m_stopRequested (in the park predicate) " + "BEFORE it rang, so a plain Notify should already have un-parked the thread; " + "Doorbell::Kill() (Doorbell.h, CondVarDoorbell::Kill; table 3 step 2) is " + "the belt to that " + "Notify's braces. If the thread is still parked after both, the wakeup was " + "lost, not slow. Aborting rather than detaching: a detached apply thread still " + "owns the context and would read rings the client is about to unmap", + kJoinTimeoutMs); } m_thread.join(); // The identity is NOT cleared here any more: the thread clears it itself, under @@ -1563,9 +1576,11 @@ namespace MobileGL::MG_Remote::Server { Bool WindowBackendFromFrameValue(Int value, MG_Backend::WindowBackend* out) { if (value < static_cast(MG_Backend::WindowBackend::Unknown) || value >= static_cast(MG_Backend::WindowBackend::WindowBackendCount)) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.windowBackend\"} - a " - "control frame carried window backend tag %d, which names no " - "WindowBackend", value); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.windowBackend\"} - a " + "control frame carried window backend tag %d, which names no " + "WindowBackend", + value); } *out = static_cast(value); return true; @@ -1618,8 +1633,8 @@ namespace MobileGL::MG_Remote::Server { } // namespace - MobileGLResult ServerLoop::ApplySurfaceControlFrame(SurfaceControlFrame& frame, - ControlProbeHook probeHook, void* probeUser) { + MobileGLResult ServerLoop::ApplySurfaceControlFrame(SurfaceControlFrame& frame, ControlProbeHook probeHook, + void* probeUser) { // The frame channel's own tally, bumped for EVERY dispatch including probes: a forwarder // that stopped posting frames (the fc red-once revert shape) leaves this unmoved, which // is what ServerLoopTest's AVoidForwarderCrossesAsOneDispatchedFrame reads. @@ -1641,8 +1656,7 @@ namespace MobileGL::MG_Remote::Server { // process boundary, so the version numbers travel back in the frame's value. EGLint major = 0; EGLint minor = 0; - frame.ok = backend->InitializeEGLDisplay(HandleFromToken(frame.display), - &major, &minor); + frame.ok = backend->InitializeEGLDisplay(HandleFromToken(frame.display), &major, &minor); frame.eglMajor = major; frame.eglMinor = minor; return MOBILEGL_OK; @@ -1656,8 +1670,7 @@ namespace MobileGL::MG_Remote::Server { frame.ok = false; return MOBILEGL_ERR_PROTOCOL_MISMATCH; // PH-1 (3): latched } - frame.ok = backend->CreateEGLWindowSurface(HandleFromToken(frame.surface), - window); + frame.ok = backend->CreateEGLWindowSurface(HandleFromToken(frame.surface), window); // N-3: BackendObject_DirectGLES destroys and recreates the native context to create // a DIFFERENT surface, so whatever tuple was bound names a dead context. if (frame.ok) ForgetCurrentTuple(); @@ -1667,9 +1680,9 @@ namespace MobileGL::MG_Remote::Server { // P12 review fix: a server-owned surface is resized by resizing the SERVER's window. if (IsServerOwnedSurface(HandleFromToken(frame.surface))) return ApplyServerOwnedWindowResize(backend, frame); - frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), - static_cast(frame.width), - static_cast(frame.height)); + frame.ok = + backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), + static_cast(frame.width), static_cast(frame.height)); return MOBILEGL_OK; case SurfaceControlOp::CreatePbufferSurface: { // A surfaceless stand-in (kPbufferFlagSurfacelessStandIn) is no rendering path: it is @@ -1690,8 +1703,8 @@ namespace MobileGL::MG_Remote::Server { SessionSurfaceModeName(m_surfaceMode)); return MOBILEGL_ERR_INVALID_ARGUMENT; } - frame.ok = backend->CreateEGLPbufferSurface(HandleFromToken(frame.surface), - frame.width, frame.height); + frame.ok = + backend->CreateEGLPbufferSurface(HandleFromToken(frame.surface), frame.width, frame.height); // N-3: as for the window surface - a (re)creation may have destroyed the context the // held tuple named. The surface's own creation binds natively, so the client's // make-current that follows is forwarded and deduped one layer down (ID-54). @@ -1724,11 +1737,9 @@ namespace MobileGL::MG_Remote::Server { // current with ITS context and not attached to a neighbour's. A frame from a client // that sends a handle names no token and leaves the binding alone. if (m_session != nullptr && frame.context != 0) (void)m_session->BindContext(frame.context); - const MakeCurrentOutcome outcome = - ApplyMakeCurrent(backend, HandleFromToken(frame.display), - HandleFromToken(frame.surface), - HandleFromToken(frame.readSurface), - HandleFromToken(frame.context)); + const MakeCurrentOutcome outcome = ApplyMakeCurrent( + backend, HandleFromToken(frame.display), HandleFromToken(frame.surface), + HandleFromToken(frame.readSurface), HandleFromToken(frame.context)); frame.ok = outcome.ok; if (!outcome.ok || !outcome.boundNatively) return MOBILEGL_OK; // R-12, arm (a): the caps snapshot is REPUBLISHED because InitCapabilities has now @@ -1765,9 +1776,8 @@ namespace MobileGL::MG_Remote::Server { // current on this thread until ~BackendObject_DirectGLES or context loss, so // forwarding would unbind and reinstate the off-thread degradation the phase // removes). A client release never republishes: nothing ran that could move caps. - const MakeCurrentOutcome outcome = - ApplyMakeCurrent(backend, HandleFromToken(frame.display), - EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + const MakeCurrentOutcome outcome = ApplyMakeCurrent(backend, HandleFromToken(frame.display), + EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); frame.ok = outcome.ok; return MOBILEGL_OK; } @@ -1788,8 +1798,7 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_ERR_NOT_INITIALIZED; } frame.ok = session->CreateContext(frame.context, frame.shareGroupToken, frame.contextFlags); - if (frame.ok && backend != nullptr && - backend->GetBackendType() == BackendType::DirectGLES) { + if (frame.ok && backend != nullptr && backend->GetBackendType() == BackendType::DirectGLES) { if (!MG_Backend::DirectGLES::CreateNativeContextFor(frame.context, frame.shareGroupToken)) { (void)session->DestroyContext(frame.context); frame.ok = false; @@ -1807,8 +1816,7 @@ namespace MobileGL::MG_Remote::Server { // P14 S4: the native context goes with the entry, and ONLY that one - the session's // other contexts, and every other session's, are untouched. The display is terminated // only when the last native context in the process has gone. - if (frame.ok && backend != nullptr && - backend->GetBackendType() == BackendType::DirectGLES) { + if (frame.ok && backend != nullptr && backend->GetBackendType() == BackendType::DirectGLES) { MG_Backend::DirectGLES::DestroyNativeContextFor(frame.context); } return MOBILEGL_OK; @@ -1894,8 +1902,10 @@ namespace MobileGL::MG_Remote::Server { // PH-1 (3): latched in an armed session child (under spawn SurfaceOpCodec has already // refused an unknown wire kind, so this is defence in depth); unarmed it dies. frame.ok = false; - (void)SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.kind\"} - the control slot held " - "kind %d, which names no dispatchable op", static_cast(frame.kind)); + (void)SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp.kind\"} - the control slot held " + "kind %d, which names no dispatchable op", + static_cast(frame.kind)); return MOBILEGL_ERR_PROTOCOL_MISMATCH; } @@ -1905,18 +1915,24 @@ namespace MobileGL::MG_Remote::Server { const char* SessionSurfaceModeName(SessionSurfaceMode mode) { switch (mode) { - case SessionSurfaceMode::None: return "undecided"; - case SessionSurfaceMode::OnScreen: return "on-screen"; - case SessionSurfaceMode::Offscreen: return "offscreen"; + case SessionSurfaceMode::None: + return "undecided"; + case SessionSurfaceMode::OnScreen: + return "on-screen"; + case SessionSurfaceMode::Offscreen: + return "offscreen"; } return ""; } Bool SessionSurfaceModeAdmits(SessionSurfaceMode current, Bool serverOwnedWindow) { switch (current) { - case SessionSurfaceMode::None: return true; - case SessionSurfaceMode::OnScreen: return serverOwnedWindow; - case SessionSurfaceMode::Offscreen: return !serverOwnedWindow; + case SessionSurfaceMode::None: + return true; + case SessionSurfaceMode::OnScreen: + return serverOwnedWindow; + case SessionSurfaceMode::Offscreen: + return !serverOwnedWindow; } return false; } @@ -2008,7 +2024,7 @@ namespace MobileGL::MG_Remote::Server { } MobileGLResult ServerLoop::ApplyServerOwnedWindowSurface(MG_Backend::BackendObject* backend, - SurfaceControlFrame& frame) { + SurfaceControlFrame& frame) { frame.ok = false; frame.refusal = static_cast(SurfaceRefusalCode::None); const Uint32 wantWidth = frame.width > 0 ? static_cast(frame.width) : 0u; @@ -2027,7 +2043,8 @@ namespace MobileGL::MG_Remote::Server { } ServerWindowLease lease; SurfaceRefusalCode refusal = SurfaceRefusalCode::None; - const MobileGLResult acquired = AcquireServerWindow(wantWidth, wantHeight, kServerWindowWaitMs, &lease, &refusal); + const MobileGLResult acquired = + AcquireServerWindow(wantWidth, wantHeight, kServerWindowWaitMs, &lease, &refusal); if (acquired != MOBILEGL_OK) { frame.refusal = static_cast(refusal); return acquired; @@ -2072,10 +2089,11 @@ namespace MobileGL::MG_Remote::Server { frame.width = static_cast(lease.width); frame.height = static_cast(lease.height); // THE ARM PROOF (ID-124), once per surface: which of the two paths ran, and at what size. - MGLOG_I("MG_Remote server: surface=window %ux%u owner=server (ServerOwned seq %llu, window %p generation %llu%s)", - lease.width, lease.height, static_cast(frame.seq), lease.window, - static_cast(lease.generation), - lease.sizeAsRequested ? "" : ", NOT the size the client asked for"); + MGLOG_I( + "MG_Remote server: surface=window %ux%u owner=server (ServerOwned seq %llu, window %p generation %llu%s)", + lease.width, lease.height, static_cast(frame.seq), lease.window, + static_cast(lease.generation), + lease.sizeAsRequested ? "" : ", NOT the size the client asked for"); return MOBILEGL_OK; } @@ -2096,7 +2114,7 @@ namespace MobileGL::MG_Remote::Server { // for (the same lease, re-taken by its holder), the backend surface follows the window's REAL // extent, and that extent is the reply's width/height - which the client adopts. MobileGLResult ServerLoop::ApplyServerOwnedWindowResize(MG_Backend::BackendObject* backend, - SurfaceControlFrame& frame) { + SurfaceControlFrame& frame) { frame.ok = false; frame.refusal = static_cast(SurfaceRefusalCode::None); const Uint32 wantWidth = frame.width > 0 ? static_cast(frame.width) : 0u; @@ -2116,7 +2134,8 @@ namespace MobileGL::MG_Remote::Server { } ServerWindowLease lease; SurfaceRefusalCode refusal = SurfaceRefusalCode::None; - const MobileGLResult acquired = AcquireServerWindow(wantWidth, wantHeight, kServerWindowWaitMs, &lease, &refusal); + const MobileGLResult acquired = + AcquireServerWindow(wantWidth, wantHeight, kServerWindowWaitMs, &lease, &refusal); if (acquired != MOBILEGL_OK) { frame.refusal = static_cast(refusal); return acquired; @@ -2124,7 +2143,8 @@ namespace MobileGL::MG_Remote::Server { m_serverWindowWantWidth = wantWidth; m_serverWindowWantHeight = wantHeight; NoteServerWindowExtent(lease.window, lease.width, lease.height); - frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), lease.width, lease.height); + frame.ok = + backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), lease.width, lease.height); frame.width = static_cast(lease.width); frame.height = static_cast(lease.height); MGLOG_I("MG_Remote server: surface=window %ux%u owner=server resized (ServerOwned seq %llu, %ux%u requested%s)", @@ -2238,10 +2258,9 @@ namespace MobileGL::MG_Remote::Server { ServerWindowAcquire acquired = ServerWindowAcquire::NoDisplay; { const ScopedBackendTurnYield yield; - acquired = ServerDisplayInstance().AcquireFor(m_serverWindowWantWidth, m_serverWindowWantHeight, - ServerDisplay::kGeometryGraceMs, this, &ServerWindowLostThunk, - &ServerWindowWaitCancelled, this, &lease, - &ServerWindowResizedThunk); + acquired = ServerDisplayInstance().AcquireFor( + m_serverWindowWantWidth, m_serverWindowWantHeight, ServerDisplay::kGeometryGraceMs, this, + &ServerWindowLostThunk, &ServerWindowWaitCancelled, this, &lease, &ServerWindowResizedThunk); } if (acquired != ServerWindowAcquire::Acquired) { // Still registered: the next new window wakes this again. diff --git a/MobileGL/MG_Remote/Server/ServerLoop.h b/MobileGL/MG_Remote/Server/ServerLoop.h index 9ede2e81..9a792b3f 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.h +++ b/MobileGL/MG_Remote/Server/ServerLoop.h @@ -60,7 +60,11 @@ namespace MobileGL::MG_Remote::Server { // nor are refused. The mode is a loop member reset by ServerLoop::Start, so each session // latches on its own; it is not read from MOBILEGL_IPC_SURFACE, because one process's // environment cannot speak for every session a unified server runs. - enum class SessionSurfaceMode : Uint8 { None, OnScreen, Offscreen }; + enum class SessionSurfaceMode : Uint8 { + None, + OnScreen, + Offscreen + }; const char* SessionSurfaceModeName(SessionSurfaceMode mode); // The decision, pure, so a unit case can drive all six pairs without a backend. Bool SessionSurfaceModeAdmits(SessionSurfaceMode current, Bool serverOwnedWindow); @@ -242,8 +246,8 @@ namespace MobileGL::MG_Remote::Server { Bool ok = false; // the request was honoured Bool boundNatively = false; // a native eglMakeCurrent ran (=> republish caps) }; - MakeCurrentOutcome ApplyMakeCurrent(MG_Backend::BackendObject* backend, EGLDisplay dpy, - EGLSurface draw, EGLSurface read, EGLContext ctx); + MakeCurrentOutcome ApplyMakeCurrent(MG_Backend::BackendObject* backend, EGLDisplay dpy, EGLSurface draw, + EGLSurface read, EGLContext ctx); // N-3: forget the tuple ApplyMakeCurrent last bound. Apply thread only, like the tuple // itself (the forwarders that call these run their Args::Run there). Called on every @@ -281,8 +285,8 @@ namespace MobileGL::MG_Remote::Server { // (NoServerWindow). The ServerOwned arm of the dispatch is its production caller; a test // reaches it through RunProbeOnApplyThreadForTesting. The lease is held until the backend // has let go of the window: ServerDisplay::Detach's lost hook (D6) or the session's end. - MobileGLResult AcquireServerWindow(Uint32 width, Uint32 height, Uint32 timeoutMs, - ServerWindowLease* out, SurfaceRefusalCode* refusal); + MobileGLResult AcquireServerWindow(Uint32 width, Uint32 height, Uint32 timeoutMs, ServerWindowLease* out, + SurfaceRefusalCode* refusal); // APPLY THREAD ONLY: this session's surface mode (D4). SessionSurfaceMode SurfaceMode() const { return m_surfaceMode; } // How many times this loop released a lost server window and latched ServerWindowLost (D6) - @@ -396,8 +400,8 @@ namespace MobileGL::MG_Remote::Server { // The dispatch, on the apply thread (or inline for a re-entrant post): executes the // frame's op against the private backend and fills the frame's reply half. The eleven // wire+inproc op bodies from the old forwarder Args structs live here now. - MobileGLResult ApplySurfaceControlFrame(SurfaceControlFrame& frame, - ControlProbeHook probeHook = nullptr, void* probeUser = nullptr); + MobileGLResult ApplySurfaceControlFrame(SurfaceControlFrame& frame, ControlProbeHook probeHook = nullptr, + void* probeUser = nullptr); // The caller already holds m_callerMutex; shared by normal and test-probe posting so // the probe's metadata is protected for the same lifetime as its frame. MobileGLResult PostSurfaceControlFrameWithCallerLock(SurfaceControlFrame& frame); @@ -515,10 +519,14 @@ namespace MobileGL::MG_Remote::Server { // (the native bind itself needs the joint lane). Given the tuple currently held on the apply // thread and the request, is this a native bind, an identical no-op repeat, or a client // release-current the server must record without forwarding? - enum class EglBindAction { NativeBind, RepeatNoOp, ClientRelease }; - EglBindAction ClassifyEglMakeCurrent(Bool haveCurrent, EGLDisplay curDpy, EGLSurface curDraw, - EGLSurface curRead, EGLContext curCtx, EGLDisplay dpy, - EGLSurface draw, EGLSurface read, EGLContext ctx); + enum class EglBindAction { + NativeBind, + RepeatNoOp, + ClientRelease + }; + EglBindAction ClassifyEglMakeCurrent(Bool haveCurrent, EGLDisplay curDpy, EGLSurface curDraw, EGLSurface curRead, + EGLContext curCtx, EGLDisplay dpy, EGLSurface draw, EGLSurface read, + EGLContext ctx); // P11 B1 (CONTRACT-P11 B1): the cpu core this process's `auto` policy reserves for a dialled-in // shared-segment client's apply thread (ApplyThreadPolicy.h ReservedApplyCoreForConfig over this diff --git a/MobileGL/MG_Remote/Server/ServerMain.cpp b/MobileGL/MG_Remote/Server/ServerMain.cpp index 4ef5171f..e98f1771 100644 --- a/MobileGL/MG_Remote/Server/ServerMain.cpp +++ b/MobileGL/MG_Remote/Server/ServerMain.cpp @@ -53,895 +53,913 @@ #endif namespace { -using namespace MobileGL::MG_Remote; -using namespace MobileGL::MG_Remote::Transport; -namespace Protocol = ::MobileGL::Wire; + using namespace MobileGL::MG_Remote; + using namespace MobileGL::MG_Remote::Transport; + namespace Protocol = ::MobileGL::Wire; -void Refuse(SocketTransport& transport, Protocol::RefuseCode code, const char* detail) { - flatbuffers::FlatBufferBuilder builder(256); - auto refusal = Protocol::CreateRefuseDirect(builder, code, detail); - auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::Refuse, refusal.Union()); - Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); - transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()}); - WireLogError("MG_Remote server: Refuse{%s} %s", Protocol::EnumNameRefuseCode(code), detail); -} - -// P9 W3. The log forward's unblock hook (LogForward.h, Close): the channel's sender is still inside a -// write to a client that has not read for the whole close budget, at the end of the session. Cutting -// the control socket is what returns that write (EPIPE) so the thread can be joined - the session is -// over by then (every caller is on RunSession's way out), and the socket is closed right after anyway. -// Not through SocketTransport::Shutdown: that takes the send mutex the blocked write holds. -void UnblockControlSend(void* user) { - const int fd = static_cast(user)->StreamFd(); - if (fd >= 0) (void)::shutdown(fd, SHUT_RDWR); -} - -// The forward's counters, once, into the server's OWN log - the forwarder is already gone by the time -// they are final, so this line is local; what the client saw of the same story is the in-stream -// LogForward{Dropped} / {Coalesced} lines at each gap. WARN when anything was lost, delayed past a -// deadline or cut, so it survives a production build; otherwise a D line. -void LogForwardSummary(const Transport::LogForwardCounters& counters) { - const bool eventful = counters.lossyDropped != 0 || counters.errorsCoalesced != 0 || counters.abandoned != 0 || - counters.fatalFlushTimeouts != 0 || counters.transportFailed; - if (eventful) { - MGLOG_W("MG_Remote server: log forwarding ended - forwarded=%llu dropped(<=WARN)=%llu (%llu bytes) " - "coalesced(ERROR)=%llu paced(ERROR)=%llu notices=%llu abandoned=%llu fatal-flush-timeouts=%llu " - "transport-failed=%d max-offer-us=%llu", - static_cast(counters.forwarded), - static_cast(counters.lossyDropped), - static_cast(counters.lossyDroppedBytes), - static_cast(counters.errorsCoalesced), - static_cast(counters.errorsPaced), - static_cast(counters.notices), - static_cast(counters.abandoned), - static_cast(counters.fatalFlushTimeouts), counters.transportFailed ? 1 : 0, - static_cast(counters.maxOfferNs / 1000)); - } else { - MGLOG_D("MG_Remote server: log forwarding ended - forwarded=%llu paced(ERROR)=%llu max-offer-us=%llu", - static_cast(counters.forwarded), - static_cast(counters.errorsPaced), - static_cast(counters.maxOfferNs / 1000)); + void Refuse(SocketTransport& transport, Protocol::RefuseCode code, const char* detail) { + flatbuffers::FlatBufferBuilder builder(256); + auto refusal = Protocol::CreateRefuseDirect(builder, code, detail); + auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::Refuse, refusal.Union()); + Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); + transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()}); + WireLogError("MG_Remote server: Refuse{%s} %s", Protocol::EnumNameRefuseCode(code), detail); } -} -// p7/spawnhang. THE PROGRESS REPORT: Wire::SurfaceProgress, sent by the thread that posted the op -// - RunSession's control pump, waiting in ServerApplyWireSurfaceOp - after each -// ServerLoop::kControlProgressIntervalMs that mgl-srv-apply has spent running it -// (ServerLoop::SetControlProgressSink). The client's reply budget restarts on each one, so a cold -// native bring-up that outruns MOBILEGL_IPC_COLD_START_MS is waited for instead of being reported -// ALIVE BUT SILENT (retrace-split run 35912252677). A send failure is left to the reply's send, -// which already ends the session on it. -void SendSurfaceProgress(void* user, Server::SurfaceControlOp, std::uint64_t seq, std::uint32_t elapsedMs) { - auto& transport = *static_cast(user); - flatbuffers::FlatBufferBuilder builder(64); - auto progress = Protocol::CreateSurfaceProgress(builder, seq, elapsedMs); - auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::SurfaceProgress, progress.Union()); - Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); - (void)transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()}); -} - -void RefuseBusy(SocketTransport& transport, const char* detail) { - // Unread Hello bytes can make close send RST and discard our Refuse. - std::uint64_t bytes = 0; - if (transport.ReceiveFrame({nullptr, 0}, &bytes, 2000) == MOBILEGL_ERR_BUFFER_TOO_SMALL && - bytes <= 1024 * 1024) { - std::vector hello(static_cast(bytes)); - (void)transport.ReceiveFrame({hello.data(), hello.size()}, &bytes, 0); + // P9 W3. The log forward's unblock hook (LogForward.h, Close): the channel's sender is still inside a + // write to a client that has not read for the whole close budget, at the end of the session. Cutting + // the control socket is what returns that write (EPIPE) so the thread can be joined - the session is + // over by then (every caller is on RunSession's way out), and the socket is closed right after anyway. + // Not through SocketTransport::Shutdown: that takes the send mutex the blocked write holds. + void UnblockControlSend(void* user) { + const int fd = static_cast(user)->StreamFd(); + if (fd >= 0) (void)::shutdown(fd, SHUT_RDWR); } - Refuse(transport, Protocol::RefuseCode::Busy, detail); -} -// P11 B1 (CONTRACT-P11 B1): HOW LONG A PAIRED UNIX SESSION WAITS FOR ITS HELLO - FOR EVER; ITS END -// IS THE PEER'S EOF. The pair's identity is settled before this wait (PairAcceptor paired the two -// connections by the nonce each presented), and a unix endpoint is reachable only by this user / this -// app (SELinux on Android, 0600 on a path). Since B1 the server app's broker presents the PairBinds at -// hand-off, the moment it connects, while the program it is for may still be loading - a Termux program -// can spend many seconds on its assets before its first EGL call sends the Hello. The old 10 s (the -// same budget as TCP's pre-auth) refused such a program by name; there is no bound here that is long -// enough for every program, so there is none: a program that exits (or closes the descriptors) ends -// the wait with EOF ("control peer closed before sending a first frame"), and while it lives it holds -// the one session slot, as a rendering session would. TCP keeps its pre-auth deadline (PreAuthKnobs), -// and the in-process display server keeps 10 s (its stop waits for the session thread). -constexpr std::uint32_t kPairedUnixHelloWaitMs = Transport::kWaitForever; - -// The single-session TCP shape's wait for its data connection's DataBind (ListenerSource). -constexpr std::uint32_t kFirstFrameWaitMs = 2000; -constexpr std::uint64_t kFirstFrameMaxBytes = Server::kPreAuthFirstFrameMaxBytes; -// The one detail both ends of the hand-off use for a data connection the live session will not -// take: the supervisor when the send fails, and the child for the ones queued before it closed -// its end (CloseHandoffRefusingQueued). -constexpr const char* kHandoffRefused = "data connection could not be handed to the live session"; - -// Whatever the peer has already sent, read and dropped (non-blocking, at most 1 MiB), so a close -// with bytes still unread does not become an RST that discards the refusal in flight - the -// reason RefuseBusy reads the Hello before refusing. -void DrainUnread(int fd) { - char sink[4096]; - for (int rounds = 0; rounds < 256; ++rounds) { - if (::recv(fd, sink, sizeof(sink), MSG_DONTWAIT) <= 0) return; + // The forward's counters, once, into the server's OWN log - the forwarder is already gone by the time + // they are final, so this line is local; what the client saw of the same story is the in-stream + // LogForward{Dropped} / {Coalesced} lines at each gap. WARN when anything was lost, delayed past a + // deadline or cut, so it survives a production build; otherwise a D line. + void LogForwardSummary(const Transport::LogForwardCounters& counters) { + const bool eventful = counters.lossyDropped != 0 || counters.errorsCoalesced != 0 || counters.abandoned != 0 || + counters.fatalFlushTimeouts != 0 || counters.transportFailed; + if (eventful) { + MGLOG_W("MG_Remote server: log forwarding ended - forwarded=%llu dropped(<=WARN)=%llu (%llu bytes) " + "coalesced(ERROR)=%llu paced(ERROR)=%llu notices=%llu abandoned=%llu fatal-flush-timeouts=%llu " + "transport-failed=%d max-offer-us=%llu", + static_cast(counters.forwarded), + static_cast(counters.lossyDropped), + static_cast(counters.lossyDroppedBytes), + static_cast(counters.errorsCoalesced), + static_cast(counters.errorsPaced), + static_cast(counters.notices), + static_cast(counters.abandoned), + static_cast(counters.fatalFlushTimeouts), counters.transportFailed ? 1 : 0, + static_cast(counters.maxOfferNs / 1000)); + } else { + MGLOG_D("MG_Remote server: log forwarding ended - forwarded=%llu paced(ERROR)=%llu max-offer-us=%llu", + static_cast(counters.forwarded), + static_cast(counters.errorsPaced), + static_cast(counters.maxOfferNs / 1000)); + } } -} -// Refuses a connection the supervisor still holds as a bare descriptor, and closes it. -void RefuseAndClose(int fd, Protocol::RefuseCode code, const char* detail) { - SocketTransport connection(fd, -1, TransportRole::Server); - DrainUnread(fd); - Refuse(connection, code, detail); -} + // p7/spawnhang. THE PROGRESS REPORT: Wire::SurfaceProgress, sent by the thread that posted the op + // - RunSession's control pump, waiting in ServerApplyWireSurfaceOp - after each + // ServerLoop::kControlProgressIntervalMs that mgl-srv-apply has spent running it + // (ServerLoop::SetControlProgressSink). The client's reply budget restarts on each one, so a cold + // native bring-up that outruns MOBILEGL_IPC_COLD_START_MS is waited for instead of being reported + // ALIVE BUT SILENT (retrace-split run 35912252677). A send failure is left to the reply's send, + // which already ends the session on it. + void SendSurfaceProgress(void* user, Server::SurfaceControlOp, std::uint64_t seq, std::uint32_t elapsedMs) { + auto& transport = *static_cast(user); + flatbuffers::FlatBufferBuilder builder(64); + auto progress = Protocol::CreateSurfaceProgress(builder, seq, elapsedMs); + auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::SurfaceProgress, progress.Union()); + Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); + (void)transport.SendFrame({builder.GetBufferPointer(), builder.GetSize()}); + } -// The numeric address of a TCP peer, without its port: the backoff's key. A peer whose address -// cannot be read is keyed "?", so it is still counted, just not on its own. -std::string PeerAddress(int fd) { - sockaddr_storage address{}; - socklen_t size = sizeof(address); - if (::getpeername(fd, reinterpret_cast(&address), &size) != 0) return "?"; - char text[INET6_ADDRSTRLEN] = {}; - const void* raw = nullptr; - if (address.ss_family == AF_INET) raw = &reinterpret_cast(&address)->sin_addr; - else if (address.ss_family == AF_INET6) raw = &reinterpret_cast(&address)->sin6_addr; - if (raw == nullptr || ::inet_ntop(address.ss_family, raw, text, sizeof(text)) == nullptr) return "?"; - return text; -} + void RefuseBusy(SocketTransport& transport, const char* detail) { + // Unread Hello bytes can make close send RST and discard our Refuse. + std::uint64_t bytes = 0; + if (transport.ReceiveFrame({nullptr, 0}, &bytes, 2000) == MOBILEGL_ERR_BUFFER_TOO_SMALL && + bytes <= 1024 * 1024) { + std::vector hello(static_cast(bytes)); + (void)transport.ReceiveFrame({hello.data(), hello.size()}, &bytes, 0); + } + Refuse(transport, Protocol::RefuseCode::Busy, detail); + } -// True when the control peer has closed (EOF or error); false while it is open, readable or not. -bool ControlPeerGone(int controlFd) { - char byte = 0; - const auto peeked = ::recv(controlFd, &byte, 1, MSG_PEEK | MSG_DONTWAIT); - return peeked == 0 || (peeked < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR); -} + // P11 B1 (CONTRACT-P11 B1): HOW LONG A PAIRED UNIX SESSION WAITS FOR ITS HELLO - FOR EVER; ITS END + // IS THE PEER'S EOF. The pair's identity is settled before this wait (PairAcceptor paired the two + // connections by the nonce each presented), and a unix endpoint is reachable only by this user / this + // app (SELinux on Android, 0600 on a path). Since B1 the server app's broker presents the PairBinds at + // hand-off, the moment it connects, while the program it is for may still be loading - a Termux program + // can spend many seconds on its assets before its first EGL call sends the Hello. The old 10 s (the + // same budget as TCP's pre-auth) refused such a program by name; there is no bound here that is long + // enough for every program, so there is none: a program that exits (or closes the descriptors) ends + // the wait with EOF ("control peer closed before sending a first frame"), and while it lives it holds + // the one session slot, as a rendering session would. TCP keeps its pre-auth deadline (PreAuthKnobs), + // and the in-process display server keeps 10 s (its stop waits for the session thread). + constexpr std::uint32_t kPairedUnixHelloWaitMs = Transport::kWaitForever; -// The session child's source: descriptors the supervisor forwarded, each with the nonce its -// DataBind presented. Watching the control connection too, so a client that went away after -// Welcome ends the wait at once instead of holding the supervisor Busy for the whole deadline. -Server::ServerSession::DataConnectionSource HandoffSource(int handoff, int controlFd) { - return [handoff, controlFd](std::uint32_t timeoutMs, int* outFd, std::uint8_t* outNonce) -> MobileGLResult { - pollfd fds[2] = {{handoff, POLLIN, 0}, {controlFd, POLLIN, 0}}; - // A control connection with bytes waiting is not a hang-up and must not spin this loop: - // after the first look it is only asked again once per slice. - const int rc = ::poll(fds, 2, static_cast(std::min(timeoutMs, 50))); - if (rc < 0) return errno == EINTR ? MOBILEGL_ERR_TIMEOUT : MOBILEGL_ERR_TRANSPORT_CLOSED; - if ((fds[1].revents & (POLLIN | POLLHUP | POLLERR)) != 0 && ControlPeerGone(controlFd)) - return MOBILEGL_ERR_TRANSPORT_CLOSED; - if ((fds[0].revents & (POLLIN | POLLHUP | POLLERR)) == 0) { - if ((fds[1].revents & POLLIN) != 0) { - pollfd only{handoff, POLLIN, 0}; - if (::poll(&only, 1, static_cast(std::min(timeoutMs, 50))) <= 0) + // The single-session TCP shape's wait for its data connection's DataBind (ListenerSource). + constexpr std::uint32_t kFirstFrameWaitMs = 2000; + constexpr std::uint64_t kFirstFrameMaxBytes = Server::kPreAuthFirstFrameMaxBytes; + // The one detail both ends of the hand-off use for a data connection the live session will not + // take: the supervisor when the send fails, and the child for the ones queued before it closed + // its end (CloseHandoffRefusingQueued). + constexpr const char* kHandoffRefused = "data connection could not be handed to the live session"; + + // Whatever the peer has already sent, read and dropped (non-blocking, at most 1 MiB), so a close + // with bytes still unread does not become an RST that discards the refusal in flight - the + // reason RefuseBusy reads the Hello before refusing. + void DrainUnread(int fd) { + char sink[4096]; + for (int rounds = 0; rounds < 256; ++rounds) { + if (::recv(fd, sink, sizeof(sink), MSG_DONTWAIT) <= 0) return; + } + } + + // Refuses a connection the supervisor still holds as a bare descriptor, and closes it. + void RefuseAndClose(int fd, Protocol::RefuseCode code, const char* detail) { + SocketTransport connection(fd, -1, TransportRole::Server); + DrainUnread(fd); + Refuse(connection, code, detail); + } + + // The numeric address of a TCP peer, without its port: the backoff's key. A peer whose address + // cannot be read is keyed "?", so it is still counted, just not on its own. + std::string PeerAddress(int fd) { + sockaddr_storage address{}; + socklen_t size = sizeof(address); + if (::getpeername(fd, reinterpret_cast(&address), &size) != 0) return "?"; + char text[INET6_ADDRSTRLEN] = {}; + const void* raw = nullptr; + if (address.ss_family == AF_INET) + raw = &reinterpret_cast(&address)->sin_addr; + else if (address.ss_family == AF_INET6) + raw = &reinterpret_cast(&address)->sin6_addr; + if (raw == nullptr || ::inet_ntop(address.ss_family, raw, text, sizeof(text)) == nullptr) return "?"; + return text; + } + + // True when the control peer has closed (EOF or error); false while it is open, readable or not. + bool ControlPeerGone(int controlFd) { + char byte = 0; + const auto peeked = ::recv(controlFd, &byte, 1, MSG_PEEK | MSG_DONTWAIT); + return peeked == 0 || (peeked < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR); + } + + // The session child's source: descriptors the supervisor forwarded, each with the nonce its + // DataBind presented. Watching the control connection too, so a client that went away after + // Welcome ends the wait at once instead of holding the supervisor Busy for the whole deadline. + Server::ServerSession::DataConnectionSource HandoffSource(int handoff, int controlFd) { + return [handoff, controlFd](std::uint32_t timeoutMs, int* outFd, std::uint8_t* outNonce) -> MobileGLResult { + pollfd fds[2] = {{handoff, POLLIN, 0}, {controlFd, POLLIN, 0}}; + // A control connection with bytes waiting is not a hang-up and must not spin this loop: + // after the first look it is only asked again once per slice. + const int rc = ::poll(fds, 2, static_cast(std::min(timeoutMs, 50))); + if (rc < 0) return errno == EINTR ? MOBILEGL_ERR_TIMEOUT : MOBILEGL_ERR_TRANSPORT_CLOSED; + if ((fds[1].revents & (POLLIN | POLLHUP | POLLERR)) != 0 && ControlPeerGone(controlFd)) + return MOBILEGL_ERR_TRANSPORT_CLOSED; + if ((fds[0].revents & (POLLIN | POLLHUP | POLLERR)) == 0) { + if ((fds[1].revents & POLLIN) != 0) { + pollfd only{handoff, POLLIN, 0}; + if (::poll(&only, 1, static_cast(std::min(timeoutMs, 50))) <= 0) + return MOBILEGL_ERR_TIMEOUT; + } else { return MOBILEGL_ERR_TIMEOUT; - } else { + } + } + std::uint8_t sideband[FdPassing::kMaxSidebandBytes] = {}; + std::uint64_t size = 0; + const auto received = + FdPassing::ReceiveFd(handoff, outFd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &size, 0); + if (received != MOBILEGL_OK) return received; + // The supervisor always sends exactly the presented nonce; a short sideband compares as + // zeros, which no minted nonce is going to equal by accident. + std::memcpy(outNonce, sideband, kDataNonceBytes); + return MOBILEGL_OK; + }; + } + + // The single-session (no --serve) source: this process still owns the listener, so it accepts + // the data connection itself and reads its DataBind the same way the supervisor would. + Server::ServerSession::DataConnectionSource ListenerSource(int listener, int controlFd) { + return [listener, controlFd](std::uint32_t timeoutMs, int* outFd, std::uint8_t* outNonce) -> MobileGLResult { + if (ControlPeerGone(controlFd)) return MOBILEGL_ERR_TRANSPORT_CLOSED; + int fd = -1; + const auto accepted = SocketTransport::AcceptOne(listener, std::min(timeoutMs, 250), &fd); + if (accepted != MOBILEGL_OK) return accepted; + std::vector frame; + std::uint8_t presented[kDataNonceBytes] = {}; + const auto read = SocketTransport::ReceiveOneFrame(fd, kFirstFrameWaitMs, kFirstFrameMaxBytes, &frame); + if (read != MOBILEGL_OK || !DecodeDataBind(frame, presented)) { + SocketTransport stray(fd, -1, TransportRole::Server); + Refuse(stray, Protocol::RefuseCode::Authentication, "data connection's first frame is not a DataBind"); return MOBILEGL_ERR_TIMEOUT; } - } - std::uint8_t sideband[FdPassing::kMaxSidebandBytes] = {}; - std::uint64_t size = 0; - const auto received = FdPassing::ReceiveFd(handoff, outFd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, - &size, 0); - if (received != MOBILEGL_OK) return received; - // The supervisor always sends exactly the presented nonce; a short sideband compares as - // zeros, which no minted nonce is going to equal by accident. - std::memcpy(outNonce, sideband, kDataNonceBytes); - return MOBILEGL_OK; - }; -} - -// The single-session (no --serve) source: this process still owns the listener, so it accepts -// the data connection itself and reads its DataBind the same way the supervisor would. -Server::ServerSession::DataConnectionSource ListenerSource(int listener, int controlFd) { - return [listener, controlFd](std::uint32_t timeoutMs, int* outFd, std::uint8_t* outNonce) -> MobileGLResult { - if (ControlPeerGone(controlFd)) return MOBILEGL_ERR_TRANSPORT_CLOSED; - int fd = -1; - const auto accepted = SocketTransport::AcceptOne(listener, std::min(timeoutMs, 250), &fd); - if (accepted != MOBILEGL_OK) return accepted; - std::vector frame; - std::uint8_t presented[kDataNonceBytes] = {}; - const auto read = SocketTransport::ReceiveOneFrame(fd, kFirstFrameWaitMs, kFirstFrameMaxBytes, &frame); - if (read != MOBILEGL_OK || !DecodeDataBind(frame, presented)) { - SocketTransport stray(fd, -1, TransportRole::Server); - Refuse(stray, Protocol::RefuseCode::Authentication, "data connection's first frame is not a DataBind"); - return MOBILEGL_ERR_TIMEOUT; - } - std::memcpy(outNonce, presented, kDataNonceBytes); - *outFd = fd; - return MOBILEGL_OK; - }; -} - -// `forward` is the session's log forward when there is one (P9 W3). The ack then goes out THROUGH it, -// queued behind every line forwarded before the flush, because a forwarded line is no longer on the -// wire when its MGLOG returns - sending the ack directly could overtake lines still queued, and -// MGPipeSyncPeerLog's promise is that they are in the client's file when the ack arrives. -struct FlushAck { ITransport* transport; Transport::LogForwardChannel* forward; std::uint64_t seq; }; -void SendLogAck(void* pointer) { - auto& ack = *static_cast(pointer); - flatbuffers::FlatBufferBuilder builder(64); - auto flush = Protocol::CreateLogFlush(builder, ack.seq, true); - auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogFlush, flush.Union()); - Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); - if (ack.forward != nullptr) { - ack.forward->OfferControlFrame( - std::vector(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize())); - return; + std::memcpy(outNonce, presented, kDataNonceBytes); + *outFd = fd; + return MOBILEGL_OK; + }; } - (void)ack.transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()}); -} -// What `sourceFd` is, so RunSession knows how to let go of it (ID-P7-44). -enum class DataSource { None, Handoff, Listener }; - -// ID-P7-44. THE HAND-OFF CLOSES WITHOUT A WINDOW. -// -// The F fix round closed the child's end of the hand-off the moment Accept returned, so that a -// DataBind arriving after the session bound fails to send and the supervisor refuses it by name. -// Between BindDataConnection taking the matching descriptor and that close, though, the -// supervisor could still queue one more - and a close with datagrams queued drops their -// descriptors in the kernel: the peer saw its connection end with no frame at all. So the end is -// cut for reading FIRST (after shutdown(SHUT_RD) every further sendmsg to it fails with EPIPE, which -// FdPassing reports as "peer gone" and the supervisor answers by name), and then whatever was -// queued before the cut is received here and refused with the supervisor's own words. No -// DataBind can be in neither place - on the way out after Accept here, and on every way out -// before it through ExitBeforeAccept below; only a crashed child still drops them. -void CloseHandoffRefusingQueued(int handoff) { - (void)::shutdown(handoff, SHUT_RD); - for (;;) { - int fd = -1; - std::uint8_t sideband[FdPassing::kMaxSidebandBytes] = {}; - std::uint64_t size = 0; - if (FdPassing::ReceiveFd(handoff, &fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &size, 0) != - MOBILEGL_OK || - fd < 0) - break; - RefuseAndClose(fd, Protocol::RefuseCode::Authentication, kHandoffRefused); + // `forward` is the session's log forward when there is one (P9 W3). The ack then goes out THROUGH it, + // queued behind every line forwarded before the flush, because a forwarded line is no longer on the + // wire when its MGLOG returns - sending the ack directly could overtake lines still queued, and + // MGPipeSyncPeerLog's promise is that they are in the client's file when the ack arrives. + struct FlushAck { + ITransport* transport; + Transport::LogForwardChannel* forward; + std::uint64_t seq; + }; + void SendLogAck(void* pointer) { + auto& ack = *static_cast(pointer); + flatbuffers::FlatBufferBuilder builder(64); + auto flush = Protocol::CreateLogFlush(builder, ack.seq, true); + auto envelope = Protocol::CreateCtrlEnvelope(builder, Protocol::CtrlMsg::LogFlush, flush.Union()); + Protocol::FinishCtrlEnvelopeBuffer(builder, envelope); + if (ack.forward != nullptr) { + ack.forward->OfferControlFrame( + std::vector(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize())); + return; + } + (void)ack.transport->SendFrame({builder.GetBufferPointer(), builder.GetSize()}); } - ::close(handoff); -} -// ID-P7-44, f2-auth fix round. EVERY WAY OUT OF A SESSION CHILD BEFORE ACCEPT LETS GO OF THE -// HAND-OFF THE WAY THE WAY OUT AFTER IT DOES. The supervisor forwards DataBinds to a child from the -// moment it forks it, so a child that refused its Hello at ValidatePeerHandshake, refused its -// backend, or could not bring one up used to `_exit` with DataBinds queued on the hand-off - and an -// exit drops queued descriptors exactly as a close does: those peers saw their connections end -// with no frame. On any other source (the single-session listener, the unix endpoint's none) there -// is nothing queued to refuse, and this is the `fflush` + `_exit` it replaces. -// -// P12 (D5): RunSession RETURNS its exit code now instead of `_exit`ing, so the in-process display -// server can run one session after another on a thread. This is the part of the old -// ExitBeforeAccept that belongs to the session; the `fflush` + `_exit` belong to the process and -// moved to the fork sites, which are `ExitSessionProcess(RunSession(...))` - the forked shapes end -// exactly as before. -int LeaveBeforeAccept(int sourceFd, DataSource sourceKind, int code) { - if (sourceFd >= 0 && sourceKind == DataSource::Handoff) CloseHandoffRefusingQueued(sourceFd); - return code; -} + // What `sourceFd` is, so RunSession knows how to let go of it (ID-P7-44). + enum class DataSource { + None, + Handoff, + Listener + }; -// P12 (D5). A forked session child (and the single-session shapes, whose process IS the session) -// ends here, with RunSession's code, exactly as RunSession's own `_exit`s used to end it. -[[noreturn]] void ExitSessionProcess(int code) { - std::fflush(nullptr); - ::_exit(code); -} + // ID-P7-44. THE HAND-OFF CLOSES WITHOUT A WINDOW. + // + // The F fix round closed the child's end of the hand-off the moment Accept returned, so that a + // DataBind arriving after the session bound fails to send and the supervisor refuses it by name. + // Between BindDataConnection taking the matching descriptor and that close, though, the + // supervisor could still queue one more - and a close with datagrams queued drops their + // descriptors in the kernel: the peer saw its connection end with no frame at all. So the end is + // cut for reading FIRST (after shutdown(SHUT_RD) every further sendmsg to it fails with EPIPE, which + // FdPassing reports as "peer gone" and the supervisor answers by name), and then whatever was + // queued before the cut is received here and refused with the supervisor's own words. No + // DataBind can be in neither place - on the way out after Accept here, and on every way out + // before it through ExitBeforeAccept below; only a crashed child still drops them. + void CloseHandoffRefusingQueued(int handoff) { + (void)::shutdown(handoff, SHUT_RD); + for (;;) { + int fd = -1; + std::uint8_t sideband[FdPassing::kMaxSidebandBytes] = {}; + std::uint64_t size = 0; + if (FdPassing::ReceiveFd(handoff, &fd, MobileGLMutableByteSpan{sideband, sizeof(sideband)}, &size, 0) != + MOBILEGL_OK || + fd < 0) + break; + RefuseAndClose(fd, Protocol::RefuseCode::Authentication, kHandoffRefused); + } + ::close(handoff); + } -// P12 (D8). A FORKED SESSION CHILD DIES WITH ITS SUPERVISOR. The child used to outlive a supervisor -// that was killed (MobileGLServerService.onDestroy destroys only the supervisor), holding its client -// and its GPU context until that client went away (docs/archive/notes/p65/code-review-findings.md:9). With the -// parent-death signal the kernel SIGKILLs it the moment the supervisor goes - and the getppid() -// check closes the race of a supervisor that died between fork() and the prctl. Linux (and Android) -// only; the signal fires when the THREAD that forked exits, and both fork sites run on the -// supervisor's main thread, which is the process. -void DieWithSupervisor(pid_t supervisor) { + // ID-P7-44, f2-auth fix round. EVERY WAY OUT OF A SESSION CHILD BEFORE ACCEPT LETS GO OF THE + // HAND-OFF THE WAY THE WAY OUT AFTER IT DOES. The supervisor forwards DataBinds to a child from the + // moment it forks it, so a child that refused its Hello at ValidatePeerHandshake, refused its + // backend, or could not bring one up used to `_exit` with DataBinds queued on the hand-off - and an + // exit drops queued descriptors exactly as a close does: those peers saw their connections end + // with no frame. On any other source (the single-session listener, the unix endpoint's none) there + // is nothing queued to refuse, and this is the `fflush` + `_exit` it replaces. + // + // P12 (D5): RunSession RETURNS its exit code now instead of `_exit`ing, so the in-process display + // server can run one session after another on a thread. This is the part of the old + // ExitBeforeAccept that belongs to the session; the `fflush` + `_exit` belong to the process and + // moved to the fork sites, which are `ExitSessionProcess(RunSession(...))` - the forked shapes end + // exactly as before. + int LeaveBeforeAccept(int sourceFd, DataSource sourceKind, int code) { + if (sourceFd >= 0 && sourceKind == DataSource::Handoff) CloseHandoffRefusingQueued(sourceFd); + return code; + } + + // P12 (D5). A forked session child (and the single-session shapes, whose process IS the session) + // ends here, with RunSession's code, exactly as RunSession's own `_exit`s used to end it. + [[noreturn]] void ExitSessionProcess(int code) { + std::fflush(nullptr); + ::_exit(code); + } + + // P12 (D8). A FORKED SESSION CHILD DIES WITH ITS SUPERVISOR. The child used to outlive a supervisor + // that was killed (MobileGLServerService.onDestroy destroys only the supervisor), holding its client + // and its GPU context until that client went away (docs/archive/notes/p65/code-review-findings.md:9). With the + // parent-death signal the kernel SIGKILLs it the moment the supervisor goes - and the getppid() + // check closes the race of a supervisor that died between fork() and the prctl. Linux (and Android) + // only; the signal fires when the THREAD that forked exits, and both fork sites run on the + // supervisor's main thread, which is the process. + void DieWithSupervisor(pid_t supervisor) { #if defined(__linux__) - (void)::prctl(PR_SET_PDEATHSIG, SIGKILL); - if (::getppid() != supervisor) ::_exit(0); + (void)::prctl(PR_SET_PDEATHSIG, SIGKILL); + if (::getppid() != supervisor) ::_exit(0); #else - (void)supervisor; + (void)supervisor; #endif -} + } -// P12 (D5): THE IN-PROCESS DISPLAY SERVER'S PROCESS-WIDE STATE. One server per process, sessions -// one at a time (TcpSupervisor's thread hand-off). `g_inProcessStop` is raised by -// mobilegl_server_stop_inprocess and read by the supervisor's loop and by a live session's control -// loop; `g_inProcessBackendPin` is the backend the first session chose when no MOBILEGL_BACKEND_TYPE -// pins one - Espryt and Magma never share a process (BenchService.java's rule), so a later session -// asking for the other is refused like a pinned-backend mismatch. -std::atomic g_inProcessServing{false}; -std::atomic g_inProcessStop{false}; -std::atomic g_inProcessBackendPin{-1}; + // P12 (D5): THE IN-PROCESS DISPLAY SERVER'S PROCESS-WIDE STATE. One server per process, sessions + // one at a time (TcpSupervisor's thread hand-off). `g_inProcessStop` is raised by + // mobilegl_server_stop_inprocess and read by the supervisor's loop and by a live session's control + // loop; `g_inProcessBackendPin` is the backend the first session chose when no MOBILEGL_BACKEND_TYPE + // pins one - Espryt and Magma never share a process (BenchService.java's rule), so a later session + // asking for the other is refused like a pinned-backend mismatch. + std::atomic g_inProcessServing{false}; + std::atomic g_inProcessStop{false}; + std::atomic g_inProcessBackendPin{-1}; -// A TEST-ONLY lever (f2-auth fix round, ID-P7-44): MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS holds a -// session child for that many milliseconds before it looks at its Hello, so a control can queue -// DataBinds on the hand-off of a child that is about to refuse that Hello - a window of -// microseconds otherwise. Like MOBILEGL_TEST_DELAY_FIRST_CAPS_MS (ServerSession.cpp) it exists only -// in the disaggregated server, does nothing unless set, and says so when it is. -void DelaySessionHandshakeForTest() { - const char* text = std::getenv("MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS"); - if (text == nullptr || text[0] == 0) return; - const long ms = std::strtol(text, nullptr, 10); - if (ms <= 0) return; - WireLogError("MG_Remote server: pid=%d MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS=%ld - this session child " - "waits that long before its handshake. A test-only lever; never set it in a measured run", - static_cast(::getpid()), ms); - std::this_thread::sleep_for(std::chrono::milliseconds(std::min(ms, 60000))); -} + // A TEST-ONLY lever (f2-auth fix round, ID-P7-44): MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS holds a + // session child for that many milliseconds before it looks at its Hello, so a control can queue + // DataBinds on the hand-off of a child that is about to refuse that Hello - a window of + // microseconds otherwise. Like MOBILEGL_TEST_DELAY_FIRST_CAPS_MS (ServerSession.cpp) it exists only + // in the disaggregated server, does nothing unless set, and says so when it is. + void DelaySessionHandshakeForTest() { + const char* text = std::getenv("MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS"); + if (text == nullptr || text[0] == 0) return; + const long ms = std::strtol(text, nullptr, 10); + if (ms <= 0) return; + WireLogError("MG_Remote server: pid=%d MOBILEGL_TEST_DELAY_SESSION_HANDSHAKE_MS=%ld - this session child " + "waits that long before its handshake. A test-only lever; never set it in a measured run", + static_cast(::getpid()), ms); + std::this_thread::sleep_for(std::chrono::milliseconds(std::min(ms, 60000))); + } -// Runs one served session; the supervisor never creates a backend or EGL context. `dataSource` is -// set on TCP and empty on a unix endpoint, whose aux connection PairAcceptor already paired. -// `sourceFd` (the hand-off socketpair end under --serve, the listener in the single-session shape, -// or -1) is released when Accept returns and on every earlier exit (LeaveBeforeAccept). -// `helloFrame` holds the first frame if the caller already read and authenticated it; otherwise it -// is read here within `helloWaitMs`. Returns the session's exit code and never exits; with -// `inProcess` (a session thread of the in-process display server) every way out cleans up by hand -// what a process exit would, and the supervisor resets the latch between sessions. -int RunSession(std::unique_ptr control, std::vector helloFrame, - Server::ServerSession::DataConnectionSource dataSource, int sourceFd, DataSource sourceKind, - std::uint32_t helloWaitMs, bool inProcess = false) { - const int selfPid = static_cast(::getpid()); - const bool tcp = control->IsTcp(); - if (helloFrame.empty()) { - // Every way this read can go wrong is the PEER's (silence, a stream that is not ours, a - // close), so each is answered by name where the peer is still there to read it and is a - // clean exit rather than a counted fault (PH-7 (5)). - std::uint64_t bytes = 0; - const auto received = control->ReceiveFrame({nullptr, 0}, &bytes, helloWaitMs); - if (received == MOBILEGL_ERR_TIMEOUT) { - Refuse(*control, Protocol::RefuseCode::Authentication, Server::kPreAuthDeadline); - return LeaveBeforeAccept(sourceFd, sourceKind, 0); - } - if (received == MOBILEGL_ERR_PROTOCOL_MISMATCH || - (received == MOBILEGL_ERR_BUFFER_TOO_SMALL && bytes > Server::kPreAuthFirstFrameMaxBytes)) { - DrainUnread(control->StreamFd()); - Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameNotAFrame); - return LeaveBeforeAccept(sourceFd, sourceKind, 0); - } - if (received == MOBILEGL_ERR_TRANSPORT_CLOSED) { - // Fix round: this used to be a bare `_exit(0)` whatever had arrived. A close INSIDE - // the frame is answered by name, as the TCP supervisor answers it (the peer may have - // closed only its write side); a close before any byte has nobody to answer, and the - // log says which of the two it was. - if (control->BufferedBytes() != 0) - Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameTruncated); - else - WireLogError("MG_Remote server: pid=%d control peer closed before sending a first frame", selfPid); - return LeaveBeforeAccept(sourceFd, sourceKind, 0); - } - if (received == MOBILEGL_ERR_BUFFER_TOO_SMALL) { - // The reassembler holds the whole frame already; this second call copies it out. - helloFrame.resize(static_cast(bytes)); - if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) { - WireLogError("MG_Remote server: pid=%d first frame of %llu bytes could not be copied out", selfPid, - static_cast(helloFrame.size())); + // Runs one served session; the supervisor never creates a backend or EGL context. `dataSource` is + // set on TCP and empty on a unix endpoint, whose aux connection PairAcceptor already paired. + // `sourceFd` (the hand-off socketpair end under --serve, the listener in the single-session shape, + // or -1) is released when Accept returns and on every earlier exit (LeaveBeforeAccept). + // `helloFrame` holds the first frame if the caller already read and authenticated it; otherwise it + // is read here within `helloWaitMs`. Returns the session's exit code and never exits; with + // `inProcess` (a session thread of the in-process display server) every way out cleans up by hand + // what a process exit would, and the supervisor resets the latch between sessions. + int RunSession(std::unique_ptr control, std::vector helloFrame, + Server::ServerSession::DataConnectionSource dataSource, int sourceFd, DataSource sourceKind, + std::uint32_t helloWaitMs, bool inProcess = false) { + const int selfPid = static_cast(::getpid()); + const bool tcp = control->IsTcp(); + if (helloFrame.empty()) { + // Every way this read can go wrong is the PEER's (silence, a stream that is not ours, a + // close), so each is answered by name where the peer is still there to read it and is a + // clean exit rather than a counted fault (PH-7 (5)). + std::uint64_t bytes = 0; + const auto received = control->ReceiveFrame({nullptr, 0}, &bytes, helloWaitMs); + if (received == MOBILEGL_ERR_TIMEOUT) { + Refuse(*control, Protocol::RefuseCode::Authentication, Server::kPreAuthDeadline); return LeaveBeforeAccept(sourceFd, sourceKind, 0); } - } else if (received != MOBILEGL_OK) { - WireLogError("MG_Remote server: pid=%d first frame could not be read (rc=%d)", selfPid, - static_cast(received)); + if (received == MOBILEGL_ERR_PROTOCOL_MISMATCH || + (received == MOBILEGL_ERR_BUFFER_TOO_SMALL && bytes > Server::kPreAuthFirstFrameMaxBytes)) { + DrainUnread(control->StreamFd()); + Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameNotAFrame); + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + } + if (received == MOBILEGL_ERR_TRANSPORT_CLOSED) { + // Fix round: this used to be a bare `_exit(0)` whatever had arrived. A close INSIDE + // the frame is answered by name, as the TCP supervisor answers it (the peer may have + // closed only its write side); a close before any byte has nobody to answer, and the + // log says which of the two it was. + if (control->BufferedBytes() != 0) + Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameTruncated); + else + WireLogError("MG_Remote server: pid=%d control peer closed before sending a first frame", selfPid); + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + } + if (received == MOBILEGL_ERR_BUFFER_TOO_SMALL) { + // The reassembler holds the whole frame already; this second call copies it out. + helloFrame.resize(static_cast(bytes)); + if (control->ReceiveFrame({helloFrame.data(), helloFrame.size()}, &bytes, 0) != MOBILEGL_OK) { + WireLogError("MG_Remote server: pid=%d first frame of %llu bytes could not be copied out", selfPid, + static_cast(helloFrame.size())); + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + } + } else if (received != MOBILEGL_OK) { + WireLogError("MG_Remote server: pid=%d first frame could not be read (rc=%d)", selfPid, + static_cast(received)); + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + } + // MOBILEGL_OK with nothing copied is a ZERO-LENGTH frame. It was the one complete frame + // this read exited on without a word (fix round); `helloFrame` stays empty and the + // classification below names it, as the TCP supervisor's does ("no identifier"). + } + DelaySessionHandshakeForTest(); + // THE FIRST FRAME, CLASSIFIED BY THE SAME FUNCTION THE TCP SUPERVISOR USES (plan §1.1, fuzz + // arm 1 row: add CtrlEnvelopeBufferHasIdentifier to this first-frame read). The identifier is asked + // first, and every malformed shape is refused BY NAME - this read used to `_exit(67)` on all + // of them, so a peer that sent garbage got a bare close and the supervisor counted a fault. + const auto shape = Server::ClassifyFirstFrame(helloFrame.data(), helloFrame.size()); + // PH-7 (4). A DataBind with no live session to join: the supervisor routes a data connection + // to a child only while one is live, so this is a stale connection (its session already + // ended) or one that never had a session. Refused by name, and the child is a clean exit - + // it is a refusal, not a fault. (On TCP --serve the supervisor answers this itself now; this + // arm is the single-session shape's.) + if (shape == Server::FirstFrameShape::DataBind) { + Refuse(*control, Protocol::RefuseCode::Authentication, "data connection names no live session"); return LeaveBeforeAccept(sourceFd, sourceKind, 0); } - // MOBILEGL_OK with nothing copied is a ZERO-LENGTH frame. It was the one complete frame - // this read exited on without a word (fix round); `helloFrame` stays empty and the - // classification below names it, as the TCP supervisor's does ("no identifier"). - } - DelaySessionHandshakeForTest(); - // THE FIRST FRAME, CLASSIFIED BY THE SAME FUNCTION THE TCP SUPERVISOR USES (plan §1.1, fuzz - // arm 1 row: add CtrlEnvelopeBufferHasIdentifier to this first-frame read). The identifier is asked - // first, and every malformed shape is refused BY NAME - this read used to `_exit(67)` on all - // of them, so a peer that sent garbage got a bare close and the supervisor counted a fault. - const auto shape = Server::ClassifyFirstFrame(helloFrame.data(), helloFrame.size()); - // PH-7 (4). A DataBind with no live session to join: the supervisor routes a data connection - // to a child only while one is live, so this is a stale connection (its session already - // ended) or one that never had a session. Refused by name, and the child is a clean exit - - // it is a refusal, not a fault. (On TCP --serve the supervisor answers this itself now; this - // arm is the single-session shape's.) - if (shape == Server::FirstFrameShape::DataBind) { - Refuse(*control, Protocol::RefuseCode::Authentication, "data connection names no live session"); - return LeaveBeforeAccept(sourceFd, sourceKind, 0); - } - if (shape != Server::FirstFrameShape::Hello) { - Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::FirstFrameRefusalDetail(shape)); - return LeaveBeforeAccept(sourceFd, sourceKind, 0); - } - const auto* hello = Protocol::GetCtrlEnvelope(helloFrame.data())->msg_as_Hello(); - // PH-7 (1). The SAME policy ServerSession::Accept applies, from Handshake.h, so the - // supervisor's pre-fork answer and the session's post-fork answer cannot drift. What went: - // a `std::string` comparison (byte-at-a-time, early-returning) that ALSO refused a peer for - // presenting a token to a server that had configured none, and a `tcp` guard that made the - // unix control socket exempt by construction rather than by the policy's own reasoning. - // RefuseHandshake logs `Refuse{Authentication}` and sends the peer the frame; the supervisor's - // local Refuse() helper stays for the refusals that are its own (Busy, backend). - // - // PH-7 (5) (ph-f.md §6.4 (b)): the token is asked FIRST. ValidatePeerHandshake used to run - // before it, so an unauthenticated peer with a wrong fingerprint was answered - // Refuse{WireFingerprint} carrying this build's wire fingerprint - and Refuse{BuildFingerprint} - // this build's stamp. An unauthenticated peer now learns only Refuse{Authentication}. On TCP - // --serve the supervisor has already asked (the answer is the same; it is asked again so this - // function holds the rule on its own for the unix and single-session shapes). - if (AuthenticatePeerToken(*control, hello->token()) != MOBILEGL_OK) return LeaveBeforeAccept(sourceFd, sourceKind, 0); - // Reject incompatible wire before backend bring-up can obscure the cause. - if (ValidatePeerHandshake(*control, hello->abiMajor(), hello->abiMinor(), - hello->wireFingerprint(), hello->buildFingerprint() ? hello->buildFingerprint()->c_str() : nullptr, - hello->dialMode()) != MOBILEGL_OK) return LeaveBeforeAccept(sourceFd, sourceKind, 0); - // ONCE PER PROCESS, NOT PER SESSION. The configuration is the process's environment, the same - // for every session it serves, and re-reading it is not a no-op: Init() rewrites the globals - // in place - Transport first falls back to Monolith before the line below puts Spawn back - so - // a second session accepted while the first is applying showed that session's apply thread a - // monolith process for a moment. It then skipped the server stamp on a draw and died on - // Fatal{UnmigratedPipeInput}, taking every session in the process with it. - static std::once_flag s_serverConfigured; - std::call_once(s_serverConfigured, [] { - MobileGL::MG_ConfigLoader::Init(); - MobileGL::MG_Config::Transport = MobileGL::MG_Config::TransportMode::Spawn; - MobileGL::MG_Pipe::MGPipeSetServerProcessRole(true); - }); - // A served session's process (or in-process thread) is the session, so a named fault on the - // peer's bytes may end it cleanly instead of aborting (FatalFunnel.h's latch); inproc keeps - // every Fatal, and the supervisor applies no record. SessionRuntime holds this session's - // session object, apply loop and PipeInputs block; the scope binds this thread's - // ServerSessionInstance(), ServerLoopInstance(), gPipeInputs, latch domain and segment - // resolver to it, so ArmSessionLatch arms this session's domain only. The apply thread opens - // the same scope first (ServerLoop::ApplyThreadMain). - auto runtime = std::make_unique(); - Server::Detail::ThreadSessionScope threadScope(*runtime); - ArmSessionLatch(); - // The child bypasses MobileGL::Initialize: arm its own role counters. - MobileGL::MG_Util::PipeStats::Init(); - auto& session = runtime->Session(); - auto& loop = runtime->Loop(); - // P12 (D5): WHAT A PROCESS EXIT CLEANED UP FOR FREE, THE IN-PROCESS SHAPE CLEANS UP BY HAND, - // on every way out from here - so the next session in this process starts where a fresh - // session child would. A no-op for every forked / single-session shape: those `_exit` with the - // code, exactly as before. - bool backendBuilt = false; - bool acceptTried = false; - // P9 W3: the TCP session's log forward (LogForward.h), made where forwarding is switched on below. - // Declared up here because every way out has to take it down the same way: the logger stops - // calling it FIRST (so no line is offered to a channel being destroyed), then the channel drains - // within its close budget and its sender is joined, then its counters are said locally. Every - // shape, forked or not: a forked child's early return destroys the channel with this frame, and a - // forwarder left pointing at it would be a dangling pointer for however many lines remain. - std::unique_ptr logForward; - const auto stopLogForwarding = [&] { - MobileGL::MG_Util::Debug::SetLogForwarder(nullptr, nullptr); - if (!logForward) return; - const auto counters = logForward->Close(); - logForward.reset(); - LogForwardSummary(counters); - }; - const auto endSession = [&](int code) -> int { - stopLogForwarding(); - if (!inProcess) return code; - // Points at `control`, which dies with this frame (the log forwarder did too, above). - loop.SetControlProgressSink(nullptr, nullptr); - if (backendBuilt) { - // Stop() joins a running apply thread (it destroys the backend on it), or - never - // started - drops the backend here; either way the display lease goes with it. - if (g_inProcessStop.load(std::memory_order_acquire)) loop.AbandonQueuedRecords(); - loop.Stop(); - session.SetBackend(nullptr); + if (shape != Server::FirstFrameShape::Hello) { + Refuse(*control, Protocol::RefuseCode::MalformedHello, Server::FirstFrameRefusalDetail(shape)); + return LeaveBeforeAccept(sourceFd, sourceKind, 0); } - if (acceptTried) session.Close(); - MobileGL::MG_Util::PipeStats::Shutdown(); - return code; - }; - const auto backend = static_cast(hello->backendType()); - if (backend != MobileGL::BackendType::DirectGLES && backend != MobileGL::BackendType::DirectVulkan) { - Refuse(*control, Protocol::RefuseCode::Backend, "unsupported Hello.backendType"); - return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 0)); - } - const char* pinned = std::getenv("MOBILEGL_BACKEND_TYPE"); - if (pinned && *pinned && backend != MobileGL::MG_Config::ActiveBackendType) { - Refuse(*control, Protocol::RefuseCode::Backend, "Hello.backendType disagrees with pinned backend"); - return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 0)); - } - if (inProcess) { - // P12 (D5): THE BACKEND IS PINNED FOR THE PROCESS'S LIFETIME. With no MOBILEGL_BACKEND_TYPE - // the first session's backend becomes the pin: Espryt and Magma must never share a process - // (BenchService.java:12-17), and here every session shares this one. The refusal is the - // pinned-backend refusal above, word for word. - int pin = g_inProcessBackendPin.load(std::memory_order_acquire); - if (pin < 0 && g_inProcessBackendPin.compare_exchange_strong(pin, static_cast(backend), - std::memory_order_acq_rel)) { - // Which pin it is, said as it is: the environment's (the display Activity's `backend` - // extra, already enforced by the refusal above) or the first session's. - WireLogError("MG_Remote server: pid=%d in-process display server pinned backend type %u for the " - "process lifetime (%s)", - selfPid, static_cast(backend), - pinned && *pinned ? "MOBILEGL_BACKEND_TYPE" : "the first session's; MOBILEGL_BACKEND_TYPE unset"); - pin = static_cast(backend); + const auto* hello = Protocol::GetCtrlEnvelope(helloFrame.data())->msg_as_Hello(); + // PH-7 (1). The SAME policy ServerSession::Accept applies, from Handshake.h, so the + // supervisor's pre-fork answer and the session's post-fork answer cannot drift. What went: + // a `std::string` comparison (byte-at-a-time, early-returning) that ALSO refused a peer for + // presenting a token to a server that had configured none, and a `tcp` guard that made the + // unix control socket exempt by construction rather than by the policy's own reasoning. + // RefuseHandshake logs `Refuse{Authentication}` and sends the peer the frame; the supervisor's + // local Refuse() helper stays for the refusals that are its own (Busy, backend). + // + // PH-7 (5) (ph-f.md §6.4 (b)): the token is asked FIRST. ValidatePeerHandshake used to run + // before it, so an unauthenticated peer with a wrong fingerprint was answered + // Refuse{WireFingerprint} carrying this build's wire fingerprint - and Refuse{BuildFingerprint} + // this build's stamp. An unauthenticated peer now learns only Refuse{Authentication}. On TCP + // --serve the supervisor has already asked (the answer is the same; it is asked again so this + // function holds the rule on its own for the unix and single-session shapes). + if (AuthenticatePeerToken(*control, hello->token()) != MOBILEGL_OK) + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + // Reject incompatible wire before backend bring-up can obscure the cause. + if (ValidatePeerHandshake(*control, hello->abiMajor(), hello->abiMinor(), hello->wireFingerprint(), + hello->buildFingerprint() ? hello->buildFingerprint()->c_str() : nullptr, + hello->dialMode()) != MOBILEGL_OK) + return LeaveBeforeAccept(sourceFd, sourceKind, 0); + // ONCE PER PROCESS, NOT PER SESSION. The configuration is the process's environment, the same + // for every session it serves, and re-reading it is not a no-op: Init() rewrites the globals + // in place - Transport first falls back to Monolith before the line below puts Spawn back - so + // a second session accepted while the first is applying showed that session's apply thread a + // monolith process for a moment. It then skipped the server stamp on a draw and died on + // Fatal{UnmigratedPipeInput}, taking every session in the process with it. + static std::once_flag s_serverConfigured; + std::call_once(s_serverConfigured, [] { + MobileGL::MG_ConfigLoader::Init(); + MobileGL::MG_Config::Transport = MobileGL::MG_Config::TransportMode::Spawn; + MobileGL::MG_Pipe::MGPipeSetServerProcessRole(true); + }); + // A served session's process (or in-process thread) is the session, so a named fault on the + // peer's bytes may end it cleanly instead of aborting (FatalFunnel.h's latch); inproc keeps + // every Fatal, and the supervisor applies no record. SessionRuntime holds this session's + // session object, apply loop and PipeInputs block; the scope binds this thread's + // ServerSessionInstance(), ServerLoopInstance(), gPipeInputs, latch domain and segment + // resolver to it, so ArmSessionLatch arms this session's domain only. The apply thread opens + // the same scope first (ServerLoop::ApplyThreadMain). + auto runtime = std::make_unique(); + Server::Detail::ThreadSessionScope threadScope(*runtime); + ArmSessionLatch(); + // The child bypasses MobileGL::Initialize: arm its own role counters. + MobileGL::MG_Util::PipeStats::Init(); + auto& session = runtime->Session(); + auto& loop = runtime->Loop(); + // P12 (D5): WHAT A PROCESS EXIT CLEANED UP FOR FREE, THE IN-PROCESS SHAPE CLEANS UP BY HAND, + // on every way out from here - so the next session in this process starts where a fresh + // session child would. A no-op for every forked / single-session shape: those `_exit` with the + // code, exactly as before. + bool backendBuilt = false; + bool acceptTried = false; + // P9 W3: the TCP session's log forward (LogForward.h), made where forwarding is switched on below. + // Declared up here because every way out has to take it down the same way: the logger stops + // calling it FIRST (so no line is offered to a channel being destroyed), then the channel drains + // within its close budget and its sender is joined, then its counters are said locally. Every + // shape, forked or not: a forked child's early return destroys the channel with this frame, and a + // forwarder left pointing at it would be a dangling pointer for however many lines remain. + std::unique_ptr logForward; + const auto stopLogForwarding = [&] { + MobileGL::MG_Util::Debug::SetLogForwarder(nullptr, nullptr); + if (!logForward) return; + const auto counters = logForward->Close(); + logForward.reset(); + LogForwardSummary(counters); + }; + const auto endSession = [&](int code) -> int { + stopLogForwarding(); + if (!inProcess) return code; + // Points at `control`, which dies with this frame (the log forwarder did too, above). + loop.SetControlProgressSink(nullptr, nullptr); + if (backendBuilt) { + // Stop() joins a running apply thread (it destroys the backend on it), or - never + // started - drops the backend here; either way the display lease goes with it. + if (g_inProcessStop.load(std::memory_order_acquire)) loop.AbandonQueuedRecords(); + loop.Stop(); + session.SetBackend(nullptr); + } + if (acceptTried) session.Close(); + MobileGL::MG_Util::PipeStats::Shutdown(); + return code; + }; + const auto backend = static_cast(hello->backendType()); + if (backend != MobileGL::BackendType::DirectGLES && backend != MobileGL::BackendType::DirectVulkan) { + Refuse(*control, Protocol::RefuseCode::Backend, "unsupported Hello.backendType"); + return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 0)); } - if (pin != static_cast(backend)) { + const char* pinned = std::getenv("MOBILEGL_BACKEND_TYPE"); + if (pinned && *pinned && backend != MobileGL::MG_Config::ActiveBackendType) { Refuse(*control, Protocol::RefuseCode::Backend, "Hello.backendType disagrees with pinned backend"); return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 0)); } - } - MobileGL::MG_Config::ActiveBackendType = backend; - backendBuilt = true; // InitServerRoleForSpawn may build it and then fail: Stop() drops either way - if (!MobileGL::MG_Backend::InitServerRoleForSpawn()) return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 66)); - int serverBell[2] = {-1, -1}, clientBell[2] = {-1, -1}; - std::unique_ptr selfBell, peerBell; - if (!tcp) { - if (::socketpair(AF_UNIX, SOCK_STREAM, 0, serverBell) != 0 || - ::socketpair(AF_UNIX, SOCK_STREAM, 0, clientBell) != 0) - return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 74)); - selfBell = std::make_unique(serverBell[0], serverBell[1], 1, true); - peerBell = std::make_unique(-1, clientBell[1], 1, true); - session.SetExternalDoorbells(selfBell.get(), peerBell.get()); - } - if (tcp) session.SetDataConnectionSource(std::move(dataSource)); - acceptTried = true; - const auto accepted = session.Accept(*control, &helloFrame); - // THE SOURCE IS DONE WHEN ACCEPT IS, whichever way Accept went (F fix round). Under --serve - // this is the child's end of the hand-off pair: closing it turns every later DataBind the - // supervisor would have queued here into a send failure the supervisor refuses by name on - // that connection ("data connection could not be handed to the live session"), instead of a - // descriptor nobody will read holding a peer's connection open and, past max_dgram_qlen, - // holding the supervisor itself. In the single-session shape it is the listener: kept open - // only until the data connection arrived on it, and closed here so a second client or a - // retry is refused at connect (ECONNREFUSED, as before PH-7 (4)) rather than completing a - // TCP handshake this process would never accept and waiting out its whole connect budget. - // The source object goes with it, so no later call can poll a descriptor number the kernel - // may have reused. The hand-off is cut before it is closed, and what was already queued on - // it is refused by name rather than dropped (ID-P7-44, CloseHandoffRefusingQueued). - if (sourceFd >= 0) { - if (sourceKind == DataSource::Handoff) CloseHandoffRefusingQueued(sourceFd); - else ::close(sourceFd); - session.SetDataConnectionSource({}); - } - if (accepted != MOBILEGL_OK) { - WireLogError("MG_Remote server: pid=%d Accept failed (rc=%d)", selfPid, static_cast(accepted)); - // A refusal and a peer that went away before its data connection bound (PH-7 (4): the - // client closed after Welcome) are outcomes, not faults; the supervisor counts only 67. - return endSession(accepted == MOBILEGL_ERR_PROTOCOL_MISMATCH || accepted == MOBILEGL_ERR_TRANSPORT_CLOSED ? 0 - : 67); - } - if (!tcp) - { - struct Sideband { - std::uint32_t slot; - std::uint64_t bytes; - }; - using Slot = SessionSegmentSlot; - static constexpr Slot kSlots[4] = {Slot::Cmd, Slot::Stage, Slot::Reply, Slot::Event}; - for (const Slot slot : kSlots) { - const std::uint32_t tag = static_cast(slot); - const int fd = session.Shm().DescriptorFor(slot); - if (fd < 0) { - std::fprintf(stderr, "MG_Remote server: pid=%d segment %u has no descriptor\n", - selfPid, tag); - return endSession(68); + if (inProcess) { + // P12 (D5): THE BACKEND IS PINNED FOR THE PROCESS'S LIFETIME. With no MOBILEGL_BACKEND_TYPE + // the first session's backend becomes the pin: Espryt and Magma must never share a process + // (BenchService.java:12-17), and here every session shares this one. The refusal is the + // pinned-backend refusal above, word for word. + int pin = g_inProcessBackendPin.load(std::memory_order_acquire); + if (pin < 0 && g_inProcessBackendPin.compare_exchange_strong(pin, static_cast(backend), + std::memory_order_acq_rel)) { + // Which pin it is, said as it is: the environment's (the display Activity's `backend` + // extra, already enforced by the refusal above) or the first session's. + WireLogError("MG_Remote server: pid=%d in-process display server pinned backend type %u for the " + "process lifetime (%s)", + selfPid, static_cast(backend), + pinned && *pinned ? "MOBILEGL_BACKEND_TYPE" + : "the first session's; MOBILEGL_BACKEND_TYPE unset"); + pin = static_cast(backend); } - Sideband sideband{tag, session.Shm().AnnouncedSize(slot)}; - if (control->ShareFd(fd, MobileGLByteSpan{&sideband, sizeof(sideband)}) != - MOBILEGL_OK) { - std::fprintf(stderr, "MG_Remote server: pid=%d could not share segment %u\n", - selfPid, tag); - return endSession(69); + if (pin != static_cast(backend)) { + Refuse(*control, Protocol::RefuseCode::Backend, "Hello.backendType disagrees with pinned backend"); + return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 0)); } } - // SLOTS 4, 5 AND 6 ARE THE BELLS - three, not two, and the third is the - // one the two-fd form above forces: - // 4 serverBell[1] the client's handle to RING THE SERVER - // 5 clientBell[0] the client's PARK end - // 6 clientBell[1] the client's handle to ring ITSELF, for the same - // reason the server needs serverBell[1] - // Sharing DUPs, so the descriptors these bells own stay valid here. - const int bellFds[3] = {serverBell[1], clientBell[0], clientBell[1]}; - for (std::uint32_t index = 0; index < 3; ++index) { - Sideband sideband{4 + index, 0}; - if (control->ShareFd(bellFds[index], MobileGLByteSpan{&sideband, sizeof(sideband)}) != - MOBILEGL_OK) { - std::fprintf(stderr, "MG_Remote server: pid=%d could not share bell %u\n", selfPid, - index); - return endSession(69); + MobileGL::MG_Config::ActiveBackendType = backend; + backendBuilt = true; // InitServerRoleForSpawn may build it and then fail: Stop() drops either way + if (!MobileGL::MG_Backend::InitServerRoleForSpawn()) + return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 66)); + int serverBell[2] = {-1, -1}, clientBell[2] = {-1, -1}; + std::unique_ptr selfBell, peerBell; + if (!tcp) { + if (::socketpair(AF_UNIX, SOCK_STREAM, 0, serverBell) != 0 || + ::socketpair(AF_UNIX, SOCK_STREAM, 0, clientBell) != 0) + return endSession(LeaveBeforeAccept(sourceFd, sourceKind, 74)); + selfBell = std::make_unique(serverBell[0], serverBell[1], 1, true); + peerBell = std::make_unique(-1, clientBell[1], 1, true); + session.SetExternalDoorbells(selfBell.get(), peerBell.get()); + } + if (tcp) session.SetDataConnectionSource(std::move(dataSource)); + acceptTried = true; + const auto accepted = session.Accept(*control, &helloFrame); + // THE SOURCE IS DONE WHEN ACCEPT IS, whichever way Accept went (F fix round). Under --serve + // this is the child's end of the hand-off pair: closing it turns every later DataBind the + // supervisor would have queued here into a send failure the supervisor refuses by name on + // that connection ("data connection could not be handed to the live session"), instead of a + // descriptor nobody will read holding a peer's connection open and, past max_dgram_qlen, + // holding the supervisor itself. In the single-session shape it is the listener: kept open + // only until the data connection arrived on it, and closed here so a second client or a + // retry is refused at connect (ECONNREFUSED, as before PH-7 (4)) rather than completing a + // TCP handshake this process would never accept and waiting out its whole connect budget. + // The source object goes with it, so no later call can poll a descriptor number the kernel + // may have reused. The hand-off is cut before it is closed, and what was already queued on + // it is refused by name rather than dropped (ID-P7-44, CloseHandoffRefusingQueued). + if (sourceFd >= 0) { + if (sourceKind == DataSource::Handoff) + CloseHandoffRefusingQueued(sourceFd); + else + ::close(sourceFd); + session.SetDataConnectionSource({}); + } + if (accepted != MOBILEGL_OK) { + WireLogError("MG_Remote server: pid=%d Accept failed (rc=%d)", selfPid, static_cast(accepted)); + // A refusal and a peer that went away before its data connection bound (PH-7 (4): the + // client closed after Welcome) are outcomes, not faults; the supervisor counts only 67. + return endSession( + accepted == MOBILEGL_ERR_PROTOCOL_MISMATCH || accepted == MOBILEGL_ERR_TRANSPORT_CLOSED ? 0 : 67); + } + if (!tcp) { + struct Sideband { + std::uint32_t slot; + std::uint64_t bytes; + }; + using Slot = SessionSegmentSlot; + static constexpr Slot kSlots[4] = {Slot::Cmd, Slot::Stage, Slot::Reply, Slot::Event}; + for (const Slot slot : kSlots) { + const std::uint32_t tag = static_cast(slot); + const int fd = session.Shm().DescriptorFor(slot); + if (fd < 0) { + std::fprintf(stderr, "MG_Remote server: pid=%d segment %u has no descriptor\n", selfPid, tag); + return endSession(68); + } + Sideband sideband{tag, session.Shm().AnnouncedSize(slot)}; + if (control->ShareFd(fd, MobileGLByteSpan{&sideband, sizeof(sideband)}) != MOBILEGL_OK) { + std::fprintf(stderr, "MG_Remote server: pid=%d could not share segment %u\n", selfPid, tag); + return endSession(69); + } } - } - // clientBell[0] is the CLIENT's park end and no bell here owns it; the - // other three belong to selfBell and peerBell and are closed with them. - ::close(clientBell[0]); - // PH-6 fix round: THE SERVER'S BELL GETS A DEATH WITNESS TOO (CONTRACT-P6 D5c, mirrored). - // selfBell parks on serverBell[0] and this process rings itself through serverBell[1], so - // it holds both ends of that pair and the pair can never hang up, however dead the client - // is: Dead() stayed false for the life of a shm session, and ReserveEventOrBlock's "dead - // first" check could only ever report a peer killed mid-wait as NotDraining after the - // whole MOBILEGL_IPC_EVENT_WAIT_MS. The control socket's far end is the client's alone, so - // its hangup IS the death; the bell polls it for hangup only and never reads from it, so - // SocketTransport's reassembler below keeps every byte. Set before loop.Start(): the apply - // thread is the one that parks on this bell. (The stream plane's bell is the data socket - // itself and dies with the peer already.) - selfBell->SetDeathWitness(control->StreamFd()); - } - - const char* forwarding = std::getenv("MOBILEGL_IPC_LOG_FORWARD"); - if (tcp && (!forwarding || std::strcmp(forwarding, "0") != 0)) { - // F2 fault injection: bound the kernel queue as well as the log queue so a - // non-reading peer reliably blocks the sender, regardless of host autotuning. - if (const char* stalled = std::getenv("MOBILEGL_TEST_LOG_SMALL_SNDBUF"); - stalled && std::strcmp(stalled, "1") == 0) { - const int bytes = 16 * 1024; - if (::setsockopt(control->StreamFd(), SOL_SOCKET, SO_SNDBUF, &bytes, sizeof(bytes)) != 0) { - WireLogError("MG_Remote server: could not set F2 control send buffer"); - return endSession(70); + // SLOTS 4, 5 AND 6 ARE THE BELLS - three, not two, and the third is the + // one the two-fd form above forces: + // 4 serverBell[1] the client's handle to RING THE SERVER + // 5 clientBell[0] the client's PARK end + // 6 clientBell[1] the client's handle to ring ITSELF, for the same + // reason the server needs serverBell[1] + // Sharing DUPs, so the descriptors these bells own stay valid here. + const int bellFds[3] = {serverBell[1], clientBell[0], clientBell[1]}; + for (std::uint32_t index = 0; index < 3; ++index) { + Sideband sideband{4 + index, 0}; + if (control->ShareFd(bellFds[index], MobileGLByteSpan{&sideband, sizeof(sideband)}) != MOBILEGL_OK) { + std::fprintf(stderr, "MG_Remote server: pid=%d could not share bell %u\n", selfPid, index); + return endSession(69); + } } + // clientBell[0] is the CLIENT's park end and no bell here owns it; the + // other three belong to selfBell and peerBell and are closed with them. + ::close(clientBell[0]); + // PH-6 fix round: THE SERVER'S BELL GETS A DEATH WITNESS TOO (CONTRACT-P6 D5c, mirrored). + // selfBell parks on serverBell[0] and this process rings itself through serverBell[1], so + // it holds both ends of that pair and the pair can never hang up, however dead the client + // is: Dead() stayed false for the life of a shm session, and ReserveEventOrBlock's "dead + // first" check could only ever report a peer killed mid-wait as NotDraining after the + // whole MOBILEGL_IPC_EVENT_WAIT_MS. The control socket's far end is the client's alone, so + // its hangup IS the death; the bell polls it for hangup only and never reads from it, so + // SocketTransport's reassembler below keeps every byte. Set before loop.Start(): the apply + // thread is the one that parks on this bell. (The stream plane's bell is the data socket + // itself and dies with the peer already.) + selfBell->SetDeathWitness(control->StreamFd()); } - // P9 W3: the forward is a queue with a sender thread of its own, graded by severity - // (LogForward.h): a client that stops reading control no longer holds any logging thread - - // the apply thread above all - inside a socket write. - logForward = std::make_unique(*control, &UnblockControlSend, control.get()); - // P12 review fix: in-process, only THIS session's threads (this one and its apply thread) - // forward, and outside the log mutex - the UI thread, the listener and the next session's - // refusals share this process and must neither reach this client nor wait on it. - if (inProcess) - MobileGL::MG_Util::Debug::SetSessionLogForwarder(&Transport::LogForwardChannel::ForwardThunk, - logForward.get()); - else - MobileGL::MG_Util::Debug::SetLogForwarder(&Transport::LogForwardChannel::ForwardThunk, logForward.get()); - } - // p7/spawnhang: this thread posts every surface op the client sends (ServerApplyWireSurfaceOp, - // below) and is the one that says "still running" while the apply thread runs it. Cleared - // after the loop, on this same thread, so no post can be in flight when it goes. - loop.SetControlProgressSink(&SendSurfaceProgress, control.get()); - // P14 S2: the apply thread opens the same scope this thread is on, so its reads of the - // session, this loop and the PipeInputs block resolve to THIS session's. - loop.SetRuntime(*runtime); - if (loop.Start(session) != MOBILEGL_OK) return endSession(70); - WireLogError("MG_Remote server: pid=%d transport=spawn role=server ready control=%s data=%s", - selfPid, tcp ? "tcp" : "unix", tcp ? "stream" : "shm"); - std::vector buffer(64 * 1024); - // The control wait is sliced so a session can end from the server's side. Each turn first asks - // whether the session latched (a fault on the apply thread, which also stops that thread, so - // the latch is asked first and names the end), then whether the apply thread still runs (it - // stops on a forfeited reverse channel or a dead data bell). Both are asked every turn, not - // only on timeout, so a peer that keeps sending frames cannot hold the supervisor's session - // slot. ReceiveFrame keeps a partial frame across a timeout, so slicing loses no bytes. - constexpr std::uint32_t kControlSliceMs = 100; - for (;;) { - if (SessionLatched()) break; - // P12 (D5): the in-process display server is stopping (mobilegl_server_stop_inprocess). - // The session ends by the same orderly path as a peer's EOF, within one slice. - if (inProcess && g_inProcessStop.load(std::memory_order_acquire)) { - WireLogError("MG_Remote server: pid=%d the in-process display server is stopping; ending the session", - selfPid); - break; - } - if (!loop.Running()) { - WireLogError("MG_Remote server: pid=%d the apply thread has stopped (%s, %llu event(s) dropped); " - "ending the session", - selfPid, session.ReverseChannelForfeited() ? "ReverseChannelForfeit" : "its bell died", - static_cast(session.ForfeitDrops())); - break; - } - std::uint64_t size = 0; - const auto result = control->ReceiveFrame({buffer.data(), buffer.size()}, &size, kControlSliceMs); - if (result == MOBILEGL_ERR_TIMEOUT) continue; - if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL) { buffer.resize(static_cast(size)); continue; } - if (result != MOBILEGL_OK) { - // EOF or a transport error: the peer is gone. Said to the session BEFORE loop.Stop() - // below, so an apply thread that the stop wakes inside ReserveEventOrBlock names the - // hangup (PeerGone) rather than the stop (PH-6 fix round). - session.NoteControlStreamEnded(); - // Fuzz arm 1, steady state. A bad magic or an over-cap length has already been named - // by the framing latch (Framing.h) as it latched; a clean close is the client's normal - // end and says nothing. The one silent case was a close INSIDE a frame - a truncated - // length prefix or payload - which ended the session exactly like a clean close. - if (result == MOBILEGL_ERR_TRANSPORT_CLOSED && control->BufferedBytes() != 0) - WireLogError("MG_Remote server: control connection closed inside a frame (%llu bytes of it " - "received); ending the session", - static_cast(control->BufferedBytes())); - break; - } - // The identifier is checked before the verifier, which would also reject a wrong one, so - // the log names the specific cause; every refusal here is logged, because a silent end - // cannot be told apart from a crashed child. BufferHasIdentifier reads bytes [4, 8), - // hence the length guard. - if (size < 8) { - WireLogError("MG_Remote server: control frame is %llu bytes, too short to carry a " - "CtrlEnvelope root and identifier; ending the session", - static_cast(size)); - break; - } - if (!Protocol::CtrlEnvelopeBufferHasIdentifier(buffer.data())) { - WireLogError("MG_Remote server: control frame carries no CtrlEnvelope identifier " - "(size=%llu); ending the session rather than reading its union tag", - static_cast(size)); - break; - } - flatbuffers::Verifier check(buffer.data(), static_cast(size)); - if (!Protocol::VerifyCtrlEnvelopeBuffer(check)) { - WireLogError("MG_Remote server: control frame did not verify as a CtrlEnvelope " - "(size=%llu); ending the session", static_cast(size)); - break; - } - const auto* envelope = Protocol::GetCtrlEnvelope(buffer.data()); - if (const auto* flush = envelope->msg_as_LogFlush()) { - if (flush->ack()) break; - if (const char* inspect = std::getenv("MOBILEGL_TEST_SERVER_COUNTERS"); - inspect && std::strcmp(inspect, "1") == 0) { - // SyncPeerLog has fenced the client's complete command prefix. - // Acquire the applier's publication before reading its counters; - // this serial client sends no more records until this ack. - const auto applied = session.DataLink()->Progress()->appliedSeq.load(std::memory_order_acquire); - const auto& verbs = session.Applier().Verbs(); - MGLOG_I("MGPipe server counters: applied=%llu resets=%llu reset-serial=%llu deaths=%llu", - static_cast(applied), - static_cast(verbs.ApplierResets()), - static_cast(verbs.ExpectedApplierResetSerial()), - static_cast(verbs.ObjectDeaths())); + + const char* forwarding = std::getenv("MOBILEGL_IPC_LOG_FORWARD"); + if (tcp && (!forwarding || std::strcmp(forwarding, "0") != 0)) { + // F2 fault injection: bound the kernel queue as well as the log queue so a + // non-reading peer reliably blocks the sender, regardless of host autotuning. + if (const char* stalled = std::getenv("MOBILEGL_TEST_LOG_SMALL_SNDBUF"); + stalled && std::strcmp(stalled, "1") == 0) { + const int bytes = 16 * 1024; + if (::setsockopt(control->StreamFd(), SOL_SOCKET, SO_SNDBUF, &bytes, sizeof(bytes)) != 0) { + WireLogError("MG_Remote server: could not set F2 control send buffer"); + return endSession(70); + } } - FlushAck ack{control.get(), logForward.get(), flush->seq()}; - MobileGL::MG_Util::Debug::WithLogBarrier(&SendLogAck, &ack); - continue; + // P9 W3: the forward is a queue with a sender thread of its own, graded by severity + // (LogForward.h): a client that stops reading control no longer holds any logging thread - + // the apply thread above all - inside a socket write. + logForward = std::make_unique(*control, &UnblockControlSend, control.get()); + // P12 review fix: in-process, only THIS session's threads (this one and its apply thread) + // forward, and outside the log mutex - the UI thread, the listener and the next session's + // refusals share this process and must neither reach this client nor wait on it. + if (inProcess) + MobileGL::MG_Util::Debug::SetSessionLogForwarder(&Transport::LogForwardChannel::ForwardThunk, + logForward.get()); + else + MobileGL::MG_Util::Debug::SetLogForwarder(&Transport::LogForwardChannel::ForwardThunk, + logForward.get()); } - const auto* operation = envelope->msg_as_SurfaceOp(); - if (!operation) { - // Fuzz arm 1, steady state: a well-formed envelope of a type this loop does not take - // (or a SurfaceOp tag with no table behind it) ended the session without a word. - WireLogError("MG_Remote server: control frame of type %u (%s) is not a LogFlush or SurfaceOp " - "(size=%llu); ending the session", - static_cast(envelope->msg_type()), Protocol::EnumNameCtrlMsg(envelope->msg_type()), - static_cast(size)); - break; - } - Server::SurfaceControlFrame reply{}; - (void)ServerApplyWireSurfaceOp(*operation, &reply); - // PH-1 (3): an op that latched (its own bytes, or a record the apply thread was on) is - // answered by the SessionFault already sent and the close below, not by a reply. - if (SessionLatched()) break; - session.FlushDataProgress(); - if (session.DataLink()) reply.eventHead = session.DataLink()->EventPublishedHead(); - flatbuffers::FlatBufferBuilder builder(256); - EncodeSurfaceReplyFrame(reply, &builder); - if (control->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) { - session.NoteControlStreamEnded(); - break; - } - } - loop.SetControlProgressSink(nullptr, nullptr); - // P12 review fix: A STOPPING SERVER DOES NOT DRAIN A STREAMING CLIENT. The session ends because - // the display server is going away, not because its client finished - and a client still - // streaming would keep the apply thread's final drain going past Stop()'s bounded join, whose - // Fatal{ApplyThreadJoinTimeout} aborts this (the display Activity's) process. - if (inProcess && g_inProcessStop.load(std::memory_order_acquire)) loop.AbandonQueuedRecords(); - loop.Stop(); - // Publish the final server window while log forwarding is still attached. - MobileGL::MG_Util::PipeStats::Shutdown(); - // PH-1 (3): a latched session ends HERE, by the same orderly path as a peer's EOF - the apply - // thread has already left its loop and destroyed the backend on its own thread (loop.Stop() - // joined it), the session closes, and the exit status names the latch so the supervisor - // counts it. The first fault's line was logged (and sent as a SessionFault) when it latched. - const bool latched = SessionLatched(); - if (latched) { - WireLogError("MG_Remote server: pid=%d session closed on a latched fault (%s; %llu named " - "fault(s) latched or declined); exiting %d for the supervisor to reap - PH-1", - selfPid, FatalFamilyName(SessionLatchedFamily()), - static_cast(SessionLatchCount()), kSessionLatchedExitCode); - } - session.Close(); - // P9 W3: the forwarder is cleared, the forward drains (bounded) and its counters are logged. - stopLogForwarding(); - // P12 (D5): the backend loop.Stop() destroyed is not this session's to point at any more. - if (inProcess) session.SetBackend(nullptr); - // The caller's exit (a forked child's ExitSessionProcess) closes control after cleanup/flush - - // or, in-process, `control` is destroyed on the way out; peer EOF is the client's fence. - return latched ? kSessionLatchedExitCode : 0; -} - -// P7 wave 0, Ph slice (1) (ID-P7-1). THE SUPERVISOR OBSERVES AND NAMES A SESSION'S DEATH. -// -// Three `waitpid(active, nullptr, WNOHANG)` calls threw the status away, so a child that died of -// SIGABRT inside SessionFail and one that returned from its control loop and _exit(0)'d were the -// SAME EVENT to the supervisor: `active = -1`, no line, nothing to count. ID-P7-1 declines the -// literal 98-site latch translation and asks for this instead, because P6.5's fork-per-session -// already delivers "the next connection is served as usual" (:302) - what was missing is that -// anybody could tell a faulted session from a finished one, which is the half of PH-1 an -// operator, a fuzz arm and a flake triage all actually need. -// -// One function where there were three call sites, so the three cannot drift on what a reap means. -// Returns true when `active` was reaped. `sessionsFaulted` is the SUPERVISOR's lifetime count, -// not this child's: a supervisor that has served twenty sessions can say how many ended badly. -bool ReapActiveSession(pid_t& active, unsigned long& sessionsFaulted) { - if (active <= 0) return false; - int status = 0; - if (::waitpid(active, &status, WNOHANG) != active) return false; - const int reaped = static_cast(active); - active = -1; - // WIFEXITED / WIFSIGNALED / WTERMSIG, the same three questions ReapServer (ServerSpawn.cpp) asks of - // the same kind of child - but logged rather than folded into one signed integer, because - // this side has nobody to return the answer to. - if (WIFEXITED(status)) { - const int code = WEXITSTATUS(status); - if (code != 0) ++sessionsFaulted; - // PH-1 (3): a latched session is a named, orderly end - still a fault (counted), and said - // as one so the reap line alone tells it from a crash or a refused bring-up. - WireLogError("MG_Remote server: session pid=%d reaped exit=%d%s sessionsFaulted=%lu", - reaped, code, code == kSessionLatchedExitCode ? " (latched fault)" : "", - sessionsFaulted); - } else if (WIFSIGNALED(status)) { - ++sessionsFaulted; - WireLogError("MG_Remote server: session pid=%d reaped signal=%d sessionsFaulted=%lu", - reaped, WTERMSIG(status), sessionsFaulted); - } else { - // Neither exited nor signalled: WUNTRACED/WCONTINUED are not passed, so this is a status - // this build does not understand. Counted as a fault rather than silently ignored. - ++sessionsFaulted; - WireLogError("MG_Remote server: session pid=%d reaped status=0x%x sessionsFaulted=%lu", - reaped, status, sessionsFaulted); - } - return true; -} - -// The lifetime figure, once, on the way out. Every reap also prints the running total, so a -// supervisor taken down by SIGTERM (which is how the fixtures end it) still leaves the number -// behind - this line is for the exits the supervisor chooses. -void LogSupervisorSummary(unsigned long sessionsFaulted) { - WireLogError("MG_Remote server: supervisor pid=%d shutting down sessionsFaulted=%lu", - static_cast(::getpid()), sessionsFaulted); -} - -// The TCP supervisor reads every new connection's first frame itself without blocking on any -// one peer, authenticates a Hello (token first) and only then hands the session off (fork or -// thread); it routes DataBinds to the live session and backs off failing addresses -// (ARCHITECTURE.md §15.1.1, PreAuthGate.h). One session at a time: an authenticated second Hello -// is answered Busy. -class TcpSupervisor { -public: - using Clock = std::chrono::steady_clock; - - // P12 (D5): HOW AN AUTHENTICATED SESSION IS HANDED OFF. `Fork` is the exec'd supervisor's shape - // (a session child per session, crash isolation, D9's eglTerminate isolation). `Thread` is the - // in-process display server's: the session runs on a thread of THIS process, because the window - // it renders into lives here and nowhere else (an ANativeWindow does not survive fork or exec). - // Everything before the hand-off - pre-auth, the backoff, Busy for an authenticated second - // Hello, DataBind routing over the socketpair - is the same code for both. What differs is what - // a separate fd table used to do for free: the thread shape closes neither the listener nor the - // other pending peers (they are the supervisor's, and still in use), keeps the session's end of - // the hand-off (the session closes it), and "reaps" by joining the thread. - enum class Handoff { Fork, Thread }; - - TcpSupervisor(int listener, const Server::PreAuthKnobs& knobs, unsigned long& sessionsFaulted, - Handoff handoff = Handoff::Fork, const std::atomic* stop = nullptr) - : m_listener(listener), m_knobs(knobs), m_backoff(knobs), m_sessionsFaulted(sessionsFaulted), - m_handoffShape(handoff), m_stop(stop) {} - - ~TcpSupervisor() { - // A session thread is never left running behind its supervisor: Run() only returns after - // StopServing joined it, but a listener failure (73) returns from the loop directly. - if (m_sessionThread.joinable()) m_sessionThread.join(); - } - - // Returns only on a failure of the listener itself (73, as the old loop did) - or, with a stop - // flag (the in-process display server), 0 once the flag is raised and the live session ended. - int Run() { - WireLogError("MG_Remote server: pre-auth deadline=%u ms, at most %u pending, backoff after %u failures " - "from %u ms (0 = off)", - m_knobs.deadlineMs, m_knobs.maxPending, m_knobs.backoffAfter, m_knobs.backoffBaseMs); - std::vector fds; + // p7/spawnhang: this thread posts every surface op the client sends (ServerApplyWireSurfaceOp, + // below) and is the one that says "still running" while the apply thread runs it. Cleared + // after the loop, on this same thread, so no post can be in flight when it goes. + loop.SetControlProgressSink(&SendSurfaceProgress, control.get()); + // P14 S2: the apply thread opens the same scope this thread is on, so its reads of the + // session, this loop and the PipeInputs block resolve to THIS session's. + loop.SetRuntime(*runtime); + if (loop.Start(session) != MOBILEGL_OK) return endSession(70); + WireLogError("MG_Remote server: pid=%d transport=spawn role=server ready control=%s data=%s", selfPid, + tcp ? "tcp" : "unix", tcp ? "stream" : "shm"); + std::vector buffer(64 * 1024); + // The control wait is sliced so a session can end from the server's side. Each turn first asks + // whether the session latched (a fault on the apply thread, which also stops that thread, so + // the latch is asked first and names the end), then whether the apply thread still runs (it + // stops on a forfeited reverse channel or a dead data bell). Both are asked every turn, not + // only on timeout, so a peer that keeps sending frames cannot hold the supervisor's session + // slot. ReceiveFrame keeps a partial frame across a timeout, so slicing loses no bytes. + constexpr std::uint32_t kControlSliceMs = 100; for (;;) { - if (m_stop != nullptr && m_stop->load(std::memory_order_acquire)) return StopServing(); - Reap(); - auto now = Clock::now(); - // While accepting is paused (out of descriptors, AcceptNew) the listener sits out the - // poll - it stays readable, and would spin the loop - and the pause's end is a wakeup. - const bool acceptPaused = now < m_acceptResumeAt; - fds.clear(); - fds.push_back({acceptPaused ? -1 : m_listener, POLLIN, 0}); - for (const auto& peer : m_pending) fds.push_back({peer.fd, POLLIN, 0}); - // 250 ms is the old accept timeout, i.e. how often a finished child is reaped while - // nothing arrives; a pending deadline that falls sooner shortens it. - std::int64_t timeout = 250; - for (const auto& peer : m_pending) - timeout = std::clamp( - std::chrono::duration_cast(peer.deadline - now).count(), 0, timeout); - if (acceptPaused) - timeout = std::clamp( - std::chrono::duration_cast(m_acceptResumeAt - now).count() + 1, 0, - timeout); - const int polled = ::poll(fds.data(), static_cast(fds.size()), static_cast(timeout)); - if (polled < 0 && errno != EINTR) { - WireLogError("MG_Remote server: supervisor poll failed: %s", std::strerror(errno)); - return 73; + if (SessionLatched()) break; + // P12 (D5): the in-process display server is stopping (mobilegl_server_stop_inprocess). + // The session ends by the same orderly path as a peer's EOF, within one slice. + if (inProcess && g_inProcessStop.load(std::memory_order_acquire)) { + WireLogError("MG_Remote server: pid=%d the in-process display server is stopping; ending the session", + selfPid); + break; } - now = Clock::now(); - std::vector waiting; - for (std::size_t index = 0; index < m_pending.size(); ++index) { - PendingPeer& peer = m_pending[index]; - if (polled > 0 && fds[index + 1].revents != 0) { - switch (peer.frame.Pump(peer.fd)) { + if (!loop.Running()) { + WireLogError("MG_Remote server: pid=%d the apply thread has stopped (%s, %llu event(s) dropped); " + "ending the session", + selfPid, session.ReverseChannelForfeited() ? "ReverseChannelForfeit" : "its bell died", + static_cast(session.ForfeitDrops())); + break; + } + std::uint64_t size = 0; + const auto result = control->ReceiveFrame({buffer.data(), buffer.size()}, &size, kControlSliceMs); + if (result == MOBILEGL_ERR_TIMEOUT) continue; + if (result == MOBILEGL_ERR_BUFFER_TOO_SMALL) { + buffer.resize(static_cast(size)); + continue; + } + if (result != MOBILEGL_OK) { + // EOF or a transport error: the peer is gone. Said to the session BEFORE loop.Stop() + // below, so an apply thread that the stop wakes inside ReserveEventOrBlock names the + // hangup (PeerGone) rather than the stop (PH-6 fix round). + session.NoteControlStreamEnded(); + // Fuzz arm 1, steady state. A bad magic or an over-cap length has already been named + // by the framing latch (Framing.h) as it latched; a clean close is the client's normal + // end and says nothing. The one silent case was a close INSIDE a frame - a truncated + // length prefix or payload - which ended the session exactly like a clean close. + if (result == MOBILEGL_ERR_TRANSPORT_CLOSED && control->BufferedBytes() != 0) + WireLogError("MG_Remote server: control connection closed inside a frame (%llu bytes of it " + "received); ending the session", + static_cast(control->BufferedBytes())); + break; + } + // The identifier is checked before the verifier, which would also reject a wrong one, so + // the log names the specific cause; every refusal here is logged, because a silent end + // cannot be told apart from a crashed child. BufferHasIdentifier reads bytes [4, 8), + // hence the length guard. + if (size < 8) { + WireLogError("MG_Remote server: control frame is %llu bytes, too short to carry a " + "CtrlEnvelope root and identifier; ending the session", + static_cast(size)); + break; + } + if (!Protocol::CtrlEnvelopeBufferHasIdentifier(buffer.data())) { + WireLogError("MG_Remote server: control frame carries no CtrlEnvelope identifier " + "(size=%llu); ending the session rather than reading its union tag", + static_cast(size)); + break; + } + flatbuffers::Verifier check(buffer.data(), static_cast(size)); + if (!Protocol::VerifyCtrlEnvelopeBuffer(check)) { + WireLogError("MG_Remote server: control frame did not verify as a CtrlEnvelope " + "(size=%llu); ending the session", + static_cast(size)); + break; + } + const auto* envelope = Protocol::GetCtrlEnvelope(buffer.data()); + if (const auto* flush = envelope->msg_as_LogFlush()) { + if (flush->ack()) break; + if (const char* inspect = std::getenv("MOBILEGL_TEST_SERVER_COUNTERS"); + inspect && std::strcmp(inspect, "1") == 0) { + // SyncPeerLog has fenced the client's complete command prefix. + // Acquire the applier's publication before reading its counters; + // this serial client sends no more records until this ack. + const auto applied = session.DataLink()->Progress()->appliedSeq.load(std::memory_order_acquire); + const auto& verbs = session.Applier().Verbs(); + MGLOG_I("MGPipe server counters: applied=%llu resets=%llu reset-serial=%llu deaths=%llu", + static_cast(applied), + static_cast(verbs.ApplierResets()), + static_cast(verbs.ExpectedApplierResetSerial()), + static_cast(verbs.ObjectDeaths())); + } + FlushAck ack{control.get(), logForward.get(), flush->seq()}; + MobileGL::MG_Util::Debug::WithLogBarrier(&SendLogAck, &ack); + continue; + } + const auto* operation = envelope->msg_as_SurfaceOp(); + if (!operation) { + // Fuzz arm 1, steady state: a well-formed envelope of a type this loop does not take + // (or a SurfaceOp tag with no table behind it) ended the session without a word. + WireLogError("MG_Remote server: control frame of type %u (%s) is not a LogFlush or SurfaceOp " + "(size=%llu); ending the session", + static_cast(envelope->msg_type()), + Protocol::EnumNameCtrlMsg(envelope->msg_type()), static_cast(size)); + break; + } + Server::SurfaceControlFrame reply{}; + (void)ServerApplyWireSurfaceOp(*operation, &reply); + // PH-1 (3): an op that latched (its own bytes, or a record the apply thread was on) is + // answered by the SessionFault already sent and the close below, not by a reply. + if (SessionLatched()) break; + session.FlushDataProgress(); + if (session.DataLink()) reply.eventHead = session.DataLink()->EventPublishedHead(); + flatbuffers::FlatBufferBuilder builder(256); + EncodeSurfaceReplyFrame(reply, &builder); + if (control->SendFrame({builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) { + session.NoteControlStreamEnded(); + break; + } + } + loop.SetControlProgressSink(nullptr, nullptr); + // P12 review fix: A STOPPING SERVER DOES NOT DRAIN A STREAMING CLIENT. The session ends because + // the display server is going away, not because its client finished - and a client still + // streaming would keep the apply thread's final drain going past Stop()'s bounded join, whose + // Fatal{ApplyThreadJoinTimeout} aborts this (the display Activity's) process. + if (inProcess && g_inProcessStop.load(std::memory_order_acquire)) loop.AbandonQueuedRecords(); + loop.Stop(); + // Publish the final server window while log forwarding is still attached. + MobileGL::MG_Util::PipeStats::Shutdown(); + // PH-1 (3): a latched session ends HERE, by the same orderly path as a peer's EOF - the apply + // thread has already left its loop and destroyed the backend on its own thread (loop.Stop() + // joined it), the session closes, and the exit status names the latch so the supervisor + // counts it. The first fault's line was logged (and sent as a SessionFault) when it latched. + const bool latched = SessionLatched(); + if (latched) { + WireLogError("MG_Remote server: pid=%d session closed on a latched fault (%s; %llu named " + "fault(s) latched or declined); exiting %d for the supervisor to reap - PH-1", + selfPid, FatalFamilyName(SessionLatchedFamily()), + static_cast(SessionLatchCount()), kSessionLatchedExitCode); + } + session.Close(); + // P9 W3: the forwarder is cleared, the forward drains (bounded) and its counters are logged. + stopLogForwarding(); + // P12 (D5): the backend loop.Stop() destroyed is not this session's to point at any more. + if (inProcess) session.SetBackend(nullptr); + // The caller's exit (a forked child's ExitSessionProcess) closes control after cleanup/flush - + // or, in-process, `control` is destroyed on the way out; peer EOF is the client's fence. + return latched ? kSessionLatchedExitCode : 0; + } + + // P7 wave 0, Ph slice (1) (ID-P7-1). THE SUPERVISOR OBSERVES AND NAMES A SESSION'S DEATH. + // + // Three `waitpid(active, nullptr, WNOHANG)` calls threw the status away, so a child that died of + // SIGABRT inside SessionFail and one that returned from its control loop and _exit(0)'d were the + // SAME EVENT to the supervisor: `active = -1`, no line, nothing to count. ID-P7-1 declines the + // literal 98-site latch translation and asks for this instead, because P6.5's fork-per-session + // already delivers "the next connection is served as usual" (:302) - what was missing is that + // anybody could tell a faulted session from a finished one, which is the half of PH-1 an + // operator, a fuzz arm and a flake triage all actually need. + // + // One function where there were three call sites, so the three cannot drift on what a reap means. + // Returns true when `active` was reaped. `sessionsFaulted` is the SUPERVISOR's lifetime count, + // not this child's: a supervisor that has served twenty sessions can say how many ended badly. + bool ReapActiveSession(pid_t& active, unsigned long& sessionsFaulted) { + if (active <= 0) return false; + int status = 0; + if (::waitpid(active, &status, WNOHANG) != active) return false; + const int reaped = static_cast(active); + active = -1; + // WIFEXITED / WIFSIGNALED / WTERMSIG, the same three questions ReapServer (ServerSpawn.cpp) asks of + // the same kind of child - but logged rather than folded into one signed integer, because + // this side has nobody to return the answer to. + if (WIFEXITED(status)) { + const int code = WEXITSTATUS(status); + if (code != 0) ++sessionsFaulted; + // PH-1 (3): a latched session is a named, orderly end - still a fault (counted), and said + // as one so the reap line alone tells it from a crash or a refused bring-up. + WireLogError("MG_Remote server: session pid=%d reaped exit=%d%s sessionsFaulted=%lu", reaped, code, + code == kSessionLatchedExitCode ? " (latched fault)" : "", sessionsFaulted); + } else if (WIFSIGNALED(status)) { + ++sessionsFaulted; + WireLogError("MG_Remote server: session pid=%d reaped signal=%d sessionsFaulted=%lu", reaped, + WTERMSIG(status), sessionsFaulted); + } else { + // Neither exited nor signalled: WUNTRACED/WCONTINUED are not passed, so this is a status + // this build does not understand. Counted as a fault rather than silently ignored. + ++sessionsFaulted; + WireLogError("MG_Remote server: session pid=%d reaped status=0x%x sessionsFaulted=%lu", reaped, status, + sessionsFaulted); + } + return true; + } + + // The lifetime figure, once, on the way out. Every reap also prints the running total, so a + // supervisor taken down by SIGTERM (which is how the fixtures end it) still leaves the number + // behind - this line is for the exits the supervisor chooses. + void LogSupervisorSummary(unsigned long sessionsFaulted) { + WireLogError("MG_Remote server: supervisor pid=%d shutting down sessionsFaulted=%lu", + static_cast(::getpid()), sessionsFaulted); + } + + // The TCP supervisor reads every new connection's first frame itself without blocking on any + // one peer, authenticates a Hello (token first) and only then hands the session off (fork or + // thread); it routes DataBinds to the live session and backs off failing addresses + // (ARCHITECTURE.md §15.1.1, PreAuthGate.h). One session at a time: an authenticated second Hello + // is answered Busy. + class TcpSupervisor { + public: + using Clock = std::chrono::steady_clock; + + // P12 (D5): HOW AN AUTHENTICATED SESSION IS HANDED OFF. `Fork` is the exec'd supervisor's shape + // (a session child per session, crash isolation, D9's eglTerminate isolation). `Thread` is the + // in-process display server's: the session runs on a thread of THIS process, because the window + // it renders into lives here and nowhere else (an ANativeWindow does not survive fork or exec). + // Everything before the hand-off - pre-auth, the backoff, Busy for an authenticated second + // Hello, DataBind routing over the socketpair - is the same code for both. What differs is what + // a separate fd table used to do for free: the thread shape closes neither the listener nor the + // other pending peers (they are the supervisor's, and still in use), keeps the session's end of + // the hand-off (the session closes it), and "reaps" by joining the thread. + enum class Handoff { + Fork, + Thread + }; + + TcpSupervisor(int listener, const Server::PreAuthKnobs& knobs, unsigned long& sessionsFaulted, + Handoff handoff = Handoff::Fork, const std::atomic* stop = nullptr) + : m_listener(listener), m_knobs(knobs), m_backoff(knobs), m_sessionsFaulted(sessionsFaulted), + m_handoffShape(handoff), m_stop(stop) {} + + ~TcpSupervisor() { + // A session thread is never left running behind its supervisor: Run() only returns after + // StopServing joined it, but a listener failure (73) returns from the loop directly. + if (m_sessionThread.joinable()) m_sessionThread.join(); + } + + // Returns only on a failure of the listener itself (73, as the old loop did) - or, with a stop + // flag (the in-process display server), 0 once the flag is raised and the live session ended. + int Run() { + WireLogError("MG_Remote server: pre-auth deadline=%u ms, at most %u pending, backoff after %u failures " + "from %u ms (0 = off)", + m_knobs.deadlineMs, m_knobs.maxPending, m_knobs.backoffAfter, m_knobs.backoffBaseMs); + std::vector fds; + for (;;) { + if (m_stop != nullptr && m_stop->load(std::memory_order_acquire)) return StopServing(); + Reap(); + auto now = Clock::now(); + // While accepting is paused (out of descriptors, AcceptNew) the listener sits out the + // poll - it stays readable, and would spin the loop - and the pause's end is a wakeup. + const bool acceptPaused = now < m_acceptResumeAt; + fds.clear(); + fds.push_back({acceptPaused ? -1 : m_listener, POLLIN, 0}); + for (const auto& peer : m_pending) + fds.push_back({peer.fd, POLLIN, 0}); + // 250 ms is the old accept timeout, i.e. how often a finished child is reaped while + // nothing arrives; a pending deadline that falls sooner shortens it. + std::int64_t timeout = 250; + for (const auto& peer : m_pending) + timeout = std::clamp( + std::chrono::duration_cast(peer.deadline - now).count(), 0, timeout); + if (acceptPaused) + timeout = std::clamp( + std::chrono::duration_cast(m_acceptResumeAt - now).count() + 1, 0, + timeout); + const int polled = ::poll(fds.data(), static_cast(fds.size()), static_cast(timeout)); + if (polled < 0 && errno != EINTR) { + WireLogError("MG_Remote server: supervisor poll failed: %s", std::strerror(errno)); + return 73; + } + now = Clock::now(); + std::vector waiting; + for (std::size_t index = 0; index < m_pending.size(); ++index) { + PendingPeer& peer = m_pending[index]; + if (polled > 0 && fds[index + 1].revents != 0) { + switch (peer.frame.Pump(peer.fd)) { case Step::NeedMore: break; case Step::Complete: @@ -967,500 +985,511 @@ public: RefuseAndClose(peer.fd, Protocol::RefuseCode::MalformedHello, Server::kFirstFrameTruncated); NoteFailure(peer.address, "truncated first frame"); continue; + } } + if (now >= peer.deadline) { + RefuseAndClose(peer.fd, Protocol::RefuseCode::Authentication, Server::kPreAuthDeadline); + NoteFailure(peer.address, "pre-auth deadline"); + continue; + } + waiting.push_back(std::move(peer)); } - if (now >= peer.deadline) { - RefuseAndClose(peer.fd, Protocol::RefuseCode::Authentication, Server::kPreAuthDeadline); - NoteFailure(peer.address, "pre-auth deadline"); - continue; + m_pending.swap(waiting); + for (auto& peer : m_ready) + Judge(peer); + m_ready.clear(); + if (polled > 0 && (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) != 0) { + const int failed = AcceptNew(now); + if (failed != 0) return failed; } - waiting.push_back(std::move(peer)); - } - m_pending.swap(waiting); - for (auto& peer : m_ready) Judge(peer); - m_ready.clear(); - if (polled > 0 && (fds[0].revents & (POLLIN | POLLERR | POLLHUP)) != 0) { - const int failed = AcceptNew(now); - if (failed != 0) return failed; } } - } -private: - using Step = Server::FirstFrameAssembler::Step; + private: + using Step = Server::FirstFrameAssembler::Step; - struct PendingPeer { - int fd = -1; - std::string address; - Clock::time_point accepted{}; - Clock::time_point deadline{}; - Server::FirstFrameAssembler frame; - }; - - bool Reap() { - const bool reaped = m_handoffShape == Handoff::Thread ? ReapSessionThread() - : ReapActiveSession(m_active, m_sessionsFaulted); - if (m_active <= 0 && m_handoff >= 0) { ::close(m_handoff); m_handoff = -1; } - return reaped; - } - - // P12 (D5): the thread shape's reap - the same line a forked child's reap prints, and the same - // count, then the latch put back for the next session (ResetSessionLatch: the thread took no - // process with it, so the latch it armed and maybe latched is still this process's). - bool ReapSessionThread() { - if (m_active <= 0 || !m_sessionDone.load(std::memory_order_acquire)) return false; - if (m_sessionThread.joinable()) m_sessionThread.join(); - const int code = m_sessionExit.load(std::memory_order_acquire); - m_active = -1; - if (code != 0) ++m_sessionsFaulted; - ResetSessionLatch(); - WireLogError("MG_Remote server: in-process session #%lu pid=%d reaped exit=%d%s sessionsFaulted=%lu", - m_sessionOrdinal, static_cast(::getpid()), code, - code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted); - return true; - } - - // P12 (D5): the in-process server is stopping. Nobody new is served (every pending peer is told - // so by name), the live session - which reads the same flag each control slice - is waited out - // and reaped, and the supervisor returns 0. - int StopServing() { - for (auto& peer : m_pending) { - if (peer.fd >= 0) RefuseAndClose(peer.fd, Protocol::RefuseCode::Busy, "the server is stopping"); - peer.fd = -1; - } - m_pending.clear(); - while (m_active > 0) { - if (!Reap()) ::usleep(10000); - } - if (m_handoff >= 0) { ::close(m_handoff); m_handoff = -1; } - WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast(::getpid())); - return 0; - } - - // exit_group closes files just before waitpid can observe the exit. Allow that small - // scheduling window before deciding whether a session is live - the old loop gave it to every - // connection it accepted while a child existed; this gives it to the one decision that - // depends on the answer: an AUTHENTICATED Hello's Busy (a client reconnecting the moment its - // last session ended must not be told Busy by a zombie). Only a peer holding the token can - // make the loop wait here; a DataBind's routing takes one look (Judge, fix round). - void WaitOutAnExitingSession() { - const auto reapDeadline = Clock::now() + std::chrono::milliseconds(50); - while (m_active > 0 && Clock::now() < reapDeadline) { - if (Reap()) break; - ::usleep(1000); - } - } - - void NoteFailure(const std::string& address, const char* why) { - const std::uint32_t window = m_backoff.NoteFailure(address, Clock::now()); - if (window != 0) - WireLogError("MG_Remote server: %s failed pre-auth %u times (last: %s); refusing it at accept for %u ms", - address.c_str(), m_backoff.Failures(address), why, window); - } - - // Accepts everything the listener has, deciding at accept what can be decided without reading. - int AcceptNew(Clock::time_point now) { - for (int burst = 0; burst < 64; ++burst) { + struct PendingPeer { int fd = -1; - const auto accepted = SocketTransport::AcceptOne(m_listener, 0, &fd); - if (accepted == MOBILEGL_ERR_TIMEOUT) return 0; - if (accepted == MOBILEGL_ERR_OUT_OF_MEMORY) { - // f2-auth fix round. Out of descriptors (or memory) is not a dead listener: the - // connection waits in the backlog, and pending connections give descriptors back - // as they are judged or reach their deadline. The first cut returned 73 here, so - // a connection flood against a low RLIMIT_NOFILE (or a large pending cap) took the - // supervisor down. Now accepting pauses for kAcceptPauseMs and the loop goes on. - m_acceptResumeAt = now + std::chrono::milliseconds(kAcceptPauseMs); - WireLogError("MG_Remote server: accept paused for %u ms: out of descriptors or memory with %zu " - "connections awaiting authentication", - kAcceptPauseMs, m_pending.size()); - return 0; + std::string address; + Clock::time_point accepted{}; + Clock::time_point deadline{}; + Server::FirstFrameAssembler frame; + }; + + bool Reap() { + const bool reaped = m_handoffShape == Handoff::Thread ? ReapSessionThread() + : ReapActiveSession(m_active, m_sessionsFaulted); + if (m_active <= 0 && m_handoff >= 0) { + ::close(m_handoff); + m_handoff = -1; } - if (accepted != MOBILEGL_OK) return 73; - std::string address = PeerAddress(fd); - if (const std::uint32_t retry = m_backoff.RetryAfterMs(address, now); retry != 0) { - WireLogError("MG_Remote server: connection from %s refused at accept: authentication backoff, " - "%u ms left", address.c_str(), retry); - RefuseAndClose(fd, Protocol::RefuseCode::Authentication, Server::kAuthBackoffRefusal); - continue; - } - if (m_pending.size() >= m_knobs.maxPending) { - // Full. The queue is shared between addresses (PreAuthGate.h - // ChoosePendingToDisplace): the newcomer takes the busiest address's oldest slot, - // unless its own address is the busiest - then it is the one refused. - std::vector holders; - holders.reserve(m_pending.size()); - for (const auto& peer : m_pending) holders.push_back(peer.address); - const std::size_t victim = Server::ChoosePendingToDisplace(holders, address); - if (victim >= m_pending.size()) { - WireLogError("MG_Remote server: connection from %s refused at accept: %zu connections are " - "already awaiting authentication", address.c_str(), m_pending.size()); - RefuseAndClose(fd, Protocol::RefuseCode::Busy, Server::kPreAuthFull); - continue; - } - PendingPeer displaced = std::move(m_pending[victim]); - m_pending.erase(m_pending.begin() + static_cast(victim)); - WireLogError("MG_Remote server: pending connection from %s displaced by one from %s: the pre-auth " - "queue is full and %s holds the most of it", displaced.address.c_str(), address.c_str(), - displaced.address.c_str()); - RefuseAndClose(displaced.fd, Protocol::RefuseCode::Busy, Server::kPreAuthDisplaced); - // A displaced connection that had already held its slot past the grace is a silent - // holder like one that closed; otherwise displacement would be a free way out. - if (now - displaced.accepted >= std::chrono::milliseconds(Server::kPreAuthProbeGraceMs)) - NoteFailure(displaced.address, "held a pre-auth slot until displaced"); - } - PendingPeer peer; - peer.fd = fd; - peer.address = std::move(address); - peer.accepted = now; - peer.deadline = now + std::chrono::milliseconds(m_knobs.deadlineMs); - m_pending.push_back(std::move(peer)); + return reaped; } - return 0; - } - // A complete first frame. Every refusal here is by name; only an authenticated Hello forks. - void Judge(PendingPeer& peer) { - auto connection = std::make_unique(peer.fd, -1, TransportRole::Server); - const int fd = peer.fd; - peer.fd = -1; // `connection` owns it now - const auto& payload = peer.frame.Payload(); - const auto shape = Server::ClassifyFirstFrame(payload.data(), payload.size()); - if (shape == Server::FirstFrameShape::DataBind) { - // PH-7 (4). While a session is live, its data connection goes to its child with the - // nonce it presented (the child compares, once); with none live there is nobody it - // could belong to. Neither is an authentication failure: a data connection left over - // from a session that just ended is a legitimate client's. - std::uint8_t nonce[kDataNonceBytes] = {}; - (void)DecodeDataBind(payload, nonce); - // One look, not WaitOutAnExitingSession's 50 ms (fix round): DataBinds are - // unauthenticated and never counted, so waiting for each would let any peer stall this - // loop 50 ms per connection. Nothing needs the wait here - a child that is exiting - // either has cut its hand-off already (the send below fails and is refused by name) or - // refuses what is queued on its way out (ExitBeforeAccept / CloseHandoffRefusingQueued). - Reap(); - if (m_active <= 0) { - // StreamLink's frames may already follow the DataBind; read them away so the close - // does not become an RST that discards the refusal. - DrainUnread(fd); - Refuse(*connection, Protocol::RefuseCode::Authentication, "data connection names no live session"); - return; - } - // The send is MSG_DONTWAIT and the child cuts its end the moment its own data - // connection is bound (CloseHandoffRefusingQueued), so this either queues the - // descriptor for a child that will read or refuse it, or fails and is refused here. - if (m_handoff >= 0 && FdPassing::SendFd(m_handoff, fd, MobileGLByteSpan{nonce, sizeof(nonce)}, - /*dontWait=*/true) == MOBILEGL_OK) { - // The child holds its own descriptor now; ours goes without a shutdown(2), which - // would disconnect the child's too. - connection->CloseLocalCopy(); - return; - } - DrainUnread(fd); - Refuse(*connection, Protocol::RefuseCode::Authentication, kHandoffRefused); - return; - } - if (shape != Server::FirstFrameShape::Hello) { - DrainUnread(fd); - Refuse(*connection, Protocol::RefuseCode::MalformedHello, Server::FirstFrameRefusalDetail(shape)); - NoteFailure(peer.address, "malformed first frame"); - return; - } - const auto* hello = Protocol::GetCtrlEnvelope(payload.data())->msg_as_Hello(); - // The token FIRST, and the only answer an unauthenticated peer gets is this one - // (RefuseHandshake: Refuse{Authentication} "token mismatch", no fingerprint, no stamp). - if (AuthenticatePeerToken(*connection, hello->token()) != MOBILEGL_OK) { - // Anything the peer sent after its Hello is read away before the close, so the close - // cannot become an RST that discards the refusal already in flight. - DrainUnread(fd); - WireLogError("MG_Remote server: unauthenticated Hello from %s refused before any session work", - peer.address.c_str()); - NoteFailure(peer.address, "token mismatch"); - return; - } - m_backoff.NoteSuccess(peer.address); - WaitOutAnExitingSession(); - if (m_active > 0) { - // The frame is consumed, so closing cannot RST the refusal away. - Refuse(*connection, Protocol::RefuseCode::Busy, "one session is already active"); - return; - } - Fork(std::move(connection), payload); - } - - void Fork(std::unique_ptr connection, const std::vector& hello) { - int pair[2] = {-1, -1}; - if (FdPassing::CreateSocketPair(pair) != MOBILEGL_OK) { - Refuse(*connection, Protocol::RefuseCode::Busy, "could not create the data-connection hand-off"); - return; - } - if (m_handoffShape == Handoff::Thread) { - StartSessionThread(std::move(connection), hello, pair); - return; - } - std::fflush(nullptr); - const pid_t supervisor = ::getpid(); - const pid_t child = ::fork(); - if (child == 0) { - // P12 (D8): the child dies with this supervisor, not after its client has gone. - DieWithSupervisor(supervisor); - // The child owns this one connection and its end of the hand-off. Every OTHER pending - // peer's descriptor is closed here, or the supervisor's refusal of that peer would not - // end its connection while this session lives. - ::close(m_listener); - ::close(pair[0]); - for (const auto& other : m_pending) - if (other.fd >= 0) ::close(other.fd); - for (const auto& other : m_ready) - if (other.fd >= 0) ::close(other.fd); - const int controlFd = connection->StreamFd(); - ExitSessionProcess(RunSession(std::move(connection), hello, HandoffSource(pair[1], controlFd), pair[1], - DataSource::Handoff, 0)); - } - if (child < 0) { - ::close(pair[0]); - ::close(pair[1]); - Refuse(*connection, Protocol::RefuseCode::Busy, "could not create session child"); - return; - } - m_active = child; - ::close(pair[1]); - m_handoff = pair[0]; - connection->CloseLocalCopy(); - } - - // P12 (D5): THE THREAD HAND-OFF. The session thread owns the connection (moved in) and the - // hand-off's session end (pair[1]; RunSession cuts and closes it when Accept returns); this - // thread keeps pair[0] to route DataBinds, exactly as the fork shape's parent does. There is no - // CloseLocalCopy: nothing was duplicated. `m_active` is this process's pid while the session - // lives - "a session is live" is all the rest of the supervisor asks of it. - void StartSessionThread(std::unique_ptr connection, const std::vector& hello, - const int (&pair)[2]) { - if (m_sessionThread.joinable()) m_sessionThread.join(); // reaped already; never live here - const int controlFd = connection->StreamFd(); - const int sessionEnd = pair[1]; - m_sessionDone.store(false, std::memory_order_release); - m_sessionExit.store(0, std::memory_order_release); - ++m_sessionOrdinal; - try { - m_sessionThread = std::thread( - [this, controlFd, sessionEnd, hello, conn = std::move(connection)]() mutable { - const int code = RunSession(std::move(conn), hello, HandoffSource(sessionEnd, controlFd), sessionEnd, - DataSource::Handoff, 0, /*inProcess=*/true); - m_sessionExit.store(code, std::memory_order_release); - m_sessionDone.store(true, std::memory_order_release); - }); - } catch (...) { - ::close(pair[0]); - ::close(pair[1]); - WireLogError("MG_Remote server: could not start the in-process session thread; the connection is " - "closed (it cannot be refused: it moved into the failed thread)"); - return; - } - m_active = ::getpid(); - m_handoff = pair[0]; - WireLogError("MG_Remote server: in-process session #%lu started on a thread (pid=%d)", m_sessionOrdinal, - static_cast(::getpid())); - } - - int m_listener; - Server::PreAuthKnobs m_knobs; - Server::AuthBackoff m_backoff; - unsigned long& m_sessionsFaulted; - Handoff m_handoffShape = Handoff::Fork; - const std::atomic* m_stop = nullptr; - std::thread m_sessionThread; - std::atomic m_sessionDone{false}; - std::atomic m_sessionExit{0}; - unsigned long m_sessionOrdinal = 0; - pid_t m_active = -1; - // The supervisor's end of the live child's data-connection hand-off (PH-7 (4)); -1 while no - // session is live. It goes with the child: a reaped session can be handed nothing. - int m_handoff = -1; - std::vector m_pending; - std::vector m_ready; - // Accepting resumes at this time after accept(2) ran out of descriptors or memory. - static constexpr std::uint32_t kAcceptPauseMs = 100; - Clock::time_point m_acceptResumeAt{}; -}; - -// THE UNIX SHAPE OF THE IN-PROCESS DISPLAY SERVER (P12 D5 extended to unix endpoints). The -// endpoint is an @abstract name or a filesystem path. A client's control AND aux connections -// pair by the nonce each presents first (PairAcceptor, P11 PAIR - never by arrival order) and the -// shm segments' descriptors cross by SCM_RIGHTS, so there is no DataBind to route and no pre-auth -// gate - both are TCP exposure machinery, and a unix peer is local by construction (the forked -// unix supervisor runs without either). Sessions take the TCP in-process shape's thread hand-off: -// a thread per session, up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (default 16) and Refuse{Busy} -// over it, the latch per session, stop by -// g_inProcessStop. P14 S2: THE SESSIONS ARE CONCURRENT - each one owns its ServerSession, its -// ServerLoop and its PipeInputs block through its own ThreadSessionScope (SessionRuntime.h), so a -// session's close, latch or fault reaches no other, and the server's own stop waits for all of -// them rather than for the one. -class UnixInProcessSupervisor { -public: - // P14 S2: MAX SESSIONS AT ONCE. Each live session is one control thread (RunSession) plus its - // own mgl-srv-apply, both carrying their own ServerSession/ServerLoop/PipeInputs block, so the - // cap is a resource limit and not a correctness one. The default is the display server's - // (mobilegl_server_serve_inprocess), which is where this class is used. - static constexpr unsigned kDefaultMaxSessions = 16; - - // MOBILEGL_IPC_INPROC_MAX_SESSIONS, capped, 0/unset/invalid = the default. An environment - // knob rather than a new argument to mobilegl_server_serve_inprocess: the embedded ABI has one - // parameter (the endpoint) and the display server passes none of this, exactly as it passes - // none of the transport knobs. - static unsigned MaxSessionsFromEnvironment() { - const char* text = std::getenv("MOBILEGL_IPC_INPROC_MAX_SESSIONS"); - if (text == nullptr || text[0] == 0) return kDefaultMaxSessions; - char* end = nullptr; - const long parsed = std::strtol(text, &end, 10); - if (end == text || parsed <= 0) return kDefaultMaxSessions; - return static_cast(std::min(parsed, 256)); - } - - UnixInProcessSupervisor(int listener, unsigned long& sessionsFaulted, - unsigned maxSessions = kDefaultMaxSessions) - : m_listener(listener), m_pairs(listener), m_sessionsFaulted(sessionsFaulted), - m_maxSessions(maxSessions == 0 ? kDefaultMaxSessions : maxSessions) {} - - ~UnixInProcessSupervisor() { - // Run() only returns with every session thread joined; a listener failure (73) is the one - // way out with one live, and a session is never left running behind the supervisor. - for (auto& session : m_live) { - if (session->thread.joinable()) session->thread.join(); - } - } - - int Run() { - for (;;) { - if (g_inProcessStop.load(std::memory_order_acquire)) return StopServing(); - Reap(); - std::unique_ptr control; - const auto accepted = m_pairs.Accept(250, control); - if (accepted == MOBILEGL_ERR_TIMEOUT) continue; - if (accepted != MOBILEGL_OK) { - WireLogError("MG_Remote server: the in-process unix listener failed (rc=%d)", - static_cast(accepted)); - return 73; - } - // P14 S2: THE CAP, NOT "ONE AT A TIME". A peer over the limit is refused by the same - // words the forked `--max-sessions` supervisor uses, and is not a latch: the sessions - // already running carry on and the connection is simply closed. - if (m_live.size() >= m_maxSessions) { - RefuseBusy(*control, "the session limit is reached"); - continue; - } - StartSessionThread(std::move(control)); - } - } - -private: - // One live session: its control thread, and the two facts the reap needs from it. The session - // itself (its ServerSession, its apply thread, its PipeInputs block) is owned by RunSession's - // own frame, on that thread - nothing here reaches into it. - struct LiveSession { - std::thread thread; - std::atomic done{false}; - std::atomic exit{0}; - unsigned long long ordinal = 0; - }; - - // The same reap as the TCP shape's ReapSessionThread: join, count faults, reset the latch for - // the next session in this process. Every finished session is reaped, in whatever order they - // finished. - void Reap() { - for (auto session = m_live.begin(); session != m_live.end();) { - LiveSession& live = **session; - if (!live.done.load(std::memory_order_acquire)) { - ++session; - continue; - } - if (live.thread.joinable()) live.thread.join(); - const int code = live.exit.load(std::memory_order_acquire); + // P12 (D5): the thread shape's reap - the same line a forked child's reap prints, and the same + // count, then the latch put back for the next session (ResetSessionLatch: the thread took no + // process with it, so the latch it armed and maybe latched is still this process's). + bool ReapSessionThread() { + if (m_active <= 0 || !m_sessionDone.load(std::memory_order_acquire)) return false; + if (m_sessionThread.joinable()) m_sessionThread.join(); + const int code = m_sessionExit.load(std::memory_order_acquire); + m_active = -1; if (code != 0) ++m_sessionsFaulted; ResetSessionLatch(); - WireLogError("MG_Remote server: in-process unix session #%llu pid=%d reaped exit=%d%s " - "sessionsFaulted=%lu (live %zu)", - static_cast(live.ordinal), - static_cast(::getpid()), code, - code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted, - m_live.size() - 1); - session = m_live.erase(session); + WireLogError("MG_Remote server: in-process session #%lu pid=%d reaped exit=%d%s sessionsFaulted=%lu", + m_sessionOrdinal, static_cast(::getpid()), code, + code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted); + return true; } - } - void StartSessionThread(std::unique_ptr connection) { - // No hand-off pair and no pre-read Hello: the session reads its own first frame and binds - // its shm segments from the fds the pair's second connection carries. - auto live = std::make_unique(); - live->ordinal = ++m_sessionOrdinal; - try { - live->thread = std::thread([live = live.get(), conn = std::move(connection)]() mutable { - const int code = RunSession(std::move(conn), {}, {}, -1, DataSource::None, 10000, - /*inProcess=*/true); - live->exit.store(code, std::memory_order_release); - live->done.store(true, std::memory_order_release); - }); - } catch (...) { - WireLogError("MG_Remote server: could not start the in-process unix session thread; the " - "connection is closed (it cannot be refused: it moved into the failed thread)"); - return; + // P12 (D5): the in-process server is stopping. Nobody new is served (every pending peer is told + // so by name), the live session - which reads the same flag each control slice - is waited out + // and reaped, and the supervisor returns 0. + int StopServing() { + for (auto& peer : m_pending) { + if (peer.fd >= 0) RefuseAndClose(peer.fd, Protocol::RefuseCode::Busy, "the server is stopping"); + peer.fd = -1; + } + m_pending.clear(); + while (m_active > 0) { + if (!Reap()) ::usleep(10000); + } + if (m_handoff >= 0) { + ::close(m_handoff); + m_handoff = -1; + } + WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast(::getpid())); + return 0; } - m_live.push_back(std::move(live)); - WireLogError("MG_Remote server: in-process unix session #%llu started on a thread (pid=%d, " - "%zu live of at most %u; %u accepted in this process)", - static_cast(m_sessionOrdinal), - static_cast(::getpid()), m_live.size(), m_maxSessions, - Server::LiveServerSessionCount()); - } - int StopServing() { - // P14 S2: EVERY live session, not one. Each reads g_inProcessStop on its own control - // slice, so this is the same orderly end the single-session shape had, N times over. - while (!m_live.empty()) { - Reap(); - if (!m_live.empty()) ::usleep(10000); + // exit_group closes files just before waitpid can observe the exit. Allow that small + // scheduling window before deciding whether a session is live - the old loop gave it to every + // connection it accepted while a child existed; this gives it to the one decision that + // depends on the answer: an AUTHENTICATED Hello's Busy (a client reconnecting the moment its + // last session ended must not be told Busy by a zombie). Only a peer holding the token can + // make the loop wait here; a DataBind's routing takes one look (Judge, fix round). + void WaitOutAnExitingSession() { + const auto reapDeadline = Clock::now() + std::chrono::milliseconds(50); + while (m_active > 0 && Clock::now() < reapDeadline) { + if (Reap()) break; + ::usleep(1000); + } + } + + void NoteFailure(const std::string& address, const char* why) { + const std::uint32_t window = m_backoff.NoteFailure(address, Clock::now()); + if (window != 0) + WireLogError( + "MG_Remote server: %s failed pre-auth %u times (last: %s); refusing it at accept for %u ms", + address.c_str(), m_backoff.Failures(address), why, window); + } + + // Accepts everything the listener has, deciding at accept what can be decided without reading. + int AcceptNew(Clock::time_point now) { + for (int burst = 0; burst < 64; ++burst) { + int fd = -1; + const auto accepted = SocketTransport::AcceptOne(m_listener, 0, &fd); + if (accepted == MOBILEGL_ERR_TIMEOUT) return 0; + if (accepted == MOBILEGL_ERR_OUT_OF_MEMORY) { + // f2-auth fix round. Out of descriptors (or memory) is not a dead listener: the + // connection waits in the backlog, and pending connections give descriptors back + // as they are judged or reach their deadline. The first cut returned 73 here, so + // a connection flood against a low RLIMIT_NOFILE (or a large pending cap) took the + // supervisor down. Now accepting pauses for kAcceptPauseMs and the loop goes on. + m_acceptResumeAt = now + std::chrono::milliseconds(kAcceptPauseMs); + WireLogError("MG_Remote server: accept paused for %u ms: out of descriptors or memory with %zu " + "connections awaiting authentication", + kAcceptPauseMs, m_pending.size()); + return 0; + } + if (accepted != MOBILEGL_OK) return 73; + std::string address = PeerAddress(fd); + if (const std::uint32_t retry = m_backoff.RetryAfterMs(address, now); retry != 0) { + WireLogError("MG_Remote server: connection from %s refused at accept: authentication backoff, " + "%u ms left", + address.c_str(), retry); + RefuseAndClose(fd, Protocol::RefuseCode::Authentication, Server::kAuthBackoffRefusal); + continue; + } + if (m_pending.size() >= m_knobs.maxPending) { + // Full. The queue is shared between addresses (PreAuthGate.h + // ChoosePendingToDisplace): the newcomer takes the busiest address's oldest slot, + // unless its own address is the busiest - then it is the one refused. + std::vector holders; + holders.reserve(m_pending.size()); + for (const auto& peer : m_pending) + holders.push_back(peer.address); + const std::size_t victim = Server::ChoosePendingToDisplace(holders, address); + if (victim >= m_pending.size()) { + WireLogError("MG_Remote server: connection from %s refused at accept: %zu connections are " + "already awaiting authentication", + address.c_str(), m_pending.size()); + RefuseAndClose(fd, Protocol::RefuseCode::Busy, Server::kPreAuthFull); + continue; + } + PendingPeer displaced = std::move(m_pending[victim]); + m_pending.erase(m_pending.begin() + static_cast(victim)); + WireLogError("MG_Remote server: pending connection from %s displaced by one from %s: the pre-auth " + "queue is full and %s holds the most of it", + displaced.address.c_str(), address.c_str(), displaced.address.c_str()); + RefuseAndClose(displaced.fd, Protocol::RefuseCode::Busy, Server::kPreAuthDisplaced); + // A displaced connection that had already held its slot past the grace is a silent + // holder like one that closed; otherwise displacement would be a free way out. + if (now - displaced.accepted >= std::chrono::milliseconds(Server::kPreAuthProbeGraceMs)) + NoteFailure(displaced.address, "held a pre-auth slot until displaced"); + } + PendingPeer peer; + peer.fd = fd; + peer.address = std::move(address); + peer.accepted = now; + peer.deadline = now + std::chrono::milliseconds(m_knobs.deadlineMs); + m_pending.push_back(std::move(peer)); + } + return 0; + } + + // A complete first frame. Every refusal here is by name; only an authenticated Hello forks. + void Judge(PendingPeer& peer) { + auto connection = std::make_unique(peer.fd, -1, TransportRole::Server); + const int fd = peer.fd; + peer.fd = -1; // `connection` owns it now + const auto& payload = peer.frame.Payload(); + const auto shape = Server::ClassifyFirstFrame(payload.data(), payload.size()); + if (shape == Server::FirstFrameShape::DataBind) { + // PH-7 (4). While a session is live, its data connection goes to its child with the + // nonce it presented (the child compares, once); with none live there is nobody it + // could belong to. Neither is an authentication failure: a data connection left over + // from a session that just ended is a legitimate client's. + std::uint8_t nonce[kDataNonceBytes] = {}; + (void)DecodeDataBind(payload, nonce); + // One look, not WaitOutAnExitingSession's 50 ms (fix round): DataBinds are + // unauthenticated and never counted, so waiting for each would let any peer stall this + // loop 50 ms per connection. Nothing needs the wait here - a child that is exiting + // either has cut its hand-off already (the send below fails and is refused by name) or + // refuses what is queued on its way out (ExitBeforeAccept / CloseHandoffRefusingQueued). + Reap(); + if (m_active <= 0) { + // StreamLink's frames may already follow the DataBind; read them away so the close + // does not become an RST that discards the refusal. + DrainUnread(fd); + Refuse(*connection, Protocol::RefuseCode::Authentication, "data connection names no live session"); + return; + } + // The send is MSG_DONTWAIT and the child cuts its end the moment its own data + // connection is bound (CloseHandoffRefusingQueued), so this either queues the + // descriptor for a child that will read or refuse it, or fails and is refused here. + if (m_handoff >= 0 && FdPassing::SendFd(m_handoff, fd, MobileGLByteSpan{nonce, sizeof(nonce)}, + /*dontWait=*/true) == MOBILEGL_OK) { + // The child holds its own descriptor now; ours goes without a shutdown(2), which + // would disconnect the child's too. + connection->CloseLocalCopy(); + return; + } + DrainUnread(fd); + Refuse(*connection, Protocol::RefuseCode::Authentication, kHandoffRefused); + return; + } + if (shape != Server::FirstFrameShape::Hello) { + DrainUnread(fd); + Refuse(*connection, Protocol::RefuseCode::MalformedHello, Server::FirstFrameRefusalDetail(shape)); + NoteFailure(peer.address, "malformed first frame"); + return; + } + const auto* hello = Protocol::GetCtrlEnvelope(payload.data())->msg_as_Hello(); + // The token FIRST, and the only answer an unauthenticated peer gets is this one + // (RefuseHandshake: Refuse{Authentication} "token mismatch", no fingerprint, no stamp). + if (AuthenticatePeerToken(*connection, hello->token()) != MOBILEGL_OK) { + // Anything the peer sent after its Hello is read away before the close, so the close + // cannot become an RST that discards the refusal already in flight. + DrainUnread(fd); + WireLogError("MG_Remote server: unauthenticated Hello from %s refused before any session work", + peer.address.c_str()); + NoteFailure(peer.address, "token mismatch"); + return; + } + m_backoff.NoteSuccess(peer.address); + WaitOutAnExitingSession(); + if (m_active > 0) { + // The frame is consumed, so closing cannot RST the refusal away. + Refuse(*connection, Protocol::RefuseCode::Busy, "one session is already active"); + return; + } + Fork(std::move(connection), payload); + } + + void Fork(std::unique_ptr connection, const std::vector& hello) { + int pair[2] = {-1, -1}; + if (FdPassing::CreateSocketPair(pair) != MOBILEGL_OK) { + Refuse(*connection, Protocol::RefuseCode::Busy, "could not create the data-connection hand-off"); + return; + } + if (m_handoffShape == Handoff::Thread) { + StartSessionThread(std::move(connection), hello, pair); + return; + } + std::fflush(nullptr); + const pid_t supervisor = ::getpid(); + const pid_t child = ::fork(); + if (child == 0) { + // P12 (D8): the child dies with this supervisor, not after its client has gone. + DieWithSupervisor(supervisor); + // The child owns this one connection and its end of the hand-off. Every OTHER pending + // peer's descriptor is closed here, or the supervisor's refusal of that peer would not + // end its connection while this session lives. + ::close(m_listener); + ::close(pair[0]); + for (const auto& other : m_pending) + if (other.fd >= 0) ::close(other.fd); + for (const auto& other : m_ready) + if (other.fd >= 0) ::close(other.fd); + const int controlFd = connection->StreamFd(); + ExitSessionProcess(RunSession(std::move(connection), hello, HandoffSource(pair[1], controlFd), pair[1], + DataSource::Handoff, 0)); + } + if (child < 0) { + ::close(pair[0]); + ::close(pair[1]); + Refuse(*connection, Protocol::RefuseCode::Busy, "could not create session child"); + return; + } + m_active = child; + ::close(pair[1]); + m_handoff = pair[0]; + connection->CloseLocalCopy(); + } + + // P12 (D5): THE THREAD HAND-OFF. The session thread owns the connection (moved in) and the + // hand-off's session end (pair[1]; RunSession cuts and closes it when Accept returns); this + // thread keeps pair[0] to route DataBinds, exactly as the fork shape's parent does. There is no + // CloseLocalCopy: nothing was duplicated. `m_active` is this process's pid while the session + // lives - "a session is live" is all the rest of the supervisor asks of it. + void StartSessionThread(std::unique_ptr connection, const std::vector& hello, + const int (&pair)[2]) { + if (m_sessionThread.joinable()) m_sessionThread.join(); // reaped already; never live here + const int controlFd = connection->StreamFd(); + const int sessionEnd = pair[1]; + m_sessionDone.store(false, std::memory_order_release); + m_sessionExit.store(0, std::memory_order_release); + ++m_sessionOrdinal; + try { + m_sessionThread = + std::thread([this, controlFd, sessionEnd, hello, conn = std::move(connection)]() mutable { + const int code = RunSession(std::move(conn), hello, HandoffSource(sessionEnd, controlFd), + sessionEnd, DataSource::Handoff, 0, /*inProcess=*/true); + m_sessionExit.store(code, std::memory_order_release); + m_sessionDone.store(true, std::memory_order_release); + }); + } + catch (...) { + ::close(pair[0]); + ::close(pair[1]); + WireLogError("MG_Remote server: could not start the in-process session thread; the connection is " + "closed (it cannot be refused: it moved into the failed thread)"); + return; + } + m_active = ::getpid(); + m_handoff = pair[0]; + WireLogError("MG_Remote server: in-process session #%lu started on a thread (pid=%d)", m_sessionOrdinal, + static_cast(::getpid())); + } + + int m_listener; + Server::PreAuthKnobs m_knobs; + Server::AuthBackoff m_backoff; + unsigned long& m_sessionsFaulted; + Handoff m_handoffShape = Handoff::Fork; + const std::atomic* m_stop = nullptr; + std::thread m_sessionThread; + std::atomic m_sessionDone{false}; + std::atomic m_sessionExit{0}; + unsigned long m_sessionOrdinal = 0; + pid_t m_active = -1; + // The supervisor's end of the live child's data-connection hand-off (PH-7 (4)); -1 while no + // session is live. It goes with the child: a reaped session can be handed nothing. + int m_handoff = -1; + std::vector m_pending; + std::vector m_ready; + // Accepting resumes at this time after accept(2) ran out of descriptors or memory. + static constexpr std::uint32_t kAcceptPauseMs = 100; + Clock::time_point m_acceptResumeAt{}; + }; + + // THE UNIX SHAPE OF THE IN-PROCESS DISPLAY SERVER (P12 D5 extended to unix endpoints). The + // endpoint is an @abstract name or a filesystem path. A client's control AND aux connections + // pair by the nonce each presents first (PairAcceptor, P11 PAIR - never by arrival order) and the + // shm segments' descriptors cross by SCM_RIGHTS, so there is no DataBind to route and no pre-auth + // gate - both are TCP exposure machinery, and a unix peer is local by construction (the forked + // unix supervisor runs without either). Sessions take the TCP in-process shape's thread hand-off: + // a thread per session, up to MOBILEGL_IPC_INPROC_MAX_SESSIONS (default 16) and Refuse{Busy} + // over it, the latch per session, stop by + // g_inProcessStop. P14 S2: THE SESSIONS ARE CONCURRENT - each one owns its ServerSession, its + // ServerLoop and its PipeInputs block through its own ThreadSessionScope (SessionRuntime.h), so a + // session's close, latch or fault reaches no other, and the server's own stop waits for all of + // them rather than for the one. + class UnixInProcessSupervisor { + public: + // P14 S2: MAX SESSIONS AT ONCE. Each live session is one control thread (RunSession) plus its + // own mgl-srv-apply, both carrying their own ServerSession/ServerLoop/PipeInputs block, so the + // cap is a resource limit and not a correctness one. The default is the display server's + // (mobilegl_server_serve_inprocess), which is where this class is used. + static constexpr unsigned kDefaultMaxSessions = 16; + + // MOBILEGL_IPC_INPROC_MAX_SESSIONS, capped, 0/unset/invalid = the default. An environment + // knob rather than a new argument to mobilegl_server_serve_inprocess: the embedded ABI has one + // parameter (the endpoint) and the display server passes none of this, exactly as it passes + // none of the transport knobs. + static unsigned MaxSessionsFromEnvironment() { + const char* text = std::getenv("MOBILEGL_IPC_INPROC_MAX_SESSIONS"); + if (text == nullptr || text[0] == 0) return kDefaultMaxSessions; + char* end = nullptr; + const long parsed = std::strtol(text, &end, 10); + if (end == text || parsed <= 0) return kDefaultMaxSessions; + return static_cast(std::min(parsed, 256)); + } + + UnixInProcessSupervisor(int listener, unsigned long& sessionsFaulted, + unsigned maxSessions = kDefaultMaxSessions) + : m_listener(listener), m_pairs(listener), m_sessionsFaulted(sessionsFaulted), + m_maxSessions(maxSessions == 0 ? kDefaultMaxSessions : maxSessions) {} + + ~UnixInProcessSupervisor() { + // Run() only returns with every session thread joined; a listener failure (73) is the one + // way out with one live, and a session is never left running behind the supervisor. + for (auto& session : m_live) { + if (session->thread.joinable()) session->thread.join(); + } + } + + int Run() { + for (;;) { + if (g_inProcessStop.load(std::memory_order_acquire)) return StopServing(); + Reap(); + std::unique_ptr control; + const auto accepted = m_pairs.Accept(250, control); + if (accepted == MOBILEGL_ERR_TIMEOUT) continue; + if (accepted != MOBILEGL_OK) { + WireLogError("MG_Remote server: the in-process unix listener failed (rc=%d)", + static_cast(accepted)); + return 73; + } + // P14 S2: THE CAP, NOT "ONE AT A TIME". A peer over the limit is refused by the same + // words the forked `--max-sessions` supervisor uses, and is not a latch: the sessions + // already running carry on and the connection is simply closed. + if (m_live.size() >= m_maxSessions) { + RefuseBusy(*control, "the session limit is reached"); + continue; + } + StartSessionThread(std::move(control)); + } + } + + private: + // One live session: its control thread, and the two facts the reap needs from it. The session + // itself (its ServerSession, its apply thread, its PipeInputs block) is owned by RunSession's + // own frame, on that thread - nothing here reaches into it. + struct LiveSession { + std::thread thread; + std::atomic done{false}; + std::atomic exit{0}; + unsigned long long ordinal = 0; + }; + + // The same reap as the TCP shape's ReapSessionThread: join, count faults, reset the latch for + // the next session in this process. Every finished session is reaped, in whatever order they + // finished. + void Reap() { + for (auto session = m_live.begin(); session != m_live.end();) { + LiveSession& live = **session; + if (!live.done.load(std::memory_order_acquire)) { + ++session; + continue; + } + if (live.thread.joinable()) live.thread.join(); + const int code = live.exit.load(std::memory_order_acquire); + if (code != 0) ++m_sessionsFaulted; + ResetSessionLatch(); + WireLogError("MG_Remote server: in-process unix session #%llu pid=%d reaped exit=%d%s " + "sessionsFaulted=%lu (live %zu)", + static_cast(live.ordinal), static_cast(::getpid()), code, + code == kSessionLatchedExitCode ? " (latched fault)" : "", m_sessionsFaulted, + m_live.size() - 1); + session = m_live.erase(session); + } + } + + void StartSessionThread(std::unique_ptr connection) { + // No hand-off pair and no pre-read Hello: the session reads its own first frame and binds + // its shm segments from the fds the pair's second connection carries. + auto live = std::make_unique(); + live->ordinal = ++m_sessionOrdinal; + try { + live->thread = std::thread([live = live.get(), conn = std::move(connection)]() mutable { + const int code = RunSession(std::move(conn), {}, {}, -1, DataSource::None, 10000, + /*inProcess=*/true); + live->exit.store(code, std::memory_order_release); + live->done.store(true, std::memory_order_release); + }); + } + catch (...) { + WireLogError("MG_Remote server: could not start the in-process unix session thread; the " + "connection is closed (it cannot be refused: it moved into the failed thread)"); + return; + } + m_live.push_back(std::move(live)); + WireLogError("MG_Remote server: in-process unix session #%llu started on a thread (pid=%d, " + "%zu live of at most %u; %u accepted in this process)", + static_cast(m_sessionOrdinal), static_cast(::getpid()), m_live.size(), + m_maxSessions, Server::LiveServerSessionCount()); + } + + int StopServing() { + // P14 S2: EVERY live session, not one. Each reads g_inProcessStop on its own control + // slice, so this is the same orderly end the single-session shape had, N times over. + while (!m_live.empty()) { + Reap(); + if (!m_live.empty()) ::usleep(10000); + } + WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast(::getpid())); + return 0; + } + + int m_listener; + // The connections that have not been paired yet live here between Accept calls. + Server::PairAcceptor m_pairs; + unsigned long& m_sessionsFaulted; + unsigned m_maxSessions; + // P14 S2: every live session, not one. Reaped by Run()'s Reap and by StopServing. + std::vector> m_live; + unsigned long long m_sessionOrdinal = 0; + }; + + // The server's environment preconditions, shared by the exec'd supervisor and the in-process + // display server (P12 D5: "same env preconditions as mobilegl_server_main"). 0 when they hold. + int CheckServerEnvironment() { + // The supervisor and every forked session child log under the server role. + // Set it here rather than trusting the launcher to have done so: log role is + // resolved from this env at first use, and a launcher that omits it would + // silently misfile this process's main-thread server lines as the client + // role and never forward them. Done before any logging so the value is cached + // as server and inherited by the fork children. DIAL/scrub stay hard checks. + if (const char* role = std::getenv("MOBILEGL_IPC_ROLE"); !role || std::strcmp(role, "server") != 0) { + ::setenv("MOBILEGL_IPC_ROLE", "server", 1); + } + const char* dial = std::getenv("MOBILEGL_IPC_DIAL"); + if (!dial || std::strcmp(dial, "no") != 0) { + std::fprintf(stderr, "MG_Remote server: MOBILEGL_IPC_DIAL=no anti-recursion catch (a) missing\n"); + return 64; + } + for (const char* name : + {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB"}) { + if (std::getenv(name)) { + std::fprintf(stderr, "MG_Remote server: envp scrub FAILED, catch (b): %s\n", name); + return 65; + } } - WireLogError("MG_Remote server: pid=%d in-process display server stopped", static_cast(::getpid())); return 0; } - - int m_listener; - // The connections that have not been paired yet live here between Accept calls. - Server::PairAcceptor m_pairs; - unsigned long& m_sessionsFaulted; - unsigned m_maxSessions; - // P14 S2: every live session, not one. Reaped by Run()'s Reap and by StopServing. - std::vector> m_live; - unsigned long long m_sessionOrdinal = 0; -}; - -// The server's environment preconditions, shared by the exec'd supervisor and the in-process -// display server (P12 D5: "same env preconditions as mobilegl_server_main"). 0 when they hold. -int CheckServerEnvironment() { - // The supervisor and every forked session child log under the server role. - // Set it here rather than trusting the launcher to have done so: log role is - // resolved from this env at first use, and a launcher that omits it would - // silently misfile this process's main-thread server lines as the client - // role and never forward them. Done before any logging so the value is cached - // as server and inherited by the fork children. DIAL/scrub stay hard checks. - if (const char* role = std::getenv("MOBILEGL_IPC_ROLE"); - !role || std::strcmp(role, "server") != 0) { - ::setenv("MOBILEGL_IPC_ROLE", "server", 1); - } - const char* dial = std::getenv("MOBILEGL_IPC_DIAL"); - if (!dial || std::strcmp(dial, "no") != 0) { - std::fprintf(stderr, "MG_Remote server: MOBILEGL_IPC_DIAL=no anti-recursion catch (a) missing\n"); - return 64; - } - for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB"}) { - if (std::getenv(name)) { - std::fprintf(stderr, "MG_Remote server: envp scrub FAILED, catch (b): %s\n", name); - return 65; - } - } - return 0; -} -} +} // namespace extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int argc, char** argv) { if (const int refused = CheckServerEnvironment(); refused != 0) return refused; @@ -1469,7 +1498,8 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a unsigned long maxSessions = 1; for (int i = 1; i < argc; ++i) { const std::string arg(argv[i]); - if (arg == "--serve") serve = true; + if (arg == "--serve") + serve = true; else if (arg == "--max-sessions" && i + 1 < argc) { char* end = nullptr; const auto count = std::strtoul(argv[++i], &end, 10); @@ -1477,10 +1507,13 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a // a process and independent backend state. In-process displays remain singleton. if (*end || count < 1 || count > 64) return 71; maxSessions = count; - } else if (endpoint.empty() && arg.compare(0, 2, "--") != 0) endpoint = arg; - else return 71; + } else if (endpoint.empty() && arg.compare(0, 2, "--") != 0) + endpoint = arg; + else + return 71; } - if (endpoint.empty()) if (const char* value = std::getenv("MOBILEGL_IPC_ENDPOINT")) endpoint = value; + if (endpoint.empty()) + if (const char* value = std::getenv("MOBILEGL_IPC_ENDPOINT")) endpoint = value; if (endpoint.empty()) return 71; const bool tcpEndpoint = endpoint.compare(0, 6, "tcp://") == 0; if (maxSessions > 1 && (!serve || tcpEndpoint)) return 71; @@ -1494,7 +1527,8 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a // off the core THIS server picks rather than a Java re-derivation of the rule. const char* rule = ""; const auto reserved = static_cast(Server::ReservedApplyCoreForThisProcess(&rule)); - std::fprintf(stdout, "MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x%llx (%s)\n", + std::fprintf(stdout, + "MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x%llx (%s)\n", reserved, rule); std::fflush(stdout); WireLogError("MG_Remote server: apply core reserved for dialled-in shared-segment clients: 0x%llx (%s)", @@ -1541,7 +1575,12 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_main(int a accepted = pairs->Accept(serve ? 250 : 30000, control); } if (accepted == MOBILEGL_ERR_TIMEOUT && serve) continue; - if (accepted != MOBILEGL_OK) { pairs.reset(); LogSupervisorSummary(sessionsFaulted); ::close(listener); return 73; } + if (accepted != MOBILEGL_OK) { + pairs.reset(); + LogSupervisorSummary(sessionsFaulted); + ::close(listener); + return 73; + } if (!serve) { // The process IS this one session: whatever else is waiting to be paired is let go of by // name now, not held for the session's lifetime. @@ -1611,7 +1650,8 @@ extern "C" __attribute__((visibility("default"))) int mobilegl_server_serve_inpr bool idle = false; if (!g_inProcessServing.compare_exchange_strong(idle, true, std::memory_order_acq_rel)) { WireLogError("MG_Remote server: an in-process display server is already serving in this process; " - "refusing a second one on %s", endpoint); + "refusing a second one on %s", + endpoint); return 74; } // The stop flag is NOT cleared here: a stop asked for before this thread got going still diff --git a/MobileGL/MG_Remote/Server/ServerSession.cpp b/MobileGL/MG_Remote/Server/ServerSession.cpp index 9acd4b17..5906bd07 100644 --- a/MobileGL/MG_Remote/Server/ServerSession.cpp +++ b/MobileGL/MG_Remote/Server/ServerSession.cpp @@ -109,8 +109,7 @@ namespace MobileGL::MG_Remote::Server { // belong in shm, never here), so one stack-free vector sized from PeekFrameSize is // the whole reader. The BUFFER_TOO_SMALL half of ReceiveFrame's contract is what // makes the two-step safe: a short buffer leaves the message queued. - MobileGLResult ReceiveEnvelope(Transport::ITransport& transport, std::vector& out, - Uint32 timeoutMs) { + MobileGLResult ReceiveEnvelope(Transport::ITransport& transport, std::vector& out, Uint32 timeoutMs) { Uint64 size = 0; MobileGLMutableByteSpan empty{nullptr, 0}; const MobileGLResult probe = transport.ReceiveFrame(empty, &size, timeoutMs); @@ -124,10 +123,8 @@ namespace MobileGL::MG_Remote::Server { return transport.ReceiveFrame(span, &size, 0); } - MobileGLResult SendEnvelope(Transport::ITransport& transport, - ::flatbuffers::FlatBufferBuilder& builder) { - return transport.SendFrame( - MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); + MobileGLResult SendEnvelope(Transport::ITransport& transport, ::flatbuffers::FlatBufferBuilder& builder) { + return transport.SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}); } // Every message from the peer is verified before a single field is read: the control @@ -181,16 +178,16 @@ namespace MobileGL::MG_Remote::Server { // SetCapabilityBits / SetConsumedSubsystems for why the one that used to be here was // the phase's marquee defect committed from the server's side. [[noreturn]] void FatalUnsetCallMask(Bool capBitsSet, Bool consumedSet) { - SessionFail(MGFatalFamily::UnsetCallMask, "MGPipe: Fatal{UnsetCallMask} - %s%s%s was never set on this ServerSession, " - "and there is no default: a guessed consumer mask makes the client's R-8 " - "liveness gates answer from a server-side fact the server never stated. The " - "client would stop emitting whole record families, clear its dirty flags on " - "acceptance anyway, and the lane would go green with the uploads lost " - "(ID-39, reflected). Call SetConsumedSubsystems() and SetCapabilityBits() " - "before Accept(); SetCapabilityBits(0) is a legitimate explicit answer", - capBitsSet ? "" : "SetCapabilityBits", - (!capBitsSet && !consumedSet) ? " and " : "", - consumedSet ? "" : "SetConsumedSubsystems"); + SessionFail(MGFatalFamily::UnsetCallMask, + "MGPipe: Fatal{UnsetCallMask} - %s%s%s was never set on this ServerSession, " + "and there is no default: a guessed consumer mask makes the client's R-8 " + "liveness gates answer from a server-side fact the server never stated. The " + "client would stop emitting whole record families, clear its dirty flags on " + "acceptance anyway, and the lane would go green with the uploads lost " + "(ID-39, reflected). Call SetConsumedSubsystems() and SetCapabilityBits() " + "before Accept(); SetCapabilityBits(0) is a legitimate explicit answer", + capBitsSet ? "" : "SetCapabilityBits", (!capBitsSet && !consumedSet) ? " and " : "", + consumedSet ? "" : "SetConsumedSubsystems"); } // ---- P5c ev: the reverse channel's PRODUCER callbacks (CONTRACT-P5C §4.1) -------- @@ -208,11 +205,12 @@ namespace MobileGL::MG_Remote::Server { // a Reserve that returns nullptr is Fatal{EventRingOverflow}. CountDrop and the // eventRingFull latch stay built and stay unused-by-policy; P9 owns the policy. [[noreturn]] void FatalEventRingOverflow(const char* eventName, Uint64 payloadBytes) { - SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - the producer of %s could not reserve " - "%llu bytes on SEG_EVENT. P5c's events are lossless and a full ring under " - "lockstep is a producer burst no measured workload has, so this is a " - "defect, not a drop (CONTRACT-P5C §4.4; the drop policy is P9's)", - eventName, static_cast(payloadBytes)); + SessionFail(MGFatalFamily::EventRingOverflow, + "MGPipe: Fatal{EventRingOverflow} - the producer of %s could not reserve " + "%llu bytes on SEG_EVENT. P5c's events are lossless and a full ring under " + "lockstep is a producer burst no measured workload has, so this is a " + "defect, not a drop (CONTRACT-P5C §4.4; the drop policy is P9's)", + eventName, static_cast(payloadBytes)); } // With run-ahead the client drains SEG_EVENT only at its waits, so a full event ring is an @@ -240,13 +238,13 @@ namespace MobileGL::MG_Remote::Server { } Uint32 MillisecondsSince(std::chrono::steady_clock::time_point start) { - const auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count(); + const auto elapsed = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); return elapsed <= 0 ? 0u : static_cast(elapsed); } - void* ReserveEventOrBlock(ServerSession& session, Transport::EventKind kind, - const char* eventName, Uint64 payloadBytes) { + void* ReserveEventOrBlock(ServerSession& session, Transport::EventKind kind, const char* eventName, + Uint64 payloadBytes) { // FORFEITED: every event after the first drop is a drop too, at once - no Reserve and // no second wait. The session is on its way down, and the peer has already shown it // does not read this ring. @@ -260,28 +258,25 @@ namespace MobileGL::MG_Remote::Server { // be a hang with a comment on it. const Uint64 cap = session.Events().Ring().MaxRecordBytes(); if (payloadBytes + sizeof(Transport::RingRecordHeader) > cap) { - SessionFail(MGFatalFamily::EventRingOverflow, "MGPipe: Fatal{EventRingOverflow} - %s needs %llu bytes and SEG_EVENT " - "caps ONE record at %llu (half its capacity). Flow control cannot help: " - "no amount of draining makes a record fit a ring that is too small for " - "it. SEG_EVENT's size is fixed (SessionSegmentSizes::EventRingBytes; the " - "MOBILEGL_IPC_EVENT_KB knob P5e proposed was never built), so slice the " - "event at its producer, as the writeback path already does", - eventName, static_cast(payloadBytes), - static_cast(cap)); + SessionFail(MGFatalFamily::EventRingOverflow, + "MGPipe: Fatal{EventRingOverflow} - %s needs %llu bytes and SEG_EVENT " + "caps ONE record at %llu (half its capacity). Flow control cannot help: " + "no amount of draining makes a record fit a ring that is too small for " + "it. SEG_EVENT's size is fixed (SessionSegmentSizes::EventRingBytes; the " + "MOBILEGL_IPC_EVENT_KB knob P5e proposed was never built), so slice the " + "event at its producer, as the writeback path already does", + eventName, static_cast(payloadBytes), + static_cast(cap)); } const auto signals = session.DataLink()->Signals(); Transport::Doorbell& bell = session.DataLink()->ConsumerBell(); - const auto drained = [signals] { - return signals.EventRingFull->load(std::memory_order_acquire) == 0; - }; + const auto drained = [signals] { return signals.EventRingFull->load(std::memory_order_acquire) == 0; }; // THE WAIT ALSO ENDS ON A STOP (PH-6 fix round). ServerLoop::Stop() raises the session's // request and rings this same bell; a predicate that asked only "drained?" swallowed // that ring and parked again for the rest of the knob, so any knob over Stop()'s 5000 ms // join turned an ordinary end of the control stream into Fatal{ApplyThreadJoinTimeout}. - const auto drainedOrStopping = [&drained, &session] { - return drained() || session.ApplyStopRequested(); - }; + const auto drainedOrStopping = [&drained, &session] { return drained() || session.ApplyStopRequested(); }; // ONE DEADLINE FOR THE WHOLE RESERVATION, NOT ONE PER PARK. The loop goes round as // often as the client clears the latch - that is how a client that drains in pieces // eventually makes room - but every round spends the same budget, so a peer that @@ -299,13 +294,13 @@ namespace MobileGL::MG_Remote::Server { const auto outOfPatience = [&]() { return shortDrains == 0 ? session.ForfeitReverseChannel("NotDraining", "the client did not drain SEG_EVENT", - eventName, payloadBytes, MillisecondsSince(start), - budgetMs, shortDrains) + eventName, payloadBytes, MillisecondsSince(start), budgetMs, + shortDrains) : session.ForfeitReverseChannel("TooSlow", "the client drained SEG_EVENT but never made room for " "this record", - eventName, payloadBytes, MillisecondsSince(start), - budgetMs, shortDrains); + eventName, payloadBytes, MillisecondsSince(start), budgetMs, + shortDrains); }; // Asked AFTER Dead() everywhere below: under spawn a stop usually FOLLOWS the peer's // hangup (control EOF), and the hangup is the more specific answer. The same fact can @@ -341,7 +336,8 @@ namespace MobileGL::MG_Remote::Server { if (!drained()) { if (session.ApplyStopRequested()) return stopped(); const auto left = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + deadline - std::chrono::steady_clock::now()) + .count(); if (left <= 0) return outOfPatience(); if (!bell.Wait(*signals.ConsumerParked, drainedOrStopping, /*spinUs=*/0, static_cast(left))) { @@ -386,8 +382,10 @@ namespace MobileGL::MG_Remote::Server { // @Ph-declined (ID-P7-1): returns ServerSession& - there is no session to hand // back and no honest "declined" reference, and the input is the backend's own // reverse call, not a peer's bytes. Stays Fatal. - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"%s.session-missing\"} - " - "a reverse callback has no accepted session owner", callback); + SessionFail(MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"%s.session-missing\"} - " + "a reverse callback has no accepted session owner", + callback); } return *session; } @@ -407,8 +405,7 @@ namespace MobileGL::MG_Remote::Server { return session->Events().Ring().Capacity(); } - void ServerOnBufferWriteback(MG_Pipe::MGPipeHandle res, Uint64 offset, - MG_Pipe::MGPBlobRef bytes) { + void ServerOnBufferWriteback(MG_Pipe::MGPipeHandle res, Uint64 offset, MG_Pipe::MGPBlobRef bytes) { ServerSession& session = ReverseCallbackOwner("OnBufferWriteback"); if (bytes.Seg != MG_Pipe::kMGHostSpanSegNone) { // The backend handed over a segment-tagged blobref. The ONLY legal shape at @@ -417,15 +414,16 @@ namespace MobileGL::MG_Remote::Server { // @Ph-declined (ID-P7-1): the blobref is the BACKEND's (a reverse callback with // a void signature and no caller that could act on a decline), and it rides the // ServerSession& above; not a peer's bytes. Stays Fatal. - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Seg\"} - the " - "writeback producer was handed Seg %u; at the backend boundary the " - "blobref names the backend's own mapped bytes (Seg = " - "kMGHostSpanSegNone) and the copy into SEG_EVENT is the producer's", - bytes.Seg); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"OnBufferWriteback.Seg\"} - the " + "writeback producer was handed Seg %u; at the backend boundary the " + "blobref names the backend's own mapped bytes (Seg = " + "kMGHostSpanSegNone) and the copy into SEG_EVENT is the producer's", + bytes.Seg); } const Uint64 payloadBytes = sizeof(Transport::EventBufferWritebackHead) + bytes.Size; - void* slot = ReserveEventOrBlock(session, Transport::kEventBufferWriteback, - "kEventBufferWriteback", payloadBytes); + void* slot = + ReserveEventOrBlock(session, Transport::kEventBufferWriteback, "kEventBufferWriteback", payloadBytes); Transport::EventBufferWritebackHead head{}; head.Resource = Transport::EventHandle{res.Slot, res.Gen}; head.Offset = offset; @@ -440,13 +438,11 @@ namespace MobileGL::MG_Remote::Server { session.PublishEvents(); } - void ServerOnGpuWritten(MG_Pipe::MGPipeHandle res, Uint rangeCount, - const MG_Pipe::MGPRange* ranges) { + void ServerOnGpuWritten(MG_Pipe::MGPipeHandle res, Uint rangeCount, const MG_Pipe::MGPRange* ranges) { ServerSession& session = ReverseCallbackOwner("OnGpuWritten"); const Uint64 tailBytes = static_cast(rangeCount) * sizeof(Transport::EventRange); const Uint64 payloadBytes = sizeof(Transport::EventGpuWrittenHead) + tailBytes; - void* slot = ReserveEventOrBlock(session, Transport::kEventGpuWritten, - "kEventGpuWritten", payloadBytes); + void* slot = ReserveEventOrBlock(session, Transport::kEventGpuWritten, "kEventGpuWritten", payloadBytes); Transport::EventGpuWrittenHead head{}; head.Resource = Transport::EventHandle{res.Slot, res.Gen}; head.RangeCount = static_cast(rangeCount); @@ -454,8 +450,7 @@ namespace MobileGL::MG_Remote::Server { if (tailBytes != 0) { // EventRange and MGPRange are the same two Uint64s (EventRing.h asserts it at // EventRange's declaration), so the tail is a plain copy rather than a per-element conversion. - std::memcpy(static_cast(slot) + sizeof(head), ranges, - static_cast(tailBytes)); + std::memcpy(static_cast(slot) + sizeof(head), ranges, static_cast(tailBytes)); } session.PublishEvents(); } @@ -463,8 +458,8 @@ namespace MobileGL::MG_Remote::Server { void ServerOnSurfaceChanged(const MG_Pipe::MGPSurfaceInfo* info) { ServerSession& session = ReverseCallbackOwner("OnSurfaceChanged"); constexpr Uint64 payloadBytes = sizeof(Transport::EventSurfaceChangedHead); - void* slot = ReserveEventOrBlock(session, Transport::kEventSurfaceChanged, - "kEventSurfaceChanged", payloadBytes); + void* slot = + ReserveEventOrBlock(session, Transport::kEventSurfaceChanged, "kEventSurfaceChanged", payloadBytes); Transport::EventSurfaceChangedHead head{}; if (info != nullptr) { head.Width = info->Width; @@ -492,10 +487,11 @@ namespace MobileGL::MG_Remote::Server { template void InstallReverseCallback(Fn& entry, Fn producer) { if (entry != nullptr && entry != producer) { - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - a reverse " - "MGPipeCallbacks entry is already claimed by a different function. With " - "an active transport the four reverse entries are the server " - "session's producers; the client installs them under monolith only"); + SessionFail(MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - a reverse " + "MGPipeCallbacks entry is already claimed by a different function. With " + "an active transport the four reverse entries are the server " + "session's producers; the client installs them under monolith only"); } entry = producer; } @@ -518,7 +514,9 @@ namespace MobileGL::MG_Remote::Server { } // namespace - Uint32 ServerSessionCount() { return g_liveSessions.load(std::memory_order_acquire); } + Uint32 ServerSessionCount() { + return g_liveSessions.load(std::memory_order_acquire); + } ServerSession& ServerSessionInstance() { if (ServerSession* const own = Detail::CurrentSession()) return *own; @@ -536,10 +534,13 @@ namespace MobileGL::MG_Remote::Server { ServerSession::ServerSession() { m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ServerConsumer); - m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut(); + m_commands = &m_link->CommandsIn(); + m_events = &m_link->EventsOut(); } - ServerSession::~ServerSession() { Close(); } + ServerSession::~ServerSession() { + Close(); + } void ServerSession::SetSegmentSizes(const Transport::SessionSegmentSizes& sizes) { if (m_accepted) { @@ -551,7 +552,9 @@ namespace MobileGL::MG_Remote::Server { m_sizesSet = true; } - void ServerSession::SetBackend(MG_Backend::BackendObject* backend) { m_backend = backend; } + void ServerSession::SetBackend(MG_Backend::BackendObject* backend) { + m_backend = backend; + } void ServerSession::SetCapabilityBits(Uint64 capBits) { m_capBits = capBits; @@ -563,7 +566,9 @@ namespace MobileGL::MG_Remote::Server { m_consumedSet = true; } - Bool ServerSession::CallMaskIsSet() const { return m_capBitsSet && m_consumedSet; } + Bool ServerSession::CallMaskIsSet() const { + return m_capBitsSet && m_consumedSet; + } Uint64 ServerSession::CallMask() const { if (!CallMaskIsSet()) { @@ -572,10 +577,11 @@ namespace MobileGL::MG_Remote::Server { return m_capBits | MGCapsConsumerBits(m_consumedSubsystems); } - Bool ServerSession::Accepted() const { return m_accepted; } + Bool ServerSession::Accepted() const { + return m_accepted; + } - MobileGLResult ServerSession::Accept(Transport::ITransport& transport, - const std::vector* firstFrame) { + MobileGLResult ServerSession::Accept(Transport::ITransport& transport, const std::vector* firstFrame) { // P14 S2: A SERVED SESSION DOES NOT CLAIM THE PROCESS TABLE, because the table is SHARED. // Its entries are the same five functions for every session and they route by the calling // thread (ServerSession::Active()), so a second session installing them is the idempotent @@ -586,19 +592,20 @@ namespace MobileGL::MG_Remote::Server { // Fatal{callback-double-install} the single-session ruling asks for. const Bool sharedTable = Detail::CurrentSession() == this; ServerSession* expectedOwner = nullptr; - if (!sharedTable && !g_sessionOwner.compare_exchange_strong(expectedOwner, this, - std::memory_order_acq_rel, std::memory_order_acquire)) { + if (!sharedTable && !g_sessionOwner.compare_exchange_strong(expectedOwner, this, std::memory_order_acq_rel, + std::memory_order_acquire)) { if (expectedOwner == this) return MOBILEGL_ERR_INVALID_ARGUMENT; - SessionFail(MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - another " - "ServerSession already owns or is accepting the process reverse channel"); + SessionFail(MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"callback-double-install\"} - another " + "ServerSession already owns or is accepting the process reverse channel"); } struct ReleaseFailedClaim { ServerSession* self; ~ReleaseFailedClaim() { if (!self->Accepted()) { ServerSession* expected = self; - g_sessionOwner.compare_exchange_strong(expected, nullptr, - std::memory_order_release, std::memory_order_relaxed); + g_sessionOwner.compare_exchange_strong(expected, nullptr, std::memory_order_release, + std::memory_order_relaxed); } } } releaseOnFailure{this}; @@ -617,11 +624,10 @@ namespace MobileGL::MG_Remote::Server { // shared memory exists. Its whole purpose is to refuse to interpret the peer's bytes. std::vector frame; if (firstFrame != nullptr) frame = *firstFrame; - const MobileGLResult received = firstFrame != nullptr ? MOBILEGL_OK - : ReceiveEnvelope(transport, frame, kHandshakeTimeoutMs); + const MobileGLResult received = + firstFrame != nullptr ? MOBILEGL_OK : ReceiveEnvelope(transport, frame, kHandshakeTimeoutMs); if (received != MOBILEGL_OK) { - MGLOG_E("MG_Remote server: no Hello within %u ms (rc=%d)", kHandshakeTimeoutMs, - static_cast(received)); + MGLOG_E("MG_Remote server: no Hello within %u ms (rc=%d)", kHandshakeTimeoutMs, static_cast(received)); return received; } const ::MobileGL::Wire::CtrlEnvelope* envelope = ParseEnvelope(frame); @@ -637,8 +643,7 @@ namespace MobileGL::MG_Remote::Server { "first control frame is not a verifiable Hello"); } const ::MobileGL::Wire::Hello* hello = envelope->msg_as_Hello(); - const char* theirStamp = - hello->buildFingerprint() == nullptr ? nullptr : hello->buildFingerprint()->c_str(); + const char* theirStamp = hello->buildFingerprint() == nullptr ? nullptr : hello->buildFingerprint()->c_str(); // PH-7 (1). One policy, one constant-time comparison, shared with ServerMain's supervisor // and its session child (Handshake.h AuthenticatePeerToken). This site used std::strcmp, @@ -656,15 +661,14 @@ namespace MobileGL::MG_Remote::Server { if (hello->dialMode() != ::MobileGL::Wire::DialMode::No && hello->dialMode() != ::MobileGL::Wire::DialMode::Fork && hello->dialMode() != ::MobileGL::Wire::DialMode::Connect) - return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::MalformedHello, - "unknown dial mode"); + return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::MalformedHello, "unknown dial mode"); // P11 B1: remembered for the apply thread's `auto` policy (ApplyThreadPolicy.h). m_peer = hello->dialMode() == ::MobileGL::Wire::DialMode::Connect ? ApplyPeer::Connected : hello->dialMode() == ::MobileGL::Wire::DialMode::Fork ? ApplyPeer::Forked : ApplyPeer::InProcess; m_clientPid = hello->pid(); - const MobileGLResult compatible = ValidatePeerHandshake(transport, hello->abiMajor(), - hello->abiMinor(), hello->wireFingerprint(), theirStamp, hello->dialMode()); + const MobileGLResult compatible = ValidatePeerHandshake( + transport, hello->abiMajor(), hello->abiMinor(), hello->wireFingerprint(), theirStamp, hello->dialMode()); if (compatible != MOBILEGL_OK) return compatible; const Uint64 ourFingerprint = WireFingerprint(); const auto* terms = hello->linkTerms(); @@ -680,9 +684,9 @@ namespace MobileGL::MG_Remote::Server { "stream requires a data connection"); if (hello->backendType() >= static_cast(BackendType::BackendTypeCount) || (m_backend != nullptr && hello->backendType() != static_cast(m_backend->GetBackendType()))) - return RefuseHandshake(transport, ::MobileGL::Wire::RefuseCode::Backend, - "backend request differs from server", m_backend == nullptr ? 0 : - static_cast(m_backend->GetBackendType()), hello->backendType()); + return RefuseHandshake( + transport, ::MobileGL::Wire::RefuseCode::Backend, "backend request differs from server", + m_backend == nullptr ? 0 : static_cast(m_backend->GetBackendType()), hello->backendType()); // ---- P11 A1: this side's adopt tier, from its own knob and the plane the Hello asked // for, before a byte of Welcome. A stream refuses T0/T1 by name and serves T2; over @@ -767,7 +771,6 @@ namespace MobileGL::MG_Remote::Server { m_replies = ReplyPool(); m_replies.SetLink(m_link.get()); - m_consumer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); m_replies.SetLink(m_link.get()); m_applier = PipeApplier(&m_segments, &m_replies); @@ -777,8 +780,7 @@ namespace MobileGL::MG_Remote::Server { ::flatbuffers::FlatBufferBuilder builder(1024); using Slot = Transport::SessionSegmentSlot; const auto segmentRef = [&](Uint32 id, ::MobileGL::Wire::SegmentKind kind, Slot slot) { - return ::MobileGL::Wire::CreateSegmentRefDirect(builder, id, kind, - m_link->Memory().AnnouncedSize(slot), + return ::MobileGL::Wire::CreateSegmentRefDirect(builder, id, kind, m_link->Memory().AnnouncedSize(slot), m_link->Memory().AnnouncedName(slot)); }; auto cmd = segmentRef(1, ::MobileGL::Wire::SegmentKind::Cmd, Slot::Cmd); @@ -786,9 +788,8 @@ namespace MobileGL::MG_Remote::Server { auto reply = segmentRef(3, ::MobileGL::Wire::SegmentKind::Reply, Slot::Reply); auto event = segmentRef(4, ::MobileGL::Wire::SegmentKind::Event, Slot::Event); auto stamp = builder.CreateString(BuildFingerprint()); - const auto nonce = pendingStream != nullptr - ? builder.CreateVector(dataNonce, sizeof(dataNonce)) - : ::flatbuffers::Offset<::flatbuffers::Vector>(); + const auto nonce = pendingStream != nullptr ? builder.CreateVector(dataNonce, sizeof(dataNonce)) + : ::flatbuffers::Offset<::flatbuffers::Vector>(); const Uint64 maxReply = m_sizes.ReplyBytes / m_sizes.ReplySlotCount - sizeof(Transport::ReplySlotHeader); const Uint64 cmdWindow = m_link->Memory().CmdRingCapacity(); const Uint64 stageWindow = m_link->Memory().StageBytes(); @@ -808,14 +809,12 @@ namespace MobileGL::MG_Remote::Server { static_cast(terms->stageWindowBytes()), static_cast(terms->eventWindowBytes()), static_cast(terms->maxReplyBytes()), - static_cast(cmdWindow), - static_cast(stageWindow), - static_cast(eventWindow), - static_cast(maxReply)); + static_cast(cmdWindow), static_cast(stageWindow), + static_cast(eventWindow), static_cast(maxReply)); } - auto negotiated = ::MobileGL::Wire::CreateLinkTerms(builder, terms->dataPlane(), - ::MobileGL::Wire::WireForm::StructImage, maxReply, cmdWindow, stageWindow, eventWindow, - static_cast(m_adoptT0Asked ? m_adoptAsk : 2)); + auto negotiated = ::MobileGL::Wire::CreateLinkTerms( + builder, terms->dataPlane(), ::MobileGL::Wire::WireForm::StructImage, maxReply, cmdWindow, stageWindow, + eventWindow, static_cast(m_adoptT0Asked ? m_adoptAsk : 2)); auto welcome = ::MobileGL::Wire::CreateWelcome( builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, // CONTRACT-P6 4.3: THE SERVER'S OWN PID, not an echo of the client's. @@ -825,12 +824,11 @@ namespace MobileGL::MG_Remote::Server { // this is its natural carrier: under spawn it is the one value in the handshake // that a same-process session cannot produce, because there the two pids are // equal by construction. - static_cast(SelfProcessId()), - cmd, stage, reply, event, stamp, ourFingerprint, ourFingerprint, negotiated, - m_backend == nullptr ? hello->backendType() : static_cast(m_backend->GetBackendType()), - nonce); - auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Welcome, welcome.Union()); + static_cast(SelfProcessId()), cmd, stage, reply, event, stamp, ourFingerprint, ourFingerprint, + negotiated, + m_backend == nullptr ? hello->backendType() : static_cast(m_backend->GetBackendType()), nonce); + auto root = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Welcome, welcome.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); const MobileGLResult sent = SendEnvelope(transport, builder); if (sent != MOBILEGL_OK) { @@ -873,11 +871,9 @@ namespace MobileGL::MG_Remote::Server { // by definition not monolith, so finding one of them claimed here is the double // installation the check exists to name. InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnGlError, &ServerOnGlError); - InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnBufferWriteback, - &ServerOnBufferWriteback); + InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnBufferWriteback, &ServerOnBufferWriteback); InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnGpuWritten, &ServerOnGpuWritten); - InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged, - &ServerOnSurfaceChanged); + InstallReverseCallback(MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged, &ServerOnSurfaceChanged); // ... and, beside them, the one NUMBER a writeback producer has to know: how much of // SEG_EVENT one record may be. Same ownership rule as the four entries above - installed // here, released at Close only if it is still ours - because a producer left holding a @@ -948,8 +944,7 @@ namespace MobileGL::MG_Remote::Server { callMask &= ~static_cast(MG_Pipe::kCapTimerQuery); ::flatbuffers::FlatBufferBuilder builder(4096); - auto dynamicVector = - builder.CreateVector(dynamicBytes, static_cast<::flatbuffers::uoffset_t>(sizeof(dynamic))); + auto dynamicVector = builder.CreateVector(dynamicBytes, static_cast<::flatbuffers::uoffset_t>(sizeof(dynamic))); auto rendererVector = builder.CreateVector(renderer.data(), renderer.size()); auto formatsVector = builder.CreateVector(formats.data(), formats.size()); // The client reads this back through BackendObject_Remote::GetBackendAPIVersionString() @@ -960,11 +955,11 @@ namespace MobileGL::MG_Remote::Server { // answered "Espryt (MobileGL Core) (, OpenGL ES .)", // i.e. it dropped the only place the real device was named. auto apiVersion = builder.CreateString(m_backend->GetBackendAPIVersionString()); - auto snapshot = ::MobileGL::Wire::CreateCapsSnapshot( - builder, dynamicVector, rendererVector, formatsVector, /*extensions=*/0, apiVersion, - callMask, static_cast(m_backend->GetBackendType())); - auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::CapsSnapshot, snapshot.Union()); + auto snapshot = ::MobileGL::Wire::CreateCapsSnapshot(builder, dynamicVector, rendererVector, formatsVector, + /*extensions=*/0, apiVersion, callMask, + static_cast(m_backend->GetBackendType())); + auto root = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::CapsSnapshot, snapshot.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); return SendEnvelope(*m_transport, builder); } @@ -978,16 +973,18 @@ namespace MobileGL::MG_Remote::Server { // cannot end the session it failed to join. What ends the wait is the deadline or the // control peer going away; the deadline is refused on the control connection, where the // client is listening. - MobileGLResult ServerSession::BindDataConnection(Transport::ITransport& control, - Transport::ILink& link, const Uint8* nonce) { + MobileGLResult ServerSession::BindDataConnection(Transport::ITransport& control, Transport::ILink& link, + const Uint8* nonce) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(kHandshakeTimeoutMs); Uint32 refused = 0; for (;;) { - const auto left = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + const auto left = + std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()) + .count(); if (left <= 0) { MGLOG_E("MG_Remote server: no data connection presented this session's nonce within " - "%u ms (%u refused)", kHandshakeTimeoutMs, refused); + "%u ms (%u refused)", + kHandshakeTimeoutMs, refused); return RefuseHandshake(control, ::MobileGL::Wire::RefuseCode::Authentication, "no data connection presented the session nonce"); } @@ -1017,7 +1014,8 @@ namespace MobileGL::MG_Remote::Server { void ServerSession::AttachDataLink(std::unique_ptr link) { m_link = std::move(link); - m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut(); + m_commands = &m_link->CommandsIn(); + m_events = &m_link->EventsOut(); m_consumer.SetLink(m_link->Capabilities().PublishIsDelivery ? nullptr : m_link.get()); m_replies.SetLink(m_link.get()); if (m_link) SetExternalDoorbells(&m_link->ConsumerBell(), &m_link->ProducerBell()); @@ -1033,8 +1031,7 @@ namespace MobileGL::MG_Remote::Server { if (m_closed) return; m_closed = true; ServerSession* expectedActive = this; - g_active.compare_exchange_strong(expectedActive, nullptr, - std::memory_order_acq_rel, std::memory_order_acquire); + g_active.compare_exchange_strong(expectedActive, nullptr, std::memory_order_acq_rel, std::memory_order_acquire); if (m_accepted) { // P14 S2: DROP THIS SESSION FIRST, THEN ASK WHETHER ANY IS LEFT. The two process-wide // hooks below (the segment resolver and the reverse-channel table) are shared by every @@ -1071,14 +1068,13 @@ namespace MobileGL::MG_Remote::Server { // A default ask that ended on T2 says its totals at MGLOG_D, like its refusal. const bool quiet = m_adoptT0Quiet && !m_adoptT0; const char* ran = m_adoptT0 ? "ran T0" : m_adoptT0Settled ? "not granted (T2)" : "no native bind (T2)"; -#define MGL_T0_SERVER_TOTALS(LOG) \ - LOG("MG_Remote server: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ - "refused %llu, stale offers %llu, malformed %llu, unclaimed %zu", \ - ran, m_adoptT0Quiet ? " (default ask)" : "", static_cast(m_t0Stores), \ - static_cast(m_t0Bytes), static_cast(m_t0Declined), \ - static_cast(m_t0Refusals), \ - static_cast(m_adoptInbox.StaleOffers()), \ - static_cast(m_adoptInbox.MalformedOffers()), m_adoptInbox.Kept()) +#define MGL_T0_SERVER_TOTALS(LOG) \ + LOG("MG_Remote server: T0 session totals - %s%s; stores imported %llu, bytes %llu, declined %llu, " \ + "refused %llu, stale offers %llu, malformed %llu, unclaimed %zu", \ + ran, m_adoptT0Quiet ? " (default ask)" : "", static_cast(m_t0Stores), \ + static_cast(m_t0Bytes), static_cast(m_t0Declined), \ + static_cast(m_t0Refusals), static_cast(m_adoptInbox.StaleOffers()), \ + static_cast(m_adoptInbox.MalformedOffers()), m_adoptInbox.Kept()) if (quiet) { MGL_T0_SERVER_TOTALS(MGLOG_D); } else { @@ -1097,14 +1093,16 @@ namespace MobileGL::MG_Remote::Server { *m_events = Transport::EventRingProducer(); m_replies = ReplyPool(); m_link = Transport::CreateSharedLink(Transport::TransportRoleTag::ServerConsumer); - m_commands = &m_link->CommandsIn(); m_events = &m_link->EventsOut(); - m_externalConsumerBell = nullptr; m_externalProducerBell = nullptr; + m_commands = &m_link->CommandsIn(); + m_events = &m_link->EventsOut(); + m_externalConsumerBell = nullptr; + m_externalProducerBell = nullptr; m_link->Memory().Close(); m_transport = nullptr; m_accepted = false; ServerSession* expectedOwner = this; - g_sessionOwner.compare_exchange_strong(expectedOwner, nullptr, - std::memory_order_release, std::memory_order_relaxed); + g_sessionOwner.compare_exchange_strong(expectedOwner, nullptr, std::memory_order_release, + std::memory_order_relaxed); } // ---- P11 B2: T0 (docs/archive/contracts/CONTRACT-P11.md B2) ------------------------------------------ @@ -1127,8 +1125,9 @@ namespace MobileGL::MG_Remote::Server { void ServerSession::SettleAdoptT0AtBind() { if (!m_accepted || !m_adoptT0Asked || m_adoptT0Settled) return; m_adoptT0Settled = true; - const char* backendName = m_backend == nullptr ? "server" - : m_backend->GetBackendType() == BackendType::DirectVulkan ? "Magma" : "Espryt"; + const char* backendName = m_backend == nullptr ? "server" + : m_backend->GetBackendType() == BackendType::DirectVulkan ? "Magma" + : "Espryt"; std::string why; // A CLIENT IN THIS PROCESS SHARES THE SERVER'S MEMORY ALREADY: its persistent maps are the // stores' own mappings, donated as monolith donates them, and no AHardwareBuffer, import @@ -1177,15 +1176,15 @@ namespace MobileGL::MG_Remote::Server { } if (!why.empty()) { ++m_t0Refusals; - const char* word = MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed" + const char* word = MG_Config::Ipc.AllowAdoptT0 == 0 ? "disallowed" : inProcess ? "in process" : !Transport::AdoptT0::PlatformHasAhb() ? "no AHardwareBuffer" : "POST"; if (m_adoptT0Quiet) { // THE DEFAULT ASK (MOBILEGL_IPC_ADOPT_TIER unset) FALLS BACK QUIETLY: every host // session would otherwise carry this line. Counted (T0Refusals, the totals line). - MGLOG_D("MG_Remote server: T0 by default not granted (%s) - %s; the session runs T2", - word, why.c_str()); + MGLOG_D("MG_Remote server: T0 by default not granted (%s) - %s; the session runs T2", word, + why.c_str()); return; } MGLOG_W("MG_Remote server: Refuse{AdoptT0Unavailable, \"%s\"} - the client asked T0 " @@ -1226,8 +1225,7 @@ namespace MobileGL::MG_Remote::Server { int hop = -1; Transport::AdoptT0::Offer offer{}; std::string why; - const AdoptInbox::Outcome outcome = - m_adoptInbox.Take(seq, Transport::AdoptT0::kOfferWaitMs, &hop, &offer, why); + const AdoptInbox::Outcome outcome = m_adoptInbox.Take(seq, Transport::AdoptT0::kOfferWaitMs, &hop, &offer, why); const auto closeHop = [&] { #if !defined(_WIN32) if (hop >= 0) ::close(hop); @@ -1336,27 +1334,35 @@ namespace MobileGL::MG_Remote::Server { return true; } - Transport::RingConsumer& ServerSession::CommandRing() { return *m_commands; } + Transport::RingConsumer& ServerSession::CommandRing() { + return *m_commands; + } Transport::RingControl& ServerSession::Control() { Transport::RingControl* control = m_link->Memory().CmdControl(); if (control == nullptr) { // @Ph-declined (ID-P7-1): returns RingControl& - no honest reference exists before // Accept() mapped SEG_CMD, and this is a call-order invariant, not a peer's bytes. - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::Control\"} - the control " - "page does not exist until Accept() has mapped SEG_CMD"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::Control\"} - the control " + "page does not exist until Accept() has mapped SEG_CMD"); } return *control; } - Wire::SegmentTable& ServerSession::Segments() { return m_segments; } - PipeApplier& ServerSession::Applier() { return m_applier; } - ReplyPool& ServerSession::Replies() { return m_replies; } + Wire::SegmentTable& ServerSession::Segments() { + return m_segments; + } + PipeApplier& ServerSession::Applier() { + return m_applier; + } + ReplyPool& ServerSession::Replies() { + return m_replies; + } // The bell the apply thread parks on. On the server endpoint of an InProcessTransport that // is SelfDoorbell(); the client reaches the same bell through its own PeerDoorbell(). - void ServerSession::SetExternalDoorbells(Transport::Doorbell* consumer, - Transport::Doorbell* producer) { + void ServerSession::SetExternalDoorbells(Transport::Doorbell* consumer, Transport::Doorbell* producer) { m_externalConsumerBell = consumer; m_externalProducerBell = producer; } @@ -1370,8 +1376,9 @@ namespace MobileGL::MG_Remote::Server { if (m_transport == nullptr) { // @Ph-declined (ID-P7-1): returns Doorbell& - no bell exists to hand back before // Accept(); a call-order invariant, not a peer's bytes. - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ConsumerDoorbell\"} - no " - "transport; Accept() has not run"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ConsumerDoorbell\"} - no " + "transport; Accept() has not run"); } if (m_transport->Role() == Transport::TransportRole::InProcess) { return static_cast(m_transport)->SelfDoorbell(); @@ -1381,9 +1388,10 @@ namespace MobileGL::MG_Remote::Server { // every transport has to invent a home for. // @Ph-declined (ID-P7-1): returns Doorbell& for a transport role this build wires no // pair for - a build-shape fact, not a peer's bytes, and no reference to decline with. - SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ConsumerDoorbell\"} - transport " - "role %u has no doorbell pair yet; that is P6's SocketTransport", - static_cast(m_transport->Role())); + SessionFail(MGFatalFamily::UnmigratedVerb, + "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ConsumerDoorbell\"} - transport " + "role %u has no doorbell pair yet; that is P6's SocketTransport", + static_cast(m_transport->Role())); } Transport::Doorbell& ServerSession::ProducerDoorbell() { @@ -1392,22 +1400,32 @@ namespace MobileGL::MG_Remote::Server { } if (m_transport == nullptr) { // @Ph-declined (ID-P7-1): returns Doorbell& - as ConsumerDoorbell above. - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ProducerDoorbell\"} - no " - "transport; Accept() has not run"); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ServerSession::ProducerDoorbell\"} - no " + "transport; Accept() has not run"); } if (m_transport->Role() == Transport::TransportRole::InProcess) { return static_cast(m_transport)->PeerDoorbell(); } // @Ph-declined (ID-P7-1): returns Doorbell& - as ConsumerDoorbell above. - SessionFail(MGFatalFamily::UnmigratedVerb, "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ProducerDoorbell\"} - transport " - "role %u has no doorbell pair yet; that is P6's SocketTransport", - static_cast(m_transport->Role())); + SessionFail(MGFatalFamily::UnmigratedVerb, + "MGPipe: Fatal{UnmigratedVerb, \"ServerSession::ProducerDoorbell\"} - transport " + "role %u has no doorbell pair yet; that is P6's SocketTransport", + static_cast(m_transport->Role())); } - Transport::SessionSegments& ServerSession::Shm() { return m_link->Memory(); } - Transport::SessionConsumer& ServerSession::Consumer() { return m_consumer; } - Transport::EventRingProducer& ServerSession::Events() { return *m_events; } - Transport::ITransport* ServerSession::Control_Plane() { return m_transport; } + Transport::SessionSegments& ServerSession::Shm() { + return m_link->Memory(); + } + Transport::SessionConsumer& ServerSession::Consumer() { + return m_consumer; + } + Transport::EventRingProducer& ServerSession::Events() { + return *m_events; + } + Transport::ITransport* ServerSession::Control_Plane() { + return m_transport; + } void ServerSession::PublishEvents() { if (!m_accepted) { @@ -1438,8 +1456,7 @@ namespace MobileGL::MG_Remote::Server { } const Uint32 messageBytes = static_cast(length) + 1; const Uint64 payloadBytes = sizeof(Transport::EventGlErrorHead) + messageBytes; - void* slot = ReserveEventOrBlock(*this, Transport::kEventGlError, "kEventGlError", - payloadBytes); + void* slot = ReserveEventOrBlock(*this, Transport::kEventGlError, "kEventGlError", payloadBytes); Transport::EventGlErrorHead head{}; head.Code = code; head.MessageBytes = messageBytes; diff --git a/MobileGL/MG_Remote/Server/ServerSession.h b/MobileGL/MG_Remote/Server/ServerSession.h index c5a498ef..e3291057 100644 --- a/MobileGL/MG_Remote/Server/ServerSession.h +++ b/MobileGL/MG_Remote/Server/ServerSession.h @@ -58,8 +58,7 @@ namespace MobileGL::MG_Remote::Server { // CapsSnapshot. The ABI assertion (CapsCodec.h) happens HERE, before a single record is // decoded: sizeof(DynamicBackendParameters), sizeof(MGPCaps), sizeof(GLFunctionsTable) // and the build fingerprint must match, and a mismatch is Fatal{AbiMismatch}. - MobileGLResult Accept(Transport::ITransport& transport, - const std::vector* firstFrame = nullptr); + MobileGLResult Accept(Transport::ITransport& transport, const std::vector* firstFrame = nullptr); // PH-7 (4), ID-P7-3. WHERE A TCP SESSION'S DATA CONNECTION COMES FROM. // @@ -203,18 +202,15 @@ namespace MobileGL::MG_Remote::Server { // into scratch; the apply loop sees the latch and stops, and ServerMain ends the session // with status 0, not as a fault. The latch is written only on the apply thread (reset by // Accept) and read with acquire elsewhere. See ARCHITECTURE.md §11.7. - Bool ReverseChannelForfeited() const { - return m_reverseChannelForfeit.load(std::memory_order_acquire); - } + Bool ReverseChannelForfeited() const { return m_reverseChannelForfeit.load(std::memory_order_acquire); } // Drops since the latch went up, counted HERE because the shared page's eventDropped is // on a page the peer can write. Uint64 ForfeitDrops() const { return m_forfeitDrops.load(std::memory_order_acquire); } // `cause` is one word (the braces' content); `why` the sentence; `shortDrains` how many // times the client cleared the latch without making room. Returns the scratch the caller // writes the dropped event into. - void* ForfeitReverseChannel(const char* cause, const char* why, const char* eventName, - Uint64 payloadBytes, Uint32 waitedMs, Uint32 budgetMs, - Uint32 shortDrains); + void* ForfeitReverseChannel(const char* cause, const char* why, const char* eventName, Uint64 payloadBytes, + Uint32 waitedMs, Uint32 budgetMs, Uint32 shortDrains); void* DropEventAfterForfeit(Uint64 payloadBytes); // PH-6 fix round. SERVERLOOP::STOP'S REQUEST, SEEN BY THE ONE APPLY-THREAD WAIT THAT IS NOT @@ -271,8 +267,7 @@ namespace MobileGL::MG_Remote::Server { // DECLINED with a named line). // A client in this process (inproc) takes no AHardwareBuffer: the backend donates the // store's own mapping and `inProcessBase` carries its address in the OK answer. - Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status, - Uint64& inProcessBase); + Bool AdoptStoreT0(const MG_Pipe::MGPHandleOnly& handle, Uint64 seq, Int32& status, Uint64& inProcessBase); // T0 granted for a client in this process: map_persistent donates (kCapAdoptInProcess). Bool AdoptT0InProcess() const { return m_adoptT0InProcess; } // The arm's proof for this session (B2): stores imported and their bytes, declined. @@ -335,8 +330,7 @@ namespace MobileGL::MG_Remote::Server { Uint64 ContextBinds() const { return m_contextBinds; } private: - MobileGLResult BindDataConnection(Transport::ITransport& control, Transport::ILink& link, - const Uint8* nonce); + MobileGLResult BindDataConnection(Transport::ITransport& control, Transport::ILink& link, const Uint8* nonce); DataConnectionSource m_dataSource; std::unique_ptr m_link; diff --git a/MobileGL/MG_Remote/Server/ServerSpawn.cpp b/MobileGL/MG_Remote/Server/ServerSpawn.cpp index b88c9c31..51321173 100644 --- a/MobileGL/MG_Remote/Server/ServerSpawn.cpp +++ b/MobileGL/MG_Remote/Server/ServerSpawn.cpp @@ -18,17 +18,17 @@ #include #if defined(__unix__) || defined(__APPLE__) || defined(__ANDROID__) -# define MOBILEGL_SERVER_SPAWN_POSIX 1 -# include -# include -# include -# include -# include -# include -# include -# include +#define MOBILEGL_SERVER_SPAWN_POSIX 1 +#include +#include +#include +#include +#include +#include +#include +#include #else -# define MOBILEGL_SERVER_SPAWN_POSIX 0 +#define MOBILEGL_SERVER_SPAWN_POSIX 0 #endif namespace MobileGL::MG_Remote::Server { @@ -46,7 +46,7 @@ namespace MobileGL::MG_Remote::Server { if (path.empty()) { return false; } - struct stat st {}; + struct stat st{}; if (::stat(path.c_str(), &st) != 0) { return false; } @@ -66,8 +66,7 @@ namespace MobileGL::MG_Remote::Server { return configured; } Dl_info info{}; - if (::dladdr(reinterpret_cast(&ResolveImage), &info) != 0 && - info.dli_fname != nullptr) { + if (::dladdr(reinterpret_cast(&ResolveImage), &info) != 0 && info.dli_fname != nullptr) { const std::string self(info.dli_fname); const std::size_t slash = self.find_last_of('/'); if (slash != std::string::npos) { @@ -86,8 +85,9 @@ namespace MobileGL::MG_Remote::Server { // MOBILEGL_IPC_ADOPT_TIER is scrubbed: the client's ask travels in its Hello. bool ShouldScrub(const char* entry) { static constexpr const char* kServerOwned[] = { - "MOBILEGL_IPC_WIRE_DEFERRED_MB=", "MOBILEGL_IPC_SPIN_US=", "MOBILEGL_IPC_SERVER_AFFINITY=", - "MOBILEGL_IPC_AUDIT=", "MOBILEGL_IPC_EVENT_WAIT_MS=", "MOBILEGL_IPC_ALLOW_ADOPT_T0=", + "MOBILEGL_IPC_WIRE_DEFERRED_MB=", "MOBILEGL_IPC_SPIN_US=", + "MOBILEGL_IPC_SERVER_AFFINITY=", "MOBILEGL_IPC_AUDIT=", + "MOBILEGL_IPC_EVENT_WAIT_MS=", "MOBILEGL_IPC_ALLOW_ADOPT_T0=", }; for (const char* kept : kServerOwned) { if (std::strncmp(entry, kept, std::strlen(kept)) == 0) { @@ -184,14 +184,12 @@ namespace MobileGL::MG_Remote::Server { return children; } - MobileGLResult LaunchServer(const std::string& imagePath, const std::string& endpoint, - LaunchedServer* out) { + MobileGLResult LaunchServer(const std::string& imagePath, const std::string& endpoint, LaunchedServer* out) { return LaunchServerWithArgs(imagePath, endpoint, {}, out); } MobileGLResult LaunchServerWithArgs(const std::string& imagePath, const std::string& endpoint, - const std::vector& extraArgs, - LaunchedServer* out) { + const std::vector& extraArgs, LaunchedServer* out) { if (out == nullptr || endpoint.empty()) { return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -202,27 +200,27 @@ namespace MobileGL::MG_Remote::Server { // NAMED, never a fallback. ConfigLoader.cpp's own comment names the // accident this prevents: a lane that asked for spawn, silently got // monolith, and went green on the wrong arm. - Transport::WireLogError( - "MG_Remote spawn: the server image is not executable: \"%s\" - refusing. Set " - "MOBILEGL_IPC_SERVER_PATH, or place libMobileGLServer.so beside libMobileGL.so. " - "There is NO monolith fallback from here.", - image.empty() ? "" : image.c_str()); + Transport::WireLogError("MG_Remote spawn: the server image is not executable: \"%s\" - refusing. Set " + "MOBILEGL_IPC_SERVER_PATH, or place libMobileGLServer.so beside libMobileGL.so. " + "There is NO monolith fallback from here.", + image.empty() ? "" : image.c_str()); return MOBILEGL_ERR_INVALID_ARGUMENT; } // The image's directory, which is where its libMobileGL.so is. const std::string::size_type slash = image.find_last_of('/'); - ScrubbedEnv env = - BuildChildEnv(slash == std::string::npos ? std::string() : image.substr(0, slash)); + ScrubbedEnv env = BuildChildEnv(slash == std::string::npos ? std::string() : image.substr(0, slash)); // Built before the fork: the child may only make async-signal-safe calls until execve. std::vector args; args.reserve(2 + extraArgs.size()); args.push_back(image); args.push_back(endpoint); - for (const std::string& arg : extraArgs) args.push_back(arg); + for (const std::string& arg : extraArgs) + args.push_back(arg); std::vector argvStorage; argvStorage.reserve(args.size() + 1); - for (std::string& arg : args) argvStorage.push_back(arg.data()); + for (std::string& arg : args) + argvStorage.push_back(arg.data()); argvStorage.push_back(nullptr); char** argv = argvStorage.data(); @@ -266,8 +264,8 @@ namespace MobileGL::MG_Remote::Server { const ssize_t got = ::read(report[0], &childErrno, sizeof(childErrno)); ::close(report[0]); if (got == static_cast(sizeof(childErrno))) { - Transport::WireLogError("MG_Remote spawn: execve(\"%s\") failed in the child: %s", - image.c_str(), std::strerror(childErrno)); + Transport::WireLogError("MG_Remote spawn: execve(\"%s\") failed in the child: %s", image.c_str(), + std::strerror(childErrno)); int status = 0; ::waitpid(pid, &status, 0); return MOBILEGL_ERR_INVALID_ARGUMENT; @@ -281,8 +279,7 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } - MobileGLResult ReapServer(LaunchedServer& server, std::uint32_t timeoutMs, - int* outExitCode) { + MobileGLResult ReapServer(LaunchedServer& server, std::uint32_t timeoutMs, int* outExitCode) { if (server.pid < 0) { return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -318,7 +315,7 @@ namespace MobileGL::MG_Remote::Server { MobileGLResult LaunchServer(const std::string&, const std::string&, LaunchedServer*) { Transport::WireLogError("MG_Remote spawn: unsupported on this platform - P6 lands POSIX only " - "(CONTRACT-P6 §2.6); there is no fork() to build a second process from"); + "(CONTRACT-P6 §2.6); there is no fork() to build a second process from"); return MOBILEGL_ERR_UNSUPPORTED; } MobileGLResult LaunchServerWithArgs(const std::string& imagePath, const std::string& endpoint, @@ -328,7 +325,9 @@ namespace MobileGL::MG_Remote::Server { MobileGLResult ReapServer(LaunchedServer&, std::uint32_t, int*) { return MOBILEGL_ERR_UNSUPPORTED; } - int CountOwnChildren() { return -1; } + int CountOwnChildren() { + return -1; + } #endif diff --git a/MobileGL/MG_Remote/Server/ServerSpawn.h b/MobileGL/MG_Remote/Server/ServerSpawn.h index 5bc0191f..449f9c48 100644 --- a/MobileGL/MG_Remote/Server/ServerSpawn.h +++ b/MobileGL/MG_Remote/Server/ServerSpawn.h @@ -51,16 +51,14 @@ namespace MobileGL::MG_Remote::Server { // unresolvable path is a NAMED REFUSAL and never a monolith fallback - // ConfigLoader.cpp names that accident and this is the code that must not // repeat it. - MobileGLResult LaunchServer(const std::string& imagePath, const std::string& endpoint, - LaunchedServer* out); + MobileGLResult LaunchServer(const std::string& imagePath, const std::string& endpoint, LaunchedServer* out); // The same launch with arguments after the endpoint - `--serve` for the fork-per-session // supervisor a TCP deployment runs (ServerMain.cpp). PH-1 (4)'s control needs exactly that // shape on a unit rig: a session that latches must leave a supervisor that serves the next // connection, and a single-session server exits with its only session. MobileGLResult LaunchServerWithArgs(const std::string& imagePath, const std::string& endpoint, - const std::vector& extraArgs, - LaunchedServer* out); + const std::vector& extraArgs, LaunchedServer* out); // Waits for the child, up to `timeoutMs`. Returns MOBILEGL_ERR_TIMEOUT if it // is still running, in which case the caller decides whether to escalate - diff --git a/MobileGL/MG_Remote/Server/SessionRuntime.cpp b/MobileGL/MG_Remote/Server/SessionRuntime.cpp index 4c2ae342..f4b6064d 100644 --- a/MobileGL/MG_Remote/Server/SessionRuntime.cpp +++ b/MobileGL/MG_Remote/Server/SessionRuntime.cpp @@ -77,8 +77,8 @@ namespace MobileGL::MG_Remote::Server { SessionRuntime* runtime = t_runtime; if (runtime == nullptr) return false; outKey->SessionKey = reinterpret_cast(&runtime->Session()); - outKey->ShareGroupKey = MG_Pipe::MGPipeApplierShareGroupKeyFor( - outKey->SessionKey, runtime->Session().CurrentContextToken()); + outKey->ShareGroupKey = + MG_Pipe::MGPipeApplierShareGroupKeyFor(outKey->SessionKey, runtime->Session().CurrentContextToken()); return true; } @@ -106,7 +106,9 @@ namespace MobileGL::MG_Remote::Server { MG_Record::SetStagedBucketResolver(&ResolveThreadStagedBucket); } - SessionRuntime* Detail::CurrentRuntime() { return t_runtime; } + SessionRuntime* Detail::CurrentRuntime() { + return t_runtime; + } ServerSession* Detail::CurrentSession() { SessionRuntime* runtime = t_runtime; @@ -118,7 +120,9 @@ namespace MobileGL::MG_Remote::Server { return runtime != nullptr ? &runtime->Loop() : nullptr; } - MG_Pipe::PipeInputs* Detail::CurrentInputs() { return ResolveThreadInputs(); } + MG_Pipe::PipeInputs* Detail::CurrentInputs() { + return ResolveThreadInputs(); + } Detail::ThreadSessionScope::ThreadSessionScope(SessionRuntime& runtime) { InstallSessionRuntimeHooks(); @@ -141,8 +145,7 @@ namespace MobileGL::MG_Remote::Server { t_runtime = m_previous; } - SessionRuntime::SessionRuntime() - : m_latchDomain(FatalLatchDomainOpen()) {} + SessionRuntime::SessionRuntime() : m_latchDomain(FatalLatchDomainOpen()) {} SessionRuntime::~SessionRuntime() { // P14 S5: this runtime's session is the key every applier it registered was filed @@ -156,6 +159,8 @@ namespace MobileGL::MG_Remote::Server { m_latchDomain = 0; } - Uint32 LiveServerSessionCount() { return ServerSessionCount(); } + Uint32 LiveServerSessionCount() { + return ServerSessionCount(); + } } // namespace MobileGL::MG_Remote::Server \ No newline at end of file diff --git a/MobileGL/MG_Remote/Server/SharedImageRegistry.cpp b/MobileGL/MG_Remote/Server/SharedImageRegistry.cpp index 500a4ffb..9ece5c14 100644 --- a/MobileGL/MG_Remote/Server/SharedImageRegistry.cpp +++ b/MobileGL/MG_Remote/Server/SharedImageRegistry.cpp @@ -64,7 +64,7 @@ namespace MobileGL::MG_Remote::Server::SharedImages { #if !defined(_WIN32) Bool IdentityOf(int fd, Uint64* dev, Uint64* ino) { - struct stat st {}; + struct stat st{}; if (::fstat(fd, &st) != 0) return false; *dev = static_cast(st.st_dev); *ino = static_cast(st.st_ino); @@ -105,7 +105,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { if (c->cmsg_level != SOL_SOCKET || c->cmsg_type != SCM_RIGHTS) continue; const SizeT n = (c->cmsg_len - CMSG_LEN(0)) / sizeof(int); const int* in = reinterpret_cast(CMSG_DATA(c)); - for (SizeT i = 0; i < n; ++i) fds.push_back(in[i]); + for (SizeT i = 0; i < n; ++i) + fds.push_back(in[i]); } } ::close(pair[1]); @@ -144,8 +145,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { // CPU usage also keeps the layout one the platform can lock plane by plane, which is how // every layout question about it is answered (AHardwareBuffer_lockPlanes) - never by // assuming one. - constexpr uint64_t kYuvUsage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | - AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | AHARDWAREBUFFER_USAGE_CPU_READ_RARELY; + constexpr uint64_t kYuvUsage = AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN | + AHARDWAREBUFFER_USAGE_CPU_READ_RARELY; uint32_t AhbFormatOf(Uint32 fourcc) { if (fourcc == kFourccNv12) return AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420; @@ -218,7 +219,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { } image.Native = ahb; image.Fd = fds[0]; - for (SizeT i = 1; i < fds.size(); ++i) ::close(fds[i]); + for (SizeT i = 1; i < fds.size(); ++i) + ::close(fds[i]); image.Offset = 0; if (!FourccIsYuv(image.Fourcc)) { image.Stride = described.stride * 4; @@ -255,7 +257,9 @@ namespace MobileGL::MG_Remote::Server::SharedImages { // A host has no AHardwareBuffer. A memfd stands in for the dma-buf so the registry, the // descriptor round trip and identification can be exercised by the unit tests; no backend // binds one. - Bool YuvAllocatableNative(Uint32) { return true; } + Bool YuvAllocatableNative(Uint32) { + return true; + } Bool AllocateNative(Image& image, std::string& why) { Uint64 bytes = 0; @@ -288,7 +292,9 @@ namespace MobileGL::MG_Remote::Server::SharedImages { void ReleaseNative(void*) {} #else - Bool YuvAllocatableNative(Uint32) { return false; } + Bool YuvAllocatableNative(Uint32) { + return false; + } Bool AllocateNative(Image&, std::string& why) { why = "shared images need POSIX descriptors"; @@ -353,8 +359,7 @@ namespace MobileGL::MG_Remote::Server::SharedImages { void DmaBufSync(int fd, Bool start) { dma_buf_sync sync{}; sync.flags = DMA_BUF_SYNC_READ | (start ? DMA_BUF_SYNC_START : DMA_BUF_SYNC_END); - while (::ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync) != 0 && (errno == EINTR || errno == EAGAIN)) { - } + while (::ioctl(fd, DMA_BUF_IOCTL_SYNC, &sync) != 0 && (errno == EINTR || errno == EAGAIN)) {} } #endif } // namespace @@ -384,12 +389,15 @@ namespace MobileGL::MG_Remote::Server::SharedImages { } if (Fd >= 0) ::close(Fd); if (WriteFence >= 0) ::close(WriteFence); - for (const int fence : ReadFences) ::close(fence); + for (const int fence : ReadFences) + ::close(fence); #endif ReleaseNative(Native); } - Bool FourccIsYuv(Uint32 fourcc) { return fourcc == kFourccNv12 || fourcc == kFourccP010; } + Bool FourccIsYuv(Uint32 fourcc) { + return fourcc == kFourccNv12 || fourcc == kFourccP010; + } Uint32 FourccYuvSampleBytes(Uint32 fourcc) { if (fourcc == kFourccNv12) return 1; @@ -414,7 +422,9 @@ namespace MobileGL::MG_Remote::Server::SharedImages { fourcc == kFourccXrgb8888 || YuvFourccAllocatable(fourcc); } - Bool FourccIgnoresAlpha(Uint32 fourcc) { return fourcc == kFourccXbgr8888 || fourcc == kFourccXrgb8888; } + Bool FourccIgnoresAlpha(Uint32 fourcc) { + return fourcc == kFourccXbgr8888 || fourcc == kFourccXrgb8888; + } namespace { // Allocate's body. `foreign`, when given, is on the image before anything can find it. @@ -457,8 +467,7 @@ namespace MobileGL::MG_Remote::Server::SharedImages { } reg.byIdentity[key] = image; if (image->Foreign != nullptr) { - reg.foreignByIdentity[std::make_pair(image->Foreign->IdentityDev, image->Foreign->IdentityIno)] = - image; + reg.foreignByIdentity[std::make_pair(image->Foreign->IdentityDev, image->Foreign->IdentityIno)] = image; } return image; } @@ -517,8 +526,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { const Uint64 lumaEnd = static_cast(layout.Offset[0]) + static_cast(layout.Pitch[0]) * (height - 1) + static_cast(width) * sampleBytes; - const Uint64 chromaEnd = - static_cast(layout.Offset[1]) + static_cast(layout.Pitch[1]) * (chromaHeight - 1) + chromaRow; + const Uint64 chromaEnd = static_cast(layout.Offset[1]) + + static_cast(layout.Pitch[1]) * (chromaHeight - 1) + chromaRow; if (layout.Pitch[0] < width * sampleBytes || layout.Pitch[1] < chromaRow || lumaEnd > size || chromaEnd > size) { why = Format("its planes do not fit the %lld-byte buffer", static_cast(size)); @@ -633,8 +642,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { const auto it = reg.byIdentity.find(std::make_pair(dev, ino)); ImageRef image = it != reg.byIdentity.end() ? it->second.lock() : nullptr; if (image == nullptr) { - why = Format("the descriptor (dev %llu ino %llu) was not exported by this server", static_cast(dev), - static_cast(ino)); + why = Format("the descriptor (dev %llu ino %llu) was not exported by this server", + static_cast(dev), static_cast(ino)); } return image; #endif @@ -651,14 +660,17 @@ namespace MobileGL::MG_Remote::Server::SharedImages { Registry& reg = Reg(); const std::lock_guard lock(reg.mutex); SizeT live = 0; - for (const auto& [id, weak] : reg.byId) live += weak.expired() ? 0 : 1; + for (const auto& [id, weak] : reg.byId) + live += weak.expired() ? 0 : 1; return live; } // ---- synchronisation ------------------------------------------------------------------------ namespace SyncFile { - Bool Signaled(int fd) { return Wait(fd, 0); } + Bool Signaled(int fd) { + return Wait(fd, 0); + } Bool Wait(int fd, Uint32 timeoutMs) { #if defined(_WIN32) @@ -767,7 +779,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages { } void ReadTracker::PublishFrame(int fence) { - for (const auto& [id, image] : m_images) PublishRead(*image, Dup(fence)); + for (const auto& [id, image] : m_images) + PublishRead(*image, Dup(fence)); CloseFd(fence); m_images.clear(); } diff --git a/MobileGL/MG_Remote/Server/SharedImageYuv.h b/MobileGL/MG_Remote/Server/SharedImageYuv.h index 2c84fe12..a3a5b331 100644 --- a/MobileGL/MG_Remote/Server/SharedImageYuv.h +++ b/MobileGL/MG_Remote/Server/SharedImageYuv.h @@ -91,7 +91,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages::Yuv { -cScale * 2.0 * kr * (1.0 - kr) / kg, 0.0, }; - for (SizeT i = 0; i < 9; ++i) c.Matrix[i] = static_cast(m[i]); + for (SizeT i = 0; i < 9; ++i) + c.Matrix[i] = static_cast(m[i]); c.Offset[0] = static_cast(yOffset); c.Offset[1] = static_cast(cOffset); c.Offset[2] = static_cast(cOffset); @@ -130,8 +131,8 @@ namespace MobileGL::MG_Remote::Server::SharedImages::Yuv { std::memcpy(to, from, static_cast(width) * sampleBytes); } else { for (Uint32 x = 0; x < width; ++x) - std::memcpy(to + static_cast(x) * dst.Y.PixelStride, from + static_cast(x) * sampleBytes, - sampleBytes); + std::memcpy(to + static_cast(x) * dst.Y.PixelStride, + from + static_cast(x) * sampleBytes, sampleBytes); } } // Chroma. diff --git a/MobileGL/MG_Remote/Server/SurfaceControlFrame.cpp b/MobileGL/MG_Remote/Server/SurfaceControlFrame.cpp index ec89ef46..77178e6a 100644 --- a/MobileGL/MG_Remote/Server/SurfaceControlFrame.cpp +++ b/MobileGL/MG_Remote/Server/SurfaceControlFrame.cpp @@ -12,23 +12,40 @@ namespace MobileGL::MG_Remote::Server { const char* SurfaceControlOpName(SurfaceControlOp op) { switch (op) { - case SurfaceControlOp::None: return "None"; - case SurfaceControlOp::InitializeDisplay: return "InitializeDisplay"; - case SurfaceControlOp::CreateWindowSurface: return "CreateWindowSurface"; - case SurfaceControlOp::CreatePbufferSurface: return "CreatePbufferSurface"; - case SurfaceControlOp::ResizeWindowSurface: return "ResizeWindowSurface"; - case SurfaceControlOp::ReleaseSurface: return "ReleaseSurface"; - case SurfaceControlOp::MakeCurrent: return "MakeCurrent"; - case SurfaceControlOp::ReleaseCurrent: return "ReleaseCurrent"; - case SurfaceControlOp::SetSwapInterval: return "SetSwapInterval"; - case SurfaceControlOp::ReleaseResources: return "ReleaseResources"; - case SurfaceControlOp::SetWindowHandle: return "SetWindowHandle"; - case SurfaceControlOp::InitCapabilities: return "InitCapabilities"; - case SurfaceControlOp::CreateContext: return "CreateContext"; - case SurfaceControlOp::DestroyContext: return "DestroyContext"; - case SurfaceControlOp::SwapBuffersInprocOnly: return "SwapBuffersInprocOnly"; - case SurfaceControlOp::InitWindowSurfaceInprocOnly: return "InitWindowSurfaceInprocOnly"; - case SurfaceControlOp::ProbeForTesting: return "ProbeForTesting"; + case SurfaceControlOp::None: + return "None"; + case SurfaceControlOp::InitializeDisplay: + return "InitializeDisplay"; + case SurfaceControlOp::CreateWindowSurface: + return "CreateWindowSurface"; + case SurfaceControlOp::CreatePbufferSurface: + return "CreatePbufferSurface"; + case SurfaceControlOp::ResizeWindowSurface: + return "ResizeWindowSurface"; + case SurfaceControlOp::ReleaseSurface: + return "ReleaseSurface"; + case SurfaceControlOp::MakeCurrent: + return "MakeCurrent"; + case SurfaceControlOp::ReleaseCurrent: + return "ReleaseCurrent"; + case SurfaceControlOp::SetSwapInterval: + return "SetSwapInterval"; + case SurfaceControlOp::ReleaseResources: + return "ReleaseResources"; + case SurfaceControlOp::SetWindowHandle: + return "SetWindowHandle"; + case SurfaceControlOp::InitCapabilities: + return "InitCapabilities"; + case SurfaceControlOp::CreateContext: + return "CreateContext"; + case SurfaceControlOp::DestroyContext: + return "DestroyContext"; + case SurfaceControlOp::SwapBuffersInprocOnly: + return "SwapBuffersInprocOnly"; + case SurfaceControlOp::InitWindowSurfaceInprocOnly: + return "InitWindowSurfaceInprocOnly"; + case SurfaceControlOp::ProbeForTesting: + return "ProbeForTesting"; } return ""; } @@ -62,11 +79,16 @@ namespace MobileGL::MG_Remote::Server { const char* SurfaceRefusalCodeName(SurfaceRefusalCode code) { switch (code) { - case SurfaceRefusalCode::None: return "None"; - case SurfaceRefusalCode::NoServerDisplay: return "NoServerDisplay"; - case SurfaceRefusalCode::NoServerWindow: return "NoServerWindow"; - case SurfaceRefusalCode::SurfaceModeMismatch: return "SurfaceModeMismatch"; - case SurfaceRefusalCode::ServerOwnedOnSetWindowHandle: return "ServerOwnedOnSetWindowHandle"; + case SurfaceRefusalCode::None: + return "None"; + case SurfaceRefusalCode::NoServerDisplay: + return "NoServerDisplay"; + case SurfaceRefusalCode::NoServerWindow: + return "NoServerWindow"; + case SurfaceRefusalCode::SurfaceModeMismatch: + return "SurfaceModeMismatch"; + case SurfaceRefusalCode::ServerOwnedOnSetWindowHandle: + return "ServerOwnedOnSetWindowHandle"; } return ""; } diff --git a/MobileGL/MG_Remote/Server/SurfaceControlFrame.h b/MobileGL/MG_Remote/Server/SurfaceControlFrame.h index 3d8ab0d0..103b246b 100644 --- a/MobileGL/MG_Remote/Server/SurfaceControlFrame.h +++ b/MobileGL/MG_Remote/Server/SurfaceControlFrame.h @@ -62,9 +62,9 @@ namespace MobileGL::MG_Remote::Server { // is safe by construction: these three never cross a process boundary and are named // symbolically everywhere they are used, so nothing outside this build could have pinned // them - where the eleven above and the two below are wire ABI and may never move. - SwapBuffersInprocOnly = 14, // present travels as a record (the class-B Present emitter) - InitWindowSurfaceInprocOnly = 15,// a client-side no-op; kept for the inproc test lane - ProbeForTesting = 16, // MG_Test's arbitrary-work seam through the same channel + SwapBuffersInprocOnly = 14, // present travels as a record (the class-B Present emitter) + InitWindowSurfaceInprocOnly = 15, // a client-side no-op; kept for the inproc test lane + ProbeForTesting = 16, // MG_Test's arbitrary-work seam through the same channel }; // P12 (on-screen server window), D2. THE SERVER-OWNED WINDOW, AS A FRAME-LOCAL TAG. @@ -151,16 +151,15 @@ namespace MobileGL::MG_Remote::Server { SurfaceControlFrame frame{}; // Exhaustive by the language's aggregate decomposition rule: adding ANY member // without extending this binding is a compile error, so new fields cannot evade it. - const auto& [kind, seq, display, surface, readSurface, context, windowBackend, - nativeToken, width, height, swapInterval, shareGroupToken, contextFlags, ok, - eglMajor, eglMinor, eventHead, refusal] = frame; + const auto& [kind, seq, display, surface, readSurface, context, windowBackend, nativeToken, width, height, + swapInterval, shareGroupToken, contextFlags, ok, eglMajor, eglMinor, eventHead, refusal] = + frame; return IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && - IsSurfaceControlValue && - IsSurfaceControlValue && + IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue && IsSurfaceControlValue; @@ -169,8 +168,7 @@ namespace MobileGL::MG_Remote::Server { static_assert(Detail::SurfaceControlFrameHasOnlyValues(), "every surface control frame member must be an integer or enum, never a pointer"); - static_assert(std::is_trivially_copyable_v && - std::is_standard_layout_v, + static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v, "the surface control frame must remain a trivially copyable value aggregate"); const char* SurfaceControlOpName(SurfaceControlOp op); diff --git a/MobileGL/MG_Remote/Transport/AdoptT0.cpp b/MobileGL/MG_Remote/Transport/AdoptT0.cpp index c3b43c17..5fa682bb 100644 --- a/MobileGL/MG_Remote/Transport/AdoptT0.cpp +++ b/MobileGL/MG_Remote/Transport/AdoptT0.cpp @@ -34,7 +34,9 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { #if defined(__ANDROID__) - bool RealHasAhb() { return true; } + bool RealHasAhb() { + return true; + } bool RealAllocateHeld(std::uint64_t size, const void* seed, HeldStore& out, std::string& why) { out = HeldStore{}; @@ -82,8 +84,8 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { return -1; } if (store != nullptr && store->ahb != nullptr) { - const int sent = AHardwareBuffer_sendHandleToUnixSocket( - static_cast(store->ahb), pair[0]); + const int sent = + AHardwareBuffer_sendHandleToUnixSocket(static_cast(store->ahb), pair[0]); if (sent != 0) { why = Format("AHardwareBuffer_sendHandleToUnixSocket rc=%lld errno=%lld", sent, errno); ::close(pair[0]); @@ -137,7 +139,9 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { // No AHardwareBuffer: the server never grants T0 here, so a client never allocates. The // empty hop still works, so a flags-0 Offer is real on every POSIX host. - bool RealHasAhb() { return false; } + bool RealHasAhb() { + return false; + } bool RealAllocateHeld(std::uint64_t, const void*, HeldStore& out, std::string& why) { out = HeldStore{}; @@ -165,7 +169,9 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { #endif } - void RealReleaseHeld(HeldStore& store) { store = HeldStore{}; } + void RealReleaseHeld(HeldStore& store) { + store = HeldStore{}; + } bool RealReceiveFromHop(int, std::uint64_t, void** outAhb, std::string& why) { *outAhb = nullptr; @@ -196,8 +202,10 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { void ReleaseHeld(HeldStore& store) { const PlatformForTest* test = g_testPlatform.load(std::memory_order_acquire); - if (test != nullptr) test->ReleaseHeld(store); - else RealReleaseHeld(store); + if (test != nullptr) + test->ReleaseHeld(store); + else + RealReleaseHeld(store); } bool ReceiveFromHop(int hopFd, std::uint64_t size, void** outAhb, std::string& why) { @@ -208,14 +216,18 @@ namespace MobileGL::MG_Remote::Transport::AdoptT0 { void AcquireImported(void* ahb) { const PlatformForTest* test = g_testPlatform.load(std::memory_order_acquire); - if (test != nullptr) test->AcquireImported(ahb); - else RealAcquireImported(ahb); + if (test != nullptr) + test->AcquireImported(ahb); + else + RealAcquireImported(ahb); } void ReleaseImported(void* ahb) { const PlatformForTest* test = g_testPlatform.load(std::memory_order_acquire); - if (test != nullptr) test->ReleaseImported(ahb); - else RealReleaseImported(ahb); + if (test != nullptr) + test->ReleaseImported(ahb); + else + RealReleaseImported(ahb); } void SetPlatformForTest(const PlatformForTest* platform) { diff --git a/MobileGL/MG_Remote/Transport/AdoptTier.cpp b/MobileGL/MG_Remote/Transport/AdoptTier.cpp index 378cad55..fc475d92 100644 --- a/MobileGL/MG_Remote/Transport/AdoptTier.cpp +++ b/MobileGL/MG_Remote/Transport/AdoptTier.cpp @@ -19,12 +19,18 @@ namespace MobileGL::MG_Remote::Transport { namespace { std::atomic g_settled{false}; - const char* SideName(AdoptTierSide side) { return side == AdoptTierSide::Client ? "client" : "server"; } + const char* SideName(AdoptTierSide side) { + return side == AdoptTierSide::Client ? "client" : "server"; + } } // namespace - bool AdoptTierIsDefault() { return MG_Config::Ipc.AdoptTier == MG_Config::kAdoptTierUnset; } + bool AdoptTierIsDefault() { + return MG_Config::Ipc.AdoptTier == MG_Config::kAdoptTierUnset; + } - bool AdoptTierWantsT0() { return MG_Config::Ipc.AdoptTier == 0 || AdoptTierIsDefault(); } + bool AdoptTierWantsT0() { + return MG_Config::Ipc.AdoptTier == 0 || AdoptTierIsDefault(); + } std::uint32_t AdoptTierAskFor(bool streamDataPlane) { if (streamDataPlane || !AdoptTierWantsT0()) return 2u; @@ -81,8 +87,12 @@ namespace MobileGL::MG_Remote::Transport { return AdoptTierAskFor(false); } - bool AdoptTierSettledAtHandshake() { return g_settled.load(std::memory_order_acquire); } + bool AdoptTierSettledAtHandshake() { + return g_settled.load(std::memory_order_acquire); + } - void ForgetAdoptTierSettlementForTest() { g_settled.store(false, std::memory_order_release); } + void ForgetAdoptTierSettlementForTest() { + g_settled.store(false, std::memory_order_release); + } } // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Transport/AdoptTier.h b/MobileGL/MG_Remote/Transport/AdoptTier.h index 4fc3f364..3438e18c 100644 --- a/MobileGL/MG_Remote/Transport/AdoptTier.h +++ b/MobileGL/MG_Remote/Transport/AdoptTier.h @@ -20,14 +20,19 @@ namespace MobileGL::MG_Remote::Transport { - enum class AdoptTierSide : std::uint8_t { Client, Server }; + enum class AdoptTierSide : std::uint8_t { + Client, + Server + }; // THE HELLO'S T0 ASK WHEN THE KNOB IS UNSET (LinkTerms.adoptTier, protocol.fbs): T0 as asked // by 0, except that the server names a refusal only at MGLOG_D. The ask travels because only // the server knows why it did not grant T0 (its allow switch, its platform, its POST). inline constexpr std::uint32_t kAdoptAskT0Default = 0x80; // An ask that is a T0 ask (0 by name, kAdoptAskT0Default by default). - inline bool AdoptAskIsT0(std::uint32_t ask) { return ask == 0 || ask == kAdoptAskT0Default; } + inline bool AdoptAskIsT0(std::uint32_t ask) { + return ask == 0 || ask == kAdoptAskT0Default; + } // This process's knob asks for T0 (0, or unset). bool AdoptTierWantsT0(); // This process's knob is unset: T0 fallbacks are quiet. diff --git a/MobileGL/MG_Remote/Transport/AuthToken.h b/MobileGL/MG_Remote/Transport/AuthToken.h index 81090698..d84a1a55 100644 --- a/MobileGL/MG_Remote/Transport/AuthToken.h +++ b/MobileGL/MG_Remote/Transport/AuthToken.h @@ -58,17 +58,14 @@ namespace MobileGL::MG_Remote::Transport { // are folded into the accumulator rather than branched on, and nothing leaves early. The // presented length does not extend the loop: a peer's own bytes are the one thing it already // knows, and letting it choose the trip count would let it choose our work. - inline bool ConstantTimeTokenMatch(const char* expected, const char* presented, - std::size_t presentedSize) { + inline bool ConstantTimeTokenMatch(const char* expected, const char* presented, std::size_t presentedSize) { if (expected == nullptr) return false; const std::size_t expectedSize = std::strlen(expected); const std::size_t width = expectedSize > kAuthTokenCompareBytes ? expectedSize : kAuthTokenCompareBytes; std::size_t difference = expectedSize ^ presentedSize; for (std::size_t index = 0; index < width; ++index) { - const unsigned char ours = - index < expectedSize ? static_cast(expected[index]) : 0u; - const unsigned char theirs = - index < presentedSize ? static_cast(presented[index]) : 0u; + const unsigned char ours = index < expectedSize ? static_cast(expected[index]) : 0u; + const unsigned char theirs = index < presentedSize ? static_cast(presented[index]) : 0u; difference |= static_cast(ours ^ theirs); } return difference == 0; diff --git a/MobileGL/MG_Remote/Transport/ControlInbox.cpp b/MobileGL/MG_Remote/Transport/ControlInbox.cpp index 41dff142..16623d62 100644 --- a/MobileGL/MG_Remote/Transport/ControlInbox.cpp +++ b/MobileGL/MG_Remote/Transport/ControlInbox.cpp @@ -15,7 +15,9 @@ namespace MobileGL::MG_Remote::Transport { ControlInbox::ControlInbox(ITransport& transport) : m_transport(transport), m_reader([this] { Read(); }) {} - ControlInbox::~ControlInbox() { Stop(); } + ControlInbox::~ControlInbox() { + Stop(); + } void ControlInbox::Stop() { { @@ -53,7 +55,8 @@ namespace MobileGL::MG_Remote::Transport { } if (result == MOBILEGL_OK) { flatbuffers::Verifier verifier(frame.data(), frame.size()); - if (!::MobileGL::Wire::VerifyCtrlEnvelopeBuffer(verifier)) result = MOBILEGL_ERR_PROTOCOL_MISMATCH; + if (!::MobileGL::Wire::VerifyCtrlEnvelopeBuffer(verifier)) + result = MOBILEGL_ERR_PROTOCOL_MISMATCH; else { const auto* envelope = ::MobileGL::Wire::GetCtrlEnvelope(frame.data()); if (const auto* log = envelope->msg_as_LogLine()) { @@ -81,8 +84,10 @@ namespace MobileGL::MG_Remote::Transport { MobileGLResult ControlInbox::Receive(std::vector& frame, std::uint32_t timeoutMs) { std::unique_lock lock(m_mutex); const auto ready = [&] { return m_stop || !m_frames.empty() || m_result != MOBILEGL_OK; }; - if (timeoutMs == kWaitForever) m_cv.wait(lock, ready); - else if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), ready)) return MOBILEGL_ERR_TIMEOUT; + if (timeoutMs == kWaitForever) + m_cv.wait(lock, ready); + else if (!m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), ready)) + return MOBILEGL_ERR_TIMEOUT; if (m_frames.empty()) return m_result == MOBILEGL_OK ? MOBILEGL_ERR_TRANSPORT_CLOSED : m_result; frame = std::move(m_frames.front()); m_frames.pop_front(); @@ -108,4 +113,4 @@ namespace MobileGL::MG_Remote::Transport { if (m_cv.wait_until(lock, deadline) == std::cv_status::timeout) return m_result != MOBILEGL_OK; } } -} +} // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Transport/ControlInbox.h b/MobileGL/MG_Remote/Transport/ControlInbox.h index e2f6fea9..3ebe3394 100644 --- a/MobileGL/MG_Remote/Transport/ControlInbox.h +++ b/MobileGL/MG_Remote/Transport/ControlInbox.h @@ -31,6 +31,7 @@ namespace MobileGL::MG_Remote::Transport { // control connection, the peer the server's log forward (LogForward.h) must not be held by. // Frames already queued stay queued; resuming picks up where the socket is. void PauseReadingForTest(bool paused); + private: void Read(); ITransport& m_transport; @@ -43,4 +44,4 @@ namespace MobileGL::MG_Remote::Transport { MobileGLResult m_result = MOBILEGL_OK; std::thread m_reader; }; -} +} // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Transport/Doorbell.cpp b/MobileGL/MG_Remote/Transport/Doorbell.cpp index a6926c01..b98235d5 100644 --- a/MobileGL/MG_Remote/Transport/Doorbell.cpp +++ b/MobileGL/MG_Remote/Transport/Doorbell.cpp @@ -108,15 +108,15 @@ namespace MobileGL::MG_Remote::Transport { } CpuRelax(); } - const auto elapsedNs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(); + const auto elapsedNs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start) + .count(); if (elapsedNs <= 0) { continue; // clock too coarse to see this probe at all } sawUsableClock = true; - const std::uint64_t perUs = (static_cast(kProbeIterations) * 1000ull) / - static_cast(elapsedNs); + const std::uint64_t perUs = + (static_cast(kProbeIterations) * 1000ull) / static_cast(elapsedNs); const std::uint32_t clamped = perUs > kMaxSpinItersPerUs ? kMaxSpinItersPerUs : static_cast(perUs); if (clamped > best) { @@ -164,7 +164,9 @@ namespace MobileGL::MG_Remote::Transport { CondVarDoorbell::CondVarDoorbell() : m_impl(new Impl()) {} - CondVarDoorbell::~CondVarDoorbell() { delete m_impl; } + CondVarDoorbell::~CondVarDoorbell() { + delete m_impl; + } void CondVarDoorbell::Notify() { { @@ -189,9 +191,7 @@ namespace MobileGL::MG_Remote::Transport { if (timeoutMs == 0) { return false; } - const auto woken = [this] { - return m_impl->signals != 0 || m_dead.load(std::memory_order_relaxed); - }; + const auto woken = [this] { return m_impl->signals != 0 || m_dead.load(std::memory_order_relaxed); }; if (timeoutMs == kWaitForever) { m_impl->cv.wait(lock, woken); } else if (!m_impl->cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), woken)) { @@ -312,9 +312,9 @@ namespace MobileGL::MG_Remote::Transport { for (;;) { int pollTimeout = -1; if (timeoutMs != kWaitForever) { - const auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(); + const auto elapsed = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start) + .count(); const long long remaining = static_cast(timeoutMs) - elapsed; pollTimeout = remaining <= 0 ? 0 : static_cast(remaining); } @@ -366,8 +366,7 @@ namespace MobileGL::MG_Remote::Transport { // Doorbell::Wait re-parks until its deadline and kWaitForever has // none. if ((pfd.revents & (POLLERR | POLLNVAL)) != 0) { - MGLOG_D("MG_Remote doorbell: fd %d unusable (revents=0x%X)", m_fd, - static_cast(pfd.revents)); + MGLOG_D("MG_Remote doorbell: fd %d unusable (revents=0x%X)", m_fd, static_cast(pfd.revents)); m_dead.store(true, std::memory_order_release); return false; } @@ -389,8 +388,7 @@ namespace MobileGL::MG_Remote::Transport { // Readiness with no bit we requested or recognise: there is // nothing to consume and no way to make progress, so refuse to // poll this descriptor again. - MGLOG_D("MG_Remote doorbell: fd %d ready with revents=0x%X", m_fd, - static_cast(pfd.revents)); + MGLOG_D("MG_Remote doorbell: fd %d ready with revents=0x%X", m_fd, static_cast(pfd.revents)); m_dead.store(true, std::memory_order_release); return false; } diff --git a/MobileGL/MG_Remote/Transport/Doorbell.h b/MobileGL/MG_Remote/Transport/Doorbell.h index cc6ef442..a6a59efc 100644 --- a/MobileGL/MG_Remote/Transport/Doorbell.h +++ b/MobileGL/MG_Remote/Transport/Doorbell.h @@ -131,8 +131,8 @@ namespace MobileGL::MG_Remote::Transport { // caller gets parks that are a subset of its OWN waits off a bell it // shares with another subsystem; see ParkEntries() above. template - bool Wait(std::atomic& parked, Ready&& ready, std::uint32_t spinUs, - std::uint32_t timeoutMs, std::atomic* parkTally = nullptr) { + bool Wait(std::atomic& parked, Ready&& ready, std::uint32_t spinUs, std::uint32_t timeoutMs, + std::atomic* parkTally = nullptr) { if (ready()) { return true; } @@ -141,9 +141,7 @@ namespace MobileGL::MG_Remote::Transport { // and skips the calibration probe: PipeWireCodec's SEG_STAGE retirement wait passes 0 // because it wants the park, and MOBILEGL_IPC_SPIN_US=0 is a no-spin control. const std::uint64_t budget = - spinUs == 0 ? 0ull - : static_cast(SpinItersPerUs()) * - static_cast(spinUs); + spinUs == 0 ? 0ull : static_cast(SpinItersPerUs()) * static_cast(spinUs); for (std::uint64_t spun = 0; spun < budget; ++spun) { if (ready()) { return true; @@ -166,8 +164,7 @@ namespace MobileGL::MG_Remote::Transport { // safe direction for a deadline whose expiry is reported as a timeout. const auto deadline = timeoutMs == kWaitForever ? std::chrono::steady_clock::time_point::max() - : std::chrono::steady_clock::now() + - std::chrono::milliseconds(timeoutMs); + : std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); // ONE PARK PER Wait is what the ledger counts, and this loop may Park // many times for one Wait; see the counting site below. @@ -340,8 +337,8 @@ namespace MobileGL::MG_Remote::Transport { // afterwards. std::uint64_t Drain(); - int m_fd; // polled - int m_notifyFd; // written; equals m_fd in the single-fd (cross-process) form + int m_fd; // polled + int m_notifyFd; // written; equals m_fd in the single-fd (cross-process) form int m_witnessFd = -1; // polled for hangup only; NOT owned, NEVER read // Set ONLY by the witness branch of Park, never by Kill or by our own EOF. See // Doorbell::PeerHungUp for why the distinction is the whole design. diff --git a/MobileGL/MG_Remote/Transport/EventRing.h b/MobileGL/MG_Remote/Transport/EventRing.h index 50043a18..39678241 100644 --- a/MobileGL/MG_Remote/Transport/EventRing.h +++ b/MobileGL/MG_Remote/Transport/EventRing.h @@ -122,10 +122,8 @@ namespace MobileGL::MG_Remote::Transport { // `eventControl` is SEG_EVENT's own control page; `cmdControl` is the // SEG_CMD page, because Ring.h declares eventRingFull / eventDropped // there and the client's waits already look at it. - EventRingProducer(RingControl* eventControl, RingControl* cmdControl, void* base, - std::uint64_t capacityBytes) - : m_cmdControl(cmdControl), - m_producer(eventControl, base, capacityBytes, RingCursorSet::Cmd) {} + EventRingProducer(RingControl* eventControl, RingControl* cmdControl, void* base, std::uint64_t capacityBytes) + : m_cmdControl(cmdControl), m_producer(eventControl, base, capacityBytes, RingCursorSet::Cmd) {} bool Valid() const { return m_producer.Valid() && m_cmdControl != nullptr; } @@ -170,17 +168,14 @@ namespace MobileGL::MG_Remote::Transport { public: EventRingConsumer() = default; - EventRingConsumer(RingControl* eventControl, RingControl* cmdControl, void* base, - std::uint64_t capacityBytes, const void* segmentBase) - : m_cmdControl(cmdControl), - m_consumer(eventControl, base, capacityBytes, RingCursorSet::Cmd), + EventRingConsumer(RingControl* eventControl, RingControl* cmdControl, void* base, std::uint64_t capacityBytes, + const void* segmentBase) + : m_cmdControl(cmdControl), m_consumer(eventControl, base, capacityBytes, RingCursorSet::Cmd), m_segmentBase(static_cast(segmentBase)) {} bool Valid() const { return m_consumer.Valid() && m_cmdControl != nullptr; } - bool Pop(RingRecordView& out, bool* outCorrupt = nullptr) { - return m_consumer.Pop(out, outCorrupt); - } + bool Pop(RingRecordView& out, bool* outCorrupt = nullptr) { return m_consumer.Pop(out, outCorrupt); } // Release the bytes and clear the full latch. Only after the caller has // finished with every payload pointer it popped: a writeback's bytes live @@ -197,8 +192,7 @@ namespace MobileGL::MG_Remote::Transport { // the forward direction's publish-then-ring order exists to prevent. The // exchange is what makes the ring one-shot: only the drain that actually found // the flag up rings, so a steady stream of drains costs one relaxed RMW. - const bool wasFull = - m_cmdControl->eventRingFull.exchange(0, std::memory_order_acq_rel) != 0; + const bool wasFull = m_cmdControl->eventRingFull.exchange(0, std::memory_order_acq_rel) != 0; if (wasFull && m_serverBell != nullptr) { NotifyIfParked(*m_serverBell, m_cmdControl->consumerParked); } @@ -215,18 +209,14 @@ namespace MobileGL::MG_Remote::Transport { // OnBufferWriteback MGPBlobRef must carry (Seg = kSegEvent, Offset = // this, Size = the head's Size). Never a host address - R-2's rule B. std::uint64_t OffsetInSegment(const void* payload) const { - return static_cast(static_cast(payload) - - m_segmentBase); + return static_cast(static_cast(payload) - m_segmentBase); } std::uint64_t DroppedEvents() const { - return m_cmdControl == nullptr - ? 0 - : m_cmdControl->eventDropped.load(std::memory_order_relaxed); + return m_cmdControl == nullptr ? 0 : m_cmdControl->eventDropped.load(std::memory_order_relaxed); } bool RingIsFull() const { - return m_cmdControl != nullptr && - m_cmdControl->eventRingFull.load(std::memory_order_acquire) != 0; + return m_cmdControl != nullptr && m_cmdControl->eventRingFull.load(std::memory_order_acquire) != 0; } RingConsumer& Ring() { return m_consumer; } diff --git a/MobileGL/MG_Remote/Transport/FdPassing.cpp b/MobileGL/MG_Remote/Transport/FdPassing.cpp index 578bd2ad..419b91c9 100644 --- a/MobileGL/MG_Remote/Transport/FdPassing.cpp +++ b/MobileGL/MG_Remote/Transport/FdPassing.cpp @@ -34,11 +34,17 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { #if defined(_WIN32) - bool Supported() { return false; } + bool Supported() { + return false; + } - MobileGLResult CreateSocketPair(int[2]) { return MOBILEGL_ERR_UNSUPPORTED; } + MobileGLResult CreateSocketPair(int[2]) { + return MOBILEGL_ERR_UNSUPPORTED; + } - MobileGLResult SendFd(int, int, MobileGLByteSpan, bool) { return MOBILEGL_ERR_UNSUPPORTED; } + MobileGLResult SendFd(int, int, MobileGLByteSpan, bool) { + return MOBILEGL_ERR_UNSUPPORTED; + } MobileGLResult ReceiveFd(int, int*, MobileGLMutableByteSpan, std::uint64_t*, std::uint32_t) { return MOBILEGL_ERR_UNSUPPORTED; @@ -60,9 +66,9 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { for (;;) { int pollTimeout = -1; if (timeoutMs != 0xFFFFFFFFu) { - const auto elapsed = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(); + const auto elapsed = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start) + .count(); const long long remaining = static_cast(timeoutMs) - elapsed; pollTimeout = remaining <= 0 ? 0 : static_cast(remaining); } @@ -78,7 +84,9 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { } } // namespace - bool Supported() { return true; } + bool Supported() { + return true; + } MobileGLResult CreateSocketPair(int outFds[2]) { if (outFds == nullptr) { @@ -119,8 +127,7 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { header.sidebandSize = static_cast(sideband.size); std::memcpy(payload, &header, sizeof(header)); if (sideband.size != 0) { - std::memcpy(payload + sizeof(header), sideband.data, - static_cast(sideband.size)); + std::memcpy(payload + sizeof(header), sideband.data, static_cast(sideband.size)); } const std::size_t payloadSize = sizeof(header) + static_cast(sideband.size); @@ -154,8 +161,7 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { if (sent >= 0) { if (static_cast(sent) != payloadSize) { // A datagram socket sends all or nothing. - MGLOG_E("MG_Remote fd passing: short datagram (%zd of %zu bytes)", sent, - payloadSize); + MGLOG_E("MG_Remote fd passing: short datagram (%zd of %zu bytes)", sent, payloadSize); return MOBILEGL_ERR_TRANSPORT_CLOSED; } return MOBILEGL_OK; @@ -180,8 +186,8 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { } } - MobileGLResult ReceiveFd(int socket, int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) { + MobileGLResult ReceiveFd(int socket, int* outFd, MobileGLMutableByteSpan sideband, std::uint64_t* outSidebandSize, + std::uint32_t timeoutMs) { if (socket < 0 || outFd == nullptr) { return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -254,8 +260,7 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { // rather than leaked, whatever else is wrong with the message. int received[4]; int receivedCount = 0; - for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; - cmsg = CMSG_NXTHDR(&msg, cmsg)) { + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg != nullptr; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) { continue; } @@ -308,11 +313,10 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { SidebandHeader header{}; std::memcpy(&header, payload, sizeof(header)); - if (header.magic != kSidebandMagic || - header.sidebandSize > kMaxSidebandBytes || + if (header.magic != kSidebandMagic || header.sidebandSize > kMaxSidebandBytes || sizeof(SidebandHeader) + header.sidebandSize != static_cast(got)) { - MGLOG_E("MG_Remote fd passing: bad sideband header (magic=0x%08X size=%u datagram=%zd)", - header.magic, header.sidebandSize, got); + MGLOG_E("MG_Remote fd passing: bad sideband header (magic=0x%08X size=%u datagram=%zd)", header.magic, + header.sidebandSize, got); closeAll(-1); return MOBILEGL_ERR_PROTOCOL_MISMATCH; } diff --git a/MobileGL/MG_Remote/Transport/FdPassing.h b/MobileGL/MG_Remote/Transport/FdPassing.h index d9b1b3db..1a74f9b0 100644 --- a/MobileGL/MG_Remote/Transport/FdPassing.h +++ b/MobileGL/MG_Remote/Transport/FdPassing.h @@ -57,7 +57,7 @@ namespace MobileGL::MG_Remote::Transport::FdPassing { // On success *outFd owns a descriptor this process must close. // MOBILEGL_ERR_TIMEOUT when nothing arrived (timeoutMs 0 = poll), // MOBILEGL_ERR_TRANSPORT_CLOSED on peer close. - MobileGLResult ReceiveFd(int socket, int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, std::uint32_t timeoutMs); + MobileGLResult ReceiveFd(int socket, int* outFd, MobileGLMutableByteSpan sideband, std::uint64_t* outSidebandSize, + std::uint32_t timeoutMs); } // namespace MobileGL::MG_Remote::Transport::FdPassing diff --git a/MobileGL/MG_Remote/Transport/Framing.h b/MobileGL/MG_Remote/Transport/Framing.h index dac18aa7..c3cfd14e 100644 --- a/MobileGL/MG_Remote/Transport/Framing.h +++ b/MobileGL/MG_Remote/Transport/Framing.h @@ -40,13 +40,11 @@ namespace MobileGL::MG_Remote::Transport { inline constexpr std::uint64_t kFrameReaderCompactThreshold = 64ull * 1024; // Appends one framed message to `out`. - inline MobileGLResult AppendFrame(std::vector& out, const void* payload, - std::uint64_t size) { + inline MobileGLResult AppendFrame(std::vector& out, const void* payload, std::uint64_t size) { if (size > kMaxFramePayloadSize) { WireLogError("MG_Remote framing: refusing to send a %llu byte payload (cap %llu); " "bulk bytes belong in shm", - static_cast(size), - static_cast(kMaxFramePayloadSize)); + static_cast(size), static_cast(kMaxFramePayloadSize)); return MOBILEGL_ERR_INVALID_ARGUMENT; } if (size != 0 && payload == nullptr) { @@ -87,9 +85,7 @@ namespace MobileGL::MG_Remote::Transport { bool Failed() const { return m_failed; } - bool HasMessage() const { - return !m_failed && m_haveHeader && Available() >= kFrameHeaderSize + m_pendingSize; - } + bool HasMessage() const { return !m_failed && m_haveHeader && Available() >= kFrameHeaderSize + m_pendingSize; } // Size of the next complete message, or 0 when none is complete yet. std::uint64_t PendingMessageSize() const { return HasMessage() ? m_pendingSize : 0; } @@ -179,8 +175,7 @@ namespace MobileGL::MG_Remote::Transport { m_buffer.clear(); m_readPos = 0; } else if (m_readPos >= kFrameReaderCompactThreshold) { - m_buffer.erase(m_buffer.begin(), - m_buffer.begin() + static_cast(m_readPos)); + m_buffer.erase(m_buffer.begin(), m_buffer.begin() + static_cast(m_readPos)); m_readPos = 0; } // Header of the next message may already be buffered. diff --git a/MobileGL/MG_Remote/Transport/ILink.h b/MobileGL/MG_Remote/Transport/ILink.h index c74f053d..1644db24 100644 --- a/MobileGL/MG_Remote/Transport/ILink.h +++ b/MobileGL/MG_Remote/Transport/ILink.h @@ -51,18 +51,18 @@ namespace MobileGL::MG_Remote::Transport { // §11.2). The event flags stay out: the client clears eventRingFull on every drain, an RMW // that would share the cache line the client's spin predicate reads for appliedSeq. struct LinkProgress { - std::atomic appliedSeq; // records applied - std::atomic retiredSeq; // GPU finished - std::atomic completedFrameSerial; // releases *RetiredTail / SEG_ADOPT - std::atomic presentAckSerial; // returns present credit + std::atomic appliedSeq; // records applied + std::atomic retiredSeq; // GPU finished + std::atomic completedFrameSerial; // releases *RetiredTail / SEG_ADOPT + std::atomic presentAckSerial; // returns present credit }; // The reverse channel's two flags. Separated from LinkProgress for the // false-sharing reason above, not for a taxonomic one: both roles read // eventRingFull in a park predicate, and both roles write it. struct LinkEventFlags { - std::atomic eventRingFull; // server latches, client clears - std::atomic eventDropped; // server-only counter + std::atomic eventRingFull; // server latches, client clears + std::atomic eventDropped; // server-only counter }; // ---- the producer's handle ---------------------------------------------- @@ -112,7 +112,7 @@ namespace MobileGL::MG_Remote::Transport { // second clause is that a transport DECLARES what it supports and may not // invent a third answer. struct LinkCapabilities { - bool SharedMapping = false; // peers see one set of bytes; no copy on read + bool SharedMapping = false; // peers see one set of bytes; no copy on read bool DescriptorPassing = false; bool PublishIsDelivery = false; // false => Flush() is mandatory before any wait std::uint64_t MaxRecordBytes = 0; @@ -168,7 +168,6 @@ namespace MobileGL::MG_Remote::Transport { virtual EventRingConsumer& EventsIn() = 0; virtual void BindDoorbells(Doorbell*, Doorbell*) {} - // The watermark block. Taken once; every wait predicate reads through // the returned pointer with no further virtual dispatch. Never null on // an attached link. @@ -190,8 +189,7 @@ namespace MobileGL::MG_Remote::Transport { // Reserve `bytes` of staging and return where the record should name // it. MOBILEGL_ERR_RING_OVERRUN when the request cannot be satisfied at // any occupancy; the caller's existing back-pressure path is unchanged. - virtual MobileGLResult StageBytes(std::uint64_t bytes, LinkSpan* outSpan, - void** outWritePtr) = 0; + virtual MobileGLResult StageBytes(std::uint64_t bytes, LinkSpan* outSpan, void** outWritePtr) = 0; // Resolve a span the peer named. On a shared mapping this is pointer // arithmetic over the peer's segment; on a stream it points into the @@ -203,15 +201,14 @@ namespace MobileGL::MG_Remote::Transport { // progress transmission, send window, flush - did not name it, and // without it a stream link has no way to answer the eleven // ResolveOrFatal call sites in the decoder. - virtual MobileGLResult ResolveSpan(LinkSegment segment, LinkSpan span, - const void** outPtr) = 0; + virtual MobileGLResult ResolveSpan(LinkSegment segment, LinkSpan span, const void** outPtr) = 0; // ---- replies -------------------------------------------------------- - virtual MobileGLResult PostReply(std::uint64_t seq, std::int32_t status, - const void* payload, std::uint64_t size) = 0; - virtual MobileGLResult ReadReply(std::uint64_t seq, std::int32_t* outStatus, - const void** outPayload, std::uint64_t* outSize) = 0; + virtual MobileGLResult PostReply(std::uint64_t seq, std::int32_t status, const void* payload, + std::uint64_t size) = 0; + virtual MobileGLResult ReadReply(std::uint64_t seq, std::int32_t* outStatus, const void** outPayload, + std::uint64_t* outSize) = 0; // P12: DECLARE THAT THIS SEQ'S ANSWER WILL BE READ, so that a link which can retain it // does. Called by the producer side of the wire, in seq order, BEFORE the record is @@ -285,8 +282,8 @@ namespace MobileGL::MG_Remote::Transport { // Setup factories keep concrete link selection out of codec/session code. std::unique_ptr CreateSharedLink(TransportRoleTag role); - MobileGLResult CreateStreamLink(int fd, const SessionSegmentSizes& sizes, - TransportRoleTag role, std::unique_ptr& out); + MobileGLResult CreateStreamLink(int fd, const SessionSegmentSizes& sizes, TransportRoleTag role, + std::unique_ptr& out); // PH-7 (4), ID-P7-3. The server's nonce-bound stream link, in the two steps // StreamLink::AttachOwnedDeferred / BindDataFd describe: created with its owned memory and // no descriptor (Welcome announces those sizes before the data connection exists), then diff --git a/MobileGL/MG_Remote/Transport/ITransport.h b/MobileGL/MG_Remote/Transport/ITransport.h index 3c90b874..f608d7c6 100644 --- a/MobileGL/MG_Remote/Transport/ITransport.h +++ b/MobileGL/MG_Remote/Transport/ITransport.h @@ -80,8 +80,8 @@ namespace MobileGL::MG_Remote::Transport { // be half-consumed, so the capacity is checked BEFORE anything is read // and a short buffer returns MOBILEGL_ERR_BUFFER_TOO_SMALL with the // required size, having consumed nothing and dropped no descriptor. - virtual MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) = 0; + virtual MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, std::uint64_t* outSidebandSize, + std::uint32_t timeoutMs) = 0; // ---- lifecycle ------------------------------------------------------ diff --git a/MobileGL/MG_Remote/Transport/InProcessTransport.cpp b/MobileGL/MG_Remote/Transport/InProcessTransport.cpp index 255282c6..5ddcda6f 100644 --- a/MobileGL/MG_Remote/Transport/InProcessTransport.cpp +++ b/MobileGL/MG_Remote/Transport/InProcessTransport.cpp @@ -117,8 +117,7 @@ namespace MobileGL::MG_Remote::Transport { // illegal the day the delivery mode changes to `spawn`. if (bytes.size > kMaxFramePayloadSize) { MGLOG_E("MG_Remote inproc: refusing a %llu byte message (cap %llu)", - static_cast(bytes.size), - static_cast(kMaxFramePayloadSize)); + static_cast(bytes.size), static_cast(kMaxFramePayloadSize)); return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -135,8 +134,7 @@ namespace MobileGL::MG_Remote::Transport { return MOBILEGL_OK; } - MobileGLResult InProcessTransport::ReceiveFrame(MobileGLMutableByteSpan buffer, - std::uint64_t* outSize, + MobileGLResult InProcessTransport::ReceiveFrame(MobileGLMutableByteSpan buffer, std::uint64_t* outSize, std::uint32_t timeoutMs) { if (outSize != nullptr) { *outSize = 0; @@ -194,8 +192,7 @@ namespace MobileGL::MG_Remote::Transport { if (fd < 0) { return MOBILEGL_ERR_INVALID_ARGUMENT; } - if (sideband.size > FdPassing::kMaxSidebandBytes || - (sideband.size != 0 && sideband.data == nullptr)) { + if (sideband.size > FdPassing::kMaxSidebandBytes || (sideband.size != 0 && sideband.data == nullptr)) { return MOBILEGL_ERR_INVALID_ARGUMENT; } // Same ownership rule as SCM_RIGHTS: the peer gets its own descriptor @@ -228,8 +225,7 @@ namespace MobileGL::MG_Remote::Transport { } MobileGLResult InProcessTransport::ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, - std::uint32_t timeoutMs) { + std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) { if (outFd == nullptr) { return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -285,11 +281,17 @@ namespace MobileGL::MG_Remote::Transport { // directions are half-closed and both ring doorbells are KILLED, because a // peer parked on a ring doorbell mid-frame would otherwise never come back // (a mere ring is consumed once and the waiter parks again). - void InProcessTransport::Shutdown() { m_channel->Close(); } + void InProcessTransport::Shutdown() { + m_channel->Close(); + } - Doorbell& InProcessTransport::PeerDoorbell() { return m_channel->Bell(1 - m_endpoint); } + Doorbell& InProcessTransport::PeerDoorbell() { + return m_channel->Bell(1 - m_endpoint); + } - Doorbell& InProcessTransport::SelfDoorbell() { return m_channel->Bell(m_endpoint); } + Doorbell& InProcessTransport::SelfDoorbell() { + return m_channel->Bell(m_endpoint); + } namespace { std::atomic g_peerInAnotherProcessForTest{false}; diff --git a/MobileGL/MG_Remote/Transport/InProcessTransport.h b/MobileGL/MG_Remote/Transport/InProcessTransport.h index cb08ec36..a3803ec6 100644 --- a/MobileGL/MG_Remote/Transport/InProcessTransport.h +++ b/MobileGL/MG_Remote/Transport/InProcessTransport.h @@ -39,8 +39,8 @@ namespace MobileGL::MG_Remote::Transport { std::uint32_t timeoutMs) override; std::uint64_t PeekFrameSize() override; MobileGLResult ShareFd(int fd, MobileGLByteSpan sideband) override; - MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) override; + MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, std::uint64_t* outSidebandSize, + std::uint32_t timeoutMs) override; void Shutdown() override; TransportRole Role() const override { return TransportRole::InProcess; } diff --git a/MobileGL/MG_Remote/Transport/LinkMetrics.cpp b/MobileGL/MG_Remote/Transport/LinkMetrics.cpp index d714ed76..09a3ba36 100644 --- a/MobileGL/MG_Remote/Transport/LinkMetrics.cpp +++ b/MobileGL/MG_Remote/Transport/LinkMetrics.cpp @@ -18,211 +18,222 @@ #endif namespace MobileGL::MG_Remote::Transport { -namespace { - struct Window { - std::uint64_t waitReplies = 0, samples = 0, replyNs = 0, stageBytes = 0; - std::uint64_t transportWaitNs = 0; - std::array histogram{}; - // One slot per wire op, plus a last slot for anything past the table. See - // LinkMetricsMaxOps: the point is that the per-op numbers still SUM to waitReplies. - std::array waitRepliesByOp{}; - std::array emittedByOp{}; - }; - // The client already has exactly one record producer. The server never writes these. - struct Metrics { - bool active = false; - std::uint64_t frame = 0, started = 0, lastFrame = 0, cpuStarted = 0, cpuLast = 0; - Window total{}, current{}; - } metrics; - std::uint64_t ClockNs() { - return std::chrono::duration_cast( - std::chrono::steady_clock::now().time_since_epoch()).count(); - } - std::uint64_t ThreadCpuNs() { + namespace { + struct Window { + std::uint64_t waitReplies = 0, samples = 0, replyNs = 0, stageBytes = 0; + std::uint64_t transportWaitNs = 0; + std::array histogram{}; + // One slot per wire op, plus a last slot for anything past the table. See + // LinkMetricsMaxOps: the point is that the per-op numbers still SUM to waitReplies. + std::array waitRepliesByOp{}; + std::array emittedByOp{}; + }; + // The client already has exactly one record producer. The server never writes these. + struct Metrics { + bool active = false; + std::uint64_t frame = 0, started = 0, lastFrame = 0, cpuStarted = 0, cpuLast = 0; + Window total{}, current{}; + } metrics; + std::uint64_t ClockNs() { + return std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); + } + std::uint64_t ThreadCpuNs() { #if defined(CLOCK_THREAD_CPUTIME_ID) - timespec value{}; - if (::clock_gettime(CLOCK_THREAD_CPUTIME_ID, &value) == 0) - return std::uint64_t(value.tv_sec) * 1000000000ull + value.tv_nsec; + timespec value{}; + if (::clock_gettime(CLOCK_THREAD_CPUTIME_ID, &value) == 0) + return std::uint64_t(value.tv_sec) * 1000000000ull + value.tv_nsec; #endif - return 0; + return 0; + } + std::uint64_t QuantileUpperUs(const Window& w, unsigned percentile) { + if (w.samples == 0) return 0; + const auto target = (w.samples * percentile + 99) / 100; + std::uint64_t count = 0; + for (unsigned i = 0; i < w.histogram.size(); ++i) { + count += w.histogram[i]; + if (count >= target) return 1ull << i; + } + return 1ull << 31; + } + // The per-op split, formatted into a caller buffer. Raw ids: this file includes only + // and friends, not the op table, and the reader names rows with WireOpName + // (2 ResourceCreate, 3 ResourceRespecify, 47 SetTextureParams, 48 ResourceSubData). + void FormatOpSplit(const Window& w, char* out, std::size_t outSize) { + std::size_t at = 0; + out[0] = '\0'; + for (std::uint32_t op = 1; op < LinkMetricsMaxOps && at + 24 < outSize; ++op) { + const std::uint64_t waits = w.waitRepliesByOp[op]; + if (waits == 0) continue; + const int n = std::snprintf(out + at, outSize - at, "%s%u=%llu", at == 0 ? "" : ",", + static_cast(op), static_cast(waits)); + if (n < 0 || static_cast(n) >= outSize - at) break; + at += static_cast(n); + } + if (at == 0) std::snprintf(out, outSize, "(none)"); + } + // The same shape for the emission tally: one formatter, two arrays, so the two lines cannot + // drift apart in their id spelling. + void FormatCounts(const std::array& counts, char* out, std::size_t outSize) { + std::size_t at = 0; + out[0] = '\0'; + for (std::uint32_t op = 1; op < LinkMetricsMaxOps && at + 24 < outSize; ++op) { + if (counts[op] == 0) continue; + const int n = std::snprintf(out + at, outSize - at, "%s%u=%llu", at == 0 ? "" : ",", + static_cast(op), static_cast(counts[op])); + if (n < 0 || static_cast(n) >= outSize - at) break; + at += static_cast(n); + } + if (at == 0) std::snprintf(out, outSize, "(none)"); + } + void Emit(const char* kind, const Window& w, std::uint64_t wallNs, std::uint64_t cpuNs) { + char histogram[768]{}; + std::size_t at = 0; + for (unsigned i = 0; i < w.histogram.size(); ++i) { + const int n = std::snprintf(histogram + at, sizeof(histogram) - at, "%s%llu", i == 0 ? "" : ",", + static_cast(w.histogram[i])); + if (n < 0 || static_cast(n) >= sizeof(histogram) - at) break; + at += static_cast(n); + } + MGLOG_I("P65LinkMetrics kind=%s frame=%llu wait_replies=%llu rtt_samples=%llu " + "rtt_mean_us=%.3f rtt_p50_upper_us=%llu rtt_p99_upper_us=%llu " + "stage_bytes=%llu wall_ns=%llu client_thread_cpu_ns=%llu " + "transport_wait_ns=%llu rtt_hist_us_pow2=%s", + kind, static_cast(metrics.frame), + static_cast(w.waitReplies), static_cast(w.samples), + w.samples == 0 ? 0.0 : double(w.replyNs) / double(w.samples) / 1000.0, + static_cast(QuantileUpperUs(w, 50)), + static_cast(QuantileUpperUs(w, 99)), + static_cast(w.stageBytes), static_cast(wallNs), + static_cast(cpuNs), static_cast(w.transportWaitNs), + histogram); + // AND THE PER-OP SPLIT WITH EVERY FRAME. A whole-session total cannot name the rows behind + // ONE frame, and one frame is the whole shape of this problem: the device run that + // motivated this printed 43,232 of its 43,629 waits in a single atlas frame, and the exit + // summary never appeared at all because a SIGTERM does not take the session's clean stop + // path. Skipped when the frame bought nothing, so a steady-state session stays quiet. + if (w.waitReplies != 0) { + char split[1024]; + FormatOpSplit(w, split, sizeof(split)); + MGLOG_I("P65LinkMetrics kind=frame-op frame=%llu wait_replies=%llu by_op=%s", + static_cast(metrics.frame), static_cast(w.waitReplies), + split); + } + // WHAT THE FRAME SENT, not only what it paid for. Printed for the same frames - a frame + // that bought no wait still SENT records, and the count is what says whether the waits are + // one per record (a real handshake) or fewer (a batched one). + std::uint64_t emitted = 0; + for (std::uint32_t op = 1; op < LinkMetricsMaxOps; ++op) + emitted += w.emittedByOp[op]; + if (emitted != 0) { + char sent[1024]; + FormatCounts(w.emittedByOp, sent, sizeof(sent)); + MGLOG_I("P65LinkMetrics kind=frame-sent frame=%llu records=%llu by_op=%s", + static_cast(metrics.frame), static_cast(emitted), sent); + } + } + } // namespace + void LinkMetricsBegin() { + metrics = {}; + const char* enabled = std::getenv("MOBILEGL_PIPE_STATS"); + metrics.active = enabled != nullptr && std::strcmp(enabled, "1") == 0; + if (!metrics.active) return; + metrics.started = metrics.lastFrame = ClockNs(); + metrics.cpuStarted = metrics.cpuLast = ThreadCpuNs(); } - std::uint64_t QuantileUpperUs(const Window& w, unsigned percentile) { - if (w.samples == 0) return 0; - const auto target = (w.samples * percentile + 99) / 100; - std::uint64_t count = 0; - for (unsigned i = 0; i < w.histogram.size(); ++i) { - count += w.histogram[i]; - if (count >= target) return 1ull << i; - } - return 1ull << 31; + std::uint64_t LinkMetricsBeginReply(bool wantsReply, std::uint32_t op) { + if (!metrics.active || !wantsReply) return 0; + const std::uint32_t slot = op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1; + ++metrics.current.waitReplies; + ++metrics.total.waitReplies; + ++metrics.current.waitRepliesByOp[slot]; + ++metrics.total.waitRepliesByOp[slot]; + return ClockNs(); } - // The per-op split, formatted into a caller buffer. Raw ids: this file includes only - // and friends, not the op table, and the reader names rows with WireOpName - // (2 ResourceCreate, 3 ResourceRespecify, 47 SetTextureParams, 48 ResourceSubData). - void FormatOpSplit(const Window& w, char* out, std::size_t outSize) { - std::size_t at = 0; - out[0] = '\0'; - for (std::uint32_t op = 1; op < LinkMetricsMaxOps && at + 24 < outSize; ++op) { - const std::uint64_t waits = w.waitRepliesByOp[op]; - if (waits == 0) continue; - const int n = std::snprintf(out + at, outSize - at, "%s%u=%llu", at == 0 ? "" : ",", - static_cast(op), - static_cast(waits)); - if (n < 0 || static_cast(n) >= outSize - at) break; - at += static_cast(n); - } - if (at == 0) std::snprintf(out, outSize, "(none)"); - } - // The same shape for the emission tally: one formatter, two arrays, so the two lines cannot - // drift apart in their id spelling. - void FormatCounts(const std::array& counts, char* out, - std::size_t outSize) { - std::size_t at = 0; - out[0] = '\0'; - for (std::uint32_t op = 1; op < LinkMetricsMaxOps && at + 24 < outSize; ++op) { - if (counts[op] == 0) continue; - const int n = std::snprintf(out + at, outSize - at, "%s%u=%llu", at == 0 ? "" : ",", - static_cast(op), - static_cast(counts[op])); - if (n < 0 || static_cast(n) >= outSize - at) break; - at += static_cast(n); - } - if (at == 0) std::snprintf(out, outSize, "(none)"); - } - void Emit(const char* kind, const Window& w, std::uint64_t wallNs, std::uint64_t cpuNs) { - char histogram[768]{}; - std::size_t at = 0; - for (unsigned i = 0; i < w.histogram.size(); ++i) { - const int n = std::snprintf(histogram + at, sizeof(histogram) - at, "%s%llu", - i == 0 ? "" : ",", static_cast(w.histogram[i])); - if (n < 0 || static_cast(n) >= sizeof(histogram) - at) break; - at += static_cast(n); - } - MGLOG_I("P65LinkMetrics kind=%s frame=%llu wait_replies=%llu rtt_samples=%llu " - "rtt_mean_us=%.3f rtt_p50_upper_us=%llu rtt_p99_upper_us=%llu " - "stage_bytes=%llu wall_ns=%llu client_thread_cpu_ns=%llu " - "transport_wait_ns=%llu rtt_hist_us_pow2=%s", - kind, static_cast(metrics.frame), - static_cast(w.waitReplies), static_cast(w.samples), - w.samples == 0 ? 0.0 : double(w.replyNs) / double(w.samples) / 1000.0, - static_cast(QuantileUpperUs(w, 50)), - static_cast(QuantileUpperUs(w, 99)), - static_cast(w.stageBytes), static_cast(wallNs), - static_cast(cpuNs), - static_cast(w.transportWaitNs), histogram); - // AND THE PER-OP SPLIT WITH EVERY FRAME. A whole-session total cannot name the rows behind - // ONE frame, and one frame is the whole shape of this problem: the device run that - // motivated this printed 43,232 of its 43,629 waits in a single atlas frame, and the exit - // summary never appeared at all because a SIGTERM does not take the session's clean stop - // path. Skipped when the frame bought nothing, so a steady-state session stays quiet. - if (w.waitReplies != 0) { - char split[1024]; - FormatOpSplit(w, split, sizeof(split)); - MGLOG_I("P65LinkMetrics kind=frame-op frame=%llu wait_replies=%llu by_op=%s", - static_cast(metrics.frame), - static_cast(w.waitReplies), split); - } - // WHAT THE FRAME SENT, not only what it paid for. Printed for the same frames - a frame - // that bought no wait still SENT records, and the count is what says whether the waits are - // one per record (a real handshake) or fewer (a batched one). - std::uint64_t emitted = 0; - for (std::uint32_t op = 1; op < LinkMetricsMaxOps; ++op) emitted += w.emittedByOp[op]; - if (emitted != 0) { - char sent[1024]; - FormatCounts(w.emittedByOp, sent, sizeof(sent)); - MGLOG_I("P65LinkMetrics kind=frame-sent frame=%llu records=%llu by_op=%s", - static_cast(metrics.frame), - static_cast(emitted), sent); + void LinkMetricsReplyApplied(std::uint64_t startedNs) { + if (!metrics.active || startedNs == 0) return; + const auto ns = ClockNs() - startedNs; + const auto us = (ns + 999) / 1000; + unsigned bucket = 0; + while (bucket < 31 && us > (1ull << bucket)) + ++bucket; + for (Window* w : {&metrics.current, &metrics.total}) { + ++w->samples; + w->replyNs += ns; + ++w->histogram[bucket]; } } -} -void LinkMetricsBegin() { - metrics = {}; - const char* enabled = std::getenv("MOBILEGL_PIPE_STATS"); - metrics.active = enabled != nullptr && std::strcmp(enabled, "1") == 0; - if (!metrics.active) return; - metrics.started = metrics.lastFrame = ClockNs(); - metrics.cpuStarted = metrics.cpuLast = ThreadCpuNs(); -} -std::uint64_t LinkMetricsBeginReply(bool wantsReply, std::uint32_t op) { - if (!metrics.active || !wantsReply) return 0; - const std::uint32_t slot = op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1; - ++metrics.current.waitReplies; ++metrics.total.waitReplies; - ++metrics.current.waitRepliesByOp[slot]; ++metrics.total.waitRepliesByOp[slot]; - return ClockNs(); -} -void LinkMetricsReplyApplied(std::uint64_t startedNs) { - if (!metrics.active || startedNs == 0) return; - const auto ns = ClockNs() - startedNs; - const auto us = (ns + 999) / 1000; - unsigned bucket = 0; - while (bucket < 31 && us > (1ull << bucket)) ++bucket; - for (Window* w : {&metrics.current, &metrics.total}) { - ++w->samples; w->replyNs += ns; ++w->histogram[bucket]; + void LinkMetricsNoteRecord(std::uint32_t op) { + if (!metrics.active) return; + const std::uint32_t slot = op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1; + ++metrics.current.emittedByOp[slot]; + ++metrics.total.emittedByOp[slot]; + } + std::uint64_t LinkMetricsRecordsFor(std::uint32_t op) { + return metrics.total.emittedByOp[op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1]; + } + std::uint64_t LinkMetricsReplyWaits() { + return metrics.total.waitReplies; + } + std::uint64_t LinkMetricsReplyWaitsFor(std::uint32_t op) { + return metrics.total.waitRepliesByOp[op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1]; + } + void LinkMetricsStageBytes(std::uint64_t bytes) { + if (!metrics.active) return; + metrics.current.stageBytes += bytes; + metrics.total.stageBytes += bytes; + } + std::uint64_t LinkMetricsBeginTransportWait() { + return metrics.active ? ClockNs() : 0; + } + void LinkMetricsEndTransportWait(std::uint64_t startedNs) { + if (!metrics.active || startedNs == 0) return; + const auto elapsed = ClockNs() - startedNs; + metrics.current.transportWaitNs += elapsed; + metrics.total.transportWaitNs += elapsed; + } + void LinkMetricsPresent() { + if (!metrics.active) return; + ++metrics.frame; + const auto now = ClockNs(), cpu = ThreadCpuNs(); + Emit("frame", metrics.current, now - metrics.lastFrame, cpu - metrics.cpuLast); + metrics.current = {}; + metrics.lastFrame = now; + metrics.cpuLast = cpu; + } + void LinkMetricsEnd() { + if (!metrics.active) return; + Emit("summary", metrics.total, ClockNs() - metrics.started, ThreadCpuNs() - metrics.cpuStarted); + // AND THE PER-OP SPLIT AT THE SAME INSTANT, on its own line. The handoff wanted this number + // and could not get it: under spawn the client does not advance the frame window, so the + // per-frame lines never print, and a SIGKILL takes the exit-time dump with it. The summary is + // written from the one place a client that ends the ordinary way always reaches. `op` is raw - + // this file does not include the op table - and the reader names rows with WireOpName. + char split[1024]; + FormatOpSplit(metrics.total, split, sizeof(split)); + MGLOG_I("P65LinkMetrics kind=per-op wait_replies=%llu by_op=%s", + static_cast(metrics.total.waitReplies), split); + metrics.active = false; + } + void LinkMetricsServerPresent(std::uint64_t serial) { + // Independent of the client latch: spawn servers never call LinkMetricsBegin. + // The baseline belongs to the apply thread, and is discarded if that thread is replaced. + static thread_local const bool enabled = [] { + const char* value = std::getenv("MOBILEGL_PIPE_STATS"); + return value != nullptr && std::strcmp(value, "1") == 0; + }(); + if (!enabled) return; + static thread_local std::uint64_t previousSerial = 0, previousWall = 0, previousCpu = 0; + const auto wall = ClockNs(), cpu = ThreadCpuNs(); + const bool valid = previousWall != 0 && serial > previousSerial && cpu >= previousCpu; + MGLOG_I("P65ServerMetrics frame=%llu valid=%u wall_ns=%llu apply_thread_cpu_ns=%llu", + static_cast(serial), valid ? 1u : 0u, + static_cast(valid ? wall - previousWall : 0), + static_cast(valid ? cpu - previousCpu : 0)); + previousSerial = serial; + previousWall = wall; + previousCpu = cpu; } -} -void LinkMetricsNoteRecord(std::uint32_t op) { - if (!metrics.active) return; - const std::uint32_t slot = op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1; - ++metrics.current.emittedByOp[slot]; ++metrics.total.emittedByOp[slot]; -} -std::uint64_t LinkMetricsRecordsFor(std::uint32_t op) { - return metrics.total.emittedByOp[op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1]; -} -std::uint64_t LinkMetricsReplyWaits() { return metrics.total.waitReplies; } -std::uint64_t LinkMetricsReplyWaitsFor(std::uint32_t op) { - return metrics.total.waitRepliesByOp[op < LinkMetricsMaxOps ? op : LinkMetricsMaxOps - 1]; -} -void LinkMetricsStageBytes(std::uint64_t bytes) { - if (!metrics.active) return; - metrics.current.stageBytes += bytes; metrics.total.stageBytes += bytes; -} -std::uint64_t LinkMetricsBeginTransportWait() { - return metrics.active ? ClockNs() : 0; -} -void LinkMetricsEndTransportWait(std::uint64_t startedNs) { - if (!metrics.active || startedNs == 0) return; - const auto elapsed = ClockNs() - startedNs; - metrics.current.transportWaitNs += elapsed; - metrics.total.transportWaitNs += elapsed; -} -void LinkMetricsPresent() { - if (!metrics.active) return; - ++metrics.frame; - const auto now = ClockNs(), cpu = ThreadCpuNs(); - Emit("frame", metrics.current, now - metrics.lastFrame, cpu - metrics.cpuLast); - metrics.current = {}; metrics.lastFrame = now; metrics.cpuLast = cpu; -} -void LinkMetricsEnd() { - if (!metrics.active) return; - Emit("summary", metrics.total, ClockNs() - metrics.started, ThreadCpuNs() - metrics.cpuStarted); - // AND THE PER-OP SPLIT AT THE SAME INSTANT, on its own line. The handoff wanted this number - // and could not get it: under spawn the client does not advance the frame window, so the - // per-frame lines never print, and a SIGKILL takes the exit-time dump with it. The summary is - // written from the one place a client that ends the ordinary way always reaches. `op` is raw - - // this file does not include the op table - and the reader names rows with WireOpName. - char split[1024]; - FormatOpSplit(metrics.total, split, sizeof(split)); - MGLOG_I("P65LinkMetrics kind=per-op wait_replies=%llu by_op=%s", - static_cast(metrics.total.waitReplies), split); - metrics.active = false; -} -void LinkMetricsServerPresent(std::uint64_t serial) { - // Independent of the client latch: spawn servers never call LinkMetricsBegin. - // The baseline belongs to the apply thread, and is discarded if that thread is replaced. - static thread_local const bool enabled = [] { - const char* value = std::getenv("MOBILEGL_PIPE_STATS"); - return value != nullptr && std::strcmp(value, "1") == 0; - }(); - if (!enabled) return; - static thread_local std::uint64_t previousSerial = 0, previousWall = 0, previousCpu = 0; - const auto wall = ClockNs(), cpu = ThreadCpuNs(); - const bool valid = previousWall != 0 && serial > previousSerial && cpu >= previousCpu; - MGLOG_I("P65ServerMetrics frame=%llu valid=%u wall_ns=%llu apply_thread_cpu_ns=%llu", - static_cast(serial), valid ? 1u : 0u, - static_cast(valid ? wall - previousWall : 0), - static_cast(valid ? cpu - previousCpu : 0)); - previousSerial = serial; previousWall = wall; previousCpu = cpu; -} -} +} // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Transport/LinkMetrics.h b/MobileGL/MG_Remote/Transport/LinkMetrics.h index 423ad92d..b8f12baf 100644 --- a/MobileGL/MG_Remote/Transport/LinkMetrics.h +++ b/MobileGL/MG_Remote/Transport/LinkMetrics.h @@ -64,15 +64,15 @@ namespace MobileGL::MG_Remote::Transport { // ClientSession/ServerLoop. The counters themselves stay in StreamLink.cpp beside the recv / // sendmsg loops that feed them. struct LinkIoStats { - std::uint64_t bytes = 0; // payload bytes taken off the socket - std::uint64_t calls = 0; // recv() calls it took - std::uint64_t reads = 0; // Link::Read() calls (one per header, envelope or chunk) - std::uint64_t nsInRecv = 0; // wall time inside recv - blocking included - std::uint64_t nsTotal = 0; // wall time inside Read + std::uint64_t bytes = 0; // payload bytes taken off the socket + std::uint64_t calls = 0; // recv() calls it took + std::uint64_t reads = 0; // Link::Read() calls (one per header, envelope or chunk) + std::uint64_t nsInRecv = 0; // wall time inside recv - blocking included + std::uint64_t nsTotal = 0; // wall time inside Read }; LinkIoStats LinkMetricsTakeReadStats(); // The client's half: bytes handed to the io thread's sendmsg, the calls it took, and the // wall time inside sendmsg (blocking included). nsInRecv carries the send time; reads is // unused on this side. LinkIoStats LinkMetricsTakeSendStats(); -} +} // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Transport/LogForward.cpp b/MobileGL/MG_Remote/Transport/LogForward.cpp index 305aebf1..0dfa80ff 100644 --- a/MobileGL/MG_Remote/Transport/LogForward.cpp +++ b/MobileGL/MG_Remote/Transport/LogForward.cpp @@ -40,20 +40,29 @@ namespace MobileGL::MG_Remote::Transport { Wire::LogLevel WireLogLevelFor(int level) { if (level <= MOBILEGL_LOG_LEVEL_DEBUG) return Wire::LogLevel::Debug; switch (level) { - case MOBILEGL_LOG_LEVEL_INFO: return Wire::LogLevel::Info; - case MOBILEGL_LOG_LEVEL_WARN: return Wire::LogLevel::Warn; - case MOBILEGL_LOG_LEVEL_ERROR: return Wire::LogLevel::Error; - default: return Wire::LogLevel::Fatal; + case MOBILEGL_LOG_LEVEL_INFO: + return Wire::LogLevel::Info; + case MOBILEGL_LOG_LEVEL_WARN: + return Wire::LogLevel::Warn; + case MOBILEGL_LOG_LEVEL_ERROR: + return Wire::LogLevel::Error; + default: + return Wire::LogLevel::Fatal; } } int LogLevelFromWire(Wire::LogLevel level) { switch (level) { - case Wire::LogLevel::Debug: return MOBILEGL_LOG_LEVEL_DEBUG; - case Wire::LogLevel::Info: return MOBILEGL_LOG_LEVEL_INFO; - case Wire::LogLevel::Warn: return MOBILEGL_LOG_LEVEL_WARN; - case Wire::LogLevel::Error: return MOBILEGL_LOG_LEVEL_ERROR; - case Wire::LogLevel::Fatal: return MOBILEGL_LOG_LEVEL_FATAL; + case Wire::LogLevel::Debug: + return MOBILEGL_LOG_LEVEL_DEBUG; + case Wire::LogLevel::Info: + return MOBILEGL_LOG_LEVEL_INFO; + case Wire::LogLevel::Warn: + return MOBILEGL_LOG_LEVEL_WARN; + case Wire::LogLevel::Error: + return MOBILEGL_LOG_LEVEL_ERROR; + case Wire::LogLevel::Fatal: + return MOBILEGL_LOG_LEVEL_FATAL; } // A value this build's schema does not name: a newer peer's severity. Never demoted into // the lossy class - an unknown severity is filed as an error. @@ -77,13 +86,17 @@ namespace MobileGL::MG_Remote::Transport { m_sender = std::thread([this] { Run(); }); } - LogForwardChannel::~LogForwardChannel() { (void)Close(); } + LogForwardChannel::~LogForwardChannel() { + (void)Close(); + } void LogForwardChannel::ForwardThunk(void* user, int level, const char* line) { static_cast(user)->Offer(level, line); } - bool LogForwardChannel::IsUrgentLocked() const { return m_urgent > 0 || m_closing; } + bool LogForwardChannel::IsUrgentLocked() const { + return m_urgent > 0 || m_closing; + } void LogForwardChannel::QueueGapNoticesLocked(bool lossyGapToo) { // ONE NOTICE PER GAP, however many lines it swallowed, so a notice never needs room of its @@ -180,8 +193,10 @@ namespace MobileGL::MG_Remote::Transport { item.text.assign(line, length); const std::uint64_t seq = item.seq; m_queue.push_back(std::move(item)); - if (lossy) m_lossyBytes += length; - else m_errorBytes += length; + if (lossy) + m_lossyBytes += length; + else + m_errorBytes += length; if (fatal) ++m_urgent; m_cv.notify_all(); if (!fatal) { @@ -286,8 +301,10 @@ namespace MobileGL::MG_Remote::Transport { Item item = std::move(front); m_queue.pop_front(); if (item.kind == Kind::Line) { - if (item.level <= MOBILEGL_LOG_LEVEL_WARN) m_lossyBytes -= item.text.size(); - else m_errorBytes -= item.text.size(); + if (item.level <= MOBILEGL_LOG_LEVEL_WARN) + m_lossyBytes -= item.text.size(); + else + m_errorBytes -= item.text.size(); } if (item.kind == Kind::Control || (item.kind == Kind::Line && item.level >= MOBILEGL_LOG_LEVEL_FATAL)) { --m_urgent; @@ -322,9 +339,7 @@ namespace MobileGL::MG_Remote::Transport { // that the tail it has is not the whole tail. QueueGapNoticesLocked(/*lossyGapToo=*/true); m_cv.notify_all(); - const auto drained = [this] { - return (m_queue.empty() && !m_sending) || m_counters.transportFailed; - }; + const auto drained = [this] { return (m_queue.empty() && !m_sending) || m_counters.transportFailed; }; if (!m_cv.wait_for(lock, std::chrono::milliseconds(m_limits.closeDrainMs), drained)) { // Not taken within the budget: the peer is not reading. The rest is abandoned, // and a sender inside a write is blocked on that peer and needs unblocking. diff --git a/MobileGL/MG_Remote/Transport/LogForward.h b/MobileGL/MG_Remote/Transport/LogForward.h index a1223437..98dbc754 100644 --- a/MobileGL/MG_Remote/Transport/LogForward.h +++ b/MobileGL/MG_Remote/Transport/LogForward.h @@ -31,7 +31,7 @@ // flatbuffers) into every includer; the definition is protocol_generated.h's. namespace MobileGL::Wire { enum class LogLevel : std::uint8_t; -} +} // namespace MobileGL::Wire namespace MobileGL::MG_Remote::Transport { @@ -60,8 +60,8 @@ namespace MobileGL::MG_Remote::Transport { }; struct LogForwardCounters { - std::uint64_t forwarded = 0; // lines written to the transport (notices excluded) - std::uint64_t lossyDropped = 0; // DEBUG/INFO/WARN lines dropped at the lossy cap + std::uint64_t forwarded = 0; // lines written to the transport (notices excluded) + std::uint64_t lossyDropped = 0; // DEBUG/INFO/WARN lines dropped at the lossy cap std::uint64_t lossyDroppedBytes = 0; std::uint64_t errorsCoalesced = 0; // ERROR/FATAL lines past the error cap std::uint64_t errorsPaced = 0; // ERROR lines that waited for the pace (delayed, sent) @@ -99,14 +99,18 @@ namespace MobileGL::MG_Remote::Transport { static void ForwardThunk(void* user, int level, const char* line); private: - enum class Kind : std::uint8_t { Line, Notice, Control }; + enum class Kind : std::uint8_t { + Line, + Notice, + Control + }; struct Item { Kind kind = Kind::Line; int level = 0; std::uint64_t seq = 0; - std::string text; // Line, Notice + std::string text; // Line, Notice std::vector frame; // Control - bool paced = false; // already counted as paced once + bool paced = false; // already counted as paced once }; void Run(); @@ -129,7 +133,7 @@ namespace MobileGL::MG_Remote::Transport { // m_writtenSeq when a FATAL wait last ran out; while it has not moved since, the next FATAL // does not wait again (Offer). ~0 = no wait has run out. std::uint64_t m_fatalGaveUpAtWritten = ~0ull; - std::uint64_t m_urgent = 0; // FATAL lines and control frames queued: do not pace + std::uint64_t m_urgent = 0; // FATAL lines and control frames queued: do not pace // A gap not yet marked in the stream, per class. std::uint64_t m_gapLossyLines = 0; std::uint64_t m_gapLossyBytes = 0; diff --git a/MobileGL/MG_Remote/Transport/PairBind.h b/MobileGL/MG_Remote/Transport/PairBind.h index 915c6495..fc135f04 100644 --- a/MobileGL/MG_Remote/Transport/PairBind.h +++ b/MobileGL/MG_Remote/Transport/PairBind.h @@ -31,8 +31,8 @@ namespace MobileGL::MG_Remote::Transport { inline std::vector EncodePairBind(const std::uint8_t (&nonce)[kPairNonceBytes], bool aux) { ::flatbuffers::FlatBufferBuilder builder(64); auto bind = ::MobileGL::Wire::CreatePairBind(builder, builder.CreateVector(nonce, kPairNonceBytes), aux); - auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::PairBind, - bind.Union()); + auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::PairBind, bind.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); return std::vector(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); } diff --git a/MobileGL/MG_Remote/Transport/ReplySlot.h b/MobileGL/MG_Remote/Transport/ReplySlot.h index 7c7c96fd..e3d9ddbd 100644 --- a/MobileGL/MG_Remote/Transport/ReplySlot.h +++ b/MobileGL/MG_Remote/Transport/ReplySlot.h @@ -79,8 +79,7 @@ namespace MobileGL::MG_Remote::Transport { if (slotBytes <= sizeof(ReplySlotHeader)) { WireLogError("MG_Remote reply pool: rejected, %llu bytes over %u slots leaves no " "room for a payload past the %llu byte slot header", - static_cast(sizeBytes), - static_cast(slotCount), + static_cast(sizeBytes), static_cast(slotCount), static_cast(sizeof(ReplySlotHeader))); return; } @@ -91,14 +90,13 @@ namespace MobileGL::MG_Remote::Transport { // it is naturally aligned. A geometry whose slotBytes is not a // multiple of 8 puts later slots on odd boundaries, so it is refused // here rather than left to a future caller to discover. - if ((slotBytes % 8) != 0 || - (reinterpret_cast(base) % alignof(ReplySlotHeader)) != 0) { - WireLogError("MG_Remote reply pool: rejected, a %llu byte slot at base alignment " - "%llu would put a slot header on an unaligned address, and the seq " - "stamp the wrong-slot check reads has to be untorn", - static_cast(slotBytes), - static_cast( - reinterpret_cast(base) % alignof(ReplySlotHeader))); + if ((slotBytes % 8) != 0 || (reinterpret_cast(base) % alignof(ReplySlotHeader)) != 0) { + WireLogError( + "MG_Remote reply pool: rejected, a %llu byte slot at base alignment " + "%llu would put a slot header on an unaligned address, and the seq " + "stamp the wrong-slot check reads has to be untorn", + static_cast(slotBytes), + static_cast(reinterpret_cast(base) % alignof(ReplySlotHeader))); return; } m_base = static_cast(base); @@ -107,8 +105,7 @@ namespace MobileGL::MG_Remote::Transport { // Truncated to 32 bits deliberately: the header's Size field is // 32-bit, so a slot no 32-bit count could describe would let a // legal-looking Size name bytes past the slot. - m_slotBytes = slotBytes > 0xFFFFFFFFull ? 0xFFFFFFFFu - : static_cast(slotBytes); + m_slotBytes = slotBytes > 0xFFFFFFFFull ? 0xFFFFFFFFu : static_cast(slotBytes); } bool Valid() const { return m_base != nullptr; } @@ -118,8 +115,7 @@ namespace MobileGL::MG_Remote::Transport { // it emits a ReadPixels, which is the whole reason a fixed slot size is // legitimate rather than a guess. std::uint32_t MaxReplyBytes() const { - return m_slotBytes == 0 ? 0u - : m_slotBytes - static_cast(sizeof(ReplySlotHeader)); + return m_slotBytes == 0 ? 0u : m_slotBytes - static_cast(sizeof(ReplySlotHeader)); } // ID-47: THE CLIENT'S HALF of "a reply larger than one slot is fatal". True @@ -142,8 +138,8 @@ namespace MobileGL::MG_Remote::Transport { // what is refused is exactly what would have been posted. This is the one // call c1's OnReadPixels emitter makes before EmitAndWait; the // ClientSession forwards it verbatim (RequireReadPixelsReplyFits). - void RequireReadPixelsFits(std::uint32_t width, std::uint32_t height, std::uint32_t format, - std::uint32_t type, std::uint64_t bytes) const { + void RequireReadPixelsFits(std::uint32_t width, std::uint32_t height, std::uint32_t format, std::uint32_t type, + std::uint64_t bytes) const { if (CanHold(bytes)) { return; } @@ -153,9 +149,8 @@ namespace MobileGL::MG_Remote::Transport { "client before emission (ID-47): a reply is neither truncated nor " "chunked, and the client bands any read whose single pixel fits a slot, " "so this answer is one that no banding could have sent", - static_cast(width), static_cast(height), - static_cast(format), static_cast(type), - static_cast(bytes), + static_cast(width), static_cast(height), static_cast(format), + static_cast(type), static_cast(bytes), static_cast(MaxReplyBytes()), static_cast(m_slots), static_cast(m_slotBytes), static_cast(MaxReplyBytes())); } @@ -168,8 +163,7 @@ namespace MobileGL::MG_Remote::Transport { } for (std::uint32_t slot = 0; slot < m_slots; ++slot) { ReplySlotHeader header{}; - std::memcpy(m_base + static_cast(slot) * m_slotBytes, &header, - sizeof(header)); + std::memcpy(m_base + static_cast(slot) * m_slotBytes, &header, sizeof(header)); } } @@ -194,9 +188,8 @@ namespace MobileGL::MG_Remote::Transport { "for seq %llu does not fit a %u byte slot (payload cap %u). P5 does " "not chunk replies: the client knows an answer's size before it emits " "the record, so this means the two sides disagree about the frame", - static_cast(size), - static_cast(seq), static_cast(m_slotBytes), - static_cast(MaxReplyBytes())); + static_cast(size), static_cast(seq), + static_cast(m_slotBytes), static_cast(MaxReplyBytes())); } std::uint8_t* slot = SlotAt(seq); if (size != 0 && bytes != nullptr) { @@ -213,15 +206,17 @@ namespace MobileGL::MG_Remote::Transport { // The link can lend the immutable payload after appliedSeq without a // second copy. The same seq and size honesty checks guard both readers. - bool ReadView(std::uint64_t seq, std::int32_t* status, const void** payload, - std::uint64_t* size) const { + bool ReadView(std::uint64_t seq, std::int32_t* status, const void** payload, std::uint64_t* size) const { if (!m_base || !seq || !status || !payload || !size) return false; const auto* slot = SlotAt(seq); - ReplySlotHeader header{}; std::memcpy(&header, slot, sizeof header); + ReplySlotHeader header{}; + std::memcpy(&header, slot, sizeof header); std::atomic_thread_fence(std::memory_order_acquire); if (header.Seq != seq || header.Size > MaxReplyBytes()) return false; - *status = header.Status; *size = header.Size; - *payload = slot + sizeof header; return true; + *status = header.Status; + *size = header.Size; + *payload = slot + sizeof header; + return true; } // Client side. Returns false when the slot does not carry THIS seq - the @@ -231,8 +226,8 @@ namespace MobileGL::MG_Remote::Transport { // A payload larger than the caller's buffer is a caller bug rather than a // wire fault (the caller sized it from the call it made), so it returns // false with *outSize set to what was there, the ReceiveFrame shape. - bool Read(std::uint64_t seq, void* outBytes, std::uint64_t outCapacity, - std::int32_t* outStatus, std::uint64_t* outSize) const { + bool Read(std::uint64_t seq, void* outBytes, std::uint64_t outCapacity, std::int32_t* outStatus, + std::uint64_t* outSize) const { if (outStatus != nullptr) { *outStatus = kReplyStatusError; } @@ -254,16 +249,14 @@ namespace MobileGL::MG_Remote::Transport { WireLogError("MG_Remote reply pool: slot %llu carries seq %llu, not %llu - the two " "sides' sequence spaces have drifted (a counted kRecPad, R-9) or the " "addressing disagrees", - static_cast(seq & m_mask), - static_cast(header.Seq), + static_cast(seq & m_mask), static_cast(header.Seq), static_cast(seq)); return false; } if (header.Size > MaxReplyBytes()) { WireLogError("MG_Remote reply pool: slot for seq %llu declares %u payload bytes in " "a %u byte slot", - static_cast(seq), header.Size, - static_cast(m_slotBytes)); + static_cast(seq), header.Size, static_cast(m_slotBytes)); return false; } if (outStatus != nullptr) { diff --git a/MobileGL/MG_Remote/Transport/Ring.cpp b/MobileGL/MG_Remote/Transport/Ring.cpp index 194fa7ef..dbc936be 100644 --- a/MobileGL/MG_Remote/Transport/Ring.cpp +++ b/MobileGL/MG_Remote/Transport/Ring.cpp @@ -24,7 +24,9 @@ namespace MobileGL::MG_Remote::Transport { return (value + (kRingRecordAlignment - 1)) & ~(kRingRecordAlignment - 1); } - bool IsPowerOfTwo(std::uint64_t value) { return value != 0 && (value & (value - 1)) == 0; } + bool IsPowerOfTwo(std::uint64_t value) { + return value != 0 && (value & (value - 1)) == 0; + } std::atomic& Head(RingControl& c, RingCursorSet which) { return which == RingCursorSet::Cmd ? c.cmdHead : c.stageHead; @@ -54,8 +56,7 @@ namespace MobileGL::MG_Remote::Transport { control.ringGeneration.store(1, std::memory_order_relaxed); } - bool RingCursorsValid(const RingControl& control, RingCursorSet cursors, - std::uint64_t capacityBytes) { + bool RingCursorsValid(const RingControl& control, RingCursorSet cursors, std::uint64_t capacityBytes) { const std::uint64_t head = Head(control, cursors).load(std::memory_order_acquire); const std::uint64_t applied = AppliedTail(control, cursors).load(std::memory_order_acquire); const std::uint64_t retired = RetiredTail(control, cursors).load(std::memory_order_acquire); @@ -72,8 +73,7 @@ namespace MobileGL::MG_Remote::Transport { if (head != applied || applied != retired) { MGLOG_E("MG_Remote ring: hard drain refused, ring is not quiesced " "(head=%llu applied=%llu retired=%llu)", - static_cast(head), - static_cast(applied), + static_cast(head), static_cast(applied), static_cast(retired)); return MOBILEGL_ERR_INVALID_ARGUMENT; } @@ -87,18 +87,16 @@ namespace MobileGL::MG_Remote::Transport { // Producer // ----------------------------------------------------------------------- - RingProducer::RingProducer(RingControl* control, void* base, std::uint64_t capacityBytes, - RingCursorSet cursors) + RingProducer::RingProducer(RingControl* control, void* base, std::uint64_t capacityBytes, RingCursorSet cursors) : m_control(control), m_base(static_cast(base)), m_capacity(capacityBytes), m_mask(capacityBytes - 1), m_cursors(cursors) { - if (control == nullptr || base == nullptr || !IsPowerOfTwo(capacityBytes) || - capacityBytes < kMinRingCapacity || capacityBytes > kMaxRingCapacity) { + if (control == nullptr || base == nullptr || !IsPowerOfTwo(capacityBytes) || capacityBytes < kMinRingCapacity || + capacityBytes > kMaxRingCapacity) { MGLOG_E("MG_Remote ring: producer rejected, capacity %llu must be a power of two " "between %llu and %llu bytes over a non-null mapping (a record may be at most " "half the ring, and the record header's size field is 32-bit, so a bigger ring " "would truncate it)", - static_cast(capacityBytes), - static_cast(kMinRingCapacity), + static_cast(capacityBytes), static_cast(kMinRingCapacity), static_cast(kMaxRingCapacity)); m_control = nullptr; m_base = nullptr; @@ -124,8 +122,7 @@ namespace MobileGL::MG_Remote::Transport { return inFlight >= m_capacity ? 0 : m_capacity - inFlight; } - void* RingProducer::Reserve(std::uint16_t kind, std::uint16_t flags, - std::uint64_t payloadBytes) { + void* RingProducer::Reserve(std::uint16_t kind, std::uint16_t flags, std::uint64_t payloadBytes) { if (m_control == nullptr) { return nullptr; } @@ -160,8 +157,7 @@ namespace MobileGL::MG_Remote::Transport { const bool needsPad = spaceToEnd < total; const std::uint64_t needed = needsPad ? spaceToEnd + total : total; if (FreeBytes() < needed) { - MGLOG_D("MG_Remote ring: full, %llu bytes free, %llu needed", - static_cast(FreeBytes()), + MGLOG_D("MG_Remote ring: full, %llu bytes free, %llu needed", static_cast(FreeBytes()), static_cast(needed)); return nullptr; } @@ -198,18 +194,16 @@ namespace MobileGL::MG_Remote::Transport { // Consumer // ----------------------------------------------------------------------- - RingConsumer::RingConsumer(RingControl* control, void* base, std::uint64_t capacityBytes, - RingCursorSet cursors) - : m_control(control), m_base(static_cast(base)), - m_capacity(capacityBytes), m_mask(capacityBytes - 1), m_cursors(cursors) { - if (control == nullptr || base == nullptr || !IsPowerOfTwo(capacityBytes) || - capacityBytes < kMinRingCapacity || capacityBytes > kMaxRingCapacity) { + RingConsumer::RingConsumer(RingControl* control, void* base, std::uint64_t capacityBytes, RingCursorSet cursors) + : m_control(control), m_base(static_cast(base)), m_capacity(capacityBytes), + m_mask(capacityBytes - 1), m_cursors(cursors) { + if (control == nullptr || base == nullptr || !IsPowerOfTwo(capacityBytes) || capacityBytes < kMinRingCapacity || + capacityBytes > kMaxRingCapacity) { MGLOG_E("MG_Remote ring: consumer rejected, capacity %llu must be a power of two " "between %llu and %llu bytes over a non-null mapping (a record may be at most " "half the ring, and the record header's size field is 32-bit, so a bigger ring " "would truncate it)", - static_cast(capacityBytes), - static_cast(kMinRingCapacity), + static_cast(capacityBytes), static_cast(kMinRingCapacity), static_cast(kMaxRingCapacity)); m_control = nullptr; m_base = nullptr; @@ -233,8 +227,7 @@ namespace MobileGL::MG_Remote::Transport { if (available < sizeof(RingRecordHeader) || available > m_capacity) { MGLOG_E("MG_Remote ring: %llu bytes between tail and head is impossible for a %llu " "byte ring", - static_cast(available), - static_cast(m_capacity)); + static_cast(available), static_cast(m_capacity)); if (outCorrupt != nullptr) { *outCorrupt = true; } @@ -249,13 +242,12 @@ namespace MobileGL::MG_Remote::Transport { // preceded by these bounds checks and a violation is fatal, never a // retry (plan section 6.3, runtime bounds discipline). const std::uint64_t size = header.size; - if (size < sizeof(RingRecordHeader) || (size % kRingRecordAlignment) != 0 || - size > available || offset + size > m_capacity) { + if (size < sizeof(RingRecordHeader) || (size % kRingRecordAlignment) != 0 || size > available || + offset + size > m_capacity) { MGLOG_E("MG_Remote ring: corrupt record header at cursor %llu " "(kind=%u flags=0x%04X size=%u available=%llu)", - static_cast(m_localTail), - static_cast(header.kind), static_cast(header.flags), - header.size, static_cast(available)); + static_cast(m_localTail), static_cast(header.kind), + static_cast(header.flags), header.size, static_cast(available)); if (outCorrupt != nullptr) { *outCorrupt = true; } @@ -362,8 +354,7 @@ namespace MobileGL::MG_Remote::Transport { // already rules the same way for the cursor invariants on this page. // "Early" can only be caught at the call site, which is why every // advance below has exactly one caller and a named unit case. - void AdvanceMonotonic(std::atomic& watermark, std::uint64_t to, - const char* name) { + void AdvanceMonotonic(std::atomic& watermark, std::uint64_t to, const char* name) { const std::uint64_t current = watermark.load(std::memory_order_relaxed); if (to < current) { // WireLogFatal, not MGLOG_F + abort: the line has to reach stderr @@ -373,8 +364,7 @@ namespace MobileGL::MG_Remote::Transport { "cannot be un-resumed, and every later advance of this watermark " "would be a no-op, so the verb barrier and every reply wait would " "block for ever", - name, static_cast(current), - static_cast(to)); + name, static_cast(current), static_cast(to)); } if (to == current) { return; @@ -423,8 +413,8 @@ namespace MobileGL::MG_Remote::Transport { SetLink(link.Capabilities().PublishIsDelivery ? nullptr : &link); } - void SessionProducer::Attach(RingControl* control, RingProducer* cmd, Doorbell* peerBell, - Doorbell* selfBell, std::uint32_t spinUs) { + void SessionProducer::Attach(RingControl* control, RingProducer* cmd, Doorbell* peerBell, Doorbell* selfBell, + std::uint32_t spinUs) { m_control = control; m_cmd = cmd; m_peerBell = peerBell; @@ -519,8 +509,7 @@ namespace MobileGL::MG_Remote::Transport { } RingControl* control = m_control; const ScopedWakeTarget wake(control->producerWakeSeq, seq); - return Park([control, seq] { return Watermark::Reached(control->Progress.appliedSeq, seq); }, - timeoutMs); + return Park([control, seq] { return Watermark::Reached(control->Progress.appliedSeq, seq); }, timeoutMs); } SessionWait SessionProducer::WaitForPresentAck(std::uint64_t serial, std::uint32_t timeoutMs) { @@ -538,8 +527,7 @@ namespace MobileGL::MG_Remote::Transport { return m_control != nullptr && m_control->eventRingFull.load(std::memory_order_acquire) != 0; } - SessionWait SessionProducer::WaitForAppliedOrEventBacklog(std::uint64_t seq, - std::uint32_t timeoutMs) { + SessionWait SessionProducer::WaitForAppliedOrEventBacklog(std::uint64_t seq, std::uint32_t timeoutMs) { if (!Valid()) { return SessionWait::TimedOut; } @@ -553,8 +541,7 @@ namespace MobileGL::MG_Remote::Transport { timeoutMs); } - SessionWait SessionProducer::WaitForPresentAckOrEventBacklog(std::uint64_t serial, - std::uint32_t timeoutMs) { + SessionWait SessionProducer::WaitForPresentAckOrEventBacklog(std::uint64_t serial, std::uint32_t timeoutMs) { if (!Valid()) { return SessionWait::TimedOut; } @@ -588,8 +575,8 @@ namespace MobileGL::MG_Remote::Transport { SetLink(link.Capabilities().PublishIsDelivery ? nullptr : &link); } - void SessionConsumer::Attach(RingControl* control, RingConsumer* cmd, Doorbell* peerBell, - Doorbell* selfBell, std::uint32_t spinUs) { + void SessionConsumer::Attach(RingControl* control, RingConsumer* cmd, Doorbell* peerBell, Doorbell* selfBell, + std::uint32_t spinUs) { m_control = control; m_cmd = cmd; m_peerBell = peerBell; @@ -619,10 +606,8 @@ namespace MobileGL::MG_Remote::Transport { if (m_link && m_link->FlushProgress() != MOBILEGL_OK) return SessionWait::ShutDown; const bool woke = m_selfBell->Wait( control->consumerParked, - [control, cmd] { - return control->cmdHead.load(std::memory_order_acquire) != cmd->LocalTail(); - }, - m_spinUs, timeoutMs); + [control, cmd] { return control->cmdHead.load(std::memory_order_acquire) != cmd->LocalTail(); }, m_spinUs, + timeoutMs); if (woke) { return SessionWait::Reached; } diff --git a/MobileGL/MG_Remote/Transport/Ring.h b/MobileGL/MG_Remote/Transport/Ring.h index 7c1d0229..08048d69 100644 --- a/MobileGL/MG_Remote/Transport/Ring.h +++ b/MobileGL/MG_Remote/Transport/Ring.h @@ -65,12 +65,12 @@ namespace MobileGL::MG_Remote::Transport { alignas(64) LinkProgress Progress; // ---- doorbell / generation ------------------------------------------- - alignas(64) std::atomic serverEpoch; // ++ on context loss / server restart - std::atomic ringGeneration; // ++ after a hard drain - std::atomic consumerParked; // server asleep, producer must ring - std::atomic producerParked; // client asleep, server must ring - std::atomic eventRingFull; // SEG_EVENT full, server stopped applying - std::atomic eventDropped; // dropped lossy events + alignas(64) std::atomic serverEpoch; // ++ on context loss / server restart + std::atomic ringGeneration; // ++ after a hard drain + std::atomic consumerParked; // server asleep, producer must ring + std::atomic producerParked; // client asleep, server must ring + std::atomic eventRingFull; // SEG_EVENT full, server stopped applying + std::atomic eventDropped; // dropped lossy events // ---- the parked producer's wake target ------------------------------- // @@ -120,8 +120,7 @@ namespace MobileGL::MG_Remote::Transport { // there. Growth INSIDE LinkProgress is caught, by sizeof below; growth BESIDE it is // not, and the only thing standing between that and a false-sharing regression is // this comment and the group's own. - static_assert(offsetof(RingControl, Progress) % 64 == 0, - "LinkProgress starts a cache line of its own"); + static_assert(offsetof(RingControl, Progress) % 64 == 0, "LinkProgress starts a cache line of its own"); static_assert(offsetof(RingControl, serverEpoch) == offsetof(RingControl, Progress) + 64, "the Progress line holds Progress and nothing else"); @@ -130,12 +129,9 @@ namespace MobileGL::MG_Remote::Transport { // roles map, and swapping two of them passes every other check in this file. static_assert(offsetof(LinkProgress, appliedSeq) == 0, "LinkProgress field order: appliedSeq"); static_assert(offsetof(LinkProgress, retiredSeq) == 8, "LinkProgress field order: retiredSeq"); - static_assert(offsetof(LinkProgress, completedFrameSerial) == 16, - "LinkProgress field order: completedFrameSerial"); - static_assert(offsetof(LinkProgress, presentAckSerial) == 24, - "LinkProgress field order: presentAckSerial"); - static_assert(sizeof(LinkProgress) == 32, - "four consumer-written watermarks; the event flags are NOT in here"); + static_assert(offsetof(LinkProgress, completedFrameSerial) == 16, "LinkProgress field order: completedFrameSerial"); + static_assert(offsetof(LinkProgress, presentAckSerial) == 24, "LinkProgress field order: presentAckSerial"); + static_assert(sizeof(LinkProgress) == 32, "four consumer-written watermarks; the event flags are NOT in here"); // The two event flags stay in the doorbell group on purpose (see the group's comment). // LinkEventFlags mirrors them, so the mirror's shape is pinned too. @@ -143,8 +139,8 @@ namespace MobileGL::MG_Remote::Transport { "the two event flags are adjacent, in that order"); static_assert(offsetof(RingControl, eventRingFull) / 64 == offsetof(RingControl, serverEpoch) / 64, "the event flags share the doorbell group's line, NOT the Progress line"); - static_assert(offsetof(LinkEventFlags, eventRingFull) == 0 && - offsetof(LinkEventFlags, eventDropped) == 4 && sizeof(LinkEventFlags) == 8, + static_assert(offsetof(LinkEventFlags, eventRingFull) == 0 && offsetof(LinkEventFlags, eventDropped) == 4 && + sizeof(LinkEventFlags) == 8, "LinkEventFlags mirrors the pair in RingControl"); // The page's real content must fit the page it claims. sizeof(RingControl) == 4096 @@ -181,7 +177,7 @@ namespace MobileGL::MG_Remote::Transport { kRecNone = 0, kRecNeedsAck = 1u << 0, kRecHasBlob = 1u << 1, - kRecPad = 1u << 2, // filler to the wrap boundary, no payload meaning + kRecPad = 1u << 2, // filler to the wrap boundary, no payload meaning kRecBorrowSlot = 1u << 3, // slot is borrowed into the GPU timeline; retires late kRecVarTail = 1u << 4, // P12: THE PEER NEED NOT ANSWER THIS RECORD. Set by the producer for a record whose @@ -229,8 +225,7 @@ namespace MobileGL::MG_Remote::Transport { // head >= appliedTail >= retiredTail, and the ring never holds more than // its capacity. False means the shared page is corrupt (or a peer is // misbehaving), which is a Fatal{ProtocolCorruption}, never a retry. - bool RingCursorsValid(const RingControl& control, RingCursorSet cursors, - std::uint64_t capacityBytes); + bool RingCursorsValid(const RingControl& control, RingCursorSet cursors, std::uint64_t capacityBytes); // Bumps ringGeneration, invalidating every offset either side has cached. // Both sides must be quiesced and the ring fully drained @@ -254,8 +249,7 @@ namespace MobileGL::MG_Remote::Transport { // `base` is the ring's byte area (NOT the control page) and // `capacityBytes` must be a power of two between kMinRingCapacity and // kMaxRingCapacity. Anything else leaves Valid() false. - RingProducer(RingControl* control, void* base, std::uint64_t capacityBytes, - RingCursorSet cursors); + RingProducer(RingControl* control, void* base, std::uint64_t capacityBytes, RingCursorSet cursors); bool Valid() const { return m_control != nullptr; } @@ -287,9 +281,7 @@ namespace MobileGL::MG_Remote::Transport { private: std::uint64_t TailForReclaim() const; - std::uint8_t* SlotAt(std::uint64_t cursor) const { - return m_base + static_cast(cursor & m_mask); - } + std::uint8_t* SlotAt(std::uint64_t cursor) const { return m_base + static_cast(cursor & m_mask); } RingControl* m_control = nullptr; std::uint8_t* m_base = nullptr; @@ -303,8 +295,7 @@ namespace MobileGL::MG_Remote::Transport { class RingConsumer { public: RingConsumer() = default; - RingConsumer(RingControl* control, void* base, std::uint64_t capacityBytes, - RingCursorSet cursors); + RingConsumer(RingControl* control, void* base, std::uint64_t capacityBytes, RingCursorSet cursors); bool Valid() const { return m_control != nullptr; } diff --git a/MobileGL/MG_Remote/Transport/RoleMemory.h b/MobileGL/MG_Remote/Transport/RoleMemory.h index 76668cb5..442a9438 100644 --- a/MobileGL/MG_Remote/Transport/RoleMemory.h +++ b/MobileGL/MG_Remote/Transport/RoleMemory.h @@ -94,8 +94,7 @@ namespace MobileGL::MG_Remote::Transport { // (R-16's control on this rule: revert PeakRssBytes to the kernel's peak and // it does). RoleMemorySample SampleRoleMemoryInto(std::atomic& runningPeak, MemoryRole role, - std::uint64_t kernelPeakRssBytes, - std::uint64_t kernelCurrentRssBytes); + std::uint64_t kernelPeakRssBytes, std::uint64_t kernelCurrentRssBytes); // Emits one line at INFO level, which is what every P5 lane builds at, so // t1's harness can grep it out of a lane log without a new log sink. Not diff --git a/MobileGL/MG_Remote/Transport/ShmLink.cpp b/MobileGL/MG_Remote/Transport/ShmLink.cpp index ed3dcfb7..a3df4fdb 100644 --- a/MobileGL/MG_Remote/Transport/ShmLink.cpp +++ b/MobileGL/MG_Remote/Transport/ShmLink.cpp @@ -52,7 +52,11 @@ namespace MobileGL::MG_Remote::Transport { LinkCapabilities ShmLink::Capabilities() const { const ReplySlotPool replies(m_memory.ReplyBase(), m_memory.ReplyBytes(), m_memory.ReplySlotCount()); // RetainsReplies stays false: a slot is seq % slotCount and the next answer into it wins. - return {true, true, true, m_memory.CmdRingCapacity() / 2, replies.MaxReplyBytes(), + return {true, + true, + true, + m_memory.CmdRingCapacity() / 2, + replies.MaxReplyBytes(), /*RetainsReplies=*/false}; } LinkProgress* ShmLink::Progress() { diff --git a/MobileGL/MG_Remote/Transport/ShmSegment.cpp b/MobileGL/MG_Remote/Transport/ShmSegment.cpp index 79cb3508..b93200bc 100644 --- a/MobileGL/MG_Remote/Transport/ShmSegment.cpp +++ b/MobileGL/MG_Remote/Transport/ShmSegment.cpp @@ -32,9 +32,13 @@ namespace MobileGL::MG_Remote::Transport { - ShmSegment::~ShmSegment() { Close(); } + ShmSegment::~ShmSegment() { + Close(); + } - ShmSegment::ShmSegment(ShmSegment&& other) noexcept { Steal(std::move(other)); } + ShmSegment::ShmSegment(ShmSegment&& other) noexcept { + Steal(std::move(other)); + } ShmSegment& ShmSegment::operator=(ShmSegment&& other) noexcept { if (this != &other) { @@ -60,7 +64,9 @@ namespace MobileGL::MG_Remote::Transport { other.m_readOnly = false; } - bool ShmSegment::Valid() const { return m_size != 0 && (m_fd >= 0 || m_nativeHandle != nullptr); } + bool ShmSegment::Valid() const { + return m_size != 0 && (m_fd >= 0 || m_nativeHandle != nullptr); + } // ======================================================================= // P5: the role memory ledger and the VmHWM sample @@ -172,17 +178,14 @@ namespace MobileGL::MG_Remote::Transport { } RoleMemorySample SampleRoleMemoryInto(std::atomic& runningPeak, MemoryRole role, - std::uint64_t kernelPeakRssBytes, - std::uint64_t kernelCurrentRssBytes) { + std::uint64_t kernelPeakRssBytes, std::uint64_t kernelCurrentRssBytes) { // max(running, kernel peak, kernel current), as a CAS loop: two threads // sampling at once (the GL thread and the apply thread both log memory // at phase boundaries) must not let a lower value overwrite a higher. const std::uint64_t observed = kernelPeakRssBytes > kernelCurrentRssBytes ? kernelPeakRssBytes : kernelCurrentRssBytes; std::uint64_t peak = runningPeak.load(std::memory_order_relaxed); - while (observed > peak && - !runningPeak.compare_exchange_weak(peak, observed, std::memory_order_relaxed)) { - } + while (observed > peak && !runningPeak.compare_exchange_weak(peak, observed, std::memory_order_relaxed)) {} RoleMemorySample sample; sample.Role = role; // THE RUNNING MAXIMUM, never the kernel's peak verbatim - RoleMemory.h @@ -222,8 +225,7 @@ namespace MobileGL::MG_Remote::Transport { // ======================================================================= namespace { - constexpr std::size_t kSlotCount = - static_cast(SessionSegmentSlot::kSessionSegmentCount); + constexpr std::size_t kSlotCount = static_cast(SessionSegmentSlot::kSessionSegmentCount); std::size_t SlotIndex(SessionSegmentSlot slot) { const std::size_t index = static_cast(slot); @@ -231,7 +233,9 @@ namespace MobileGL::MG_Remote::Transport { } } // namespace - SessionSegments::~SessionSegments() { Close(); } + SessionSegments::~SessionSegments() { + Close(); + } MobileGLResult SessionSegments::Create(const SessionSegmentSizes& sizes, MemoryRole role) { Close(); @@ -268,12 +272,10 @@ namespace MobileGL::MG_Remote::Transport { } for (std::size_t index = 0; index < kSlotCount; ++index) { - const MobileGLResult created = - ShmSegment::Create(specs[index].name, specs[index].bytes, m_owned[index]); + const MobileGLResult created = ShmSegment::Create(specs[index].name, specs[index].bytes, m_owned[index]); if (created != MOBILEGL_OK) { - MGLOG_E("MG_Remote session: could not create segment %s of %llu bytes (rc=%d)", - specs[index].name, static_cast(specs[index].bytes), - static_cast(created)); + MGLOG_E("MG_Remote session: could not create segment %s of %llu bytes (rc=%d)", specs[index].name, + static_cast(specs[index].bytes), static_cast(created)); Close(); return created; } @@ -311,15 +313,19 @@ namespace MobileGL::MG_Remote::Transport { MobileGLResult SessionSegments::CreatePrivate(const SessionSegmentSizes& sizes, MemoryRole role) { Close(); - const std::uint64_t bytes[4] = {SegmentBytesForRing(sizes.CmdRingBytes), sizes.StageBytes, - sizes.ReplyBytes, SegmentBytesForRing(sizes.EventRingBytes)}; + const std::uint64_t bytes[4] = {SegmentBytesForRing(sizes.CmdRingBytes), sizes.StageBytes, sizes.ReplyBytes, + SegmentBytesForRing(sizes.EventRingBytes)}; for (unsigned i = 0; i != 4; ++i) { if (!bytes[i] || bytes[i] > static_cast(SIZE_MAX)) { - Close(); return MOBILEGL_ERR_INVALID_ARGUMENT; + Close(); + return MOBILEGL_ERR_INVALID_ARGUMENT; + } + m_private[i] = ::operator new(static_cast(bytes[i]), std::align_val_t{alignof(RingControl)}, + std::nothrow); + if (!m_private[i]) { + Close(); + return MOBILEGL_ERR_OUT_OF_MEMORY; } - m_private[i] = ::operator new(static_cast(bytes[i]), - std::align_val_t{alignof(RingControl)}, std::nothrow); - if (!m_private[i]) { Close(); return MOBILEGL_ERR_OUT_OF_MEMORY; } m_privateSizes[i] = bytes[i]; std::memset(m_private[i], 0, static_cast(bytes[i])); m_mappedBytes += bytes[i]; @@ -327,15 +333,21 @@ namespace MobileGL::MG_Remote::Transport { m_cmdControl = static_cast(m_private[0]); m_cmdRingBase = static_cast(m_private[0]) + sizeof(RingControl); m_cmdRingCapacity = RingCapacityForSegment(bytes[0]); - m_stageBase = m_private[1]; m_stageBytes = bytes[1]; - m_replyBase = m_private[2]; m_replyBytes = bytes[2]; m_replySlotCount = sizes.ReplySlotCount; + m_stageBase = m_private[1]; + m_stageBytes = bytes[1]; + m_replyBase = m_private[2]; + m_replyBytes = bytes[2]; + m_replySlotCount = sizes.ReplySlotCount; m_eventControl = static_cast(m_private[3]); m_eventSegmentBase = m_private[3]; m_eventRingBase = static_cast(m_private[3]) + sizeof(RingControl); m_eventRingCapacity = RingCapacityForSegment(bytes[3]); - InitRingControl(*m_cmdControl); InitRingControl(*m_eventControl); - m_role = role; m_valid = true; - LedgerAddSegment(role, m_mappedBytes); m_booked = true; + InitRingControl(*m_cmdControl); + InitRingControl(*m_eventControl); + m_role = role; + m_valid = true; + LedgerAddSegment(role, m_mappedBytes); + m_booked = true; return MOBILEGL_OK; } @@ -357,14 +369,12 @@ namespace MobileGL::MG_Remote::Transport { const ShmSegment* theirs = owner.m_segments[index]; const int duplicate = theirs == nullptr ? -1 : ::dup(theirs->Fd()); if (duplicate < 0) { - MGLOG_E("MG_Remote session: could not dup the owner's descriptor for segment %zu", - index); + MGLOG_E("MG_Remote session: could not dup the owner's descriptor for segment %zu", index); Close(); return MOBILEGL_ERR_INVALID_ARGUMENT; } // Adopt takes ownership of `duplicate` on success only. - const MobileGLResult adopted = - ShmSegment::Adopt(duplicate, theirs->Size(), m_owned[index]); + const MobileGLResult adopted = ShmSegment::Adopt(duplicate, theirs->Size(), m_owned[index]); if (adopted != MOBILEGL_OK) { ::close(duplicate); Close(); @@ -404,13 +414,14 @@ namespace MobileGL::MG_Remote::Transport { return MOBILEGL_OK; } - MobileGLResult SessionSegments::AdoptFromDescriptors(const int fds[4], - const std::uint64_t sizes[4], - std::uint32_t replySlotCount, - MemoryRole role) { + MobileGLResult SessionSegments::AdoptFromDescriptors(const int fds[4], const std::uint64_t sizes[4], + std::uint32_t replySlotCount, MemoryRole role) { Close(); #if defined(_WIN32) - (void)fds; (void)sizes; (void)replySlotCount; (void)role; + (void)fds; + (void)sizes; + (void)replySlotCount; + (void)role; // ShmSegment::Adopt is POSIX-only and a Windows peer resolves the section // by the name in SegmentRef instead. Refused BY NAME rather than silently // producing an unmapped session (CONTRACT-P6 §2.6). @@ -432,11 +443,10 @@ namespace MobileGL::MG_Remote::Transport { // against the announced size - which is the only bound there is on a // peer's claim about how big a segment is (CONTRACT-P6 §4.4 notes it // has no analogue on a link with no fd). - const MobileGLResult adopted = ShmSegment::Adopt(fds[index], sizes[index], - m_owned[index]); + const MobileGLResult adopted = ShmSegment::Adopt(fds[index], sizes[index], m_owned[index]); if (adopted != MOBILEGL_OK) { - MGLOG_E("MG_Remote session: adopting slot %zu (fd=%d, %llu bytes) failed", - index, fds[index], static_cast(sizes[index])); + MGLOG_E("MG_Remote session: adopting slot %zu (fd=%d, %llu bytes) failed", index, fds[index], + static_cast(sizes[index])); Close(); return adopted; } @@ -501,14 +511,12 @@ namespace MobileGL::MG_Remote::Transport { m_mappedBytes += m_segments[index]->Size(); } - if (m_cmdRingCapacity == 0 || m_stageBytes == 0 || m_eventRingCapacity == 0 || - m_replyBytes == 0) { + if (m_cmdRingCapacity == 0 || m_stageBytes == 0 || m_eventRingCapacity == 0 || m_replyBytes == 0) { MGLOG_E("MG_Remote session: segment sizes leave no usable ring (cmd cap=%llu stage " "cap=%llu event cap=%llu reply=%llu). A ring is the largest POWER OF TWO that " "fits after the 4096 byte control page, so a segment must be strictly larger " "than one page plus the smallest ring", - static_cast(m_cmdRingCapacity), - static_cast(m_stageBytes), + static_cast(m_cmdRingCapacity), static_cast(m_stageBytes), static_cast(m_eventRingCapacity), static_cast(m_replyBytes)); return; @@ -529,7 +537,8 @@ namespace MobileGL::MG_Remote::Transport { for (std::size_t index = 0; index < kSlotCount; ++index) { m_segments[index] = nullptr; if (m_private[index]) ::operator delete(m_private[index], std::align_val_t{alignof(RingControl)}); - m_private[index] = nullptr; m_privateSizes[index] = 0; + m_private[index] = nullptr; + m_privateSizes[index] = 0; } m_cmdControl = nullptr; m_cmdRingBase = nullptr; diff --git a/MobileGL/MG_Remote/Transport/ShmSegmentPosix.cpp b/MobileGL/MG_Remote/Transport/ShmSegmentPosix.cpp index 6c334d1d..305b8144 100644 --- a/MobileGL/MG_Remote/Transport/ShmSegmentPosix.cpp +++ b/MobileGL/MG_Remote/Transport/ShmSegmentPosix.cpp @@ -70,8 +70,7 @@ namespace MobileGL::MG_Remote::Transport { // on across the sysroots this builds against. fd = static_cast(::syscall(SYS_memfd_create, label, MFD_CLOEXEC)); if (fd >= 0 && ::ftruncate(fd, static_cast(size)) != 0) { - MGLOG_E("MG_Remote shm: ftruncate(%llu) failed (errno=%d)", - static_cast(size), errno); + MGLOG_E("MG_Remote shm: ftruncate(%llu) failed (errno=%d)", static_cast(size), errno); ::close(fd); fd = -1; } @@ -84,8 +83,7 @@ namespace MobileGL::MG_Remote::Transport { // travels by SCM_RIGHTS. char shmName[kShmNameMax]; std::snprintf(shmName, sizeof(shmName), "/mgl-%d-%u-%s", static_cast(::getpid()), - g_shmCounter.fetch_add(1, std::memory_order_relaxed), - nameHint != nullptr ? nameHint : "seg"); + g_shmCounter.fetch_add(1, std::memory_order_relaxed), nameHint != nullptr ? nameHint : "seg"); fd = ::shm_open(shmName, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd < 0) { MGLOG_E("MG_Remote shm: shm_open(%s) failed (errno=%d)", shmName, errno); @@ -93,8 +91,8 @@ namespace MobileGL::MG_Remote::Transport { } ::shm_unlink(shmName); if (::ftruncate(fd, static_cast(size)) != 0) { - MGLOG_E("MG_Remote shm: ftruncate(%llu) failed (errno=%d)", - static_cast(size), errno); + MGLOG_E("MG_Remote shm: ftruncate(%llu) failed (errno=%d)", static_cast(size), + errno); ::close(fd); return MOBILEGL_ERR_SHM_EXHAUSTED; } @@ -125,8 +123,7 @@ namespace MobileGL::MG_Remote::Transport { // the announcement claims would turn every later offset into an // out-of-bounds map. struct stat st{}; - if (::fstat(fd, &st) == 0 && st.st_size > 0 && - static_cast(st.st_size) < size) { + if (::fstat(fd, &st) == 0 && st.st_size > 0 && static_cast(st.st_size) < size) { MGLOG_E("MG_Remote shm: peer announced %llu bytes but the descriptor is %lld", static_cast(size), static_cast(st.st_size)); return MOBILEGL_ERR_INVALID_ARGUMENT; @@ -159,8 +156,8 @@ namespace MobileGL::MG_Remote::Transport { const int prot = readOnly ? PROT_READ : (PROT_READ | PROT_WRITE); void* addr = ::mmap(nullptr, static_cast(m_size), prot, MAP_SHARED, m_fd, 0); if (addr == MAP_FAILED) { - MGLOG_E("MG_Remote shm: mmap of %llu bytes failed (errno=%d)", - static_cast(m_size), errno); + MGLOG_E("MG_Remote shm: mmap of %llu bytes failed (errno=%d)", static_cast(m_size), + errno); return MOBILEGL_ERR_OUT_OF_MEMORY; } m_mapping = addr; diff --git a/MobileGL/MG_Remote/Transport/ShmSegmentWin32.cpp b/MobileGL/MG_Remote/Transport/ShmSegmentWin32.cpp index 8fc07ea1..f16027b0 100644 --- a/MobileGL/MG_Remote/Transport/ShmSegmentWin32.cpp +++ b/MobileGL/MG_Remote/Transport/ShmSegmentWin32.cpp @@ -51,23 +51,20 @@ namespace MobileGL::MG_Remote::Transport { out.Close(); char name[kShmNameMax]; - std::snprintf(name, sizeof(name), "Local\\mgl-%lu-%u-%s", - static_cast(::GetCurrentProcessId()), - g_sectionCounter.fetch_add(1, std::memory_order_relaxed), - nameHint != nullptr ? nameHint : "seg"); + std::snprintf(name, sizeof(name), "Local\\mgl-%lu-%u-%s", static_cast(::GetCurrentProcessId()), + g_sectionCounter.fetch_add(1, std::memory_order_relaxed), nameHint != nullptr ? nameHint : "seg"); wchar_t wide[kShmNameMax]; if (!ToWide(name, wide, static_cast(kShmNameMax))) { return MOBILEGL_ERR_INVALID_ARGUMENT; } - HANDLE section = ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, - static_cast(size >> 32), - static_cast(size & 0xFFFFFFFFull), wide); + HANDLE section = + ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, static_cast(size >> 32), + static_cast(size & 0xFFFFFFFFull), wide); if (section == nullptr) { MGLOG_E("MG_Remote shm: CreateFileMappingW(%s, %llu) failed (GetLastError=%lu)", name, - static_cast(size), - static_cast(::GetLastError())); + static_cast(size), static_cast(::GetLastError())); return MOBILEGL_ERR_SHM_EXHAUSTED; } if (::GetLastError() == ERROR_ALREADY_EXISTS) { @@ -126,12 +123,10 @@ namespace MobileGL::MG_Remote::Transport { Unmap(); } void* view = ::MapViewOfFile(static_cast(m_nativeHandle), - readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, 0, 0, - static_cast(m_size)); + readOnly ? FILE_MAP_READ : FILE_MAP_ALL_ACCESS, 0, 0, static_cast(m_size)); if (view == nullptr) { MGLOG_E("MG_Remote shm: MapViewOfFile of %llu bytes failed (GetLastError=%lu)", - static_cast(m_size), - static_cast(::GetLastError())); + static_cast(m_size), static_cast(::GetLastError())); return MOBILEGL_ERR_OUT_OF_MEMORY; } m_mapping = view; diff --git a/MobileGL/MG_Remote/Transport/SocketTransport.cpp b/MobileGL/MG_Remote/Transport/SocketTransport.cpp index f8437e14..51560c3c 100644 --- a/MobileGL/MG_Remote/Transport/SocketTransport.cpp +++ b/MobileGL/MG_Remote/Transport/SocketTransport.cpp @@ -24,22 +24,22 @@ #include #if defined(__unix__) || defined(__APPLE__) || defined(__ANDROID__) -# define MOBILEGL_SOCKET_TRANSPORT_POSIX 1 -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# if defined(__linux__) -# include -# endif +#define MOBILEGL_SOCKET_TRANSPORT_POSIX 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) +#include +#endif #else -# define MOBILEGL_SOCKET_TRANSPORT_POSIX 0 +#define MOBILEGL_SOCKET_TRANSPORT_POSIX 0 #endif namespace MobileGL::MG_Remote::Transport { @@ -49,7 +49,9 @@ namespace MobileGL::MG_Remote::Transport { namespace { constexpr std::uint64_t kPumpChunkBytes = 64ull * 1024; - bool TcpName(const std::string& name) { return name.compare(0, 6, "tcp://") == 0; } + bool TcpName(const std::string& name) { + return name.compare(0, 6, "tcp://") == 0; + } bool TcpAddress(const std::string& name, addrinfo** result) { *result = nullptr; @@ -57,19 +59,16 @@ namespace MobileGL::MG_Remote::Transport { std::string host, port; if (!address.empty() && address[0] == '[') { const auto end = address.find(']'); - if (end == std::string::npos || end + 1 >= address.size() || address[end + 1] != ':') - return false; + if (end == std::string::npos || end + 1 >= address.size() || address[end + 1] != ':') return false; host = address.substr(1, end - 1); port = address.substr(end + 2); } else { const auto colon = address.find(':'); - if (colon == std::string::npos || address.find(':', colon + 1) != std::string::npos) - return false; + if (colon == std::string::npos || address.find(':', colon + 1) != std::string::npos) return false; host = address.substr(0, colon); port = address.substr(colon + 1); } - if (host.empty() || port.empty() || port.find_first_not_of("0123456789") != std::string::npos) - return false; + if (host.empty() || port.empty() || port.find_first_not_of("0123456789") != std::string::npos) return false; char* end = nullptr; const auto number = std::strtoul(port.c_str(), &end, 10); if (*end || number == 0 || number > 65535) return false; @@ -99,7 +98,8 @@ namespace MobileGL::MG_Remote::Transport { bool ConfigureTcp(int fd) { const int one = 1, idle = 2, interval = 1, count = 3, timeout = 5000; if (::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)) != 0 || - ::setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) != 0) return false; + ::setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one)) != 0) + return false; #if defined(TCP_KEEPIDLE) if (::setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle)) != 0) return false; #endif @@ -118,8 +118,7 @@ namespace MobileGL::MG_Remote::Transport { MobileGLResult ConnectTcp(const std::string& name, std::uint32_t timeoutMs, int* outFd) { addrinfo* addresses = nullptr; if (!TcpAddress(name, &addresses)) return MOBILEGL_ERR_INVALID_ARGUMENT; - const auto deadline = std::chrono::steady_clock::now() + - std::chrono::milliseconds(std::max(1u, timeoutMs)); + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(std::max(1u, timeoutMs)); do { for (auto* address = addresses; address; address = address->ai_next) { const int fd = ::socket(address->ai_family, SOCK_STREAM, IPPROTO_TCP); @@ -129,14 +128,17 @@ namespace MobileGL::MG_Remote::Transport { bool connected = ::connect(fd, address->ai_addr, address->ai_addrlen) == 0; if (!connected && errno == EINPROGRESS) { const auto remaining = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + deadline - std::chrono::steady_clock::now()) + .count(); pollfd pfd{fd, POLLOUT, 0}; int rc; - do { rc = ::poll(&pfd, 1, static_cast(std::max(0, remaining))); } - while (rc < 0 && errno == EINTR && std::chrono::steady_clock::now() < deadline); + do { + rc = ::poll(&pfd, 1, static_cast(std::max(0, remaining))); + } while (rc < 0 && errno == EINTR && std::chrono::steady_clock::now() < deadline); int error = 0; socklen_t length = sizeof(error); - connected = rc > 0 && ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) == 0 && error == 0; + connected = + rc > 0 && ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &length) == 0 && error == 0; } if (connected && ConfigureTcp(fd)) { ::fcntl(fd, F_SETFL, flags); @@ -218,11 +220,10 @@ namespace MobileGL::MG_Remote::Transport { // SIGCHLD by construction). int WaitReadable(int fd, std::uint32_t timeoutMs) { for (;;) { - struct pollfd pfd {}; + struct pollfd pfd{}; pfd.fd = fd; pfd.events = POLLIN; - const int timeout = - timeoutMs == kWaitForever ? -1 : static_cast(timeoutMs); + const int timeout = timeoutMs == kWaitForever ? -1 : static_cast(timeoutMs); const int rc = ::poll(&pfd, 1, timeout); if (rc < 0 && errno == EINTR) { continue; @@ -281,8 +282,7 @@ namespace MobileGL::MG_Remote::Transport { for (std::uint32_t waited = 0;; waited += 5) { const int fd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { - WireLogError("MG_Remote SocketTransport: socket() failed: %s", - std::strerror(errno)); + WireLogError("MG_Remote SocketTransport: socket() failed: %s", std::strerror(errno)); return MOBILEGL_ERR_UNSUPPORTED; } if (::connect(fd, reinterpret_cast(&address), length) == 0) { @@ -312,7 +312,10 @@ namespace MobileGL::MG_Remote::Transport { std::size_t written = 0; while (written < framed.size()) { const ssize_t n = ::send(fd, framed.data() + written, framed.size() - written, MSG_NOSIGNAL); - if (n > 0) { written += static_cast(n); continue; } + if (n > 0) { + written += static_cast(n); + continue; + } if (n < 0 && errno == EINTR) continue; WireLogError("MG_Remote SocketTransport: the %s could not be sent (%zu of %zu bytes): %s", what, written, framed.size(), std::strerror(errno)); @@ -349,8 +352,7 @@ namespace MobileGL::MG_Remote::Transport { if (type != SOCK_STREAM) return "is not a stream socket"; sockaddr_storage local{}; socklen_t localSize = sizeof(local); - if (::getsockname(fd, reinterpret_cast(&local), &localSize) != 0 || - local.ss_family != AF_UNIX) + if (::getsockname(fd, reinterpret_cast(&local), &localSize) != 0 || local.ss_family != AF_UNIX) return "is not an AF_UNIX socket (a TCP pair has no descriptor channel; use tcp:// instead)"; sockaddr_storage peer{}; socklen_t peerSize = sizeof(peer); @@ -387,8 +389,7 @@ namespace MobileGL::MG_Remote::Transport { return MOBILEGL_ERR_UNSUPPORTED; } if (::bind(listenFd, reinterpret_cast(&address), length) != 0) { - WireLogError("MG_Remote SocketTransport: bind(\"%s\") failed: %s", path.c_str(), - std::strerror(errno)); + WireLogError("MG_Remote SocketTransport: bind(\"%s\") failed: %s", path.c_str(), std::strerror(errno)); ::close(listenFd); return MOBILEGL_ERR_UNSUPPORTED; } @@ -400,8 +401,7 @@ namespace MobileGL::MG_Remote::Transport { ::chmod(path.c_str(), 0600); } if (::listen(listenFd, 4) != 0) { - WireLogError("MG_Remote SocketTransport: listen(\"%s\") failed: %s", path.c_str(), - std::strerror(errno)); + WireLogError("MG_Remote SocketTransport: listen(\"%s\") failed: %s", path.c_str(), std::strerror(errno)); ::close(listenFd); if (!abstractEndpoint) { ::unlink(path.c_str()); @@ -423,9 +423,8 @@ namespace MobileGL::MG_Remote::Transport { if (minted != MOBILEGL_OK) return minted; const auto controlBind = EncodePairBind(nonce, false); int control = -1; - const MobileGLResult first = - ConnectPresenting(path, timeoutMs, controlBind.data(), controlBind.size(), "control connection's PairBind", - &control); + const MobileGLResult first = ConnectPresenting(path, timeoutMs, controlBind.data(), controlBind.size(), + "control connection's PairBind", &control); if (first != MOBILEGL_OK) { return first; } @@ -452,7 +451,8 @@ namespace MobileGL::MG_Remote::Transport { // instead of writing a PairBind into something that is not the server. if (controlFd == auxFd) { WireLogError("MG_Remote: Refuse{ProtocolMismatch} fd: endpoint names descriptor %d twice; the control " - "and aux connections are two sockets", controlFd); + "and aux connections are two sockets", + controlFd); return MOBILEGL_ERR_INVALID_ARGUMENT; } const int fds[2] = {controlFd, auxFd}; @@ -482,8 +482,8 @@ namespace MobileGL::MG_Remote::Transport { MobileGLResult result = MintNonce(nonce, sizeof(nonce)); if (result == MOBILEGL_OK) { const auto controlBind = EncodePairBind(nonce, false); - result = SendFirstFrame(controlFd, controlBind.data(), controlBind.size(), - "fd: control connection's PairBind"); + result = + SendFirstFrame(controlFd, controlBind.data(), controlBind.size(), "fd: control connection's PairBind"); } if (result == MOBILEGL_OK) { const auto auxBind = EncodePairBind(nonce, true); @@ -514,7 +514,8 @@ namespace MobileGL::MG_Remote::Transport { int rc; do { const auto remaining = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + deadline - std::chrono::steady_clock::now()) + .count(); rc = ::poll(&pfd, 1, static_cast(std::max(0, remaining))); } while (rc < 0 && errno == EINTR); int error = 0; @@ -603,8 +604,9 @@ namespace MobileGL::MG_Remote::Transport { const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs); const auto readExactly = [&](std::uint8_t* into, std::size_t size) -> MobileGLResult { while (size != 0) { - const auto left = std::chrono::duration_cast( - deadline - std::chrono::steady_clock::now()).count(); + const auto left = + std::chrono::duration_cast(deadline - std::chrono::steady_clock::now()) + .count(); if (left <= 0) return MOBILEGL_ERR_TIMEOUT; const int ready = WaitReadable(fd, static_cast(left)); if (ready == 0) return MOBILEGL_ERR_TIMEOUT; @@ -636,7 +638,10 @@ namespace MobileGL::MG_Remote::Transport { #if defined(__linux__) && defined(SYS_getrandom) while (filled < size) { const long n = ::syscall(SYS_getrandom, out + filled, size - filled, 0); - if (n > 0) { filled += static_cast(n); continue; } + if (n > 0) { + filled += static_cast(n); + continue; + } if (n < 0 && errno == EINTR) continue; break; // ENOSYS on an old kernel: the device file below is the same pool. } @@ -645,7 +650,10 @@ namespace MobileGL::MG_Remote::Transport { const int source = ::open("/dev/urandom", O_RDONLY | O_CLOEXEC); while (source >= 0 && filled < size) { const ssize_t n = ::read(source, out + filled, size - filled); - if (n > 0) { filled += static_cast(n); continue; } + if (n > 0) { + filled += static_cast(n); + continue; + } if (n < 0 && errno == EINTR) continue; break; } @@ -653,7 +661,8 @@ namespace MobileGL::MG_Remote::Transport { } if (filled == size) return MOBILEGL_OK; WireLogError("MG_Remote SocketTransport: the kernel CSPRNG gave %zu of %zu bytes; a data " - "nonce is not minted from anything weaker", filled, size); + "nonce is not minted from anything weaker", + filled, size); return MOBILEGL_ERR_UNSUPPORTED; } @@ -670,8 +679,7 @@ namespace MobileGL::MG_Remote::Transport { // Framing.h is what makes a stream safe: the message boundary is in the // payload, not in the socket. if (::socketpair(AF_UNIX, SOCK_STREAM, 0, stream) != 0) { - WireLogError("MG_Remote SocketTransport: socketpair(SOCK_STREAM) failed: %s", - std::strerror(errno)); + WireLogError("MG_Remote SocketTransport: socketpair(SOCK_STREAM) failed: %s", std::strerror(errno)); return MOBILEGL_ERR_UNSUPPORTED; } @@ -711,7 +719,9 @@ namespace MobileGL::MG_Remote::Transport { return ::shutdown(m_streamFd, SHUT_WR) == 0 ? MOBILEGL_OK : MOBILEGL_ERR_TRANSPORT_CLOSED; } - SocketTransport::~SocketTransport() { Shutdown(); } + SocketTransport::~SocketTransport() { + Shutdown(); + } MobileGLResult SocketTransport::SendFrame(MobileGLByteSpan bytes) { if (bytes.size > kMaxFramePayloadSize) { @@ -741,8 +751,7 @@ namespace MobileGL::MG_Remote::Transport { std::uint64_t written = 0; while (written < framed.size()) { const ssize_t n = ::send(m_streamFd, framed.data() + written, - static_cast(framed.size() - written), - MSG_NOSIGNAL); + static_cast(framed.size() - written), MSG_NOSIGNAL); if (n > 0) { written += static_cast(n); continue; @@ -759,8 +768,8 @@ namespace MobileGL::MG_Remote::Transport { } // SendFrame is also the log sink: do not recurse into the logger. std::fprintf(stderr, "MG_Remote SocketTransport: send failed after %llu of %llu bytes: %s\n", - static_cast(written), - static_cast(framed.size()), std::strerror(errno)); + static_cast(written), static_cast(framed.size()), + std::strerror(errno)); m_closed = true; return MOBILEGL_ERR_TRANSPORT_CLOSED; } @@ -789,8 +798,7 @@ namespace MobileGL::MG_Remote::Transport { for (;;) { const ssize_t n = ::recv(m_streamFd, chunk, sizeof(chunk), 0); if (n > 0) { - const MobileGLResult fed = - m_reader.Feed(chunk, static_cast(n)); + const MobileGLResult fed = m_reader.Feed(chunk, static_cast(n)); if (fed != MOBILEGL_OK) { // Bad magic or an over-long length. FrameReader latches // itself failed and never recovers; mirror that here so a @@ -824,8 +832,7 @@ namespace MobileGL::MG_Remote::Transport { } } - MobileGLResult SocketTransport::ReceiveFrame(MobileGLMutableByteSpan buffer, - std::uint64_t* outSize, + MobileGLResult SocketTransport::ReceiveFrame(MobileGLMutableByteSpan buffer, std::uint64_t* outSize, std::uint32_t timeoutMs) { if (outSize == nullptr) { return MOBILEGL_ERR_INVALID_ARGUMENT; @@ -893,8 +900,7 @@ namespace MobileGL::MG_Remote::Transport { } MobileGLResult SocketTransport::ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, - std::uint32_t timeoutMs) { + std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) { if (m_tcp || m_auxFd < 0) { return MOBILEGL_ERR_UNSUPPORTED; } @@ -926,8 +932,7 @@ namespace MobileGL::MG_Remote::Transport { // POSIX only (CONTRACT-P6 §2.6). The refusal is NAMED at construction rather // than at first use: a transport that accepts a connection and then fails // every operation is the silent-fallback shape this project forbids. - MobileGLResult SocketTransport::CreatePair(std::unique_ptr&, - std::unique_ptr&) { + MobileGLResult SocketTransport::CreatePair(std::unique_ptr&, std::unique_ptr&) { WireLogError("MG_Remote SocketTransport: unsupported on this platform - P6 lands POSIX " "only (CONTRACT-P6 §2.6); `unix:` and `pipe:` stay named refusals"); return MOBILEGL_ERR_UNSUPPORTED; @@ -939,30 +944,44 @@ namespace MobileGL::MG_Remote::Transport { return MOBILEGL_ERR_UNSUPPORTED; } - bool SocketTransport::Probe(const std::string&, std::uint32_t) { return false; } + bool SocketTransport::Probe(const std::string&, std::uint32_t) { + return false; + } SocketTransport::SocketTransport(int, int, TransportRole role) : m_role(role) {} SocketTransport::~SocketTransport() = default; - int SocketTransport::TakeDataFd() { return -1; } + int SocketTransport::TakeDataFd() { + return -1; + } void SocketTransport::CloseLocalCopy() {} - MobileGLResult SocketTransport::ShutdownSend() { return MOBILEGL_ERR_UNSUPPORTED; } - - MobileGLResult SocketTransport::SendFrame(MobileGLByteSpan) { return MOBILEGL_ERR_UNSUPPORTED; } - MobileGLResult SocketTransport::ReceiveFrame(MobileGLMutableByteSpan, std::uint64_t*, - std::uint32_t) { + MobileGLResult SocketTransport::ShutdownSend() { return MOBILEGL_ERR_UNSUPPORTED; } - std::uint64_t SocketTransport::PeekFrameSize() { return 0; } - std::uint64_t SocketTransport::BufferedBytes() { return 0; } + + MobileGLResult SocketTransport::SendFrame(MobileGLByteSpan) { + return MOBILEGL_ERR_UNSUPPORTED; + } + MobileGLResult SocketTransport::ReceiveFrame(MobileGLMutableByteSpan, std::uint64_t*, std::uint32_t) { + return MOBILEGL_ERR_UNSUPPORTED; + } + std::uint64_t SocketTransport::PeekFrameSize() { + return 0; + } + std::uint64_t SocketTransport::BufferedBytes() { + return 0; + } MobileGLResult SocketTransport::ShareFd(int, MobileGLByteSpan) { return MOBILEGL_ERR_UNSUPPORTED; } - MobileGLResult SocketTransport::ReceiveFd(int*, MobileGLMutableByteSpan, std::uint64_t*, - std::uint32_t) { + MobileGLResult SocketTransport::ReceiveFd(int*, MobileGLMutableByteSpan, std::uint64_t*, std::uint32_t) { return MOBILEGL_ERR_UNSUPPORTED; } - MobileGLResult SocketTransport::PumpOnce(std::uint32_t) { return MOBILEGL_ERR_UNSUPPORTED; } - void SocketTransport::Shutdown() { m_closed = true; } + MobileGLResult SocketTransport::PumpOnce(std::uint32_t) { + return MOBILEGL_ERR_UNSUPPORTED; + } + void SocketTransport::Shutdown() { + m_closed = true; + } #endif diff --git a/MobileGL/MG_Remote/Transport/SocketTransport.h b/MobileGL/MG_Remote/Transport/SocketTransport.h index 40f0ab57..bd0a7d2c 100644 --- a/MobileGL/MG_Remote/Transport/SocketTransport.h +++ b/MobileGL/MG_Remote/Transport/SocketTransport.h @@ -61,8 +61,7 @@ namespace MobileGL::MG_Remote::Transport { // nothing is written; otherwise this presents one - a fresh nonce, control then aux - exactly // as ConnectTo does, before the Hello. TRANSPORT_CLOSED when that send failed: the descriptors // are then left open, because the server's refusal may be waiting on the control connection. - static MobileGLResult AdoptConnectedPair(int controlFd, int auxFd, - std::unique_ptr& outClient, + static MobileGLResult AdoptConnectedPair(int controlFd, int auxFd, std::unique_ptr& outClient, bool pairedAtHandoff = false); // ---- PH-7 (4), ID-P7-3: a TCP data connection is BOUND, not paired -------- @@ -127,8 +126,8 @@ namespace MobileGL::MG_Remote::Transport { std::uint64_t PeekFrameSize() override; MobileGLResult ShareFd(int fd, MobileGLByteSpan sideband) override; - MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, - std::uint64_t* outSidebandSize, std::uint32_t timeoutMs) override; + MobileGLResult ReceiveFd(int* outFd, MobileGLMutableByteSpan sideband, std::uint64_t* outSidebandSize, + std::uint32_t timeoutMs) override; void Shutdown() override; TransportRole Role() const override { return m_role; } diff --git a/MobileGL/MG_Remote/Transport/StreamLink.cpp b/MobileGL/MG_Remote/Transport/StreamLink.cpp index c9febde6..ae4885d5 100644 --- a/MobileGL/MG_Remote/Transport/StreamLink.cpp +++ b/MobileGL/MG_Remote/Transport/StreamLink.cpp @@ -189,10 +189,10 @@ namespace MobileGL::MG_Remote::Transport { while (size) { const auto recvStarted = std::chrono::steady_clock::now(); const auto n = ::recv(fd, p, size, 0); - gReadNsInRecv.fetch_add(static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - recvStarted).count()), - std::memory_order_relaxed); + gReadNsInRecv.fetch_add(static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - recvStarted) + .count()), + std::memory_order_relaxed); ++calls; if (n < 0 && errno == EINTR) continue; if (n <= 0) { @@ -205,10 +205,10 @@ namespace MobileGL::MG_Remote::Transport { gReadCalls.fetch_add(calls, std::memory_order_relaxed); gReadReads.fetch_add(1, std::memory_order_relaxed); gReadBytes.fetch_add(wanted, std::memory_order_relaxed); - gReadNsTotal.fetch_add(static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - readStarted).count()), - std::memory_order_relaxed); + gReadNsTotal.fetch_add(static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - readStarted) + .count()), + std::memory_order_relaxed); return true; #else (void)data; @@ -237,9 +237,10 @@ namespace MobileGL::MG_Remote::Transport { while (message.msg_iovlen) { const auto syscallStarted = std::chrono::steady_clock::now(); const auto sent = ::sendmsg(fd, &message, MSG_NOSIGNAL); - gSendNsInSend.fetch_add(static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - syscallStarted).count()), + gSendNsInSend.fetch_add( + static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - syscallStarted) + .count()), std::memory_order_relaxed); ++sendCalls; if (sent < 0 && errno == EINTR) continue; @@ -260,10 +261,10 @@ namespace MobileGL::MG_Remote::Transport { } gSendCalls.fetch_add(sendCalls, std::memory_order_relaxed); gSendBytes.fetch_add(size, std::memory_order_relaxed); - gSendNsTotal.fetch_add(static_cast( - std::chrono::duration_cast( - std::chrono::steady_clock::now() - sendStarted).count()), - std::memory_order_relaxed); + gSendNsTotal.fetch_add(static_cast(std::chrono::duration_cast( + std::chrono::steady_clock::now() - sendStarted) + .count()), + std::memory_order_relaxed); return true; #else (void)payload; @@ -582,7 +583,11 @@ namespace MobileGL::MG_Remote::Transport { } LinkCapabilities StreamLink::Capabilities() const { if (!m_impl->memory) return {}; - return {false, false, false, m_impl->memory->CmdRingCapacity() / 2, m_impl->MaxReply(), + return {false, + false, + false, + m_impl->memory->CmdRingCapacity() / 2, + m_impl->MaxReply(), /*RetainsReplies=*/true}; } LinkProgress* StreamLink::Progress() { @@ -769,8 +774,7 @@ namespace MobileGL::MG_Remote::Transport { // record for the life of the session (4,536 a frame). The cursor's entries can never be // needed again: a seq is answered once. if (x.wantCursor >= 64) { - x.wanted.erase(x.wanted.begin(), - x.wanted.begin() + static_cast(x.wantCursor)); + x.wanted.erase(x.wanted.begin(), x.wanted.begin() + static_cast(x.wantCursor)); x.wantCursor = 0; } } @@ -780,9 +784,15 @@ namespace MobileGL::MG_Remote::Transport { std::lock_guard lock(x.replyMutex); return static_cast(x.retained.size()); } - std::uint64_t StreamLink::RetainedTotal() const { return m_impl->retainedTotal; } - std::uint64_t StreamLink::SkippedUnwanted() const { return m_impl->skippedUnwanted; } - std::uint64_t StreamLink::UnansweredWanted() const { return m_impl->unansweredWanted; } + std::uint64_t StreamLink::RetainedTotal() const { + return m_impl->retainedTotal; + } + std::uint64_t StreamLink::SkippedUnwanted() const { + return m_impl->skippedUnwanted; + } + std::uint64_t StreamLink::UnansweredWanted() const { + return m_impl->unansweredWanted; + } void StreamLink::Detach() { auto& x = *m_impl; diff --git a/MobileGL/MG_Remote/Transport/WireLog.cpp b/MobileGL/MG_Remote/Transport/WireLog.cpp index 4dba41d5..73af8b04 100644 --- a/MobileGL/MG_Remote/Transport/WireLog.cpp +++ b/MobileGL/MG_Remote/Transport/WireLog.cpp @@ -38,8 +38,7 @@ namespace MobileGL::MG_Remote::Transport { const int written = std::vsnprintf(line, sizeof(line), format, args); va_end(args); if (written < 0) { - std::snprintf(line, sizeof(line), - "MG_Remote wire: unformattable Fatal diagnostic (format=%s)", format); + std::snprintf(line, sizeof(line), "MG_Remote wire: unformattable Fatal diagnostic (format=%s)", format); } MGLOG_F("%s", line); // The stderr echo is the half a death test can see (WireLog.h). Unbuffered diff --git a/MobileGL/MG_Remote/Transport/WireLog.h b/MobileGL/MG_Remote/Transport/WireLog.h index 2d029572..0ebe85cb 100644 --- a/MobileGL/MG_Remote/Transport/WireLog.h +++ b/MobileGL/MG_Remote/Transport/WireLog.h @@ -21,8 +21,7 @@ namespace MobileGL::MG_Remote::Transport { #if defined(__GNUC__) || defined(__clang__) __attribute__((format(printf, 1, 2))) #endif - void - WireLogError(const char* format, ...); + void WireLogError(const char* format, ...); // Formats one line, emits it at FATAL level (MGLOG_F), ECHOES IT TO STDERR, // and aborts. Every `Fatal{...}` this layer raises goes through here. @@ -42,7 +41,6 @@ namespace MobileGL::MG_Remote::Transport { #if defined(__GNUC__) || defined(__clang__) __attribute__((format(printf, 1, 2))) #endif - void - WireLogFatal(const char* format, ...); + void WireLogFatal(const char* format, ...); } // namespace MobileGL::MG_Remote::Transport diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp index 11cad06b..85d85fdc 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.cpp @@ -68,25 +68,18 @@ namespace MobileGL::MG_Remote::Wire { // enums - every bit, the exact overlap mask, and the completeness of the translation - and // a new enumerator on either side breaks the build rather than being dropped in silence. namespace FlagSpace { - constexpr Uint16 kCallAll = static_cast(MG_Pipe::kNeedsAck) | - static_cast(MG_Pipe::kHasBlob) | - static_cast(MG_Pipe::kVarTail) | - static_cast(MG_Pipe::kHostSpan) | - static_cast(MG_Pipe::kReplySlot) | - static_cast(MG_Pipe::kOptional); - constexpr Uint16 kRingAll = static_cast(Transport::kRecNeedsAck) | - static_cast(Transport::kRecHasBlob) | - static_cast(Transport::kRecPad) | - static_cast(Transport::kRecBorrowSlot) | - static_cast(Transport::kRecVarTail) | - static_cast(Transport::kRecNoReply); + constexpr Uint16 kCallAll = static_cast(MG_Pipe::kNeedsAck) | static_cast(MG_Pipe::kHasBlob) | + static_cast(MG_Pipe::kVarTail) | static_cast(MG_Pipe::kHostSpan) | + static_cast(MG_Pipe::kReplySlot) | static_cast(MG_Pipe::kOptional); + constexpr Uint16 kRingAll = + static_cast(Transport::kRecNeedsAck) | static_cast(Transport::kRecHasBlob) | + static_cast(Transport::kRecPad) | static_cast(Transport::kRecBorrowSlot) | + static_cast(Transport::kRecVarTail) | static_cast(Transport::kRecNoReply); // The three call flags the encoder TRANSLATES, and the three it deliberately drops // because MGPipeCallFlagsFor(op) recovers them from the opcode. constexpr Uint16 kTranslated = static_cast(MG_Pipe::kNeedsAck) | - static_cast(MG_Pipe::kHasBlob) | - static_cast(MG_Pipe::kVarTail); - constexpr Uint16 kDropped = static_cast(MG_Pipe::kHostSpan) | - static_cast(MG_Pipe::kReplySlot) | + static_cast(MG_Pipe::kHasBlob) | static_cast(MG_Pipe::kVarTail); + constexpr Uint16 kDropped = static_cast(MG_Pipe::kHostSpan) | static_cast(MG_Pipe::kReplySlot) | static_cast(MG_Pipe::kOptional); } // namespace FlagSpace @@ -113,31 +106,25 @@ namespace MobileGL::MG_Remote::Wire { "the overlap between the two flag spaces moved"); // Named individually so a failure says WHICH pair, and so the three that actually bite are // impossible to overlook while reading. - static_assert(static_cast(MG_Pipe::kNeedsAck) == - static_cast(Transport::kRecNeedsAck), + static_assert(static_cast(MG_Pipe::kNeedsAck) == static_cast(Transport::kRecNeedsAck), "kNeedsAck == kRecNeedsAck (harmless: the meanings agree)"); - static_assert(static_cast(MG_Pipe::kHasBlob) == - static_cast(Transport::kRecHasBlob), + static_assert(static_cast(MG_Pipe::kHasBlob) == static_cast(Transport::kRecHasBlob), "kHasBlob == kRecHasBlob (harmless: the meanings agree)"); - static_assert(static_cast(MG_Pipe::kVarTail) == - static_cast(Transport::kRecPad), + static_assert(static_cast(MG_Pipe::kVarTail) == static_cast(Transport::kRecPad), "kVarTail == kRecPad - THE DANGEROUS ONE. Stamping MGPipeCallFlags into the " "header would make RingConsumer::Pop discard all nine kVarTail opcodes as " "wrap fillers. The encoder must translate, never stamp; if this ever stops " "being true, keep translating anyway - two flag spaces in one field is the " "hazard, not this particular overlap"); - static_assert(static_cast(MG_Pipe::kHostSpan) == - static_cast(Transport::kRecBorrowSlot), + static_assert(static_cast(MG_Pipe::kHostSpan) == static_cast(Transport::kRecBorrowSlot), "kHostSpan == kRecBorrowSlot - a stamped host-span record would look like a " "slot borrowed into the GPU timeline and retire on completedFrameSerial"); - static_assert(static_cast(MG_Pipe::kReplySlot) == - static_cast(Transport::kRecVarTail), + static_assert(static_cast(MG_Pipe::kReplySlot) == static_cast(Transport::kRecVarTail), "kReplySlot == kRecVarTail - live in the REVERSE direction: the encoder " "stamps kRecVarTail on nine opcodes, and a reader who believes " "PipeWire.inc's 'MGPipeCallFlags of the call' comment reads those nine as " "kReplySlot"); - static_assert(static_cast(MG_Pipe::kOptional) == - static_cast(Transport::kRecNoReply), + static_assert(static_cast(MG_Pipe::kOptional) == static_cast(Transport::kRecNoReply), "kOptional == kRecNoReply - the alias P12 created. Harmless because kOptional " "is in kDropped (never stamped) and kRecNoReply is only ever set by the " "encoder: a reader that saw a stamped kOptional would read \"do not answer\" off " @@ -154,8 +141,7 @@ namespace MobileGL::MG_Remote::Wire { static_assert(sizeof(MGPWireRecHeader) == sizeof(Transport::RingRecordHeader)); static_assert(offsetof(MGPWireRecHeader, Op) == offsetof(Transport::RingRecordHeader, kind), "MGPWireRecHeader::Op and RingRecordHeader::kind are the same two bytes"); - static_assert(offsetof(MGPWireRecHeader, Flags) == - offsetof(Transport::RingRecordHeader, flags), + static_assert(offsetof(MGPWireRecHeader, Flags) == offsetof(Transport::RingRecordHeader, flags), "MGPWireRecHeader::Flags and RingRecordHeader::flags are the same two bytes"); static_assert(offsetof(MGPWireRecHeader, Size) == offsetof(Transport::RingRecordHeader, size), "MGPWireRecHeader::Size and RingRecordHeader::size are the same four bytes"); @@ -170,130 +156,124 @@ namespace MobileGL::MG_Remote::Wire { // gen_pipe.py owns PipeCalls.def and PipeWire.inc; this list is held to them by the // kOpCount assertion under it, which is the strongest statement a non-generated file can // make about a generated enum. -#define MGPW_FOR_EACH_CALL(X) \ - X(GetCaps, MGPCaps) \ - X(ResourceCreate, MGPResourceDesc) \ - X(ResourceRespecify, MGPResourceDesc) \ - X(ResourceDestroy, MGPHandleOnly) \ - X(MapPersistent, MGPHandleOnly) \ - X(UnmapPersistent, MGPHandleOnly) \ - X(FenceCreate, MGPHandleOnly) \ - X(FenceStatus, MGPHandleOnly) \ - X(FenceWait, MGPFenceWait) \ - X(FenceDestroy, MGPHandleOnly) \ - X(QueryCreate, MGPQueryDesc) \ - X(QueryBegin, MGPQueryDesc) \ - X(QueryEnd, MGPQueryDesc) \ - X(QueryAvailable, MGPHandleOnly) \ - X(QueryResult, MGPQueryResultRequest) \ - X(QueryDestroy, MGPHandleOnly) \ - X(CreateRenderState, MGPRenderStateDesc) \ - X(BindRenderState, MGPBindRenderState) \ - X(DeleteRenderState, MGPHandleOnly) \ - X(CreateVertexElements, MGPVertexElements) \ - X(BindVertexElements, MGPHandleOnly) \ - X(DeleteVertexElements, MGPHandleOnly) \ - X(CreateSamplerState, MGPSamplerDesc) \ - X(DeleteSamplerState, MGPHandleOnly) \ - X(CreateSamplerView, MGPSamplerView) \ - X(DeleteSamplerView, MGPHandleOnly) \ - X(CreateShaderState, MGPProgramDesc) \ - X(BindShaderState, MGPHandleOnly) \ - X(DeleteShaderState, MGPHandleOnly) \ - X(SetDynamicState, MGPDynamicState) \ - X(SetFramebufferState, MGPFramebufferState) \ - X(SetVertexBuffers, MGPVertexBuffers) \ - X(SetIndexBuffer, MGPIndexBuffer) \ - X(SetIndirectBuffers, MGPIndirectBuffers) \ - X(SetSamplerViews, MGPSamplerViews) \ - X(BindSamplerStates, MGPSamplerStates) \ - X(SetShaderImages, MGPShaderImages) \ - X(SetShaderBuffers, MGPShaderBuffers) \ - X(SetStreamOutputTargets, MGPStreamOutputTargets) \ - X(SetGlobalConstants, MGPGlobalConstants) \ - X(SetVertexAttribDefaults, MGPVertexAttribDefaults) \ - X(SetPixelPackState, MGPPixelPackState) \ - X(SetPatchState, MGPPatchState) \ - X(SetDrawProgram, MGPHandleOnly) \ - X(SetDispatchProgram, MGPHandleOnly) \ - X(SetResidualValueState, MGPResidualValueState) \ - X(SetTextureParams, MGPTextureParams) \ - X(ResourceSubData, MGPSubData) \ - X(BufferSubDataResident, MGPSubData) \ - X(ResourceSubDataComplete, MGPSubDataComplete) \ - X(ResourceFlushRange, MGPFlushRange) \ - X(ResourceReadback, MGPReadback) \ - X(ResourceCopyRegion, MGPCopyRegion) \ - X(GenerateMipmap, MGPMipPlan) \ - X(GetTextureImage, MGPReadbackInfo) \ - X(Blit, MGPBlit) \ - X(Clear, MGPClear) \ - X(ReadPixels, MGPReadbackInfo) \ - X(DrawVbo, MGPDrawInfo) \ - X(LaunchGrid, MGPGridInfo) \ - X(MemoryBarrier, MGPMemoryBarrier) \ - X(BeginStreamOutput, MGPStreamOutputBegin) \ - X(EndStreamOutput, MGPXfbAccounting) \ - X(PauseStreamOutput, MGPStreamOutputControl) \ - X(ResumeStreamOutput, MGPStreamOutputControl) \ - X(Flush, MGPFlush) \ - X(Present, MGPPresent) \ - X(SetSwapInterval, MGPSwapInterval) \ - X(QueryTimestamp, MGPTimestampRequest) \ - X(QueryCounter, MGPQueryDesc) \ - X(FenceWaitServer, MGPFenceWait) \ - X(BindShaderImage, MGPImageBind) \ - X(PatchParameter, MGPPatchParameter) \ - X(BindStreamOutput, MGPStreamOutputBind) \ - X(SetStorageBlockBinding, MGPStorageBlockBinding) \ - X(CopyFramebufferToTexture, MGPCopyFromFramebuffer) \ - X(ApplierReset, MGPApplierReset) \ - X(ObjectDeath, MGPHandleOnly) \ - X(SetContextValues, MGPContextValues) \ - X(SetProgramBindings, MGPProgramBindings) \ - X(DeleteStreamOutput, MGPStreamOutputBind) \ - X(ReadPixelsToBuffer, MGPReadbackToBuffer) \ - X(GetTextureImageToBuffer, MGPReadbackToBuffer) \ - X(BindContext, MGPBindContext) \ +#define MGPW_FOR_EACH_CALL(X) \ + X(GetCaps, MGPCaps) \ + X(ResourceCreate, MGPResourceDesc) \ + X(ResourceRespecify, MGPResourceDesc) \ + X(ResourceDestroy, MGPHandleOnly) \ + X(MapPersistent, MGPHandleOnly) \ + X(UnmapPersistent, MGPHandleOnly) \ + X(FenceCreate, MGPHandleOnly) \ + X(FenceStatus, MGPHandleOnly) \ + X(FenceWait, MGPFenceWait) \ + X(FenceDestroy, MGPHandleOnly) \ + X(QueryCreate, MGPQueryDesc) \ + X(QueryBegin, MGPQueryDesc) \ + X(QueryEnd, MGPQueryDesc) \ + X(QueryAvailable, MGPHandleOnly) \ + X(QueryResult, MGPQueryResultRequest) \ + X(QueryDestroy, MGPHandleOnly) \ + X(CreateRenderState, MGPRenderStateDesc) \ + X(BindRenderState, MGPBindRenderState) \ + X(DeleteRenderState, MGPHandleOnly) \ + X(CreateVertexElements, MGPVertexElements) \ + X(BindVertexElements, MGPHandleOnly) \ + X(DeleteVertexElements, MGPHandleOnly) \ + X(CreateSamplerState, MGPSamplerDesc) \ + X(DeleteSamplerState, MGPHandleOnly) \ + X(CreateSamplerView, MGPSamplerView) \ + X(DeleteSamplerView, MGPHandleOnly) \ + X(CreateShaderState, MGPProgramDesc) \ + X(BindShaderState, MGPHandleOnly) \ + X(DeleteShaderState, MGPHandleOnly) \ + X(SetDynamicState, MGPDynamicState) \ + X(SetFramebufferState, MGPFramebufferState) \ + X(SetVertexBuffers, MGPVertexBuffers) \ + X(SetIndexBuffer, MGPIndexBuffer) \ + X(SetIndirectBuffers, MGPIndirectBuffers) \ + X(SetSamplerViews, MGPSamplerViews) \ + X(BindSamplerStates, MGPSamplerStates) \ + X(SetShaderImages, MGPShaderImages) \ + X(SetShaderBuffers, MGPShaderBuffers) \ + X(SetStreamOutputTargets, MGPStreamOutputTargets) \ + X(SetGlobalConstants, MGPGlobalConstants) \ + X(SetVertexAttribDefaults, MGPVertexAttribDefaults) \ + X(SetPixelPackState, MGPPixelPackState) \ + X(SetPatchState, MGPPatchState) \ + X(SetDrawProgram, MGPHandleOnly) \ + X(SetDispatchProgram, MGPHandleOnly) \ + X(SetResidualValueState, MGPResidualValueState) \ + X(SetTextureParams, MGPTextureParams) \ + X(ResourceSubData, MGPSubData) \ + X(BufferSubDataResident, MGPSubData) \ + X(ResourceSubDataComplete, MGPSubDataComplete) \ + X(ResourceFlushRange, MGPFlushRange) \ + X(ResourceReadback, MGPReadback) \ + X(ResourceCopyRegion, MGPCopyRegion) \ + X(GenerateMipmap, MGPMipPlan) \ + X(GetTextureImage, MGPReadbackInfo) \ + X(Blit, MGPBlit) \ + X(Clear, MGPClear) \ + X(ReadPixels, MGPReadbackInfo) \ + X(DrawVbo, MGPDrawInfo) \ + X(LaunchGrid, MGPGridInfo) \ + X(MemoryBarrier, MGPMemoryBarrier) \ + X(BeginStreamOutput, MGPStreamOutputBegin) \ + X(EndStreamOutput, MGPXfbAccounting) \ + X(PauseStreamOutput, MGPStreamOutputControl) \ + X(ResumeStreamOutput, MGPStreamOutputControl) \ + X(Flush, MGPFlush) \ + X(Present, MGPPresent) \ + X(SetSwapInterval, MGPSwapInterval) \ + X(QueryTimestamp, MGPTimestampRequest) \ + X(QueryCounter, MGPQueryDesc) \ + X(FenceWaitServer, MGPFenceWait) \ + X(BindShaderImage, MGPImageBind) \ + X(PatchParameter, MGPPatchParameter) \ + X(BindStreamOutput, MGPStreamOutputBind) \ + X(SetStorageBlockBinding, MGPStorageBlockBinding) \ + X(CopyFramebufferToTexture, MGPCopyFromFramebuffer) \ + X(ApplierReset, MGPApplierReset) \ + X(ObjectDeath, MGPHandleOnly) \ + X(SetContextValues, MGPContextValues) \ + X(SetProgramBindings, MGPProgramBindings) \ + X(DeleteStreamOutput, MGPStreamOutputBind) \ + X(ReadPixelsToBuffer, MGPReadbackToBuffer) \ + X(GetTextureImageToBuffer, MGPReadbackToBuffer) \ + X(BindContext, MGPBindContext) \ X(SharedImage, MGPSharedImageOp) namespace { - constexpr Uint64 Align8(Uint64 value) { return (value + 7u) & ~Uint64(7u); } + constexpr Uint64 Align8(Uint64 value) { + return (value + 7u) & ~Uint64(7u); + } #define MGPW_NAME_ROW(Name, Payload) #Name, - constexpr const char* kWireOpNames[] = { - "", - MGPW_FOR_EACH_CALL(MGPW_NAME_ROW) - }; + constexpr const char* kWireOpNames[] = {"", MGPW_FOR_EACH_CALL(MGPW_NAME_ROW)}; #undef MGPW_NAME_ROW // The catalogue grew or shrank. Add the new opcode to MGPW_FOR_EACH_CALL, give it an // arm in ApplyChecked, and decide in CONTRACT-P5.md table 1 whether it carries bytes. - static_assert(sizeof(kWireOpNames) / sizeof(kWireOpNames[0]) == - static_cast(MGPWireOp::kOpCount), + static_assert(sizeof(kWireOpNames) / sizeof(kWireOpNames[0]) == static_cast(MGPWireOp::kOpCount), "PipeCalls.def and this file's call list disagree"); // The payload struct's own sizeof, which is what the tail arithmetic starts from. // NOT sizeof(MGPWireRec_X) - 8: three payloads are not multiples of 8 and the record // is rounded up around them. #define MGPW_SIZE_ROW(Name, Payload) sizeof(MG_Pipe::Payload), - constexpr Uint64 kWirePayloadBytes[] = { - 0, - MGPW_FOR_EACH_CALL(MGPW_SIZE_ROW) - }; + constexpr Uint64 kWirePayloadBytes[] = {0, MGPW_FOR_EACH_CALL(MGPW_SIZE_ROW)}; #undef MGPW_SIZE_ROW static_assert(sizeof(kWirePayloadBytes) / sizeof(kWirePayloadBytes[0]) == - static_cast(MGPWireOp::kOpCount)); + static_cast(MGPWireOp::kOpCount)); // PH-1 (3): the pre-gate in DecodeAndApply (AdmitsTheGeneratedGate) asks "shorter than // its own type" with this file's payload list, and the generated MGP_WIRE_CHECK_BOUNDS // asks it with sizeof(MGPWireRec_X). Pinned equal row by row, so a record the pre-gate // admits can never be one the generated gate would still abort on. -#define MGPW_REC_ROW(Name, Payload) \ - static_assert(sizeof(MG_Pipe::MGPWireRec_##Name) == \ - Align8(sizeof(MGPWireRecHeader) + sizeof(MG_Pipe::Payload)), \ - "MGPWireRec_" #Name " and this file's payload row disagree on size"); +#define MGPW_REC_ROW(Name, Payload) \ + static_assert(sizeof(MG_Pipe::MGPWireRec_##Name) == Align8(sizeof(MGPWireRecHeader) + sizeof(MG_Pipe::Payload)), \ + "MGPWireRec_" #Name " and this file's payload row disagree on size"); MGPW_FOR_EACH_CALL(MGPW_REC_ROW) #undef MGPW_REC_ROW @@ -304,11 +284,9 @@ namespace MobileGL::MG_Remote::Wire { // CreateShaderState's seven blob members are one contiguous run, which is what lets // the honesty pass walk them as an array. - static_assert(offsetof(MGPProgramDesc, Reflection) == - offsetof(MGPProgramDesc, Spirv) + 6 * sizeof(MGPBlobRef), + static_assert(offsetof(MGPProgramDesc, Reflection) == offsetof(MGPProgramDesc, Spirv) + 6 * sizeof(MGPBlobRef), "MGPProgramDesc's seven MGPBlobRefs must stay contiguous"); - static_assert(offsetof(MGPCaps, RendererInfo) == - offsetof(MGPCaps, FormatCapabilities) + sizeof(MGPBlobRef), + static_assert(offsetof(MGPCaps, RendererInfo) == offsetof(MGPCaps, FormatCapabilities) + sizeof(MGPBlobRef), "MGPCaps's two MGPBlobRefs must stay contiguous"); // Where an op's MGPBlobRef members live inside its payload. Ten rows: the eight @@ -360,7 +338,9 @@ namespace MobileGL::MG_Remote::Wire { // The one place a count-times-element-size becomes a byte count. Uint32 * a small // constant cannot overflow 64 bits, and the total is bounded above before anyone // indexes with it. - Uint64 TailBytesFor(Uint64 count, Uint64 elementBytes) { return count * elementBytes; } + Uint64 TailBytesFor(Uint64 count, Uint64 elementBytes) { + return count * elementBytes; + } // The process resolver is a plain function pointer with no user datum // (MGPipeHostSpan.h:46), so the table it resolves through has to be found here. @@ -404,12 +384,14 @@ namespace MobileGL::MG_Remote::Wire { // --------------------------------------------------------------------------------- void WireProtocolFatal(const char* what, const char* detail) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what, detail != nullptr ? detail : ""); + SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} %s", what, + detail != nullptr ? detail : ""); } void WireProtocolFatalAt(const char* what, Uint64 got, Uint64 expected) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what, - static_cast(got), static_cast(expected)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what, + static_cast(got), static_cast(expected)); } // PH-1 (3): the same two lines through the latch (PipeWireCodec.h says where each is used). @@ -419,9 +401,9 @@ namespace MobileGL::MG_Remote::Wire { } Bool WireProtocolLatchAt(const char* what, Uint64 got, Uint64 expected) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", - what, static_cast(got), - static_cast(expected)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} got=%llu expected=%llu", what, + static_cast(got), static_cast(expected)); } // --------------------------------------------------------------------------------- @@ -441,8 +423,7 @@ namespace MobileGL::MG_Remote::Wire { void SegmentTable::Install(SegmentId seg, SegmentView view) { if (seg == kSegNone || static_cast(seg) > static_cast(kSegAdopt)) { - WireProtocolFatalAt("SegmentTable::Install", static_cast(seg), - static_cast(kSegAdopt)); + WireProtocolFatalAt("SegmentTable::Install", static_cast(seg), static_cast(kSegAdopt)); } m_views[static_cast(seg)] = view; } @@ -468,8 +449,9 @@ namespace MobileGL::MG_Remote::Wire { } if (m_link) { const void* bytes = nullptr; - return m_link->ResolveSpan(static_cast(seg), {offset, size}, &bytes) - == MOBILEGL_OK ? bytes : nullptr; + return m_link->ResolveSpan(static_cast(seg), {offset, size}, &bytes) == MOBILEGL_OK + ? bytes + : nullptr; } const SegmentView& view = m_views[static_cast(seg)]; if (view.Base == nullptr) { @@ -483,9 +465,13 @@ namespace MobileGL::MG_Remote::Wire { return static_cast(view.Base) + offset; } - void SegmentTable::BindThreadResolver() { t_threadResolverTable = this; } + void SegmentTable::BindThreadResolver() { + t_threadResolverTable = this; + } - void SegmentTable::UnbindThreadResolver() { t_threadResolverTable = nullptr; } + void SegmentTable::UnbindThreadResolver() { + t_threadResolverTable = nullptr; + } void SegmentTable::InstallProcessResolver() { if (t_threadResolverTable != nullptr && t_threadResolverTable != this) { @@ -525,18 +511,20 @@ namespace MobileGL::MG_Remote::Wire { // The monolith shape: Seg = None, Offset = a host address, Size = 0. Legal // there, and precisely what must not cross (rule A). Reading it as "absent" // would silently drop the bytes of every record an unconverted emitter sent. - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} a blob with Size 0 " - "declares Seg=%u Offset=%llu; under split a blobref is either fully " - "declared or all three fields zero", - WireOpName(op), static_cast(blob.Seg), - static_cast(blob.Offset)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} a blob with Size 0 " + "declares Seg=%u Offset=%llu; under split a blobref is either fully " + "declared or all three fields zero", + WireOpName(op), static_cast(blob.Seg), + static_cast(blob.Offset)); } return true; } if (blob.Seg == kSegNone) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} Size=%llu with no segment " - "(R-2.3): a non-zero Size must name a real SEG_*", - WireOpName(op), static_cast(blob.Size)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} Size=%llu with no segment " + "(R-2.3): a non-zero Size must name a real SEG_*", + WireOpName(op), static_cast(blob.Size)); } // Every client->server content blob is staged whole in SEG_STAGE, so a blob naming any // other segment is refused even when its run resolves: a SEG_REPLY run would be read from @@ -545,30 +533,32 @@ namespace MobileGL::MG_Remote::Wire { // is refused for its carrier whether or not that segment is mapped. The family stays // ProtocolCorruption, shared by every honesty check; the segment number is in the message. if (blob.Seg != kSegStage) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu " - "size=%llu is not SEG_STAGE(%u); every client->server content blob is " - "staged whole in SEG_STAGE (contract table 1 groups A/B/C, R-10) and no " - "other segment may carry one", - WireOpName(op), static_cast(blob.Seg), - static_cast(blob.Offset), - static_cast(blob.Size), - static_cast(kSegStage)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu " + "size=%llu is not SEG_STAGE(%u); every client->server content blob is " + "staged whole in SEG_STAGE (contract table 1 groups A/B/C, R-10) and no " + "other segment may carry one", + WireOpName(op), static_cast(blob.Seg), + static_cast(blob.Offset), + static_cast(blob.Size), static_cast(kSegStage)); } if (segments.Resolve(blob.Seg, blob.Offset, blob.Size) == nullptr) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu size=%llu " - "does not lie inside that segment (R-2.3)", - WireOpName(op), static_cast(blob.Seg), - static_cast(blob.Offset), - static_cast(blob.Size)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} seg=%u offset=%llu size=%llu " + "does not lie inside that segment (R-2.3)", + WireOpName(op), static_cast(blob.Seg), + static_cast(blob.Offset), + static_cast(blob.Size)); } return true; } Bool RequireDeclaredBlob(MGPWireOp op, const MGPBlobRef& blob, const SegmentTable& segments) { if (blob.Size == 0) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} the record's own fields say " - "it carries content and its blob declares none (R-2.2 / rule A)", - WireOpName(op)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s.blob\"} the record's own fields say " + "it carries content and its blob declares none (R-2.2 / rule A)", + WireOpName(op)); } return CheckBlobIsHonest(op, blob, segments); } @@ -578,9 +568,8 @@ namespace MobileGL::MG_Remote::Wire { // Rule B. In one address space this pointer WORKS, which is the whole reason the // rule has to be mechanical: an inproc implementation that kept using it is // indistinguishable from a correct one until the day it is a second process. - return WireProtocolLatch("host-span", - "MGHostSpan::Ptr is non-null under split; the encoder writes " - "nullptr and names SEG_STAGE (R-2.1)"); + return WireProtocolLatch("host-span", "MGHostSpan::Ptr is non-null under split; the encoder writes " + "nullptr and names SEG_STAGE (R-2.1)"); } if (span.Size != 0 && span.Seg == kMGHostSpanSegNone) { return WireProtocolLatchAt("host-span.seg", span.Size, 0); @@ -605,19 +594,18 @@ namespace MobileGL::MG_Remote::Wire { // resolver, reads outside the segment. P5 emits no spans, so this was latent; P8 arms // it, which is exactly when nobody will be reading this code. if (segments.Resolve(span.Seg, span.Offset, span.Size) == nullptr) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"host-span\"} seg=%u offset=%llu " - "size=%llu does not lie inside that segment (R-2.3 arm 4)", - static_cast(span.Seg), - static_cast(span.Offset), - static_cast(span.Size)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"host-span\"} seg=%u offset=%llu " + "size=%llu does not lie inside that segment (R-2.3 arm 4)", + static_cast(span.Seg), static_cast(span.Offset), + static_cast(span.Size)); } return true; } - Bool CheckDrawUserIndices(const MGPDrawInfo& info, const MGPDrawRange* ranges, - const MGHostSpan& span) { - if ((info.Flags & kDrawHasUserIndices) == 0 || (info.Flags & kDrawIsIndirect) != 0 || - info.NumDraws != 1 || ranges == nullptr) { + Bool CheckDrawUserIndices(const MGPDrawInfo& info, const MGPDrawRange* ranges, const MGHostSpan& span) { + if ((info.Flags & kDrawHasUserIndices) == 0 || (info.Flags & kDrawIsIndirect) != 0 || info.NumDraws != 1 || + ranges == nullptr) { return WireProtocolLatch("DrawVbo.userIndices.shape", "a user-index span requires exactly one direct indexed range"); } @@ -661,10 +649,9 @@ namespace MobileGL::MG_Remote::Wire { switch (op) { case MGPWireOp::SetVertexBuffers: { const auto& p = *static_cast(payload); - if (p.Count > kMGPipeMaxVertexAttribs || - static_cast(p.Start) + p.Count > kMGPipeMaxVertexAttribs) { - return WireProtocolLatchAt("SetVertexBuffers.Count", - static_cast(p.Start) + p.Count, kMGPipeMaxVertexAttribs); + if (p.Count > kMGPipeMaxVertexAttribs || static_cast(p.Start) + p.Count > kMGPipeMaxVertexAttribs) { + return WireProtocolLatchAt("SetVertexBuffers.Count", static_cast(p.Start) + p.Count, + kMGPipeMaxVertexAttribs); } tail0 = TailBytesFor(p.Count, sizeof(MGPVertexBuffer)); tails = 1; @@ -683,8 +670,8 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::BindSamplerStates: { const auto& p = *static_cast(payload); if (static_cast(p.Start) + p.Count > kMGPipeMaxTextureUnits) { - return WireProtocolLatchAt("BindSamplerStates.Count", - static_cast(p.Start) + p.Count, kMGPipeMaxTextureUnits); + return WireProtocolLatchAt("BindSamplerStates.Count", static_cast(p.Start) + p.Count, + kMGPipeMaxTextureUnits); } tail0 = TailBytesFor(p.Count, sizeof(MGPipeHandle)); tails = 1; @@ -755,8 +742,8 @@ namespace MobileGL::MG_Remote::Wire { kMGPipeMaxProgramSamplerUnits); } if (p.StorageOverrideCount > kMGPipeMaxProgramStorageOverrides) { - return WireProtocolLatchAt("SetProgramBindings.StorageOverrideCount", - p.StorageOverrideCount, kMGPipeMaxProgramStorageOverrides); + return WireProtocolLatchAt("SetProgramBindings.StorageOverrideCount", p.StorageOverrideCount, + kMGPipeMaxProgramStorageOverrides); } tail0 = TailBytesFor(p.BlockBindingCount, sizeof(Int32)); tail1 = TailBytesFor(p.SamplerUnitCount, sizeof(MGPProgramSamplerUnit)); @@ -823,9 +810,8 @@ namespace MobileGL::MG_Remote::Wire { // buffer by GL rule - and an indirect draw declares no ranges, because the server // never reads the indirect buffer to learn a count and the client has none to send. if (userIndices && indirect) { - return WireProtocolLatch("DrawVbo.Flags", - "kDrawHasUserIndices and kDrawIsIndirect are exclusive; an " - "indirect draw's indices come from the bound element buffer"); + return WireProtocolLatch("DrawVbo.Flags", "kDrawHasUserIndices and kDrawIsIndirect are exclusive; an " + "indirect draw's indices come from the bound element buffer"); } if (indirect && p.NumDraws != 0) { return WireProtocolLatchAt("DrawVbo.NumDraws", p.NumDraws, 0); @@ -877,10 +863,11 @@ namespace MobileGL::MG_Remote::Wire { // Encoder // --------------------------------------------------------------------------------- - PipeWireEncoder::PipeWireEncoder(Transport::ILink* link, SegmentTable* segments) - : m_segments(segments) { + PipeWireEncoder::PipeWireEncoder(Transport::ILink* link, SegmentTable* segments) : m_segments(segments) { if (!link) return; - m_progress = link->Progress(); m_signals = link->Signals(); m_cmd = &link->CommandsOut(); + m_progress = link->Progress(); + m_signals = link->Signals(); + m_cmd = &link->CommandsOut(); m_link = link->Capabilities().PublishIsDelivery ? nullptr : link; } @@ -889,12 +876,14 @@ namespace MobileGL::MG_Remote::Wire { : m_cmd(cmd), m_stage(stage), m_segments(segments) { if (control) { m_progress = &control->Progress; - m_signals = {&control->cmdHead, &control->submittedSeq, &control->consumerParked, - &control->producerParked, &control->eventRingFull}; + m_signals = {&control->cmdHead, &control->submittedSeq, &control->consumerParked, &control->producerParked, + &control->eventRingFull}; } } - Bool PipeWireEncoder::Valid() const { return m_progress != nullptr && m_cmd != nullptr; } + Bool PipeWireEncoder::Valid() const { + return m_progress != nullptr && m_cmd != nullptr; + } Bool PipeWireEncoder::CheckCancellation() { if (Cancelled() || (m_cancellationHook && m_cancellationHook(m_cancellationSelf)) || @@ -907,8 +896,7 @@ namespace MobileGL::MG_Remote::Wire { Uint8* PipeWireEncoder::StageAllocate(Uint64 size) { if (CheckCancellation()) return nullptr; if (m_stageBase == nullptr) { - const SegmentView view = m_segments != nullptr ? m_segments->Get(kSegStage) - : SegmentView{}; + const SegmentView view = m_segments != nullptr ? m_segments->Get(kSegStage) : SegmentView{}; if (view.Base == nullptr || view.Size == 0) { WireProtocolFatal("PipeWireEncoder::StageBytes", "SEG_STAGE has no segment view"); } @@ -929,13 +917,13 @@ namespace MobileGL::MG_Remote::Wire { const Uint64 need = Align8(size); if (need > m_stageCapacity) { - SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob cannot fit a " - "%llu byte staging segment at any occupancy; a blob is staged whole, so a " - "row that cuts its content at MGPipeStageChunkBytes() never reaches this - " - "raise MOBILEGL_IPC_STAGE_MB or report the record type with no cut to the " - "integrator", - static_cast(size), - static_cast(m_stageCapacity)); + SessionFail(MGFatalFamily::RingOverrun, + "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob cannot fit a " + "%llu byte staging segment at any occupancy; a blob is staged whole, so a " + "row that cuts its content at MGPipeStageChunkBytes() never reaches this - " + "raise MOBILEGL_IPC_STAGE_MB or report the record type with no cut to the " + "integrator", + static_cast(size), static_cast(m_stageCapacity)); } // P6 GATE 8's FIRST NUMBER (CONTRACT-P6.md §9 item 8): the bytes this producer staged. The @@ -985,13 +973,14 @@ namespace MobileGL::MG_Remote::Wire { } if (m_stageRetirementBell == nullptr || m_stageMarkFront == m_stageMarks.size()) break; const Uint64 pending = m_stageMarks[m_stageMarkFront].Seq; - const auto ready = [&] { - return m_progress->retiredSeq.load(std::memory_order_acquire) >= pending; - }; + const auto ready = [&] { return m_progress->retiredSeq.load(std::memory_order_acquire) >= pending; }; if (!ready()) { // The allocation still cannot progress after reclamation. Count this // blocked allocation once, not each watermark poll or each reclaimed mark. - if (!waited) { ++m_stageReclaimWaits; waited = true; } + if (!waited) { + ++m_stageReclaimWaits; + waited = true; + } // P5e (ra, CONTRACT-P5E §2.6): drain BEFORE parking and again after waking. // Before, because the ring may already be full and the server already stopped // - in which case retiredSeq will never move and there is nothing to wake us; @@ -1001,40 +990,39 @@ namespace MobileGL::MG_Remote::Wire { if (m_stageWaitHook != nullptr) m_stageWaitHook(m_stageWaitSelf); if (m_link) m_link->Flush(); if (CheckCancellation()) return nullptr; - if (!ready() && !m_stageRetirementBell->Wait(*m_signals.ProducerParked, ready, 0, - m_stageWaitTimeoutMs)) { + if (!ready() && + !m_stageRetirementBell->Wait(*m_signals.ProducerParked, ready, 0, m_stageWaitTimeoutMs)) { if (CheckCancellation()) return nullptr; SessionFail(MGFatalFamily::RetirementWaitFailed, - "MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} live peer did not retire " - "the pending allocation within %u ms", m_stageWaitTimeoutMs); + "MGPipe: Fatal{RetirementWaitFailed, \"SEG_STAGE\"} live peer did not retire " + "the pending allocation within %u ms", + m_stageWaitTimeoutMs); } if (m_stageWaitHook != nullptr) m_stageWaitHook(m_stageWaitSelf); } ReclaimStagedBytes(); } - SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu " - "byte staging segment with %llu bytes still in flight (retiredSeq=%llu); a " - "blob is staged whole and only the rows that cut at the stage chunk budget " - "stay small - raise MOBILEGL_IPC_STAGE_MB or report that row to the integrator", - static_cast(size), - static_cast(m_stageCapacity), - static_cast(m_stageHead - m_stageTail), - static_cast( - m_progress != nullptr ? m_progress->retiredSeq.load(std::memory_order_acquire) : 0)); + SessionFail(MGFatalFamily::RingOverrun, + "MGPipe: Fatal{RingOverrun, \"SEG_STAGE\"} a %llu byte blob does not fit a %llu " + "byte staging segment with %llu bytes still in flight (retiredSeq=%llu); a " + "blob is staged whole and only the rows that cut at the stage chunk budget " + "stay small - raise MOBILEGL_IPC_STAGE_MB or report that row to the integrator", + static_cast(size), static_cast(m_stageCapacity), + static_cast(m_stageHead - m_stageTail), + static_cast( + m_progress != nullptr ? m_progress->retiredSeq.load(std::memory_order_acquire) : 0)); } MGPBlobRef PipeWireEncoder::StageBytes(const void* bytes, Uint64 size) { if (CheckCancellation()) return {}; if (!Valid() || m_segments == nullptr) { - WireProtocolFatal("PipeWireEncoder::StageBytes", - "no command producer or segment table installed"); + WireProtocolFatal("PipeWireEncoder::StageBytes", "no command producer or segment table installed"); } if (size == 0) { // "The record declared no blob" and "the record declared an empty blob" must not // be spelled the same way on a wire (R-2.2), so this is a programming error rather // than an empty ref. - WireProtocolFatal("PipeWireEncoder::StageBytes", - "a content blob may not declare zero bytes (R-2.2)"); + WireProtocolFatal("PipeWireEncoder::StageBytes", "a content blob may not declare zero bytes (R-2.2)"); } if (bytes == nullptr) { WireProtocolFatal("PipeWireEncoder::StageBytes", "non-zero size with a null source"); @@ -1063,14 +1051,14 @@ namespace MobileGL::MG_Remote::Wire { return ref; } - Uint64 PipeWireEncoder::EncodeRecord(MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* varTail, Uint64 varTailBytes) { + Uint64 PipeWireEncoder::EncodeRecord(MGPWireOp op, const void* payload, Uint64 payloadBytes, const void* varTail, + Uint64 varTailBytes) { WireTail tail{varTail, varTailBytes}; return EncodeRecord(op, payload, payloadBytes, &tail, varTail != nullptr ? 1u : 0u); } - Uint64 PipeWireEncoder::EncodeRecord(MGPWireOp op, const void* payload, Uint64 payloadBytes, - const WireTail* tails, Uint32 tailCount, Bool noReply) { + Uint64 PipeWireEncoder::EncodeRecord(MGPWireOp op, const void* payload, Uint64 payloadBytes, const WireTail* tails, + Uint32 tailCount, Bool noReply) { if (Cancelled()) return kInvalidSeq; if (!Valid()) { WireProtocolFatal("PipeWireEncoder::EncodeRecord", "no SEG_CMD producer installed"); @@ -1081,8 +1069,7 @@ namespace MobileGL::MG_Remote::Wire { WireRecordLayout layout{}; if (!MGPipeWireRecordLayout(op, payload, layout)) { - WireProtocolFatalAt("PipeWireEncoder::EncodeRecord", - static_cast(op), + WireProtocolFatalAt("PipeWireEncoder::EncodeRecord", static_cast(op), static_cast(MGPWireOp::kOpCount)); } if (payloadBytes != layout.PayloadBytes) { @@ -1122,13 +1109,14 @@ namespace MobileGL::MG_Remote::Wire { if (total > m_cmd->MaxRecordBytes()) { // R-10's record bound. The content rows cut their BLOBS at the stage chunk budget // (MGPipeStageChunkBytes); a record's own bytes have no such budget and die here. - SessionFail(MGFatalFamily::RingOverrun, "MGPipe: Fatal{RingOverrun, \"%s\"} a %llu byte record exceeds " - "RingProducer::MaxRecordBytes() == %llu (half of a %llu byte SEG_CMD); the stage chunk " - "budget cuts blobs, not a record's own bytes - report the row to the " - "integrator", - WireOpName(op), static_cast(total), - static_cast(m_cmd->MaxRecordBytes()), - static_cast(m_cmd->Capacity())); + SessionFail(MGFatalFamily::RingOverrun, + "MGPipe: Fatal{RingOverrun, \"%s\"} a %llu byte record exceeds " + "RingProducer::MaxRecordBytes() == %llu (half of a %llu byte SEG_CMD); the stage chunk " + "budget cuts blobs, not a record's own bytes - report the row to the " + "integrator", + WireOpName(op), static_cast(total), + static_cast(m_cmd->MaxRecordBytes()), + static_cast(m_cmd->Capacity())); } // MGPipeCallFlags -> RingRecordFlags. See the file header: these are two flag spaces @@ -1152,8 +1140,7 @@ namespace MobileGL::MG_Remote::Wire { // it depends on record sizes (a stride that divides the capacity never // pads), so it is recorded, never asserted. const Uint64 headBeforeReserve = m_cmd->LocalHead(); - void* slot = m_cmd->Reserve(static_cast(op), ringFlags, - total - sizeof(MGPWireRecHeader)); + void* slot = m_cmd->Reserve(static_cast(op), ringFlags, total - sizeof(MGPWireRecHeader)); if (slot != nullptr) { const Uint64 headAfterReserve = m_cmd->LocalHead(); if ((headAfterReserve - headBeforeReserve) > total) { @@ -1186,8 +1173,8 @@ namespace MobileGL::MG_Remote::Wire { if (payloadSlack != 0) { std::memset(bytes + payloadBytes, 0, static_cast(payloadSlack)); } - Uint64 written = layout.TailCount > 0 ? layout.TailOffset[0] - sizeof(MGPWireRecHeader) - : total - sizeof(MGPWireRecHeader); + Uint64 written = + layout.TailCount > 0 ? layout.TailOffset[0] - sizeof(MGPWireRecHeader) : total - sizeof(MGPWireRecHeader); for (Uint32 i = 0; i < tailCount; ++i) { const Uint64 at = layout.TailOffset[i] - sizeof(MGPWireRecHeader); if (at > written) { @@ -1219,12 +1206,12 @@ namespace MobileGL::MG_Remote::Wire { // here and a Fatal on a peer - exactly the asymmetry EncodeRecord's own // comment says it exists to prevent. if (op == MGPWireOp::CreateShaderState && i < 6 && blob.Size != 0) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} " - "declares %llu bytes at the ENCODER; under split the modules travel " - "inside the Reflection archive and the six per-stage runs stay " - "undeclared", - static_cast(i), - static_cast(blob.Size)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} " + "declares %llu bytes at the ENCODER; under split the modules travel " + "inside the Reflection archive and the six per-stage runs stay " + "undeclared", + static_cast(i), static_cast(blob.Size)); } } } @@ -1310,8 +1297,7 @@ namespace MobileGL::MG_Remote::Wire { } const Uint64 retired = m_progress->retiredSeq.load(std::memory_order_acquire); Uint64 upTo = m_stageTail; - while (m_stageMarkFront < m_stageMarks.size() && - m_stageMarks[m_stageMarkFront].Seq <= retired) { + while (m_stageMarkFront < m_stageMarks.size() && m_stageMarks[m_stageMarkFront].Seq <= retired) { upTo = m_stageMarks[m_stageMarkFront].StageCursor; ++m_stageMarkFront; } @@ -1343,13 +1329,17 @@ namespace MobileGL::MG_Remote::Wire { } } - Uint64 PipeWireEncoder::StagedBytesInFlight() const { return m_stageHead - m_stageTail; } + Uint64 PipeWireEncoder::StagedBytesInFlight() const { + return m_stageHead - m_stageTail; + } // THE STORAGE, NOT THE LIVE COUNT. `size() - front` is the number of marks still in // flight, and it stays at one or two even while the vector behind it grows for ever - so a // control written against it would have gone green through exactly the leak it was meant // to catch. What leaks is the container, so that is what this reports. - SizeT PipeWireEncoder::StageMarksHeld() const { return m_stageMarks.size(); } + SizeT PipeWireEncoder::StageMarksHeld() const { + return m_stageMarks.size(); + } void PipeWireEncoder::Publish() { if (m_cmd == nullptr) { @@ -1376,9 +1366,13 @@ namespace MobileGL::MG_Remote::Wire { ReclaimStagedBytes(); } - Uint64 PipeWireEncoder::EmitSeq() const { return m_emitSeq; } + Uint64 PipeWireEncoder::EmitSeq() const { + return m_emitSeq; + } - Uint64 PipeWireEncoder::MaxRecordBytesSeen() const { return m_maxRecordBytes; } + Uint64 PipeWireEncoder::MaxRecordBytesSeen() const { + return m_maxRecordBytes; + } const char* PipeWireEncoder::MaxRecordOpName() const { return m_maxRecordOp == MG_Pipe::MGPWireOp::kOpCount ? "none" : WireOpName(m_maxRecordOp); @@ -1391,11 +1385,17 @@ namespace MobileGL::MG_Remote::Wire { return (m_cmd != nullptr && m_cmd->Valid()) ? m_cmd->MaxRecordBytes() : 0; } - Uint64 PipeWireEncoder::CmdWraps() const { return m_cmdWraps; } + Uint64 PipeWireEncoder::CmdWraps() const { + return m_cmdWraps; + } - Uint64 PipeWireEncoder::CmdWrapPads() const { return m_cmdWrapPads; } + Uint64 PipeWireEncoder::CmdWrapPads() const { + return m_cmdWrapPads; + } - Uint64 PipeWireEncoder::StageReclaimWaits() const { return m_stageReclaimWaits; } + Uint64 PipeWireEncoder::StageReclaimWaits() const { + return m_stageReclaimWaits; + } Uint64 PipeWireEncoder::CmdBytesWritten() const { // LocalHead(), not RingControl::head: the producer's own cursor includes records @@ -1413,8 +1413,7 @@ namespace MobileGL::MG_Remote::Wire { InstallApplyHook(); } - PipeWireDecoder::PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments, - ReplySink* replies) + PipeWireDecoder::PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments, ReplySink* replies) : m_valid(control != nullptr), m_segments(segments), m_replies(replies) { m_auditPoison = MG_Config::Ipc.Audit; // Once, at construction, beside the resolver it is modelled on - not per record from @@ -1427,34 +1426,61 @@ namespace MobileGL::MG_Remote::Wire { MG_Pipe::gMGPipeWireRecordApply = &MGPipeWireRecordApplyThunk; } - void PipeWireDecoder::UninstallApplyHook() { MG_Pipe::gMGPipeWireRecordApply = nullptr; } + void PipeWireDecoder::UninstallApplyHook() { + MG_Pipe::gMGPipeWireRecordApply = nullptr; + } - Bool PipeWireDecoder::Valid() const { return m_valid && m_segments != nullptr; } + Bool PipeWireDecoder::Valid() const { + return m_valid && m_segments != nullptr; + } - Uint64 PipeWireDecoder::AppliedSeq() const { return m_applySeq; } + Uint64 PipeWireDecoder::AppliedSeq() const { + return m_applySeq; + } - void PipeWireDecoder::SetVerbSink(WireVerbSink* sink) { m_verbs = sink; } + void PipeWireDecoder::SetVerbSink(WireVerbSink* sink) { + m_verbs = sink; + } - WireVerbSink* PipeWireDecoder::VerbSink() const { return m_verbs; } + WireVerbSink* PipeWireDecoder::VerbSink() const { + return m_verbs; + } - void PipeWireDecoder::SetAuditPoison(Bool enabled) { m_auditPoison = enabled; } + void PipeWireDecoder::SetAuditPoison(Bool enabled) { + m_auditPoison = enabled; + } - Bool PipeWireDecoder::AuditPoison() const { return m_auditPoison; } + Bool PipeWireDecoder::AuditPoison() const { + return m_auditPoison; + } - Uint64 PipeWireDecoder::PoisonedStageBytes() const { return m_poisonedBytes; } + Uint64 PipeWireDecoder::PoisonedStageBytes() const { + return m_poisonedBytes; + } - Bool PipeWireDecoder::LastAcceptanceKnown() const { return m_lastAcceptanceKnown; } + Bool PipeWireDecoder::LastAcceptanceKnown() const { + return m_lastAcceptanceKnown; + } - Bool PipeWireDecoder::LastAcceptance() const { return m_lastAcceptance; } + Bool PipeWireDecoder::LastAcceptance() const { + return m_lastAcceptance; + } - Uint64 PipeWireDecoder::AcceptedRecords() const { return m_accepted; } + Uint64 PipeWireDecoder::AcceptedRecords() const { + return m_accepted; + } - Uint64 PipeWireDecoder::DeclinedRecords() const { return m_declined; } - Uint64 PipeWireDecoder::AnsweredRecords() const { return m_answered; } - Uint64 PipeWireDecoder::SkippedAnswers() const { return m_skipped; } + Uint64 PipeWireDecoder::DeclinedRecords() const { + return m_declined; + } + Uint64 PipeWireDecoder::AnsweredRecords() const { + return m_answered; + } + Uint64 PipeWireDecoder::SkippedAnswers() const { + return m_skipped; + } - void PipeWireDecoder::PostReply(MGPWireOp op, Uint64 seq, Int32 status, const void* bytes, - Uint64 size) { + void PipeWireDecoder::PostReply(MGPWireOp op, Uint64 seq, Int32 status, const void* bytes, Uint64 size) { // THE ONE GATE ON SEG_REPLY. s1 sizes ReplyPool from MGPipeCallFlagsFor - table 0 says // that table is what "every package" reads - so writing SEG_REPLY[seq % slots] for a // record the pool reserved no slot for silently overwrites a waiter's answer. And @@ -1462,10 +1488,11 @@ namespace MobileGL::MG_Remote::Wire { // then fails for ever: the barrier HANGS rather than returning something wrong, which // is the harder failure to diagnose of the two. if ((MGPipeCallFlagsFor(op) & static_cast(kReplySlot)) == 0) { - SessionFail(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the decoder tried to answer into " - "a reply slot for a call the catalogue gives no kReplySlot; s1 sizes " - "ReplyPool from MGPipeCallFlagsFor and reserved none", - WireOpName(op)); + SessionFail(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the decoder tried to answer into " + "a reply slot for a call the catalogue gives no kReplySlot; s1 sizes " + "ReplyPool from MGPipeCallFlagsFor and reserved none", + WireOpName(op)); } if (m_replies != nullptr) { m_replies->PostReply(seq, status, bytes, size); @@ -1490,21 +1517,22 @@ namespace MobileGL::MG_Remote::Wire { // the audit has stopped covering the carrier, which is the same failure as no audit at // all - the reason the run-count overflow just below is a Fatal too. if (blob.Size == 0 || blob.Seg != kSegStage) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the audit was asked to record a " - "resolved run with seg=%u size=%llu; only declared SEG_STAGE(%u) runs " - "reach the poison fill (R-2.5)", - WireOpName(op), static_cast(blob.Seg), - static_cast(blob.Size), - static_cast(kSegStage)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the audit was asked to record a " + "resolved run with seg=%u size=%llu; only declared SEG_STAGE(%u) runs " + "reach the poison fill (R-2.5)", + WireOpName(op), static_cast(blob.Seg), + static_cast(blob.Size), static_cast(kSegStage)); } if (m_resolvedCount >= sizeof(m_resolved) / sizeof(m_resolved[0])) { // LOUD, NOT A SILENT DROP. This array is what the 0xDD fill covers, and a poison // that quietly stopped covering a run is the same failure as no poison at all - // rule C's only mechanical control going dark without a line in the log. - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} more than %llu SEG_STAGE runs in " - "one record; the audit fill would stop covering them", - WireOpName(op), - static_cast(sizeof(m_resolved) / sizeof(m_resolved[0]))); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} more than %llu SEG_STAGE runs in " + "one record; the audit fill would stop covering them", + WireOpName(op), + static_cast(sizeof(m_resolved) / sizeof(m_resolved[0]))); } m_resolved[m_resolvedCount++] = blob; return true; @@ -1538,15 +1566,13 @@ namespace MobileGL::MG_Remote::Wire { // Every run here has already been through Resolve, so this is unreachable today - // but it is the one place in the file where a wrap would be an arbitrary 0xDD // memset, and "unreachable" is not a reason to write the weaker test. - if (view.Base == nullptr || blob.Offset > view.Size || - blob.Size > view.Size - blob.Offset) { + if (view.Base == nullptr || blob.Offset > view.Size || blob.Size > view.Size - blob.Offset) { continue; } // R-2.5. The record has been applied and its bytes are retired, so an applier that // kept the pointer reads 0xDD next frame instead of bytes that merely happen to // still be there. Exactly the runs this record resolved, not a conservative window. - std::memset(static_cast(view.Base) + blob.Offset, 0xDD, - static_cast(blob.Size)); + std::memset(static_cast(view.Base) + blob.Offset, 0xDD, static_cast(blob.Size)); m_poisonedBytes += blob.Size; } m_resolvedCount = 0; @@ -1567,8 +1593,7 @@ namespace MobileGL::MG_Remote::Wire { if (!SessionLatchArmed()) return true; const SizeT index = static_cast(op); if (index == 0 || index >= static_cast(MGPWireOp::kOpCount)) { - return WireProtocolLatchAt("opcode", static_cast(op), - static_cast(MGPWireOp::kOpCount)); + return WireProtocolLatchAt("opcode", static_cast(op), static_cast(MGPWireOp::kOpCount)); } WireRecordLayout minimum{}; (void)MGPipeWireRecordLayout(op, nullptr, minimum); @@ -1583,8 +1608,7 @@ namespace MobileGL::MG_Remote::Wire { } // namespace Bool PipeWireDecoder::AdmitOrDecline(const Transport::RingRecordView& record) { - if ((record.flags & Transport::kRecPad) != 0 || - record.kind == Transport::kRingPadRecordKind) { + if ((record.flags & Transport::kRecPad) != 0 || record.kind == Transport::kRingPadRecordKind) { return true; // DecodeAndApply's pad arm names it (R-9) } const Uint64 size = record.payloadSize + sizeof(MGPWireRecHeader); @@ -1601,8 +1625,7 @@ namespace MobileGL::MG_Remote::Wire { if (!Valid()) { WireProtocolFatal("PipeWireDecoder::DecodeAndApply", "no control page or segment table"); } - if ((record.flags & Transport::kRecPad) != 0 || - record.kind == Transport::kRingPadRecordKind) { + if ((record.flags & Transport::kRecPad) != 0 || record.kind == Transport::kRingPadRecordKind) { // R-9: a pad does not advance seq, and both sides skip it BEFORE counting. // RingConsumer::Pop already does; one that reached here has been counted, and a // seq that drifted by one silently reads another call's reply slot. @@ -1661,8 +1684,7 @@ namespace MobileGL::MG_Remote::Wire { WireRecordLayout layout{}; const SizeT opIndex = static_cast(op); if (opIndex == 0 || opIndex >= static_cast(MGPWireOp::kOpCount)) { - return WireProtocolLatchAt("opcode", static_cast(op), - static_cast(MGPWireOp::kOpCount)); + return WireProtocolLatchAt("opcode", static_cast(op), static_cast(MGPWireOp::kOpCount)); } // With the opcode known, false here means a count bound in the layout latched. if (!MGPipeWireRecordLayout(op, payload, layout)) return false; @@ -1670,15 +1692,16 @@ namespace MobileGL::MG_Remote::Wire { // record type)`; it CANNOT SEE THE TAIL, so this is the first thing that holds a // record declaring Count = 4000 while carrying 8 bytes to its own arithmetic. if (size != layout.TotalBytes) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the record declares Size=%llu and " - "its own count fields describe %llu bytes (fixed payload %llu + tails " - "%llu/%llu/%llu)", - WireOpName(op), static_cast(size), - static_cast(layout.TotalBytes), - static_cast(layout.PayloadBytes), - static_cast(layout.TailBytes[0]), - static_cast(layout.TailBytes[1]), - static_cast(layout.TailBytes[2])); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"%s\"} the record declares Size=%llu and " + "its own count fields describe %llu bytes (fixed payload %llu + tails " + "%llu/%llu/%llu)", + WireOpName(op), static_cast(size), + static_cast(layout.TotalBytes), + static_cast(layout.PayloadBytes), + static_cast(layout.TailBytes[0]), + static_cast(layout.TailBytes[1]), + static_cast(layout.TailBytes[2])); } // The record's own ordinal, which is its reply-slot id (R-3). 1-based, and m_applySeq @@ -1697,8 +1720,7 @@ namespace MobileGL::MG_Remote::Wire { // unconfirmed object), so the opcode cannot. The local tally (LastAcceptance(), the // counters) is kept either way; decoder tests assert it. const Bool answerSkipped = - (static_cast(record)->Flags & - static_cast(Transport::kRecNoReply)) != 0; + (static_cast(record)->Flags & static_cast(Transport::kRecNoReply)) != 0; const auto noteAcceptance = [&](Bool accepted) { m_lastAcceptanceKnown = true; m_lastAcceptance = accepted; @@ -1719,8 +1741,7 @@ namespace MobileGL::MG_Remote::Wire { ++m_answered; // DECLINED is a real answer and carries no payload (ReplySlot.h): the four Bool // rows say `false` with it, exactly as MapPersistent says nullptr with it. - PostReply(op, seq, accepted ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, - nullptr, 0); + PostReply(op, seq, accepted ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, nullptr, 0); }; switch (op) { @@ -1759,10 +1780,8 @@ namespace MobileGL::MG_Remote::Wire { const MGPRespecifiedLevel* scope = nullptr; if (!MGPipeRespecifyIsWholeResource(desc)) { level = MGPipeMakeRespecifiedLevel(MGPipeRespecifiedUploadTargetOf(desc), - MGPipeRespecifiedLevelOf(desc), - MGPipeRespecifiedWidthOf(desc), - MGPipeRespecifiedHeightOf(desc), - MGPipeRespecifiedDepthOf(desc)); + MGPipeRespecifiedLevelOf(desc), MGPipeRespecifiedWidthOf(desc), + MGPipeRespecifiedHeightOf(desc), MGPipeRespecifiedDepthOf(desc)); scope = &level; } noteAcceptance(MGPipeApplyResourceRespecify(desc, nullptr, scope)); @@ -1782,8 +1801,10 @@ namespace MobileGL::MG_Remote::Wire { Uint64 inProcessBase = 0; if (m_verbs != nullptr && m_verbs->OnMapPersistent(*static_cast(payload), seq, t0Status, inProcessBase)) { - if (inProcessBase != 0) PostReply(op, seq, t0Status, &inProcessBase, sizeof(inProcessBase)); - else PostReply(op, seq, t0Status, nullptr, 0); + if (inProcessBase != 0) + PostReply(op, seq, t0Status, &inProcessBase, sizeof(inProcessBase)); + else + PostReply(op, seq, t0Status, nullptr, 0); return true; } } @@ -1812,12 +1833,12 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::FenceStatus: case MGPWireOp::FenceWait: { Uint32 result = 0; - const Bool ok = m_verbs != nullptr && - (op == MGPWireOp::FenceStatus - ? m_verbs->OnFenceStatus(*static_cast(payload), result) - : m_verbs->OnFenceWait(*static_cast(payload), result)); - PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, - ok ? &result : nullptr, ok ? sizeof(result) : 0); + const Bool ok = + m_verbs != nullptr && (op == MGPWireOp::FenceStatus + ? m_verbs->OnFenceStatus(*static_cast(payload), result) + : m_verbs->OnFenceWait(*static_cast(payload), result)); + PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, ok ? &result : nullptr, + ok ? sizeof(result) : 0); return ok; } case MGPWireOp::QueryCreate: @@ -1833,22 +1854,24 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::QueryAvailable: { Uint32 result = 0; const Bool ok = m_verbs && m_verbs->OnQueryAvailable(*static_cast(payload), result); - PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, - ok ? &result : nullptr, ok ? sizeof(result) : 0); + PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, ok ? &result : nullptr, + ok ? sizeof(result) : 0); return ok; } case MGPWireOp::QueryResult: { QueryResultReply result{}; - const Bool ok = m_verbs && m_verbs->OnQueryResult(*static_cast(payload), result); - PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, - ok ? &result : nullptr, ok ? sizeof(result) : 0); + const Bool ok = + m_verbs && m_verbs->OnQueryResult(*static_cast(payload), result); + PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, ok ? &result : nullptr, + ok ? sizeof(result) : 0); return ok; } case MGPWireOp::QueryTimestamp: { Int64 result = 0; - const Bool ok = m_verbs && m_verbs->OnQueryTimestamp(*static_cast(payload), result); - PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, - ok ? &result : nullptr, ok ? sizeof(result) : 0); + const Bool ok = + m_verbs && m_verbs->OnQueryTimestamp(*static_cast(payload), result); + PostReply(op, seq, ok ? ReplySink::kStatusOk : ReplySink::kStatusDeclined, ok ? &result : nullptr, + ok ? sizeof(result) : 0); return ok; } @@ -1860,8 +1883,7 @@ namespace MobileGL::MG_Remote::Wire { // Contract table 1 row 1: nothing reads Blob.Size today, so the decoder is // where the record is held to it. The expected length is not a guess - the // chunk table computes it from the mask the record itself carries. - const Uint64 expected = - static_cast(MGPipePipelineChunkBlobBytes(desc.ChunkMask)); + const Uint64 expected = static_cast(MGPipePipelineChunkBlobBytes(desc.ChunkMask)); if (!RequireDeclaredBlob(op, desc.Blob, *m_segments)) return false; if (desc.Blob.Size != expected) { return WireProtocolLatchAt("CreateRenderState.Blob", desc.Blob.Size, expected); @@ -1954,11 +1976,11 @@ namespace MobileGL::MG_Remote::Wire { for (Uint32 i = 0; i < 6; ++i) { if (!CheckBlobIsHonest(op, desc.Spirv[i], *m_segments)) return false; if (desc.Spirv[i].Size != 0) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} " - "declares %llu bytes; under split the modules travel inside the " - "Reflection archive and the six per-stage runs stay undeclared", - static_cast(i), - static_cast(desc.Spirv[i].Size)); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"CreateShaderState.Spirv[%u]\"} " + "declares %llu bytes; under split the modules travel inside the " + "Reflection archive and the six per-stage runs stay undeclared", + static_cast(i), static_cast(desc.Spirv[i].Size)); } } const void* archiveBytes = ResolveOrFatal(op, desc.Reflection); @@ -1976,8 +1998,7 @@ namespace MobileGL::MG_Remote::Wire { // ProgramArtifactsCodec.h. auto archive = MakeShared(); if (!MG_State::GLState::DecodeProgramArchive(static_cast(archiveBytes), - static_cast(desc.Reflection.Size), - *archive)) { + static_cast(desc.Reflection.Size), *archive)) { // PH-5's ArchiveVector bound (ProgramArtifactsCodec.cpp) refuses here too: a // vector count whose immediate allocation exceeds the archive's remaining bytes // makes DecodeProgramArchive answer false. @@ -2003,8 +2024,7 @@ namespace MobileGL::MG_Remote::Wire { const auto& dyn = *static_cast(payload); const void* chunks = nullptr; if (dyn.ChunkMask != 0) { - const Uint64 expected = - static_cast(MGPipeDynamicChunkBlobBytes(dyn.ChunkMask)); + const Uint64 expected = static_cast(MGPipeDynamicChunkBlobBytes(dyn.ChunkMask)); if (!RequireDeclaredBlob(op, dyn.Blob, *m_segments)) return false; if (dyn.Blob.Size != expected) { return WireProtocolLatchAt("SetDynamicState.Blob", dyn.Blob.Size, expected); @@ -2115,8 +2135,8 @@ namespace MobileGL::MG_Remote::Wire { overrideNames.reserve(static_cast(count)); for (Uint64 i = 0; i < count; ++i) { MGPProgramStorageOverride entry{}; - std::memcpy(&entry, reinterpret_cast(overrides) + - i * sizeof(MGPProgramStorageOverride), + std::memcpy(&entry, + reinterpret_cast(overrides) + i * sizeof(MGPProgramStorageOverride), sizeof(MGPProgramStorageOverride)); if (!CheckHostSpanIsHonest(entry.Name, *m_segments)) return false; // THE NUL IS THE CLIENT'S AND IS CHECKED, not assumed: the emitter stages @@ -2126,8 +2146,7 @@ namespace MobileGL::MG_Remote::Wire { // cannot see. const auto* name = static_cast( m_segments->Resolve(entry.Name.Seg, entry.Name.Offset, entry.Name.Size)); - if (entry.Name.Size == 0 || name == nullptr || - name[entry.Name.Size - 1] != '\0') { + if (entry.Name.Size == 0 || name == nullptr || name[entry.Name.Size - 1] != '\0') { return WireProtocolLatch("SetProgramBindings.StorageOverrides.Name", "an override name span is empty or is not " "NUL-terminated; the applier copies it as a C string"); @@ -2136,10 +2155,8 @@ namespace MobileGL::MG_Remote::Wire { } } MGPipeApplySetProgramBindings( - bindings, - layout.TailBytes[0] != 0 ? reinterpret_cast(tailAt(0)) : nullptr, - layout.TailBytes[1] != 0 ? reinterpret_cast(tailAt(1)) - : nullptr, + bindings, layout.TailBytes[0] != 0 ? reinterpret_cast(tailAt(0)) : nullptr, + layout.TailBytes[1] != 0 ? reinterpret_cast(tailAt(1)) : nullptr, overrides, overrideNames.empty() ? nullptr : overrideNames.data()); return true; } @@ -2192,13 +2209,12 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::ResourceSubData: { const auto& rec = *static_cast(payload); const Bool hasExtent = rec.LevelWidth || rec.LevelHeight || rec.LevelDepth; - if (hasExtent && (rec.Target == kMGPipeResourceTargetBuffer || !rec.LevelWidth || - !rec.LevelHeight || !rec.LevelDepth || rec.LevelWidth > 0x7fffffffu || - rec.LevelHeight > 0x7fffffffu || rec.LevelDepth > 0x7fffffffu || - rec.UnionBox.X < 0 || rec.UnionBox.Y < 0 || rec.UnionBox.Z < 0 || - Uint64(rec.UnionBox.X) + rec.UnionBox.W > rec.LevelWidth || - Uint64(rec.UnionBox.Y) + rec.UnionBox.H > rec.LevelHeight || - Uint64(rec.UnionBox.Z) + rec.UnionBox.D > rec.LevelDepth)) + if (hasExtent && (rec.Target == kMGPipeResourceTargetBuffer || !rec.LevelWidth || !rec.LevelHeight || + !rec.LevelDepth || rec.LevelWidth > 0x7fffffffu || rec.LevelHeight > 0x7fffffffu || + rec.LevelDepth > 0x7fffffffu || rec.UnionBox.X < 0 || rec.UnionBox.Y < 0 || + rec.UnionBox.Z < 0 || Uint64(rec.UnionBox.X) + rec.UnionBox.W > rec.LevelWidth || + Uint64(rec.UnionBox.Y) + rec.UnionBox.H > rec.LevelHeight || + Uint64(rec.UnionBox.Z) + rec.UnionBox.D > rec.LevelDepth)) return WireProtocolLatch("ResourceSubData.LevelExtent", "invalid full image extent or dirty box"); // Rule A arms both halves. The buffer half already declared a real size in // monolith and is cross-checked by PipeApply.cpp's SubDataBoxFault; THE TEXTURE HALF DECLARED 0 @@ -2211,18 +2227,17 @@ namespace MobileGL::MG_Remote::Wire { // record could describe a real destination and declare no bytes. A box is either // EMPTY (every extent zero, which is how a pull that needs nothing is spelled) or // WHOLE; a partially-zero extent is neither, and no emitter produces one. - const Bool boxIsEmpty = - rec.UnionBox.W == 0 && rec.UnionBox.H == 0 && rec.UnionBox.D == 0; - const Bool boxIsWhole = - rec.UnionBox.W != 0 && rec.UnionBox.H != 0 && rec.UnionBox.D != 0; + const Bool boxIsEmpty = rec.UnionBox.W == 0 && rec.UnionBox.H == 0 && rec.UnionBox.D == 0; + const Bool boxIsWhole = rec.UnionBox.W != 0 && rec.UnionBox.H != 0 && rec.UnionBox.D != 0; if (!namesABuffer && !boxIsEmpty && !boxIsWhole) { - return SessionLatch(MGFatalFamily::ProtocolCorruption, "MGPipe: Fatal{ProtocolCorruption, \"ResourceSubData\"} the union box " - "%ux%ux%u has a zero extent on some axes and not others; a box is " - "either empty or whole", - rec.UnionBox.W, rec.UnionBox.H, rec.UnionBox.D); + return SessionLatch(MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"ResourceSubData\"} the union box " + "%ux%ux%u has a zero extent on some axes and not others; a box is " + "either empty or whole", + rec.UnionBox.W, rec.UnionBox.H, rec.UnionBox.D); } - const Bool carriesContent = namesABuffer ? MGPipeSubDataBufferSize(rec) != 0 - : (boxIsWhole || rec.RegionCount != 0); + const Bool carriesContent = + namesABuffer ? MGPipeSubDataBufferSize(rec) != 0 : (boxIsWhole || rec.RegionCount != 0); const void* bytes = nullptr; if (carriesContent) { bytes = ResolveOrFatal(op, rec.Blob); @@ -2230,8 +2245,7 @@ namespace MobileGL::MG_Remote::Wire { } else if (!CheckBlobIsHonest(op, rec.Blob, *m_segments)) { return false; } - noteAcceptance(MGPipeApplyResourceSubData( - rec, bytes, reinterpret_cast(tailAt(0)))); + noteAcceptance(MGPipeApplyResourceSubData(rec, bytes, reinterpret_cast(tailAt(0)))); return true; } @@ -2281,12 +2295,10 @@ namespace MobileGL::MG_Remote::Wire { // bounds gate above is the whole validation; what the sink does with a renderbuffer // endpoint or an emulation site is the contract's, not the codec's. case MGPWireOp::ResourceCopyRegion: - return m_verbs != nullptr && - m_verbs->OnResourceCopyRegion(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnResourceCopyRegion(*static_cast(payload)); case MGPWireOp::GenerateMipmap: - return m_verbs != nullptr && - m_verbs->OnGenerateMipmap(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnGenerateMipmap(*static_cast(payload)); case MGPWireOp::GetTextureImage: return m_verbs != nullptr && @@ -2317,8 +2329,7 @@ namespace MobileGL::MG_Remote::Wire { MGHostSpan span{}; if ((info.Flags & kDrawHasUserIndices) != 0) { if (layout.TailCount != 2 || layout.TailBytes[1] != sizeof(MGHostSpan)) { - return WireProtocolLatchAt("DrawVbo.userIndices", layout.TailBytes[1], - sizeof(MGHostSpan)); + return WireProtocolLatchAt("DrawVbo.userIndices", layout.TailBytes[1], sizeof(MGHostSpan)); } std::memcpy(&span, tailAt(1), sizeof(span)); // ALL FOUR ARMS. WireVerbSink's header promises OnDrawVbo "a DECODED, @@ -2326,8 +2337,7 @@ namespace MobileGL::MG_Remote::Wire { // left SEG_STAGE reached the sink and that promise was false. P5b's d1 is what // arms this path (client-side index arrays staged whole, CONTRACT-P5B.md d1). if (!CheckHostSpanIsHonest(span, *m_segments)) return false; - if (!CheckDrawUserIndices(info, reinterpret_cast(tailAt(0)), span)) - return false; + if (!CheckDrawUserIndices(info, reinterpret_cast(tailAt(0)), span)) return false; userIndices = &span; } // P5b d1: the indirect block, in the span's place. The layout already refused a @@ -2336,15 +2346,13 @@ namespace MobileGL::MG_Remote::Wire { MGPDrawIndirect indirectBlock{}; if ((info.Flags & kDrawIsIndirect) != 0) { if (layout.TailCount != 2 || layout.TailBytes[1] != sizeof(MGPDrawIndirect)) { - return WireProtocolLatchAt("DrawVbo.indirect", layout.TailBytes[1], - sizeof(MGPDrawIndirect)); + return WireProtocolLatchAt("DrawVbo.indirect", layout.TailBytes[1], sizeof(MGPDrawIndirect)); } std::memcpy(&indirectBlock, tailAt(1), sizeof(indirectBlock)); indirect = &indirectBlock; } return m_verbs != nullptr && - m_verbs->OnDrawVbo(info, reinterpret_cast(tailAt(0)), - userIndices, indirect); + m_verbs->OnDrawVbo(info, reinterpret_cast(tailAt(0)), userIndices, indirect); } case MGPWireOp::Present: @@ -2356,20 +2364,17 @@ namespace MobileGL::MG_Remote::Wire { // an MGPipeApply* and none gains one (CONTRACT-P5B.md): the payloads are plain PODs the // bounds gate has already proved, so each arm hands over and stops. case MGPWireOp::LaunchGrid: - return m_verbs != nullptr && - m_verbs->OnLaunchGrid(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnLaunchGrid(*static_cast(payload)); case MGPWireOp::MemoryBarrier: - return m_verbs != nullptr && - m_verbs->OnMemoryBarrier(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnMemoryBarrier(*static_cast(payload)); case MGPWireOp::BeginStreamOutput: return m_verbs != nullptr && m_verbs->OnBeginStreamOutput(*static_cast(payload)); case MGPWireOp::EndStreamOutput: - return m_verbs != nullptr && - m_verbs->OnEndStreamOutput(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnEndStreamOutput(*static_cast(payload)); case MGPWireOp::PauseStreamOutput: return m_verbs != nullptr && @@ -2381,16 +2386,13 @@ namespace MobileGL::MG_Remote::Wire { // ---- the five P5b-appended verbs, opcodes 72..76 --------------------------------------- case MGPWireOp::BindShaderImage: - return m_verbs != nullptr && - m_verbs->OnBindShaderImage(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnBindShaderImage(*static_cast(payload)); case MGPWireOp::PatchParameter: - return m_verbs != nullptr && - m_verbs->OnPatchParameter(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnPatchParameter(*static_cast(payload)); case MGPWireOp::BindStreamOutput: - return m_verbs != nullptr && - m_verbs->OnBindStreamOutput(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnBindStreamOutput(*static_cast(payload)); case MGPWireOp::DeleteStreamOutput: return m_verbs && m_verbs->OnDeleteStreamOutput(*static_cast(payload)); @@ -2432,8 +2434,7 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::CopyFramebufferToTexture: return m_verbs != nullptr && - m_verbs->OnCopyFramebufferToTexture( - *static_cast(payload)); + m_verbs->OnCopyFramebufferToTexture(*static_cast(payload)); // ---- P5c's two control records, opcodes 77..78 (docs/archive/contracts/CONTRACT-P5C.md §5) -------- // @@ -2442,12 +2443,10 @@ namespace MobileGL::MG_Remote::Wire { // the sink's, not the codec's: the codec proves SHAPES, and "the client emits nothing // for an object that never crossed" (§5.2) is a contract fact about the peer. case MGPWireOp::ApplierReset: - return m_verbs != nullptr && - m_verbs->OnApplierReset(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnApplierReset(*static_cast(payload)); case MGPWireOp::ObjectDeath: - return m_verbs != nullptr && - m_verbs->OnObjectDeath(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnObjectDeath(*static_cast(payload)); // ---- P14 S1 (docs/Disaggregated/design/11-state-ownership.md): bind_context, opcode 84. // @@ -2456,8 +2455,7 @@ namespace MobileGL::MG_Remote::Wire { // and the arm hands over. What the sink does with the token - "this session's current // context" - is a session fact, not a shape. case MGPWireOp::BindContext: - return m_verbs != nullptr && - m_verbs->OnBindContext(*static_cast(payload)); + return m_verbs != nullptr && m_verbs->OnBindContext(*static_cast(payload)); // ---- shared images (docs/archive/notes/anland/plan-ahb-dmabuf.md), opcode 85. // @@ -2499,8 +2497,7 @@ namespace MobileGL::MG_Remote::Wire { case MGPWireOp::kInvalid: case MGPWireOp::kOpCount: default: - return WireProtocolLatchAt("opcode", static_cast(op), - static_cast(MGPWireOp::kOpCount)); + return WireProtocolLatchAt("opcode", static_cast(op), static_cast(MGPWireOp::kOpCount)); } } diff --git a/MobileGL/MG_Remote/Wire/PipeWireCodec.h b/MobileGL/MG_Remote/Wire/PipeWireCodec.h index e46ba78a..20b328cf 100644 --- a/MobileGL/MG_Remote/Wire/PipeWireCodec.h +++ b/MobileGL/MG_Remote/Wire/PipeWireCodec.h @@ -120,12 +120,10 @@ namespace MobileGL::MG_Remote::Wire { // RequireDeclaredBlob, called by the decoder where the payload implies content. // Returns true when honest; false when a fault was latched (armed session child only; // elsewhere the check does not return). Decode-path callers return on false. - Bool CheckBlobIsHonest(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob, - const SegmentTable& segments); + Bool CheckBlobIsHonest(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob, const SegmentTable& segments); // R-2.3 arm 2: the record's other fields say it carries content, so the blob must be // declared. Fatal on an absent blob, then CheckBlobIsHonest on a present one. - Bool RequireDeclaredBlob(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob, - const SegmentTable& segments); + Bool RequireDeclaredBlob(MG_Pipe::MGPWireOp op, const MG_Pipe::MGPBlobRef& blob, const SegmentTable& segments); // R-2.3 arms 1 and 3 for MGHostSpan. Our client produces ZERO host spans // (kCapNeedsHostIndexBytes / kCapNeedsHostUboBytes are RESERVED since P8-F, never set), so // this firing at all is a finding, not just a corruption check. @@ -145,8 +143,7 @@ namespace MobileGL::MG_Remote::Wire { // A legal segment run must also contain every index the draw will consume. Shared by // the encoder, decoder and sink so a direct sink call cannot bypass the extent gate. // PH-1 (3): answers like the arms above (false = latched). - Bool CheckDrawUserIndices(const MG_Pipe::MGPDrawInfo& info, - const MG_Pipe::MGPDrawRange* ranges, + Bool CheckDrawUserIndices(const MG_Pipe::MGPDrawInfo& info, const MG_Pipe::MGPDrawRange* ranges, const MG_Pipe::MGHostSpan& span); // ---- one record's shape, computed once and read by both sides ---------------------- @@ -160,7 +157,7 @@ namespace MobileGL::MG_Remote::Wire { inline constexpr Uint32 kMaxWireRecordTails = 3; struct WireRecordLayout { - Uint64 PayloadBytes = 0; // sizeof the op's payload struct + Uint64 PayloadBytes = 0; // sizeof the op's payload struct Uint64 TailOffset[kMaxWireRecordTails] = {0, 0, 0}; // from the START of the record, // header included Uint64 TailBytes[kMaxWireRecordTails] = {0, 0, 0}; @@ -196,8 +193,8 @@ namespace MobileGL::MG_Remote::Wire { public: PipeWireEncoder() = default; PipeWireEncoder(Transport::ILink* link, SegmentTable* segments); - PipeWireEncoder(Transport::RingControl* control, Transport::RingProducer* cmd, - Transport::RingProducer* stage, SegmentTable* segments); + PipeWireEncoder(Transport::RingControl* control, Transport::RingProducer* cmd, Transport::RingProducer* stage, + SegmentTable* segments); Bool Valid() const; @@ -231,8 +228,8 @@ namespace MobileGL::MG_Remote::Wire { // tail holds 8 bytes is Fatal HERE, on the producing side, rather than on a peer that // can only report a corrupt stream. That is the same arithmetic the decoder runs, so // the check is real rather than a restatement of the caller's own belief. - Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const WireTail* tails, Uint32 tailCount, Bool noReply = false); + Uint64 EncodeRecord(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const WireTail* tails, + Uint32 tailCount, Bool noReply = false); // `noReply` (P12) stamps Transport::kRecNoReply, which tells the server it may SKIP the // answer for this record. IT IS NOT "the caller is not waiting" - that is `wantReply` on @@ -302,7 +299,9 @@ namespace MobileGL::MG_Remote::Wire { void SetStageRetirementDoorbell(Transport::Doorbell* bell) { m_stageRetirementBell = bell; } using CancellationHook = Bool (*)(void* self); void SetCancellationState(const std::atomic* lost, CancellationHook hook, void* self) { - m_sessionLost = lost; m_cancellationHook = hook; m_cancellationSelf = self; + m_sessionLost = lost; + m_cancellationHook = hook; + m_cancellationSelf = self; } // The fast check is used on encode; the doorbell probe only runs while // staging or at an unsuccessful wait boundary. @@ -312,7 +311,6 @@ namespace MobileGL::MG_Remote::Wire { Bool CheckCancellation(); void SetStageWaitTimeoutMs(Uint32 timeoutMs) { m_stageWaitTimeoutMs = timeoutMs; } - // P5e (ra, CONTRACT-P5E §2.6): THE STAGE BELL IS A CLIENT WAIT, so it has to drain the // reverse channel like every other one. The encoder cannot drain it itself - SEG_EVENT // and its consumers belong to the session - so the session installs a hook and this @@ -402,8 +400,7 @@ namespace MobileGL::MG_Remote::Wire { public: PipeWireDecoder() = default; PipeWireDecoder(Transport::ILink* link, SegmentTable* segments, ReplySink* replies); - PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments, - ReplySink* replies); + PipeWireDecoder(Transport::RingControl* control, SegmentTable* segments, ReplySink* replies); Bool Valid() const; @@ -518,8 +515,7 @@ namespace MobileGL::MG_Remote::Wire { void PoisonResolvedRuns(); // The ONLY way this class answers a record. Fatals if `op` carries no kReplySlot - // see LastAcceptanceKnown() for why that is a Fatal and not a log line. - void PostReply(MG_Pipe::MGPWireOp op, Uint64 seq, Int32 status, const void* bytes, - Uint64 size); + void PostReply(MG_Pipe::MGPWireOp op, Uint64 seq, Int32 status, const void* bytes, Uint64 size); friend Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp, const void*, Uint64, Uint64); @@ -545,7 +541,6 @@ namespace MobileGL::MG_Remote::Wire { // The hook MGPipeApplyWireRecord dispatches to once its generated per-opcode bounds gate // has passed. Inert unless a decoder is active on the calling thread. - Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp op, const void* record, Uint64 size, - Uint64 remaining); + Bool MGPipeWireRecordApplyThunk(MG_Pipe::MGPWireOp op, const void* record, Uint64 size, Uint64 remaining); } // namespace MobileGL::MG_Remote::Wire diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index 6df82949..b975feb5 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -171,7 +171,8 @@ namespace MobileGL { constexpr EGLenum kPlatformX11 = 0x31D5; if (platform == EGL_NONE || platform == kPlatformX11) { const EGLint visualId = QueryDefaultX11VisualId(); - for (const auto config : displayObject.Configs) m_configs[config].NativeVisualId = visualId; + for (const auto config : displayObject.Configs) + m_configs[config].NativeVisualId = visualId; } #endif @@ -316,8 +317,8 @@ namespace MobileGL { #if MOBILEGL_BUILD_DISAGGREGATED { const auto released = context != nullptr ? m_contexts.find(context) : m_contexts.end(); - tThreadReleasedLostContext = !currentIt->second.RestoreThreadGLState && released != m_contexts.end() && - released->second.GLStateObject && + tThreadReleasedLostContext = !currentIt->second.RestoreThreadGLState && + released != m_contexts.end() && released->second.GLStateObject && released->second.GLStateObject->IsFromEndedWireSession(); } #endif @@ -744,8 +745,7 @@ namespace MobileGL { } EGLContext::EGLContextHandle EGLContext::CreateContext(EGLDisplayHandle display, EGLConfigHandle config, - EGLContextHandle shareCtx, - const EGLint* attribList, + EGLContextHandle shareCtx, const EGLint* attribList, Bool deferGLState) { const std::lock_guard lock(m_mutex); const auto* displayObject = TryGetDisplay(display); @@ -776,8 +776,8 @@ namespace MobileGL { // own native context) - with the two values they share. Contexts that share // objects must agree on it (EGL_BAD_MATCH otherwise). EGLint resetStrategy = EGL_NO_RESET_NOTIFICATION; - for (const EGLint name : - {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY}) { + for (const EGLint name : {EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY}) { if (auto value = ParseAttribValue(attribList, name); value) { if (*value != EGL_NO_RESET_NOTIFICATION && *value != EGL_LOSE_CONTEXT_ON_RESET) { SetError(EGL_BAD_ATTRIBUTE); @@ -1035,8 +1035,8 @@ namespace MobileGL { (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT)) { return false; } - return (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT) || - ctx->MajorVersion > 3 || (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1); + return (ctx->OpenGLProfileMask & EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT) || ctx->MajorVersion > 3 || + (ctx->MajorVersion == 3 && ctx->MinorVersion >= 1); } Bool EGLContext::IsCurrentContextOpenGLCompatibilityProfile() const { @@ -1324,8 +1324,8 @@ namespace MobileGL { return true; } - Bool EGLContext::ResizeSurface(EGLDisplayHandle display, EGLSurfaceHandle surface, - EGLint width, EGLint height) { + Bool EGLContext::ResizeSurface(EGLDisplayHandle display, EGLSurfaceHandle surface, EGLint width, + EGLint height) { const std::lock_guard lock(m_mutex); auto surfaceIt = m_surfaces.find(surface); if (surfaceIt == m_surfaces.end()) { @@ -1562,8 +1562,7 @@ namespace MobileGL { // P14 S3: whatever this thread's GL state was before the first EGL context was // bound to it is what a later release goes back to. SharedPtr restoreThreadGLState; - if (const auto currentIt = m_threadCurrents.find(threadKey); - currentIt != m_threadCurrents.end()) { + if (const auto currentIt = m_threadCurrents.find(threadKey); currentIt != m_threadCurrents.end()) { restoreThreadGLState = currentIt->second.RestoreThreadGLState; } else { restoreThreadGLState = pGLContext; @@ -1641,8 +1640,7 @@ namespace MobileGL { return false; } - return surfaceObject->Type == SurfaceType::Window || - surfaceObject->Type == SurfaceType::PlatformWindow; + return surfaceObject->Type == SurfaceType::Window || surfaceObject->Type == SurfaceType::PlatformWindow; } EGLContext::EGLSyncHandle EGLContext::CreateSync(EGLDisplayHandle display, EGLenum type, @@ -2015,7 +2013,8 @@ namespace MobileGL { SetError(EGL_BAD_ATTRIBUTE); return false; } - if (yuv && (!has[1][0] || !has[1][1] || !has[1][2] || has[1][3] != has[1][4] || has[1][3] != has[0][3])) { + if (yuv && + (!has[1][0] || !has[1][1] || !has[1][2] || has[1][3] != has[1][4] || has[1][3] != has[0][3])) { SetError(EGL_BAD_PARAMETER); return false; } @@ -2023,7 +2022,8 @@ namespace MobileGL { SetError(EGL_BAD_PARAMETER); return false; } - if (attribs.Offset < 0 || attribs.Pitch <= 0 || (yuv && (attribs.Plane1Offset < 0 || attribs.Plane1Pitch <= 0))) { + if (attribs.Offset < 0 || attribs.Pitch <= 0 || + (yuv && (attribs.Plane1Offset < 0 || attribs.Plane1Pitch <= 0))) { SetError(EGL_BAD_ACCESS); return false; } @@ -2094,7 +2094,7 @@ namespace MobileGL { return false; } static constexpr Uint32 kFormats[] = {kDrmFourccAbgr8888, kDrmFourccXbgr8888, kDrmFourccArgb8888, - kDrmFourccXrgb8888, kDrmFourccNv12, kDrmFourccP010}; + kDrmFourccXrgb8888, kDrmFourccNv12, kDrmFourccP010}; constexpr EGLint kRgbCount = 4; const EGLint total = !available ? 0 : (yuvAvailable ? static_cast(std::size(kFormats)) : kRgbCount); @@ -2103,7 +2103,8 @@ namespace MobileGL { return true; } const EGLint count = std::min(maxFormats, total); - for (EGLint i = 0; i < count; ++i) formats[i] = static_cast(kFormats[i]); + for (EGLint i = 0; i < count; ++i) + formats[i] = static_cast(kFormats[i]); *numFormats = count; return true; } diff --git a/MobileGL/MG_State/EGLState/Core.h b/MobileGL/MG_State/EGLState/Core.h index ef437a1a..ff0f8200 100644 --- a/MobileGL/MG_State/EGLState/Core.h +++ b/MobileGL/MG_State/EGLState/Core.h @@ -143,8 +143,8 @@ namespace MobileGL { // `platformWindow` keeps eglCreatePlatformWindowSurface's surface type. Split builds // only: the pull build's state class is unchanged (G1). EGLSurfaceHandle CreateServerOwnedWindowSurface(EGLDisplayHandle display, EGLConfigHandle config, - const void* nativeWindow, EGLint width, - EGLint height, Bool platformWindow); + const void* nativeWindow, EGLint width, EGLint height, + Bool platformWindow); // P12: the server's REAL extent of a surface - a server-owned window's geometry, from // the CreateWindowSurface reply and from every surface-changed event after it - so // eglQuerySurface(EGL_WIDTH/EGL_HEIGHT) answers what the server renders at. Unlike @@ -247,8 +247,8 @@ namespace MobileGL { // backend has shared images at all; without them the lists are empty. `yuvAvailable` // adds the YUV formats, which are external-only (OES_EGL_image_external) and LINEAR // as far as an explicit modifier goes. - Bool QueryDmaBufFormats(EGLDisplayHandle display, Bool available, Bool yuvAvailable, - EGLint maxFormats, EGLint* formats, EGLint* numFormats); + Bool QueryDmaBufFormats(EGLDisplayHandle display, Bool available, Bool yuvAvailable, EGLint maxFormats, + EGLint* formats, EGLint* numFormats); Bool QueryDmaBufModifiers(EGLDisplayHandle display, Bool available, Bool yuvAvailable, EGLint format, EGLint maxModifiers, Uint64* modifiers, EGLBoolean* externalOnly, EGLint* numModifiers); diff --git a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp index f9cccc5f..cf8f7627 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferObject.cpp @@ -63,7 +63,7 @@ namespace MobileGL::MG_State::GLState { if (!MG_Record::PersistentMapTracker::OnServerRole()) return; FatalLegacyBufferArmFromApplyThread(accessor); } - } + } // namespace namespace { // The live persistent READ maps, keyed on the never-reused lifetime id. Recursive @@ -307,9 +307,8 @@ namespace MobileGL::MG_State::GLState { void BufferObject::ReleaseMemory(Bool landStagedWrites) { if (!m_isMapped) return; - if (landStagedWrites && - (m_mappingAccess & BufferMappingAccessBit::Write)) { // if we wrote to the buffer - if (!(m_mappingAccess & BufferMappingAccessBit::FlushExplicit)) { // if we didn't flush explicitly + if (landStagedWrites && (m_mappingAccess & BufferMappingAccessBit::Write)) { // if we wrote to the buffer + if (!(m_mappingAccess & BufferMappingAccessBit::FlushExplicit)) { // if we didn't flush explicitly const SizeT mappedLength = m_mappedRange.end - m_mappedRange.start; if (m_resource.IsGpuResident()) { // A persistent map of an adopted store wrote straight into coherent @@ -452,8 +451,7 @@ namespace MobileGL::MG_State::GLState { // stage-buffer under-report what the BACKEND actually moves, which is the question // that class exists to answer; the two counters are different questions about the // same bytes and PipeStats.cpp:33-35 now says so. - MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush, - static_cast(size)); + MG_Util::PipeStats::AddBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush, static_cast(size)); } } @@ -484,8 +482,8 @@ namespace MobileGL::MG_State::GLState { // regression one layer down, and the rising-edge record is what breaks the cycle: the // server learns a host writer is live BEFORE the first probe, answers dirty while the // map lives, and the ensure - and with it the push - runs every draw. - const Bool live = m_isMapped && (m_mappingAccess & BufferMappingAccessBit::Write) && - !m_resource.IsGpuResident(); + const Bool live = + m_isMapped && (m_mappingAccess & BufferMappingAccessBit::Write) && !m_resource.IsGpuResident(); if (live == m_publishedLiveHostWrites) return; m_publishedLiveHostWrites = live; // AN ADOPTION ENDS THE STATE WITHOUT A RECORD. The one falling edge a resident store @@ -511,8 +509,7 @@ namespace MobileGL::MG_State::GLState { // nothing is published and nothing is pushed, so the probe answers clean, the frame // draws the last uploaded bytes, and PersistentCoherentMapScenario goes red. if (blockBytes == 0) return; - const SizeT length = blockBytes >= static_cast(m_size) ? m_size - : static_cast(blockBytes); + const SizeT length = blockBytes >= static_cast(m_size) ? m_size : static_cast(blockBytes); NotifySubData(0, length); } @@ -546,8 +543,7 @@ namespace MobileGL::MG_State::GLState { // mark: only a writeback produced by a record after the one the mark was taken for can // (the readback SyncGpuWrites emits always is). 0 is the synchronous answer - monolith // and every no-session case - which has nothing to be ordered against. - if (atOffset == 0 && data.size >= m_size && - (producedAtSeq == 0 || producedAtSeq > m_gpuWriteMarkSeq)) { + if (atOffset == 0 && data.size >= m_size && (producedAtSeq == 0 || producedAtSeq > m_gpuWriteMarkSeq)) { m_gpuWritePending = false; } } @@ -618,8 +614,7 @@ namespace MobileGL::MG_State::GLState { // channel makes the last slice's landing imply every earlier one. for (SizeT off = 0; off < m_size; off += sliceBytes) { const SizeT left = m_size - off; - MG_Pipe::MGPipeEmitResourceReadbackRange(*this, off, - left < sliceBytes ? left : sliceBytes); + MG_Pipe::MGPipeEmitResourceReadbackRange(*this, off, left < sliceBytes ? left : sliceBytes); } // No single writeback covered the whole buffer, so // WritebackFromBackend's clear (above) never fired - but every slice @@ -728,10 +723,9 @@ namespace MobileGL::MG_State::GLState { } void BufferObject::FillSubData(DataPtr pattern, SizeT atOffset, SizeT size) { - MOBILEGL_ASSERT(pattern.data != nullptr && pattern.size > 0, - "FillSubData requires a non-empty pattern."); - MOBILEGL_ASSERT(size % pattern.size == 0, - "FillSubData size (%zu) must be a multiple of pattern size (%zu).", size, pattern.size); + MOBILEGL_ASSERT(pattern.data != nullptr && pattern.size > 0, "FillSubData requires a non-empty pattern."); + MOBILEGL_ASSERT(size % pattern.size == 0, "FillSubData size (%zu) must be a multiple of pattern size (%zu).", + size, pattern.size); MOBILEGL_ASSERT(atOffset <= m_size && size <= m_size - atOffset, "FillSubData out of bounds: atOffset (%zu) + size (%zu) > m_size (%zu)", atOffset, size, m_size); @@ -748,8 +742,7 @@ namespace MobileGL::MG_State::GLState { // for nothing. if (m_resource.IsGpuResident() && // The same question, asked of whichever table owns the family in this build. - MG_Pipe::MGPipeResourceSubsystemEnabled() && MG_Pipe::MGPipeResourceOpsHaveSubDataResident() - ) { + MG_Pipe::MGPipeResourceSubsystemEnabled() && MG_Pipe::MGPipeResourceOpsHaveSubDataResident()) { Vector expanded(size); if (pattern.size == 1) { Memset(expanded.data(), *static_cast(pattern.data), size); @@ -905,8 +898,7 @@ namespace MobileGL::MG_State::GLState { // shadow when the backend declines (returns null). Only attempted once - the // storage is immutable and outlives unmap/remap. if (!m_resource.IsGpuResident() && (access & BufferMappingAccessBit::Write) && - !(access & BufferMappingAccessBit::FlushExplicit) && - MG_Pipe::MGPipeResourceSubsystemEnabled()) { + !(access & BufferMappingAccessBit::FlushExplicit) && MG_Pipe::MGPipeResourceSubsystemEnabled()) { if (void* pushedBase = MG_Pipe::MGPipeEmitMapPersistent(*this)) { // P11 B2 made this reachable (a T0 adoption), so the tracker lets go of the // shadow range WHILE THE SHADOW IS STILL OURS: AdoptPersistentMap frees it, and diff --git a/MobileGL/MG_State/GLState/BufferState/BufferState.cpp b/MobileGL/MG_State/GLState/BufferState/BufferState.cpp index 3ca9b372..59b1b26a 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferState.cpp +++ b/MobileGL/MG_State/GLState/BufferState/BufferState.cpp @@ -56,8 +56,8 @@ namespace MobileGL::MG_State::GLState { // generation the next draw or dispatch that walks the window asks the // backend for the dead one - Espryt dies with Fatal{ProtocolCorruption, // "BackendSlotTable.Generation"} (DeletedBoundBufferScenario). - NoteBindPointChanged(BufferBindPointTargets[static_cast( - &bindingPointArray - m_bufferBindPointTargets.data())]); + NoteBindPointChanged(BufferBindPointTargets[static_cast(&bindingPointArray - + m_bufferBindPointTargets.data())]); } } } diff --git a/MobileGL/MG_State/GLState/BufferState/BufferState.h b/MobileGL/MG_State/GLState/BufferState/BufferState.h index c0b77303..5a286076 100644 --- a/MobileGL/MG_State/GLState/BufferState/BufferState.h +++ b/MobileGL/MG_State/GLState/BufferState/BufferState.h @@ -68,48 +68,48 @@ namespace MobileGL::MG_State::GLState { // resets the deleting context's bindings, and other contexts keep their own). void UnbindBufferObject(const SharedPtr& bufferObject); - // P2 brief D4: "did the contents of ANY buffer object move". One counter for every - // BufferObject ++m_changeSerial site, which is what NEW_VERTEX_BUFFERS / - // NEW_INDEX_BUFFER / NEW_CONST_BUFFERS / NEW_SHADER_BUFFERS / NEW_SO_TARGETS all - // shutter on in P2 - five bits over one aggregate until P3b splits them. - void NoteBufferChanged() { ++m_anyBufferChangeGeneration; } - Uint64 GetAnyBufferChangeGeneration() const { return m_anyBufferChangeGeneration; } + // P2 brief D4: "did the contents of ANY buffer object move". One counter for every + // BufferObject ++m_changeSerial site, which is what NEW_VERTEX_BUFFERS / + // NEW_INDEX_BUFFER / NEW_CONST_BUFFERS / NEW_SHADER_BUFFERS / NEW_SO_TARGETS all + // shutter on in P2 - five bits over one aggregate until P3b splits them. + void NoteBufferChanged() { ++m_anyBufferChangeGeneration; } + Uint64 GetAnyBufferChangeGeneration() const { return m_anyBufferChangeGeneration; } - // P5e (sb, docs/archive/contracts/CONTRACT-P5E.md §1, §5.6). "DID ANY INDEXED BINDING POINT OF THIS - // TARGET MOVE" - one counter per BufferBindPointTargets entry, and it is the P4b hole - // closed rather than a new convenience. - // - // WHAT WAS WRONG. Dirty bits 15/16/17 shuttered on GetAnyBufferChangeGeneration() - the - // CONTENT aggregate - while glBindBufferBase / glBindBufferRange mutate a binding point - // through a returned reference (BindingSlotRange1D::Bind / SetRange), which moves the - // slot's own Uint16 version and nothing the tracker reads. So - // `glBindBufferBase(UNIFORM,1,A); draw; glBindBufferBase(UNIFORM,1,B); draw` fired no bit - // at all: harmless while nothing was emitted for those bits, and an UNDER-FIRE the moment - // set_shader_buffers is, because the server would go on binding A. It is the same defect - // class as P4a's glBindSampler hole (c0d) and it is closed the same way - the shutter - // reads the generation the mutator moves. - // - // A COUNTER AND NOT THE SLOT'S OWN VERSION: the slot version is a WRAPPING Uint16 per - // point, and a shutter over 84 of them would have to observe all 84 through the widened - // counter every draw. One monotone Uint64 per target answers "could this target's window - // have moved" in one read, which is what a per-draw shutter can afford. - // - // #if MOBILEGL_PIPE_PUSH so the pull build's BufferState does not resize (G1) - the same - // licence TextureState::NoteImageUnitTouched took at P5d r3. - void NoteBindPointChanged(const BufferTarget target) { - auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); - if (it == BufferBindPointTargets.end()) return; - ++m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)]; - } - Uint64 GetBindPointGeneration(const BufferTarget target) const { - auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); - if (it == BufferBindPointTargets.end()) return 0; - return m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)]; - } + // P5e (sb, docs/archive/contracts/CONTRACT-P5E.md §1, §5.6). "DID ANY INDEXED BINDING POINT OF THIS + // TARGET MOVE" - one counter per BufferBindPointTargets entry, and it is the P4b hole + // closed rather than a new convenience. + // + // WHAT WAS WRONG. Dirty bits 15/16/17 shuttered on GetAnyBufferChangeGeneration() - the + // CONTENT aggregate - while glBindBufferBase / glBindBufferRange mutate a binding point + // through a returned reference (BindingSlotRange1D::Bind / SetRange), which moves the + // slot's own Uint16 version and nothing the tracker reads. So + // `glBindBufferBase(UNIFORM,1,A); draw; glBindBufferBase(UNIFORM,1,B); draw` fired no bit + // at all: harmless while nothing was emitted for those bits, and an UNDER-FIRE the moment + // set_shader_buffers is, because the server would go on binding A. It is the same defect + // class as P4a's glBindSampler hole (c0d) and it is closed the same way - the shutter + // reads the generation the mutator moves. + // + // A COUNTER AND NOT THE SLOT'S OWN VERSION: the slot version is a WRAPPING Uint16 per + // point, and a shutter over 84 of them would have to observe all 84 through the widened + // counter every draw. One monotone Uint64 per target answers "could this target's window + // have moved" in one read, which is what a per-draw shutter can afford. + // + // #if MOBILEGL_PIPE_PUSH so the pull build's BufferState does not resize (G1) - the same + // licence TextureState::NoteImageUnitTouched took at P5d r3. + void NoteBindPointChanged(const BufferTarget target) { + auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); + if (it == BufferBindPointTargets.end()) return; + ++m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)]; + } + Uint64 GetBindPointGeneration(const BufferTarget target) const { + auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target); + if (it == BufferBindPointTargets.end()) return 0; + return m_bindPointGeneration[std::distance(BufferBindPointTargets.begin(), it)]; + } private: - Uint64 m_anyBufferChangeGeneration = 0; - Array m_bindPointGeneration{}; + Uint64 m_anyBufferChangeGeneration = 0; + Array m_bindPointGeneration{}; Array, GlobalBufferTargets.size()> m_bindingSlots; // BufferTarget -> index into m_bindingSlots, -1 for a target with no global slot. Array(BufferTarget::BufferTargetCount)> m_bindingSlotIndex{}; diff --git a/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp index 8b97208c..81b629bc 100644 --- a/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp +++ b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.cpp @@ -99,12 +99,14 @@ namespace MobileGL::MG_Record { TrackedWriteMap g_trackedMaps[kMaxTrackedMaps]; #if MGL_TRACKER_MPROTECT_ARM - struct sigaction g_prevSegvAction {}; + struct sigaction g_prevSegvAction{}; #else // Never called: without the arm nothing is ever protected (g_segvInstalled stays false). constexpr int PROT_READ = 1; constexpr int PROT_WRITE = 2; - inline int mprotect(void*, SizeT, int) { return -1; } + inline int mprotect(void*, SizeT, int) { + return -1; + } #endif std::atomic g_segvInstalled{false}; // Bumped by the handler for every write fault it answers, and by TrackWriteMap @@ -223,7 +225,8 @@ namespace MobileGL::MG_Record { } SizeT AppendText(char* out, SizeT at, const char* text) { - while (*text != '\0') out[at++] = *text++; + while (*text != '\0') + out[at++] = *text++; return at; } @@ -236,8 +239,8 @@ namespace MobileGL::MG_Record { // the slack is deliberate, because this formats inside a signal handler where an // overrun would be the second bug in the same crash. char line[384]; - SizeT at = AppendText(line, 0, - "MGPipe: persistent-map tracker DECLINED a SEGV_ACCERR as foreign - si_addr="); + SizeT at = + AppendText(line, 0, "MGPipe: persistent-map tracker DECLINED a SEGV_ACCERR as foreign - si_addr="); at = AppendHex(line, at, address); at = AppendText(line, at, " nearest-tracked=["); at = AppendHex(line, at, nearestBase); @@ -251,11 +254,9 @@ namespace MobileGL::MG_Record { uintptr_t FaultPcFrom(void* ucontext) { #if defined(__aarch64__) - return static_cast( - reinterpret_cast(ucontext)->uc_mcontext.pc); + return static_cast(reinterpret_cast(ucontext)->uc_mcontext.pc); #elif defined(__x86_64__) - return static_cast( - reinterpret_cast(ucontext)->uc_mcontext.gregs[REG_RIP]); + return static_cast(reinterpret_cast(ucontext)->uc_mcontext.gregs[REG_RIP]); #else // No PC access on this arch: the refault test is skipped, ownership rests // on the si_code and range tests alone. @@ -336,13 +337,12 @@ namespace MobileGL::MG_Record { g_prevSegvAction.sa_sigaction(sig, info, ucontext); return; } - } else if (g_prevSegvAction.sa_handler != SIG_DFL && - g_prevSegvAction.sa_handler != SIG_IGN && + } else if (g_prevSegvAction.sa_handler != SIG_DFL && g_prevSegvAction.sa_handler != SIG_IGN && g_prevSegvAction.sa_handler != nullptr) { g_prevSegvAction.sa_handler(sig); return; } - struct sigaction restore {}; + struct sigaction restore{}; restore.sa_handler = SIG_DFL; sigemptyset(&restore.sa_mask); sigaction(SIGSEGV, &restore, nullptr); @@ -369,8 +369,7 @@ namespace MobileGL::MG_Record { // first live slot is enough to tell "nothing was tracked at all" // apart from "something was tracked and the comparison missed it". if (nearestBase == 0) { - const uintptr_t base = - g_trackedMaps[i].base.load(std::memory_order_acquire); + const uintptr_t base = g_trackedMaps[i].base.load(std::memory_order_acquire); if (base > kSlotSettingUp) { nearestBase = base; nearestEnd = g_trackedMaps[i].end.load(std::memory_order_acquire); @@ -381,14 +380,12 @@ namespace MobileGL::MG_Record { std::atomic* bits = g_trackedMaps[i].pageBits; if (bits != nullptr) { const Uint64 mask = 1ull << (pageIndex & 63); - const Uint64 old = - bits[pageIndex >> 6].fetch_or(mask, std::memory_order_relaxed); + const Uint64 old = bits[pageIndex >> 6].fetch_or(mask, std::memory_order_relaxed); if (!matched) bitWasSet = (old & mask) != 0; } matched = true; } - if (matched && - !RefaultOfANonWriteAccess(FaultPcFrom(ucontext), address, bitWasSet)) { + if (matched && !RefaultOfANonWriteAccess(FaultPcFrom(ucontext), address, bitWasSet)) { // The epoch moves BEFORE the unprotect: a walk that starts the // moment the page becomes writable must still see this fault. And it // moves with RELEASE, after the bit: a walk that acquires the moved @@ -396,8 +393,8 @@ namespace MobileGL::MG_Record { g_faultEpoch.fetch_add(1, std::memory_order_release); // Un-arm is a raw syscall, deliberately not the libc wrapper's // bookkeeping: the handler's whole job is to get out of the way. - mprotect(reinterpret_cast(UntagAddress(address) & ~(kPageBytes - 1)), - kPageBytes, PROT_READ | PROT_WRITE); + mprotect(reinterpret_cast(UntagAddress(address) & ~(kPageBytes - 1)), kPageBytes, + PROT_READ | PROT_WRITE); return; } // A SEGV_ACCERR this handler does not own. Legitimate (the JVM's implicit @@ -445,7 +442,7 @@ namespace MobileGL::MG_Record { if (!KernelPageIsTheTrackerPage()) return; Bool expected = false; if (!g_segvInstalled.compare_exchange_strong(expected, true)) return; - struct sigaction action {}; + struct sigaction action{}; action.sa_sigaction = &PersistentWriteFaultHandler; sigemptyset(&action.sa_mask); action.sa_flags = SA_SIGINFO; @@ -471,8 +468,8 @@ namespace MobileGL::MG_Record { // `shadowExtent` is the shadow allocation's own size in bytes as its allocator // sized it (BufferObject::ShadowAllocationBytes), or 0 when the caller cannot vouch // for it; it is what decides between the two alignments below. - Bool TrackWriteMap(Uint64 lifetimeId, const Uint8* shadow, - SizeT rangeBegin, SizeT rangeEnd, SizeT shadowExtent) { + Bool TrackWriteMap(Uint64 lifetimeId, const Uint8* shadow, SizeT rangeBegin, SizeT rangeEnd, + SizeT shadowExtent) { if (shadow == nullptr || rangeEnd <= rangeBegin) return false; InstallWriteFaultHandler(); if (!MprotectArmLive()) return false; @@ -590,11 +587,9 @@ namespace MobileGL::MG_Record { // exist to keep out. const SizeT wordCount = (pageCount + 63) / 64; for (SizeT w = 0; w < wordCount; ++w) { - const SizeT bitsInWord = - (w == wordCount - 1 && (pageCount & 63) != 0) ? (pageCount & 63) : 64; - g_trackedMaps[i].pageBits[w].store( - bitsInWord == 64 ? ~0ull : ((1ull << bitsInWord) - 1), - std::memory_order_relaxed); + const SizeT bitsInWord = (w == wordCount - 1 && (pageCount & 63) != 0) ? (pageCount & 63) : 64; + g_trackedMaps[i].pageBits[w].store(bitsInWord == 64 ? ~0ull : ((1ull << bitsInWord) - 1), + std::memory_order_relaxed); } // AND THE RANGE IS *NOT* PROTECTED HERE. REGISTRATION ARMS NOTHING. // @@ -644,11 +639,9 @@ namespace MobileGL::MG_Record { "allocator=MapAlignedAllocator shadow=0x%llx tag=0x%02llx extent=%zu " "range=[%zu,%zu) tracked=[0x%llx,0x%llx) pages=%zu align=%s " "handed-out-writable=yes (pages arm at the first push, never here)", - static_cast(lifetimeId), - static_cast(tagged), - static_cast(tagged >> 56), shadowExtent, rangeBegin, - rangeEnd, static_cast(base), - static_cast(end), pageCount, + static_cast(lifetimeId), static_cast(tagged), + static_cast(tagged >> 56), shadowExtent, rangeBegin, rangeEnd, + static_cast(base), static_cast(end), pageCount, outward ? "outward" : "inward"); return true; } @@ -748,7 +741,9 @@ namespace MobileGL::MG_Record { // server header inside PersistentMapTracker.h, which MG_State's BufferObject.cpp includes. // P13 W5: the apply-thread question through MG_Pipe's role probe (MG_Remote installs it; a library // without a transport has no server role, so it is false there). - Bool PersistentMapTracker::OnServerRole() { return MG_Pipe::MGPipeServerArm(); } + Bool PersistentMapTracker::OnServerRole() { + return MG_Pipe::MGPipeServerArm(); + } // SyncPersistentMappedRange's early-out chain (BufferObject.cpp:341-353), in its order, // read as a membership test. Every line here has a line there; if one of them moves, the @@ -800,8 +795,8 @@ namespace MobileGL::MG_Record { // the tracker can align outward (TrackWriteMap): a live member's MappedData() // IS the shadow vector's storage - the predicate's IsBackendPersistentMapped // row keeps every adopted store out - so the extent describes that pointer. - TrackWriteMap(key, buffer.MappedData(), static_cast(range.start), - static_cast(range.end), buffer.ShadowAllocationBytes()); + TrackWriteMap(key, buffer.MappedData(), static_cast(range.start), static_cast(range.end), + buffer.ShadowAllocationBytes()); TrackedWriteMap* slot = TrackedMapFor(key); m_livePersistentMaps[key] = MemberEntry{&buffer, slot}; if (slot == nullptr) { @@ -851,8 +846,8 @@ namespace MobileGL::MG_Record { // the gate that has to catch it is to hand the real predicate the two addresses the // device produced. A scratch slot, never one of the live ones - the handler scans // g_trackedMaps concurrently and a test may not publish a base into it. - Bool PersistentMapTracker::OwnershipProbeForTest(uintptr_t slotBase, uintptr_t slotEnd, - uintptr_t faultAddress, SizeT* pageIndexOut) { + Bool PersistentMapTracker::OwnershipProbeForTest(uintptr_t slotBase, uintptr_t slotEnd, uintptr_t faultAddress, + SizeT* pageIndexOut) { TrackedWriteMap probe; probe.base.store(slotBase, std::memory_order_relaxed); probe.end.store(slotEnd, std::memory_order_relaxed); @@ -880,14 +875,15 @@ namespace MobileGL::MG_Record { return TrackedMapFor(buffer.GetLifetimeId()); } - const TrackedWriteMap* PersistentMapTracker::TrackForTest(Uint64 lifetimeId, const Uint8* shadow, - SizeT rangeBegin, SizeT rangeEnd, - SizeT shadowExtent) { + const TrackedWriteMap* PersistentMapTracker::TrackForTest(Uint64 lifetimeId, const Uint8* shadow, SizeT rangeBegin, + SizeT rangeEnd, SizeT shadowExtent) { TrackWriteMap(lifetimeId, shadow, rangeBegin, rangeEnd, shadowExtent); return TrackedMapFor(lifetimeId); } - void PersistentMapTracker::UntrackForTest(Uint64 lifetimeId) { UntrackWriteMap(lifetimeId); } + void PersistentMapTracker::UntrackForTest(Uint64 lifetimeId) { + UntrackWriteMap(lifetimeId); + } Uint64 PersistentMapTracker::FaultEpochForTest() { return g_faultEpoch.load(std::memory_order_acquire); @@ -896,8 +892,9 @@ namespace MobileGL::MG_Record { void PersistentMapTracker::PushBlocksFor(BufferObject& buffer) { if (!PushIsArmed()) return; if (OnServerRole()) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map " - "producer belongs to the client; the server consumes transported bytes"); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"PushBlocksFor\"} - the persistent-map " + "producer belongs to the client; the server consumes transported bytes"); } PushBlocksForChecked(buffer); } @@ -994,16 +991,14 @@ namespace MobileGL::MG_Record { const auto pushSpan = [&](Uint64 spanBegin, Uint64 spanEnd) { if (spanBegin < pushedUpTo) spanBegin = pushedUpTo; if (spanBegin >= spanEnd) return; - buffer.PushMappedSpanBlock(static_cast(spanBegin), - static_cast(spanEnd - spanBegin)); + buffer.PushMappedSpanBlock(static_cast(spanBegin), static_cast(spanEnd - spanBegin)); ++m_blocksPushed; m_bytesPushed += spanEnd - spanBegin; pushedUpTo = spanEnd; }; if (interiorBegin > begin) { - const Uint64 edgeHash = XXH3_64bits( - shadowBytes + begin, - static_cast(interiorBegin - begin)); + const Uint64 edgeHash = + XXH3_64bits(shadowBytes + begin, static_cast(interiorBegin - begin)); if (edgeHash != tracked->edgeHashHead) { tracked->edgeHashHead = edgeHash; const Uint64 headBlockEnd = (begin / blockBytes + 1) * blockBytes; @@ -1023,15 +1018,13 @@ namespace MobileGL::MG_Record { // every page of the run - clear, then re-arm, then read - and a // write in the clear-to-re-arm window lands before the run's // reads, so it is inside the shipped bytes. - tracked->pageBits[wordIndex].fetch_and(~(1ull << bit), - std::memory_order_relaxed); + tracked->pageBits[wordIndex].fetch_and(~(1ull << bit), std::memory_order_relaxed); SizeT runEnd = pageIndex + 1; while (word != 0) { const SizeT nextBit = static_cast(std::countr_zero(word)); if ((wordIndex << 6) + nextBit != runEnd) break; word &= word - 1; - tracked->pageBits[wordIndex].fetch_and(~(1ull << nextBit), - std::memory_order_relaxed); + tracked->pageBits[wordIndex].fetch_and(~(1ull << nextBit), std::memory_order_relaxed); ++runEnd; } mprotect(reinterpret_cast(trackedBase + (pageIndex << kPageShift)), @@ -1046,21 +1039,17 @@ namespace MobileGL::MG_Record { // [begin, end): a write to the shadow's own bytes outside the // mapped range faults here too and ships, at worst, one block // of the range redundantly. - const Uint64 pageOffset = - (trackedBase + (runPage << kPageShift)) - shadowBase; + const Uint64 pageOffset = (trackedBase + (runPage << kPageShift)) - shadowBase; const Uint64 blockFloor = (pageOffset / blockBytes) * blockBytes; - const Uint64 blockStart = - blockFloor < begin ? begin : blockFloor; - const Uint64 blockEnd = - (end - blockStart) < blockBytes ? end : blockStart + blockBytes; + const Uint64 blockStart = blockFloor < begin ? begin : blockFloor; + const Uint64 blockEnd = (end - blockStart) < blockBytes ? end : blockStart + blockBytes; pushSpan(blockStart, blockEnd); } } } if (interiorEnd < end) { - const Uint64 edgeHash = XXH3_64bits( - shadowBytes + interiorEnd, - static_cast(end - interiorEnd)); + const Uint64 edgeHash = + XXH3_64bits(shadowBytes + interiorEnd, static_cast(end - interiorEnd)); if (edgeHash != tracked->edgeHashTail) { tracked->edgeHashTail = edgeHash; const Uint64 tailBlockStart = ((end - 1) / blockBytes) * blockBytes; @@ -1118,15 +1107,17 @@ namespace MobileGL::MG_Record { void PersistentMapTracker::PushAllMembers() { if (!PushIsArmed()) return; if (OnServerRole()) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map " - "producer belongs to the client; the server consumes transported bytes"); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"PushAllMembers\"} - the persistent-map " + "producer belongs to the client; the server consumes transported bytes"); } if (m_livePersistentMaps.empty()) return; // Copied out first: PushBlocksFor can erase its own entry (a member that stopped // being one), and ska::flat_hash_map invalidates on erase. Vector members; members.reserve(m_livePersistentMaps.size()); - for (const auto& entry : m_livePersistentMaps) members.push_back(entry.second.buffer); + for (const auto& entry : m_livePersistentMaps) + members.push_back(entry.second.buffer); for (BufferObject* buffer : members) { if (buffer != nullptr) PushBlocksForChecked(*buffer); } @@ -1135,8 +1126,9 @@ namespace MobileGL::MG_Record { void PersistentMapTracker::PushDrawConsumers() { if (!PushIsArmed()) return; if (OnServerRole()) { - MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map " - "producer belongs to the client; the server consumes transported bytes"); + MG_Pipe::MGPipeRecordFail(MG_Pipe::MGFatalFamily::RoleViolation, + "MGPipe: Fatal{RoleViolation, \"PushDrawConsumers\"} - the persistent-map " + "producer belongs to the client; the server consumes transported bytes"); } if (m_livePersistentMaps.empty()) return; // THE EPOCH SKIP, AND WHAT IT RESTS ON. A page of a tracked map is marked by @@ -1165,7 +1157,8 @@ namespace MobileGL::MG_Record { // stopped being one, and ska::flat_hash_map invalidates on erase. Vector edged; edged.reserve(m_edgedMembers.size()); - for (const auto& entry : m_edgedMembers) edged.push_back(entry.first); + for (const auto& entry : m_edgedMembers) + edged.push_back(entry.first); for (const Uint64 key : edged) { const auto it = m_livePersistentMaps.find(key); if (it == m_livePersistentMaps.end() || it->second.buffer == nullptr) continue; @@ -1236,9 +1229,10 @@ namespace MobileGL::MG_Record { } } static constexpr BufferTarget kIndexedTargets[] = { - BufferTarget::Uniform, BufferTarget::ShaderStorage, BufferTarget::AtomicCounter, - BufferTarget::TransformFeedback, BufferTarget::CopyRead, BufferTarget::CopyWrite, - BufferTarget::Query, BufferTarget::Texture}; + BufferTarget::Uniform, BufferTarget::ShaderStorage, + BufferTarget::AtomicCounter, BufferTarget::TransformFeedback, + BufferTarget::CopyRead, BufferTarget::CopyWrite, + BufferTarget::Query, BufferTarget::Texture}; for (const BufferTarget target : kIndexedTargets) { const SizeT count = ctx->GetBufferBindingPointCount(target); for (SizeT i = 0; i < count; ++i) { @@ -1247,8 +1241,7 @@ namespace MobileGL::MG_Record { } } } - static constexpr BufferTarget kSlotTargets[] = {BufferTarget::DrawIndirect, - BufferTarget::DispatchIndirect, + static constexpr BufferTarget kSlotTargets[] = {BufferTarget::DrawIndirect, BufferTarget::DispatchIndirect, BufferTarget::Parameter}; for (const BufferTarget target : kSlotTargets) { if (const auto& bound = ctx->GetBufferBindingSlot(target).GetBoundObject()) { @@ -1265,9 +1258,8 @@ namespace MobileGL::MG_Record { // twitch this comment is written against. const Int maxUnit = ctx->GetMaxTouchedTextureUnit(); for (Int unit = 0; unit <= maxUnit; ++unit) { - const auto& texture = ctx->GetTextureUnitObject(unit) - .GetBindingSlot(TextureTarget::TextureBuffer) - .GetBoundObject(); + const auto& texture = + ctx->GetTextureUnitObject(unit).GetBindingSlot(TextureTarget::TextureBuffer).GetBoundObject(); if (texture == nullptr || texture->GetStorageType() != TextureStorageType::Buffer) { continue; } @@ -1292,5 +1284,4 @@ namespace MobileGL::MG_Record { PersistentMapTracker::Instance().PushAllMembers(); } - } // namespace MobileGL::MG_Record diff --git a/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h index 3ff5634b..9a9a2987 100644 --- a/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h +++ b/MobileGL/MG_State/GLState/BufferState/PersistentMapTracker.h @@ -60,7 +60,7 @@ namespace MobileGL::MG_State::GLState { class BufferObject; -} +} // namespace MobileGL::MG_State::GLState namespace MobileGL::MG_Record { @@ -271,5 +271,4 @@ namespace MobileGL::MG_Record { // draw-path binding walks move to the client. void PushPersistentMapsBeforeVerb(); - } // namespace MobileGL::MG_Record diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 5ee3714e..7621d535 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -33,11 +33,15 @@ namespace MobileGL::MG_State { #if MOBILEGL_BUILD_DISAGGREGATED std::atomic g_wireEpoch{0}; #endif - } + } // namespace #if MOBILEGL_BUILD_DISAGGREGATED - Uint64 CurrentWireEpoch() { return g_wireEpoch.load(std::memory_order_acquire); } - void AdvanceWireEpoch() { g_wireEpoch.fetch_add(1, std::memory_order_acq_rel); } + Uint64 CurrentWireEpoch() { + return g_wireEpoch.load(std::memory_order_acquire); + } + void AdvanceWireEpoch() { + g_wireEpoch.fetch_add(1, std::memory_order_acq_rel); + } SharedPtr ReplaceProcessDefaultGLContext() { auto& slot = DefaultGLContextSlot(); @@ -246,19 +250,32 @@ namespace MobileGL::MG_State { VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType) { switch (glType) { - case GL_FLOAT: return {VertexAttribBaseType::Float, 1}; - case GL_FLOAT_VEC2: return {VertexAttribBaseType::Float, 2}; - case GL_FLOAT_VEC3: return {VertexAttribBaseType::Float, 3}; - case GL_FLOAT_VEC4: return {VertexAttribBaseType::Float, 4}; - case GL_INT: return {VertexAttribBaseType::Int, 1}; - case GL_INT_VEC2: return {VertexAttribBaseType::Int, 2}; - case GL_INT_VEC3: return {VertexAttribBaseType::Int, 3}; - case GL_INT_VEC4: return {VertexAttribBaseType::Int, 4}; - case GL_UNSIGNED_INT: return {VertexAttribBaseType::Uint, 1}; - case GL_UNSIGNED_INT_VEC2: return {VertexAttribBaseType::Uint, 2}; - case GL_UNSIGNED_INT_VEC3: return {VertexAttribBaseType::Uint, 3}; - case GL_UNSIGNED_INT_VEC4: return {VertexAttribBaseType::Uint, 4}; - default: return {}; + case GL_FLOAT: + return {VertexAttribBaseType::Float, 1}; + case GL_FLOAT_VEC2: + return {VertexAttribBaseType::Float, 2}; + case GL_FLOAT_VEC3: + return {VertexAttribBaseType::Float, 3}; + case GL_FLOAT_VEC4: + return {VertexAttribBaseType::Float, 4}; + case GL_INT: + return {VertexAttribBaseType::Int, 1}; + case GL_INT_VEC2: + return {VertexAttribBaseType::Int, 2}; + case GL_INT_VEC3: + return {VertexAttribBaseType::Int, 3}; + case GL_INT_VEC4: + return {VertexAttribBaseType::Int, 4}; + case GL_UNSIGNED_INT: + return {VertexAttribBaseType::Uint, 1}; + case GL_UNSIGNED_INT_VEC2: + return {VertexAttribBaseType::Uint, 2}; + case GL_UNSIGNED_INT_VEC3: + return {VertexAttribBaseType::Uint, 3}; + case GL_UNSIGNED_INT_VEC4: + return {VertexAttribBaseType::Uint, 4}; + default: + return {}; } } @@ -280,7 +297,7 @@ namespace MobileGL::MG_State { // The two views above are CONVERSIONS, not bit copies, so which one was written // is part of the value; set_vertex_attrib_defaults carries it. m_currentVertexAttributeClasses[index] = kVertexAttribValueClassFloat; - MGP_NOTE_AGGREGATE(VertexAttribDefault); + MGP_NOTE_AGGREGATE(VertexAttribDefault); } void GLContext::SetCurrentVertexAttributeInt(Uint index, const Array& value) { @@ -296,7 +313,7 @@ namespace MobileGL::MG_State { current.uintValue[component] = static_cast(value[component]); } m_currentVertexAttributeClasses[index] = kVertexAttribValueClassInt; - MGP_NOTE_AGGREGATE(VertexAttribDefault); + MGP_NOTE_AGGREGATE(VertexAttribDefault); } void GLContext::SetCurrentVertexAttributeUint(Uint index, const Array& value) { @@ -312,7 +329,7 @@ namespace MobileGL::MG_State { current.intValue[component] = static_cast(value[component]); } m_currentVertexAttributeClasses[index] = kVertexAttribValueClassUint; - MGP_NOTE_AGGREGATE(VertexAttribDefault); + MGP_NOTE_AGGREGATE(VertexAttribDefault); } const CurrentVertexAttributeValue& GLContext::GetCurrentVertexAttribute(Uint index) const { @@ -656,7 +673,8 @@ namespace MobileGL::MG_State { // programs hold and it does not: uniform values, and interface block bindings. Runs on // every draw through a pipeline, so the common case is the version compare below and // nothing else. - static void RefreshCompositeUniforms(ProgramPipelineObject& pipeline, const SharedPtr& composite) { + static void RefreshCompositeUniforms(ProgramPipelineObject& pipeline, + const SharedPtr& composite) { if (!composite) return; const auto versions = pipeline.ComputeUniformMirrorVersions(); if (versions == pipeline.GetMirroredUniformVersions()) return; @@ -810,9 +828,8 @@ namespace MobileGL::MG_State { // the last one in a pipeline that has a TCS but no evaluation or geometry stage, // which is why it sits after TessEval. Same four stages, same order, as // ProgramLinkTask::ResolveTransformFeedbackVaryings - see rule (2). - for (const ShaderStage captureStage: - {ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl, - ShaderStage::Vertex}) { + for (const ShaderStage captureStage : + {ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl, ShaderStage::Vertex}) { if (!compositeHasStage[static_cast(captureStage)]) continue; const auto& captureProgram = pipeline->GetStageProgram(captureStage); if (!captureProgram) continue; @@ -1563,7 +1580,6 @@ namespace MobileGL::MG_State { return GetProgramPipelineObject(m_boundProgramPipeline); } - Bool GLContext::ValidateTransformFeedbackName(Uint index) const { return index == 0 || m_transformFeedbackNames.IsValid(index); } @@ -1693,7 +1709,7 @@ namespace MobileGL::MG_State { }; namespace { thread_local CurrentGLContextOwner tCurrentGLContextOwner; - } + } // namespace const SharedPtr& CurrentGLContextSlot::Shared() const { return tCurrentGLContextOwner.context; diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 10e31391..3fdc785a 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -24,7 +24,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { struct CompileEnv; -} +} // namespace MobileGL::MG_Util::ShaderTranspiler namespace MobileGL { namespace MG_State { @@ -62,7 +62,12 @@ namespace MobileGL { }; // Which of the three views above a shader input of a given GLSL type consumes. - enum class VertexAttribBaseType { Unsupported, Float, Int, Uint }; + enum class VertexAttribBaseType { + Unsupported, + Float, + Int, + Uint + }; struct VertexAttribTypeInfo { VertexAttribBaseType baseType = VertexAttribBaseType::Unsupported; @@ -276,9 +281,7 @@ namespace MobileGL { // objects need no back-pointer, and it is push-only so the pull build's // GLContext is byte-identical (G1). void NoteVaoAttributeChanged() { m_vertexArrayState.NoteAttributeChanged(); } - Uint64 GetAnyVaoAttributeGeneration() const { - return m_vertexArrayState.GetAnyAttributeGeneration(); - } + Uint64 GetAnyVaoAttributeGeneration() const { return m_vertexArrayState.GetAnyAttributeGeneration(); } // The framebuffer and the two texture aggregates (and the sampling resolution // generation above) also bump the share group's twin: the objects behind them are // the group's, and a sibling context's walk has to see the change too @@ -301,28 +304,20 @@ namespace MobileGL { m_textureState.NoteTextureParamsChanged(); m_shareGroup->BumpSharedClock(ShareGroupState::SharedClock::TextureParams); } - Uint64 GetAnyTextureParamsGeneration() const { - return m_textureState.GetAnyTextureParamsGeneration(); - } + Uint64 GetAnyTextureParamsGeneration() const { return m_textureState.GetAnyTextureParamsGeneration(); } void NoteBufferChanged() { m_bufferState.NoteBufferChanged(); } - Uint64 GetAnyBufferChangeGeneration() const { - return m_bufferState.GetAnyBufferChangeGeneration(); - } + Uint64 GetAnyBufferChangeGeneration() const { return m_bufferState.GetAnyBufferChangeGeneration(); } // P5e (sb): the per-target INDEXED BINDING POINT generation, which is what dirty // bits 15/16/17 shutter on now. See BufferState::NoteBindPointChanged for why // the content aggregate above could not answer the question. - void NoteBufferBindPointChanged(BufferTarget target) { - m_bufferState.NoteBindPointChanged(target); - } + void NoteBufferBindPointChanged(BufferTarget target) { m_bufferState.NoteBindPointChanged(target); } Uint64 GetBufferBindPointGeneration(BufferTarget target) const { return m_bufferState.GetBindPointGeneration(target); } // The sixth aggregate lives here rather than on a state container because // the values it guards do too (m_currentVertexAttributes). void NoteVertexAttribDefaultChanged() { ++m_anyVertexAttribDefaultGeneration; } - Uint64 GetAnyVertexAttribDefaultGeneration() const { - return m_anyVertexAttribDefaultGeneration; - } + Uint64 GetAnyVertexAttribDefaultGeneration() const { return m_anyVertexAttribDefaultGeneration; } // Which of the three views of m_currentVertexAttributes[index] the last // glVertexAttrib* write filled DIRECTLY. The other two are NUMERIC @@ -341,9 +336,8 @@ namespace MobileGL { // only decides how to REBUILD the three views, so two writes that leave the // three views identical rebuild identically whichever class they carried. Uint32 GetCurrentVertexAttributeClass(Uint index) const { - return index < m_currentVertexAttributeClasses.size() - ? m_currentVertexAttributeClasses[index] - : kVertexAttribValueClassFloat; + return index < m_currentVertexAttributeClasses.size() ? m_currentVertexAttributeClasses[index] + : kVertexAttribValueClassFloat; } // RenderState diff --git a/MobileGL/MG_State/GLState/ErrorState/Error.cpp b/MobileGL/MG_State/GLState/ErrorState/Error.cpp index a18617c7..c2950c3a 100644 --- a/MobileGL/MG_State/GLState/ErrorState/Error.cpp +++ b/MobileGL/MG_State/GLState/ErrorState/Error.cpp @@ -24,8 +24,8 @@ namespace MobileGL::MG_State::GLState { // error flags, each is set only while currently unset — repeated errors of the same // code are discarded until glGetError reads the flag. Unbounded accumulation leaked // stale errors into later, unrelated glGetError checks (GL CTS deinit noise). - const Bool alreadyPending = std::any_of(m_errors.begin(), m_errors.end(), - [code](const auto& e) { return e->code == code; }); + const Bool alreadyPending = + std::any_of(m_errors.begin(), m_errors.end(), [code](const auto& e) { return e->code == code; }); if (!alreadyPending) { m_errors.push_back(MakeUnique(code, Move(info))); } diff --git a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp index 4a9f7bf5..0d2d8472 100644 --- a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp +++ b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.cpp @@ -46,8 +46,8 @@ namespace MobileGL::MG_State::GLState { FramebufferAttachmentObject::FramebufferAttachmentObject( const SharedPtr& texture, TextureUploadTarget textureUploadTarget, Int level, Int layer, Bool layered) - : m_texture(texture), m_textureUploadTarget(textureUploadTarget), m_textureLevel(level), - m_textureLayer(layer), m_layered(layered) {} + : m_texture(texture), m_textureUploadTarget(textureUploadTarget), m_textureLevel(level), m_textureLayer(layer), + m_layered(layered) {} FramebufferAttachmentObject::FramebufferAttachmentObject(const SharedPtr& renderbuffer) : m_renderbuffer(renderbuffer) {} FramebufferAttachmentObject::FramebufferAttachmentObject(Bool IsValid) @@ -267,12 +267,12 @@ namespace MobileGL::MG_State::GLState { return m_externalIndex; } -#define MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(name, member, type) \ +#define MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(name, member, type) \ void FramebufferObject::Set##name(type value) { \ - if (member == value) return; \ - member = value; \ - ++m_objectVersion; \ - MGP_NOTE_AGGREGATE(FramebufferAttachment); \ + if (member == value) return; \ + member = value; \ + ++m_objectVersion; \ + MGP_NOTE_AGGREGATE(FramebufferAttachment); \ } MOBILEGL_DEFINE_FRAMEBUFFER_DEFAULT_SETTER(DefaultWidth, m_defaultWidth, Int) diff --git a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h index 689cd61b..19e75155 100644 --- a/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h +++ b/MobileGL/MG_State/GLState/FramebufferState/FramebufferObject.h @@ -74,8 +74,8 @@ namespace MobileGL { class FramebufferAttachmentObject { public: explicit FramebufferAttachmentObject(const SharedPtr& texture, - TextureUploadTarget textureUploadTarget, - Int level = 0, Int layer = 0, Bool layered = false); + TextureUploadTarget textureUploadTarget, Int level = 0, Int layer = 0, + Bool layered = false); explicit FramebufferAttachmentObject(const SharedPtr& renderbuffer); explicit FramebufferAttachmentObject(Bool IsValid = true); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h index 1d734e3e..f015087a 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h @@ -38,17 +38,17 @@ namespace MobileGL::MG_State::GLState { Bool isOpaque = false; Bool isTexture = false; Bool isImage = false; - Bool isDouble = false; // getBasicType() == EbtDouble - Bool isVoid = false; // getBasicType() == EbtVoid (hidden block members) - Bool isBuffer = false; // getQualifier().storage == EvqBuffer - Bool isPatch = false; // getQualifier().patch - Bool hasIndex = false; // getQualifier().hasIndex() - Bool hasFormat = false; // getQualifier().hasFormat() + Bool isDouble = false; // getBasicType() == EbtDouble + Bool isVoid = false; // getBasicType() == EbtVoid (hidden block members) + Bool isBuffer = false; // getQualifier().storage == EvqBuffer + Bool isPatch = false; // getQualifier().patch + Bool hasIndex = false; // getQualifier().hasIndex() + Bool hasFormat = false; // getQualifier().hasFormat() Int vectorSize = 0; Int matrixCols = 0; Int matrixRows = 0; - Int layoutIndex = 0; // getQualifier().layoutIndex - Uint layoutFormat = 0; // getQualifier().getFormat() + Int layoutIndex = 0; // getQualifier().layoutIndex + Uint layoutFormat = 0; // getQualifier().getFormat() // glslang TLayoutMatrix, widened. For a uniform this is already RESOLVED against // the owning block's qualifier, so the getUniformBlock() fallback the old // accessors carried is gone. @@ -134,10 +134,10 @@ namespace MobileGL::MG_State::GLState { struct XfbVarying { String name; GLenum type = GL_FLOAT; - GLint size = 1; // array element count - Uint32 bufferIndex = 0; // capture buffer slot - Uint32 offsetBytes = 0; // offset within the capture buffer - Uint32 byteSize = 0; // bytes captured per vertex for this varying + GLint size = 1; // array element count + Uint32 bufferIndex = 0; // capture buffer slot + Uint32 offsetBytes = 0; // offset within the capture buffer + Uint32 byteSize = 0; // bytes captured per vertex for this varying // Offset within the gap-free record a backend that cannot express the GL // layout captures into; see NeedsScatteredTransformFeedbackCapture. Uint32 packedOffsetBytes = 0; @@ -622,7 +622,8 @@ namespace MobileGL::MG_State::GLState { // ---- trip wires ---- // TypeFacts is a POD on every ABI: 13 Bool + 3 bytes of padding + 7 x 4-byte scalars. static_assert(std::is_trivially_copyable_v && sizeof(TypeFacts) == 44, - "TypeFacts changed: add the field to VisitFields(TypeFacts) (and ProgramArtifactsCodec.cpp's serializer), then update this number"); + "TypeFacts changed: add the field to VisitFields(TypeFacts) (and ProgramArtifactsCodec.cpp's " + "serializer), then update this number"); // The container-bearing structs have one size per standard library (std::string and // std::set differ between libstdc++ and libc++), so their numbers are pinned PER STL: // libstdc++ (the Linux CI toolchain) here, libc++ (the NDK) by the integrator, MSVC @@ -640,12 +641,16 @@ namespace MobileGL::MG_State::GLState { #endif #ifdef MGL_LINKARTIFACTS_SIZE static_assert(sizeof(ResourceReflection) == MGL_RESOURCEREFLECTION_SIZE, - "ResourceReflection changed size: add the field to VisitFields(ResourceReflection) (and ProgramArtifactsCodec.cpp's serializer), then update this number"); + "ResourceReflection changed size: add the field to VisitFields(ResourceReflection) (and " + "ProgramArtifactsCodec.cpp's serializer), then update this number"); static_assert(sizeof(XfbVarying) == MGL_XFBVARYING_SIZE, - "XfbVarying changed size: add the field to VisitFields(XfbVarying) (and ProgramArtifactsCodec.cpp's serializer), then update this number"); + "XfbVarying changed size: add the field to VisitFields(XfbVarying) (and ProgramArtifactsCodec.cpp's " + "serializer), then update this number"); static_assert(sizeof(LinkArtifacts) == MGL_LINKARTIFACTS_SIZE, - "LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) (and ProgramArtifactsCodec.cpp's serializer), then update this number"); + "LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) (and " + "ProgramArtifactsCodec.cpp's serializer), then update this number"); static_assert(sizeof(SpirvArtifacts) == MGL_SPIRVARTIFACTS_SIZE, - "SpirvArtifacts changed size: add the field to VisitFields(SpirvArtifacts) (and ProgramArtifactsCodec.cpp's serializer), then update this number"); + "SpirvArtifacts changed size: add the field to VisitFields(SpirvArtifacts) (and " + "ProgramArtifactsCodec.cpp's serializer), then update this number"); #endif } // namespace MobileGL::MG_State::GLState diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp index dbd15cc5..1bdff499 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.cpp @@ -107,9 +107,10 @@ namespace MobileGL::MG_State::GLState { SchemaName(hash, "enum"); DescribeWireType>(hash); } else { - SchemaName(hash, std::is_same_v ? "bool" : - std::is_floating_point_v ? "ieee-float" : - std::is_signed_v ? "signed" : "unsigned"); + SchemaName(hash, std::is_same_v ? "bool" + : std::is_floating_point_v ? "ieee-float" + : std::is_signed_v ? "signed" + : "unsigned"); SchemaWord(hash, sizeof(T)); if constexpr (std::is_floating_point_v) { static_assert(std::numeric_limits::is_iec559); @@ -144,8 +145,10 @@ namespace MobileGL::MG_State::GLState { SchemaName(hash, name); DescribeWireType>(hash); }; - if constexpr (ArchiveUniformInitializer) VisitUniformInitializer(value, field); - else VisitFields(value, field); + if constexpr (ArchiveUniformInitializer) + VisitUniformInitializer(value, field); + else + VisitFields(value, field); SchemaWord(hash, fields); SchemaName(hash, "end-record"); } @@ -171,7 +174,8 @@ namespace MobileGL::MG_State::GLState { template void WriteSequence(Vector& out, const T& value) { PutCount(out, value.size()); - for (const auto& element : value) WriteValue(out, element); + for (const auto& element : value) + WriteValue(out, element); } template @@ -194,13 +198,12 @@ namespace MobileGL::MG_State::GLState { } else if constexpr (ArchiveFixedArray) { // No count: the width is part of the type, and writing one would let a reader // believe a stream that disagrees with the struct. - for (const auto& element : value) WriteValue(out, element); + for (const auto& element : value) + WriteValue(out, element); } else if constexpr (ArchiveVector) { WriteSequence(out, value); } else if constexpr (ArchiveUniformInitializer) { - VisitUniformInitializer(value, [&out](const char*, const auto& member) { - WriteValue(out, member); - }); + VisitUniformInitializer(value, [&out](const char*, const auto& member) { WriteValue(out, member); }); } else { // The archive's own structs: TypeFacts, ResourceReflection, XfbVarying. ONE // table serves both directions, so a member added to any of them is carried by @@ -258,8 +261,10 @@ namespace MobileGL::MG_State::GLState { const auto field = [&bytes](const char*, const auto& member) { bytes += MinEncodedBytesOf>(); }; - if constexpr (ArchiveUniformInitializer) VisitUniformInitializer(value, field); - else VisitFields(value, field); + if constexpr (ArchiveUniformInitializer) + VisitUniformInitializer(value, field); + else + VisitFields(value, field); return bytes; } } @@ -381,8 +386,7 @@ namespace MobileGL::MG_State::GLState { return fingerprint; } - void EncodeProgramArtifacts(const LinkArtifacts& link, const SpirvArtifacts& spirv, - Vector& out) { + void EncodeProgramArtifacts(const LinkArtifacts& link, const SpirvArtifacts& spirv, Vector& out) { PutRaw(out, kProgramArtifactsCodecVersion); PutRaw(out, ProgramArtifactsSchemaFingerprint()); // `link` is walked through its own VisitFields table, which omits the live @@ -393,8 +397,7 @@ namespace MobileGL::MG_State::GLState { WriteValue(out, spirv); } - Bool DecodeProgramArtifacts(const Uint8* bytes, SizeT size, LinkArtifacts& link, - SpirvArtifacts& spirv) { + Bool DecodeProgramArtifacts(const Uint8* bytes, SizeT size, LinkArtifacts& link, SpirvArtifacts& spirv) { // Both outputs are left in a DEFINED state on every exit, including every failure: // a caller that ignores the return value gets an empty archive rather than half of a // truncated one. @@ -443,7 +446,8 @@ namespace MobileGL::MG_State::GLState { void EncodeProgramArchive(const LinkArtifacts& link, const SpirvArtifacts& spirv, const Vector& linkedStages, Vector& out) { PutRaw(out, static_cast(linkedStages.size())); - for (const Uint32 stage : linkedStages) PutRaw(out, stage); + for (const Uint32 stage : linkedStages) + PutRaw(out, stage); EncodeProgramArtifacts(link, spirv, out); } @@ -463,8 +467,7 @@ namespace MobileGL::MG_State::GLState { out.LinkedStages.resize(stageCount); for (Uint32 i = 0; i < stageCount; ++i) { - std::memcpy(&out.LinkedStages[i], bytes + sizeof(Uint32) + i * sizeof(Uint32), - sizeof(Uint32)); + std::memcpy(&out.LinkedStages[i], bytes + sizeof(Uint32) + i * sizeof(Uint32), sizeof(Uint32)); } if (!DecodeProgramArtifacts(bytes + framed, size - framed, out.Link, out.Spirv)) { out = ProgramArchive{}; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.h b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.h index c9ac83f4..45f48a1b 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramArtifactsCodec.h @@ -74,15 +74,13 @@ namespace MobileGL::MG_State::GLState { // Appends the archive to `out` (which is not cleared, so a caller may frame it). Never // fails: everything it walks is owned plain data. - void EncodeProgramArtifacts(const LinkArtifacts& link, const SpirvArtifacts& spirv, - Vector& out); + void EncodeProgramArtifacts(const LinkArtifacts& link, const SpirvArtifacts& spirv, Vector& out); // Replaces `link` and `spirv` with what `bytes` describes. Returns false - with both // outputs left in a defined, default state - for a truncated stream, a version mismatch, a // wire-schema mismatch (native-size echo for local v1), or trailing bytes. `link.program` // is always null on return. - Bool DecodeProgramArtifacts(const Uint8* bytes, SizeT size, LinkArtifacts& link, - SpirvArtifacts& spirv); + Bool DecodeProgramArtifacts(const Uint8* bytes, SizeT size, LinkArtifacts& link, SpirvArtifacts& spirv); // ---- P5e (pg): the archive as the SERVER OWNS it ------------------------------------- // diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.cpp index 58d82057..73b6e3fc 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.cpp @@ -28,8 +28,7 @@ namespace { // GL_MAX_VERTEX_ATTRIBS would make a legal attribute location invisible to them -- DirectGLES would // then never feed the shader that attribute's current value. Bounded by the state layer's storage // capacity, which is also the width of the Uint32 masks backends build from it. - static MobileGL::Int GetReflectionVertexAttribLimit( - const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { + static MobileGL::Int GetReflectionVertexAttribLimit(const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { // One shared definition with glGetIntegerv(GL_MAX_VERTEX_ATTRIBS) and with // BuildTBuiltInResource's gl_MaxVertexAttribs - the three used to carry three copies of // this formula and glslang's copy was a hardcoded 64. @@ -96,8 +95,8 @@ namespace { // TObjectReflection::size carries the element count only for a NON-block array, so // the sized-array outer count wins whenever it exists. const glslang::TType* type = object.getType(); - record.arraySize = (type != nullptr && type->isSizedArray()) ? type->getOuterArraySize() - : (object.size < 1 ? 1 : object.size); + record.arraySize = + (type != nullptr && type->isSizedArray()) ? type->getOuterArraySize() : (object.size < 1 ? 1 : object.size); return record; } @@ -114,12 +113,18 @@ namespace { // skips rather than guesses at. static EShLanguage ShaderStageToEShLanguage(MobileGL::ShaderStage stage) { switch (stage) { - case MobileGL::ShaderStage::Vertex: return EShLangVertex; - case MobileGL::ShaderStage::TessControl: return EShLangTessControl; - case MobileGL::ShaderStage::TessEval: return EShLangTessEvaluation; - case MobileGL::ShaderStage::Geometry: return EShLangGeometry; - case MobileGL::ShaderStage::Fragment: return EShLangFragment; - case MobileGL::ShaderStage::Compute: return EShLangCompute; + case MobileGL::ShaderStage::Vertex: + return EShLangVertex; + case MobileGL::ShaderStage::TessControl: + return EShLangTessControl; + case MobileGL::ShaderStage::TessEval: + return EShLangTessEvaluation; + case MobileGL::ShaderStage::Geometry: + return EShLangGeometry; + case MobileGL::ShaderStage::Fragment: + return EShLangFragment; + case MobileGL::ShaderStage::Compute: + return EShLangCompute; case MobileGL::ShaderStage::ShaderStageCount: case MobileGL::ShaderStage::Unknown: break; @@ -264,8 +269,8 @@ namespace { // that happens in the backend after this link, and counting the expanded set here would // reject programs that are legal by the numbers GL advertises. Returns the info-log line for // a program over a limit, empty for one within them. - static MobileGL::String ValidateImageUniformLimits( - glslang::TProgram& reflection, const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { + static MobileGL::String ValidateImageUniformLimits(glslang::TProgram& reflection, + const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { using MobileGL::Array; using MobileGL::Int; using MobileGL::SizeT; @@ -274,16 +279,14 @@ namespace { static constexpr EShLanguage kStages[] = {EShLangVertex, EShLangTessControl, EShLangTessEvaluation, EShLangGeometry, EShLangFragment, EShLangCompute}; static constexpr const char* kLimitNames[] = { - "GL_MAX_VERTEX_IMAGE_UNIFORMS", "GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS", + "GL_MAX_VERTEX_IMAGE_UNIFORMS", "GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS", "GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS", "GL_MAX_GEOMETRY_IMAGE_UNIFORMS", - "GL_MAX_FRAGMENT_IMAGE_UNIFORMS", "GL_MAX_COMPUTE_IMAGE_UNIFORMS"}; + "GL_MAX_FRAGMENT_IMAGE_UNIFORMS", "GL_MAX_COMPUTE_IMAGE_UNIFORMS"}; constexpr SizeT kStageCount = sizeof(kStages) / sizeof(kStages[0]); - const Int limits[kStageCount] = {env.params.MaxVertexImageUniforms, - env.params.MaxTessControlImageUniforms, - env.params.MaxTessEvaluationImageUniforms, - env.params.MaxGeometryImageUniforms, - env.params.MaxFragmentImageUniforms, - env.params.MaxComputeImageUniforms}; + const Int limits[kStageCount] = { + env.params.MaxVertexImageUniforms, env.params.MaxTessControlImageUniforms, + env.params.MaxTessEvaluationImageUniforms, env.params.MaxGeometryImageUniforms, + env.params.MaxFragmentImageUniforms, env.params.MaxComputeImageUniforms}; // Reflection spells an image ARRAY one of two ways, and which one it picks depends on how // the shader indexed it: a variable index makes glslang expand the array into one entry @@ -379,13 +382,19 @@ namespace { "GEOMETRY", "FRAGMENT", "COMPUTE"}; constexpr SizeT kStageCount = sizeof(kStages) / sizeof(kStages[0]); - enum Family : SizeT { kUniform = 0, kStorage, kFamilyCount }; + enum Family : SizeT { + kUniform = 0, + kStorage, + kFamilyCount + }; static constexpr const char* kFamilyNames[] = {"UNIFORM_BLOCKS", "SHADER_STORAGE_BLOCKS"}; static constexpr const char* kFamilyNouns[] = {"uniform blocks", "shader storage blocks"}; const auto& p = env.params; const Int storageBindings = std::max(p.MaxShaderStorageBufferBindings, 0); - const auto clampStorage = [storageBindings](Int limit) { return std::min(std::max(limit, 0), storageBindings); }; + const auto clampStorage = [storageBindings](Int limit) { + return std::min(std::max(limit, 0), storageBindings); + }; const Int graphicsUniform = Transpiler::MAX_GRAPHICS_STAGE_UNIFORM_BLOCKS; const Int combinedUniform = 6 * graphicsUniform; const Int limits[kFamilyCount][kStageCount] = { @@ -437,8 +446,8 @@ namespace { if (counts[stage] > limits[family][stage]) { return std::format("This program uses {} active {} in one stage, more than the {} " "GL_MAX_{}_{} allows.", - counts[stage], kFamilyNouns[family], limits[family][stage], - kStageNames[stage], kFamilyNames[family]); + counts[stage], kFamilyNouns[family], limits[family][stage], kStageNames[stage], + kFamilyNames[family]); } } if (combined > combinedLimits[family]) { @@ -497,7 +506,8 @@ namespace { const Int uniformCount = reflection.getNumUniformVariables(); for (Int i = 0; i < uniformCount; ++i) { const auto& uniform = reflection.getUniform(i); - if (uniform.index < 0 || uniform.index >= blockCount || !isCounterBlock[static_cast(uniform.index)]) { + if (uniform.index < 0 || uniform.index >= blockCount || + !isCounterBlock[static_cast(uniform.index)]) { continue; } Int elements = uniform.size > 1 ? uniform.size : 1; @@ -638,41 +648,51 @@ namespace MobileGL::MG_State::GLState { Uint32& outBytesPerElement) { outArraySize = type.isArray() ? type.getOuterArraySize() : 1; const Int columns = type.isMatrix() ? type.getMatrixCols() : 1; - const Int components = type.isMatrix() ? type.getMatrixRows() - : (type.isVector() ? type.getVectorSize() : 1); + const Int components = + type.isMatrix() ? type.getMatrixRows() : (type.isVector() ? type.getVectorSize() : 1); const glslang::TBasicType basic = type.getBasicType(); static constexpr GLenum kFloatTypes[5] = {0, GL_FLOAT, GL_FLOAT_VEC2, GL_FLOAT_VEC3, GL_FLOAT_VEC4}; static constexpr GLenum kIntTypes[5] = {0, GL_INT, GL_INT_VEC2, GL_INT_VEC3, GL_INT_VEC4}; static constexpr GLenum kUintTypes[5] = {0, GL_UNSIGNED_INT, GL_UNSIGNED_INT_VEC2, GL_UNSIGNED_INT_VEC3, GL_UNSIGNED_INT_VEC4}; - static constexpr GLenum kDoubleTypes[5] = {0, GL_DOUBLE, GL_DOUBLE_VEC2, GL_DOUBLE_VEC3, - GL_DOUBLE_VEC4}; + static constexpr GLenum kDoubleTypes[5] = {0, GL_DOUBLE, GL_DOUBLE_VEC2, GL_DOUBLE_VEC3, GL_DOUBLE_VEC4}; if (type.isMatrix()) { if (basic != glslang::EbtFloat && basic != glslang::EbtDouble) return false; static constexpr GLenum kMatTypes[5][5] = { - {}, {}, + {}, + {}, {0, 0, GL_FLOAT_MAT2, GL_FLOAT_MAT2x3, GL_FLOAT_MAT2x4}, {0, 0, GL_FLOAT_MAT3x2, GL_FLOAT_MAT3, GL_FLOAT_MAT3x4}, {0, 0, GL_FLOAT_MAT4x2, GL_FLOAT_MAT4x3, GL_FLOAT_MAT4}, }; static constexpr GLenum kDoubleMatTypes[5][5] = { - {}, {}, + {}, + {}, {0, 0, GL_DOUBLE_MAT2, GL_DOUBLE_MAT2x3, GL_DOUBLE_MAT2x4}, {0, 0, GL_DOUBLE_MAT3x2, GL_DOUBLE_MAT3, GL_DOUBLE_MAT3x4}, {0, 0, GL_DOUBLE_MAT4x2, GL_DOUBLE_MAT4x3, GL_DOUBLE_MAT4}, }; if (columns < 2 || columns > 4 || components < 2 || components > 4) return false; - outType = basic == glslang::EbtDouble ? kDoubleMatTypes[columns][components] - : kMatTypes[columns][components]; + outType = + basic == glslang::EbtDouble ? kDoubleMatTypes[columns][components] : kMatTypes[columns][components]; } else if (components >= 1 && components <= 4) { switch (basic) { - case glslang::EbtFloat: outType = kFloatTypes[components]; break; - case glslang::EbtInt: outType = kIntTypes[components]; break; - case glslang::EbtUint: outType = kUintTypes[components]; break; + case glslang::EbtFloat: + outType = kFloatTypes[components]; + break; + case glslang::EbtInt: + outType = kIntTypes[components]; + break; + case glslang::EbtUint: + outType = kUintTypes[components]; + break; // A double-typed varying is capturable like any other; rejecting it here reported // the varying as "not an output of the vertex stage", which it plainly was. - case glslang::EbtDouble: outType = kDoubleTypes[components]; break; - default: return false; + case glslang::EbtDouble: + outType = kDoubleTypes[components]; + break; + default: + return false; } } else { return false; @@ -763,7 +783,8 @@ namespace MobileGL::MG_State::GLState { // EnsureLinkJoined(), which is a far worse failure than a link reported as not linked. try { MG_Util::Async::ShaderCompilePool::Get().Post(shared_from_this()); - } catch (...) { + } + catch (...) { Cancel(); } } @@ -825,11 +846,9 @@ namespace MobileGL::MG_State::GLState { const glslang::TIntermediate* intermediate = shader ? shader->getIntermediate() : nullptr; if (intermediate == nullptr) continue; for (const auto& initializer : intermediate->getUniformInitializers()) { - const auto known = std::find_if(artifacts.uniformInitialValues.begin(), - artifacts.uniformInitialValues.end(), - [&initializer](const auto& existing) { - return existing.name == initializer.name; - }); + const auto known = + std::find_if(artifacts.uniformInitialValues.begin(), artifacts.uniformInitialValues.end(), + [&initializer](const auto& existing) { return existing.name == initializer.name; }); if (known != artifacts.uniformInitialValues.end()) continue; artifacts.uniformInitialValues.push_back(initializer); } @@ -866,12 +885,11 @@ namespace MobileGL::MG_State::GLState { artifacts.program.get()); } else { artifacts.infoLog = result.error().log; - DeferLog(std::format("ProgramObject {}: LinkProgram failed. InfoLog:\n{}", in.externalIndex, - artifacts.infoLog)); + DeferLog( + std::format("ProgramObject {}: LinkProgram failed. InfoLog:\n{}", in.externalIndex, artifacts.infoLog)); return; } - // A compute program must have a fixed local group size, and GL states that as a // property of the PROGRAM: "at least one" of its compute shaders declares it (GL 4.6 // core 7.13 / GLSL 4.30 4.4.1.4). MobileGL used to answer that question per SHADER, @@ -907,18 +925,36 @@ namespace MobileGL::MG_State::GLState { artifacts.gsInvocations = 0; if (const glslang::TIntermediate* gs = artifacts.program->getIntermediate(EShLangGeometry)) { switch (gs->getInputPrimitive()) { - case glslang::ElgPoints: artifacts.gsInputPrimitive = GL_POINTS; break; - case glslang::ElgLines: artifacts.gsInputPrimitive = GL_LINES; break; - case glslang::ElgLinesAdjacency: artifacts.gsInputPrimitive = GL_LINES_ADJACENCY; break; - case glslang::ElgTriangles: artifacts.gsInputPrimitive = GL_TRIANGLES; break; - case glslang::ElgTrianglesAdjacency: artifacts.gsInputPrimitive = GL_TRIANGLES_ADJACENCY; break; - default: break; + case glslang::ElgPoints: + artifacts.gsInputPrimitive = GL_POINTS; + break; + case glslang::ElgLines: + artifacts.gsInputPrimitive = GL_LINES; + break; + case glslang::ElgLinesAdjacency: + artifacts.gsInputPrimitive = GL_LINES_ADJACENCY; + break; + case glslang::ElgTriangles: + artifacts.gsInputPrimitive = GL_TRIANGLES; + break; + case glslang::ElgTrianglesAdjacency: + artifacts.gsInputPrimitive = GL_TRIANGLES_ADJACENCY; + break; + default: + break; } switch (gs->getOutputPrimitive()) { - case glslang::ElgPoints: artifacts.gsOutputPrimitive = GL_POINTS; break; - case glslang::ElgLineStrip: artifacts.gsOutputPrimitive = GL_LINE_STRIP; break; - case glslang::ElgTriangleStrip: artifacts.gsOutputPrimitive = GL_TRIANGLE_STRIP; break; - default: break; + case glslang::ElgPoints: + artifacts.gsOutputPrimitive = GL_POINTS; + break; + case glslang::ElgLineStrip: + artifacts.gsOutputPrimitive = GL_LINE_STRIP; + break; + case glslang::ElgTriangleStrip: + artifacts.gsOutputPrimitive = GL_TRIANGLE_STRIP; + break; + default: + break; } // glslang leaves both at TQualifier::layoutNotSet (-1) when the shader declared no // such layout, and `invocations` defaults to one per GLSL 4.60 4.4.2.2 - so clamp @@ -936,21 +972,38 @@ namespace MobileGL::MG_State::GLState { artifacts.tessGenPointMode = false; if (const glslang::TIntermediate* tes = artifacts.program->getIntermediate(EShLangTessEvaluation)) { switch (tes->getInputPrimitive()) { - case glslang::ElgTriangles: artifacts.tessGenMode = GL_TRIANGLES; break; - case glslang::ElgQuads: artifacts.tessGenMode = GL_QUADS; break; - case glslang::ElgIsolines: artifacts.tessGenMode = GL_ISOLINES; break; - default: break; + case glslang::ElgTriangles: + artifacts.tessGenMode = GL_TRIANGLES; + break; + case glslang::ElgQuads: + artifacts.tessGenMode = GL_QUADS; + break; + case glslang::ElgIsolines: + artifacts.tessGenMode = GL_ISOLINES; + break; + default: + break; } // GLSL 4.60 4.4.2.3: equal_spacing and ccw are the defaults, which is what an unset // qualifier means here. switch (tes->getVertexSpacing()) { - case glslang::EvsFractionalEven: artifacts.tessGenSpacing = GL_FRACTIONAL_EVEN; break; - case glslang::EvsFractionalOdd: artifacts.tessGenSpacing = GL_FRACTIONAL_ODD; break; - default: artifacts.tessGenSpacing = GL_EQUAL; break; + case glslang::EvsFractionalEven: + artifacts.tessGenSpacing = GL_FRACTIONAL_EVEN; + break; + case glslang::EvsFractionalOdd: + artifacts.tessGenSpacing = GL_FRACTIONAL_ODD; + break; + default: + artifacts.tessGenSpacing = GL_EQUAL; + break; } switch (tes->getVertexOrder()) { - case glslang::EvoCw: artifacts.tessGenVertexOrder = GL_CW; break; - default: artifacts.tessGenVertexOrder = GL_CCW; break; + case glslang::EvoCw: + artifacts.tessGenVertexOrder = GL_CW; + break; + default: + artifacts.tessGenVertexOrder = GL_CCW; + break; } artifacts.tessGenPointMode = tes->getPointMode(); } @@ -978,10 +1031,10 @@ namespace MobileGL::MG_State::GLState { // Same invariant as the compute local-size gate above: a rejected link leaves no // TProgram behind for a query surface to find. artifacts.program.reset(); - artifacts.infoLog = std::format( - "Tessellation control shader declares an output patch of {} vertices, more than the {} " - "GL_MAX_PATCH_VERTICES allows.", - artifacts.tcsOutputVertices, env.params.MaxPatchVertices); + artifacts.infoLog = + std::format("Tessellation control shader declares an output patch of {} vertices, more than the {} " + "GL_MAX_PATCH_VERTICES allows.", + artifacts.tcsOutputVertices, env.params.MaxPatchVertices); DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog)); return; } @@ -1039,8 +1092,8 @@ namespace MobileGL::MG_State::GLState { } if (!ResolveTransformFeedbackVaryings()) { artifacts.linkStatus = false; - DeferLog(std::format("ProgramObject {}: transform feedback varying resolution failed: {}", - in.externalIndex, artifacts.infoLog)); + DeferLog(std::format("ProgramObject {}: transform feedback varying resolution failed: {}", in.externalIndex, + artifacts.infoLog)); return; } @@ -1139,9 +1192,9 @@ namespace MobileGL::MG_State::GLState { // did not populate. Refuse to key rather than key on nothing. return {}; } - keyInputs.stages.push_back(SpirvTranslationKeyInputs::Stage{ - .type = MG_Util::ConvertShaderStageToGLEnum(shader.stage), - .preprocessedSource = StringView(compiled.preprocessedSource)}); + keyInputs.stages.push_back( + SpirvTranslationKeyInputs::Stage{.type = MG_Util::ConvertShaderStageToGLEnum(shader.stage), + .preprocessedSource = StringView(compiled.preprocessedSource)}); } if (keyInputs.stages.empty()) return {}; keyInputs.explicitVertexInLocations = &in.explicitAttribLocations; @@ -1175,10 +1228,10 @@ namespace MobileGL::MG_State::GLState { for (const auto& [name, location] : compiled.explicitUniformLocations) { const auto [it, inserted] = artifacts.linkedExplicitUniformLocations.emplace(name, location); if (!inserted && it->second != location) { - artifacts.infoLog = std::format( - "Uniform '{}' is declared with conflicting explicit locations ({} and {}) " - "across stages.", - name, it->second, location); + artifacts.infoLog = + std::format("Uniform '{}' is declared with conflicting explicit locations ({} and {}) " + "across stages.", + name, it->second, location); DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog)); return; } @@ -1191,8 +1244,7 @@ namespace MobileGL::MG_State::GLState { // the payload by construction, and nothing reads it any more. Bool ProgramLinkTask::TryPublishFromTranslationCache() { if (!spirvHandoff.spirvCacheKey.Valid()) return false; - const ProgramTranslationResultPtr hit = - GetProgramTranslationCache().Find(spirvHandoff.spirvCacheKey); + const ProgramTranslationResultPtr hit = GetProgramTranslationCache().Find(spirvHandoff.spirvCacheKey); if (!hit) return false; artifacts = hit->link; @@ -1203,8 +1255,7 @@ namespace MobileGL::MG_State::GLState { // An ALIASING SharedPtr: it points at the payload's SpirvArtifacts while sharing // ownership of the whole payload, so phase B publishes them without a second copy and // without any chance of the entry being evicted from under it. - spirvHandoff.cachedSpirv = - SharedPtr(hit, &hit->spirv); + spirvHandoff.cachedSpirv = SharedPtr(hit, &hit->spirv); spirvHandoff.ready = true; MGLOG_D("ProgramObject %u: L1 cache hit - the whole front end was reused; no parse, no " "link, no SPIR-V generation", @@ -1223,8 +1274,7 @@ namespace MobileGL::MG_State::GLState { (input.stage == ShaderStage::Compute ? hasCompute : hasNonCompute) = true; } if (hasCompute && hasNonCompute) { - artifacts.infoLog = - "A compute shader cannot be linked with shaders of any other stage."; + artifacts.infoLog = "A compute shader cannot be linked with shaders of any other stage."; DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog)); return false; } @@ -1241,8 +1291,7 @@ namespace MobileGL::MG_State::GLState { for (const LinkShaderInput& input : in.shaders) { hasVertex = hasVertex || input.stage == ShaderStage::Vertex; hasLaterVertexProcessing = hasLaterVertexProcessing || input.stage == ShaderStage::TessControl || - input.stage == ShaderStage::TessEval || - input.stage == ShaderStage::Geometry; + input.stage == ShaderStage::TessEval || input.stage == ShaderStage::Geometry; } if (hasLaterVertexProcessing && !hasVertex) { artifacts.infoLog = "A program that is not separable and has a tessellation or geometry shader " @@ -1306,8 +1355,8 @@ namespace MobileGL::MG_State::GLState { Bool ProgramLinkTask::DoReflection(const MG_Util::ShaderTranspiler::CompileEnv& env) { if (!artifacts.program) { - DeferLog(std::format("ProgramObject {}: DoReflection called but the linked program is null", - in.externalIndex)); + DeferLog( + std::format("ProgramObject {}: DoReflection called but the linked program is null", in.externalIndex)); artifacts.linkStatus = false; artifacts.infoLog = "DoReflection failed: no program."; return false; @@ -1340,13 +1389,12 @@ namespace MobileGL::MG_State::GLState { EShReflectionIntermediateIO | EShReflectionUnwrapIOBlocks)) { artifacts.linkStatus = false; artifacts.infoLog = "Build reflection failed."; - DeferLog(std::format("ProgramObject {}: DoReflection - buildReflection() returned false", - in.externalIndex)); + DeferLog( + std::format("ProgramObject {}: DoReflection - buildReflection() returned false", in.externalIndex)); return false; } - if (String atomicCounterError = ValidateAtomicCounterLayout(*artifacts.program); - !atomicCounterError.empty()) { + if (String atomicCounterError = ValidateAtomicCounterLayout(*artifacts.program); !atomicCounterError.empty()) { artifacts.infoLog = Move(atomicCounterError); DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog)); ProgramObject::ResetLinkArtifacts(artifacts); @@ -1465,8 +1513,7 @@ namespace MobileGL::MG_State::GLState { // glGetUniformLocation, and nothing may write it through glUniform* either. Filtering // it here does both, and costs it no storage: BuildGlobalUboRouting takes its offset // from the SPIR-V metadata by name, not from the GL location space. - if (isGlobalUboMember(uniform) && - uniform.name == MG_Util::ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME) { + if (isGlobalUboMember(uniform) && uniform.name == MG_Util::ShaderTranspiler::NUM_SAMPLES_UNIFORM_NAME) { artifacts.usesReservedNumSamples = true; MGLOG_D("ProgramObject %u: Reflection - reserved gl_NumSamples stand-in '%s' hidden from the GL " "uniform surface", @@ -1509,8 +1556,7 @@ namespace MobileGL::MG_State::GLState { auto it = artifacts.linkedExplicitUniformLocations.find(reflectedName); if (it == artifacts.linkedExplicitUniformLocations.end() && reflectedName.length() > 3 && reflectedName.compare(reflectedName.length() - 3, 3, "[0]") == 0) { - it = artifacts.linkedExplicitUniformLocations.find( - reflectedName.substr(0, reflectedName.length() - 3)); + it = artifacts.linkedExplicitUniformLocations.find(reflectedName.substr(0, reflectedName.length() - 3)); } return it != artifacts.linkedExplicitUniformLocations.end() ? &it->second : nullptr; }; @@ -1522,8 +1568,7 @@ namespace MobileGL::MG_State::GLState { if (const Int* explicitLocation = findExplicitLocation(uniform.name)) { effectiveLocation[i] = static_cast(*explicitLocation); locationIsSourceExplicit[i] = true; - } else if (!artifacts.linkedExplicitUniformLocations.empty() && - uniform.name.find('.') != String::npos) { + } else if (!artifacts.linkedExplicitUniformLocations.empty() && uniform.name.find('.') != String::npos) { // A struct uniform's explicit location spreads consecutively over its // flattened members ("s.a", "s[1].b", ...) in reflection order. const SizeT cut = uniform.name.find_first_of(".["); @@ -1579,8 +1624,8 @@ namespace MobileGL::MG_State::GLState { const Uint location = static_cast(*explicitLocation); const Int locationSpan = GetUniformLocationSpan(uniform); if (location + static_cast(locationSpan) > kMaxUniformLocations) { - artifacts.infoLog = std::format("Uniform '{}' explicit location {} is out of range.", uniform.name, - location); + artifacts.infoLog = + std::format("Uniform '{}' explicit location {} is out of range.", uniform.name, location); ProgramObject::ResetLinkArtifacts(artifacts); return false; } @@ -1614,8 +1659,8 @@ namespace MobileGL::MG_State::GLState { } artifacts.uniformNameMaxLength = std::max(artifacts.uniformNameMaxLength, (Int)uniform.name.length()); artifacts.uniformLocations[uniform.name] = location; - MGLOG_D("ProgramObject %u: Reflection - uniform[%d] name='%s' effectiveLocation=%d", in.externalIndex, - i, uniform.name.c_str(), location); + MGLOG_D("ProgramObject %u: Reflection - uniform[%d] name='%s' effectiveLocation=%d", in.externalIndex, i, + uniform.name.c_str(), location); } MGLOG_D("ProgramObject %u: Reflection - computed maxUniformLocation=%u uniformNameMaxLength=%d", @@ -1792,8 +1837,7 @@ namespace MobileGL::MG_State::GLState { ProgramObject::ResetLinkArtifacts(artifacts); return false; } - artifacts.uniformIndexInTProgram.resize(base + locationSpan, - glslang::TQualifier::layoutLocationEnd); + artifacts.uniformIndexInTProgram.resize(base + locationSpan, glslang::TQualifier::layoutLocationEnd); artifacts.uniformSamplerOrImageUnitIndex.resize(base + locationSpan, -1); artifacts.maxUniformLocation = static_cast(base + locationSpan - 1); for (Int element = 0; element < locationSpan; ++element) { @@ -1824,15 +1868,16 @@ namespace MobileGL::MG_State::GLState { auto explicitBinding = artifacts.explicitOpaqueUniformBindings.find(uniform.name); if (explicitBinding == artifacts.explicitOpaqueUniformBindings.end() && uniform.name.length() > 3 && uniform.name.compare(uniform.name.length() - 3, 3, "[0]") == 0) { - explicitBinding = artifacts.explicitOpaqueUniformBindings.find( - uniform.name.substr(0, uniform.name.length() - 3)); + explicitBinding = + artifacts.explicitOpaqueUniformBindings.find(uniform.name.substr(0, uniform.name.length() - 3)); } const int initialUnit = explicitBinding != artifacts.explicitOpaqueUniformBindings.end() ? static_cast(explicitBinding->second) : 0; const Int locationSpan = GetUniformLocationSpan(uniform); - for (Int element = 0; element < locationSpan && - location + element < artifacts.uniformSamplerOrImageUnitIndex.size(); ++element) { + for (Int element = 0; + element < locationSpan && location + element < artifacts.uniformSamplerOrImageUnitIndex.size(); + ++element) { artifacts.uniformSamplerOrImageUnitIndex[location + element] = initialUnit + (explicitBinding != artifacts.explicitOpaqueUniformBindings.end() ? element : 0); } @@ -1846,9 +1891,8 @@ namespace MobileGL::MG_State::GLState { // fragment/geometry/tessellation program reflects its own stage inputs here, and those are // varyings - registering them as vertex attributes would hand glGetActiveAttrib and the // attribute location table interstage varyings. - Int inCount = artifacts.program->getIntermediate(EShLangVertex) != nullptr - ? artifacts.program->getNumPipeInputs() - : 0; + Int inCount = + artifacts.program->getIntermediate(EShLangVertex) != nullptr ? artifacts.program->getNumPipeInputs() : 0; MGLOG_D("ProgramObject %u: Reflection - pipe input count (attributes) = %d", in.externalIndex, inCount); Int maxLoc = -1; @@ -1901,14 +1945,10 @@ namespace MobileGL::MG_State::GLState { artifacts.attribs[expandedLocation] = inVar.name; artifacts.attribTypes[expandedLocation] = locationType; - MGLOG_D( - "ProgramObject %u: Reflection - got attrib '%s' at expanded location %d (baseLocation=%d glType=%u expandedType=%u)", - in.externalIndex, - inVar.name.c_str(), - expandedLocation, - location, - inVar.glDefineType, - static_cast(locationType)); + MGLOG_D("ProgramObject %u: Reflection - got attrib '%s' at expanded location %d (baseLocation=%d " + "glType=%u expandedType=%u)", + in.externalIndex, inVar.name.c_str(), expandedLocation, location, inVar.glDefineType, + static_cast(locationType)); } } } @@ -1973,13 +2013,11 @@ namespace MobileGL::MG_State::GLState { // Stated against the same ceiling glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS) // advertises; an instance array whose LAST element passes it is a link error even // though its base fits, same rule as the explicit-location check above. - if (artifacts.uniformBlockBinding[i] >= - static_cast(MG_State::GLState::BufferBindingPointCount)) { - artifacts.infoLog = - std::format("Uniform block '{}' declares binding {}, which is not less than " - "GL_MAX_UNIFORM_BUFFER_BINDINGS ({}).", - ubo.name, artifacts.uniformBlockBinding[i], - static_cast(MG_State::GLState::BufferBindingPointCount)); + if (artifacts.uniformBlockBinding[i] >= static_cast(MG_State::GLState::BufferBindingPointCount)) { + artifacts.infoLog = std::format("Uniform block '{}' declares binding {}, which is not less than " + "GL_MAX_UNIFORM_BUFFER_BINDINGS ({}).", + ubo.name, artifacts.uniformBlockBinding[i], + static_cast(MG_State::GLState::BufferBindingPointCount)); ProgramObject::ResetLinkArtifacts(artifacts); return false; } @@ -2054,8 +2092,7 @@ namespace MobileGL::MG_State::GLState { // and the FIRST (element 0, the base binding) is the one that survives the dedupe - // which is the element SPIRV-Reflect's single descriptor reports. std::stable_sort(ofThisStage.begin(), ofThisStage.end(), - [](const ProgramObject::BlockReflection* lhs, - const ProgramObject::BlockReflection* rhs) { + [](const ProgramObject::BlockReflection* lhs, const ProgramObject::BlockReflection* rhs) { const String lhsName = StripArrayElementSuffix(lhs->name); const String rhsName = StripArrayElementSuffix(rhs->name); if (lhsName != rhsName) return lhsName < rhsName; @@ -2071,8 +2108,8 @@ namespace MobileGL::MG_State::GLState { artifacts.storageBlocks.push_back(Move(record)); } } - MGLOG_D("ProgramObject %u: Reflection - %zu storage block(s) in the backend index space", - in.externalIndex, artifacts.storageBlocks.size()); + MGLOG_D("ProgramObject %u: Reflection - %zu storage block(s) in the backend index space", in.externalIndex, + artifacts.storageBlocks.size()); } // The last thing DoReflection does, and the thing that lets everything after it stop @@ -2107,8 +2144,8 @@ namespace MobileGL::MG_State::GLState { Vector externalSamplers; for (const LinkShaderInput& shader : in.shaders) { if (shader.compiled == nullptr) continue; - for (String& name : - MG_Util::ShaderTranspiler::ExternalSamplerUniformNames(CompiledArtifacts(shader.compiled).preprocessedSource)) + for (String& name : MG_Util::ShaderTranspiler::ExternalSamplerUniformNames( + CompiledArtifacts(shader.compiled).preprocessedSource)) externalSamplers.push_back(Move(name)); } const auto isExternalSampler = [&externalSamplers](const String& reflected) { @@ -2300,8 +2337,8 @@ namespace MobileGL::MG_State::GLState { } for (Int colorNumber = location; colorNumber < location + span; ++colorNumber) { - const Int64 slot = (static_cast(colorIndex) << 32) | - static_cast(static_cast(colorNumber)); + const Int64 slot = + (static_cast(colorIndex) << 32) | static_cast(static_cast(colorNumber)); auto [owner, inserted] = colorSlotOwners.emplace(slot, outputName); if (!inserted) { artifacts.infoLog = @@ -2353,8 +2390,7 @@ namespace MobileGL::MG_State::GLState { } } if (captureIntermediate == nullptr) { - artifacts.infoLog = - "Transform feedback varyings requested but the program has no vertex-processing stage."; + artifacts.infoLog = "Transform feedback varyings requested but the program has no vertex-processing stage."; return false; } const glslang::TIntermAggregate* linkerObjects = captureIntermediate->findLinkerObjects(); @@ -2376,8 +2412,8 @@ namespace MobileGL::MG_State::GLState { artifacts.xfbNeedsScatteredCapture = true; continue; } - if (interleaved && name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 && - name[17] >= '1' && name[17] <= '4') { + if (interleaved && name.size() == 18 && name.compare(0, 17, "gl_SkipComponents") == 0 && name[17] >= '1' && + name[17] <= '4') { interleavedOffset += static_cast(name[17] - '0') * 4; artifacts.xfbNeedsScatteredCapture = true; continue; @@ -2467,8 +2503,7 @@ namespace MobileGL::MG_State::GLState { // The spec spelling is the block name; the instance name is accepted // as a fallback so a request written the (common, non-conformant) // instance-qualified way resolves instead of failing the whole link. - if (symbolType.getTypeName() != blockName.c_str() && - symbol->getName() != blockName.c_str()) { + if (symbolType.getTypeName() != blockName.c_str() && symbol->getName() != blockName.c_str()) { continue; } const glslang::TTypeList* members = symbolType.getStruct(); @@ -2506,8 +2541,7 @@ namespace MobileGL::MG_State::GLState { } } if (!resolved) { - artifacts.infoLog = - "Transform feedback varying '" + name + "' is not an output of the vertex stage."; + artifacts.infoLog = "Transform feedback varying '" + name + "' is not an output of the vertex stage."; return false; } diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h index 58b3eeda..c0607109 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramLinkTask.h @@ -58,7 +58,7 @@ namespace MobileGL::MG_State::GLState { public: // ---- inputs, snapshotted on the GL thread in ProgramObject::Link()'s prologue ---- struct Inputs { - Uint externalIndex = 0; // logs only + Uint externalIndex = 0; // logs only Vector shaders; // already stage-sorted SharedPtr env; // Startup configuration copied with the task, never read from worker code. @@ -69,10 +69,10 @@ namespace MobileGL::MG_State::GLState { // referenced, which is precisely what makes glBindAttribLocation and friends // legal to call over a pending link without cancelling it: the pending link keeps // linking the inputs it was given. - UnorderedMap explicitAttribLocations; // glBindAttribLocation + UnorderedMap explicitAttribLocations; // glBindAttribLocation UnorderedMap explicitFragDataLocation; // glBindFragDataLocation - UnorderedMap explicitFragDataIndex; // glBindFragDataLocationIndexed - Vector requestedXfbVaryings; // glTransformFeedbackVaryings + UnorderedMap explicitFragDataIndex; // glBindFragDataLocationIndexed + Vector requestedXfbVaryings; // glTransformFeedbackVaryings GLenum requestedXfbBufferMode = GL_INTERLEAVED_ATTRIBS; Int maxFragmentOutputColorNumber = 8; // GL_MAX_DRAW_BUFFERS, stamped in by the entry point } in; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index f41c76cd..69654354 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -23,7 +23,6 @@ layout(location = 0) out vec4 FragColor; void main() {} )"; - namespace MobileGL::MG_State::GLState { static std::atomic s_nextProgramLifetimeId = 1; @@ -138,9 +137,13 @@ namespace MobileGL::MG_State::GLState { MG_Util::Async::ApplyDeferredDiagnostics(*pending); } - Bool ProgramObject::IsPendingLinkTerminal() const { return m_pendingLink->IsTerminal(); } + Bool ProgramObject::IsPendingLinkTerminal() const { + return m_pendingLink->IsTerminal(); + } - Bool ProgramObject::IsPendingSpirvTerminal() const { return m_pendingSpirv->IsTerminal(); } + Bool ProgramObject::IsPendingSpirvTerminal() const { + return m_pendingSpirv->IsTerminal(); + } void ProgramObject::JoinPendingSpirv() const { MOBILEGL_ASSERT(!MG_Util::Async::ShaderCompilePool::IsPoolThread(), @@ -191,11 +194,11 @@ namespace MobileGL::MG_State::GLState { const SizeT dataOffset = m_pendingUniformBytes.size(); m_pendingUniformBytes.resize(dataOffset + byteSize); std::memcpy(m_pendingUniformBytes.data() + dataOffset, source, byteSize); - m_pendingUniformWrites.push_back(PendingUniformWrite{.location = location, - .byteOffsetInUniform = - static_cast(byteOffsetInUniform), - .byteSize = static_cast(byteSize), - .dataOffset = static_cast(dataOffset)}); + m_pendingUniformWrites.push_back( + PendingUniformWrite{.location = location, + .byteOffsetInUniform = static_cast(byteOffsetInUniform), + .byteSize = static_cast(byteSize), + .dataOffset = static_cast(dataOffset)}); return true; } @@ -246,8 +249,7 @@ namespace MobileGL::MG_State::GLState { // has an 8-byte one, which the store width below picks up. glslang folded the value // into floatValues, a vector, in both cases. Leaving it out meant the // initializer was silently dropped and the uniform came up zero. - const Bool isFloat = init.basicType == glslang::EbtFloat || - init.basicType == glslang::EbtFloat16 || + const Bool isFloat = init.basicType == glslang::EbtFloat || init.basicType == glslang::EbtFloat16 || init.basicType == glslang::EbtDouble; const Bool isInt = init.basicType == glslang::EbtInt || init.basicType == glslang::EbtUint || init.basicType == glslang::EbtBool; @@ -276,8 +278,7 @@ namespace MobileGL::MG_State::GLState { const SizeT slotSpan = UniformStorageSpanInBytes(GetUniformTypeFacts(static_cast(location)), GetUniformSizesInBytes(static_cast(location)), nativeFloat64); - const SizeT columnStride = - columns > 0 ? slotSpan / static_cast(columns) : slotSpan; + const SizeT columnStride = columns > 0 ? slotSpan / static_cast(columns) : slotSpan; const Int componentsPerColumn = columns > 0 ? rows : componentsPerElement; const Int columnCount = columns > 0 ? columns : 1; @@ -345,8 +346,8 @@ namespace MobileGL::MG_State::GLState { // Same verdict the live write path reaches for a uniform without backing // storage: log and drop, rather than fault. MGLOG_E_ONCE("ProgramObject %u: buffered uniform write at location %u has no backing storage " - "(offset=%u size=%u uboSize=%zu); dropping write", - m_externalIndex, write.location, offset, write.byteSize, uboSize); + "(offset=%u size=%u uboSize=%zu); dropping write", + m_externalIndex, write.location, offset, write.byteSize, uboSize); continue; } Uint8* const destination = scratch + offset + write.byteOffsetInUniform; @@ -458,9 +459,6 @@ namespace MobileGL::MG_State::GLState { artifacts.linkStatus = false; } - - - bool ProgramObject::ShaderIsAttached(const SharedPtr& shader) { MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get()); auto it = std::find_if(m_shaders.begin(), m_shaders.end(), @@ -551,7 +549,7 @@ namespace MobileGL::MG_State::GLState { auto status = defaultFS->GetCompileStatus(); if (!status) { MGLOG_E_ONCE("ProgramObject %u: Failed to compile default fragment shader. InfoLog:\n%s", m_externalIndex, - defaultFS->GetInfoLog().c_str()); + defaultFS->GetInfoLog().c_str()); return; } m_shaders.push_back(defaultFS); @@ -636,9 +634,8 @@ namespace MobileGL::MG_State::GLState { // which is not a shape ARB_gl_spirv defines, and honouring what the application explicitly // asked for is the safer of the two readings. if (task->in.requestedXfbVaryings.empty()) { - for (const ShaderStage captureStage: - {ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl, - ShaderStage::Vertex}) { + for (const ShaderStage captureStage : + {ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::TessControl, ShaderStage::Vertex}) { Bool stagePresent = false; for (const auto& shader : m_shaders) { if (!shader || shader->GetShaderStage() != captureStage) continue; @@ -713,7 +710,6 @@ namespace MobileGL::MG_State::GLState { task->SubmitAfter(deps); } - void ProgramObject::MarkAsDeleted() { MGLOG_D("ProgramObject %u: MarkAsDeleted called (was %s)", m_externalIndex, m_deleteStatus ? "deleted" : "not deleted"); @@ -740,7 +736,6 @@ namespace MobileGL::MG_State::GLState { return m_shaders; } - void ProgramObject::SetExplicitVertexInLocation(Uint index, const char* name) { MGLOG_D("ProgramObject %u: SetExplicitVertexInLocation called name='%s' index=%u", m_externalIndex, name, index); @@ -763,7 +758,6 @@ namespace MobileGL::MG_State::GLState { name, colorIndex); } - Int ProgramObject::GetFragmentDataLocation(const char* name) { // Answered from the OWNED pipe-output snapshot, not from Artifacts().program. The live // TProgram is null on a translation-cache L1 hit - that is the entire point of the memo diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 29faf59b..c779019c 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -42,17 +42,17 @@ namespace MobileGL::MG_State::GLState { // (P0.5). Re-exported here so every existing spelling (ProgramObject::LinkArtifacts, // ProgramObject::TypeFacts, ...) compiles unchanged. Fully qualified on the right-hand // side on purpose: an unqualified `TypeFacts` would name the alias being declared. - using TypeFacts = MobileGL::MG_State::GLState::TypeFacts; - using ResourceReflection = MobileGL::MG_State::GLState::ResourceReflection; - using UniformReflection = ResourceReflection; - using BlockReflection = ResourceReflection; - using PipeInputReflection = ResourceReflection; + using TypeFacts = MobileGL::MG_State::GLState::TypeFacts; + using ResourceReflection = MobileGL::MG_State::GLState::ResourceReflection; + using UniformReflection = ResourceReflection; + using BlockReflection = ResourceReflection; + using PipeInputReflection = ResourceReflection; using PipeOutputReflection = ResourceReflection; // P7 OQ-8: DirectVulkan's storage-block index space, carried in LinkArtifacts. using StorageBlockReflection = MobileGL::MG_State::GLState::StorageBlockReflection; - using XfbVarying = MobileGL::MG_State::GLState::XfbVarying; - using LinkArtifacts = MobileGL::MG_State::GLState::LinkArtifacts; - using SpirvArtifacts = MobileGL::MG_State::GLState::SpirvArtifacts; + using XfbVarying = MobileGL::MG_State::GLState::XfbVarying; + using LinkArtifacts = MobileGL::MG_State::GLState::LinkArtifacts; + using SpirvArtifacts = MobileGL::MG_State::GLState::SpirvArtifacts; ProgramObject(Uint externalIndex) : m_externalIndex(externalIndex), m_lifetimeId(AllocateLifetimeId()) {} // Cancel-not-join, exactly like ~ShaderObject: the link job owns its inputs, so an @@ -85,9 +85,7 @@ namespace MobileGL::MG_State::GLState { void SetExplicitFragmentOutLocation(Uint index, const char* name); // Dual-source blend color index (glBindFragDataLocationIndexed). Takes effect on next link. void SetExplicitFragmentOutIndex(Uint colorIndex, const char* name); - void SetMaxFragmentOutputColorNumber(Int maxDrawBuffers) { - m_maxFragmentOutputColorNumber = maxDrawBuffers; - } + void SetMaxFragmentOutputColorNumber(Int maxDrawBuffers) { m_maxFragmentOutputColorNumber = maxDrawBuffers; } Int GetFragmentDataLocation(const char* name); // Bound color index for an active fragment output (0 by default), or -1 if name is not one. Int GetFragmentDataIndex(const char* name); @@ -119,21 +117,19 @@ namespace MobileGL::MG_State::GLState { // draws GL requires to execute, against an executable that does not carry the stage at // all - and stays wrong until the application happens to relink. Bool HasLinkedShaderStage(ShaderStage stage) const { - return std::any_of(m_linkedShaderSnapshot.begin(), m_linkedShaderSnapshot.end(), - [stage](const LinkedShaderRef& ref) { - return ref.shader && ref.shader->GetShaderStage() == stage; - }); + return std::any_of( + m_linkedShaderSnapshot.begin(), m_linkedShaderSnapshot.end(), + [stage](const LinkedShaderRef& ref) { return ref.shader && ref.shader->GetShaderStage() == stage; }); } // HasLinkedShaderStage without the default fragment shader Link() adds to a vertex-only // program on a backend that cannot run one (AddDefaultFragmentShaderIfMissing; its shader // object has no name). That stage is MobileGL's, not the application's, so it is not one // the GL rules about "the stages present when the program was linked" may count. Bool HasApplicationLinkedShaderStage(ShaderStage stage) const { - return std::any_of(m_linkedShaderSnapshot.begin(), m_linkedShaderSnapshot.end(), - [stage](const LinkedShaderRef& ref) { - return ref.shader && ref.shader->GetShaderStage() == stage && - ref.shader->GetExternalIndex() != 0; - }); + return std::any_of( + m_linkedShaderSnapshot.begin(), m_linkedShaderSnapshot.end(), [stage](const LinkedShaderRef& ref) { + return ref.shader && ref.shader->GetShaderStage() == stage && ref.shader->GetExternalIndex() != 0; + }); } // The stage of each module of GetGeneratedSpirv(), at the SAME index and with the same // size: phase B emits exactly one module per entry of the snapshot above, in that order @@ -238,9 +234,7 @@ namespace MobileGL::MG_State::GLState { // MGL_GLOBAL_UBO, and that block itself. DoReflection builds filtered GL-facing // index spaces; every public "index"-taking getter translates through them, so // GL and backend consumers keep seeing exactly the pre-P0a surface. - Int TProgramUniformIndex(Uint glIndex) const { - return Artifacts().glUniformIndexToTProgram[glIndex]; - } + Int TProgramUniformIndex(Uint glIndex) const { return Artifacts().glUniformIndexToTProgram[glIndex]; } Int GlUniformIndexFromTProgram(Int tIndex) const { if (tIndex < 0 || tIndex >= static_cast(Artifacts().tProgramUniformIndexToGl.size())) return -1; return Artifacts().tProgramUniformIndexToGl[tIndex]; @@ -250,11 +244,12 @@ namespace MobileGL::MG_State::GLState { // properties glslang exposes but no typed getter here does. Int TProgramBlockIndex(Uint blockIndex) const { return blockIndex < Artifacts().glBlockIndexToTProgram.size() - ? Artifacts().glBlockIndexToTProgram[blockIndex] - : -1; + ? Artifacts().glBlockIndexToTProgram[blockIndex] + : -1; } Int GlBlockIndexFromTProgram(Int tBlockIndex) const { - if (tBlockIndex < 0 || tBlockIndex >= static_cast(Artifacts().tProgramBlockIndexToGl.size())) return -1; + if (tBlockIndex < 0 || tBlockIndex >= static_cast(Artifacts().tProgramBlockIndexToGl.size())) + return -1; return Artifacts().tProgramBlockIndexToGl[tBlockIndex]; } @@ -265,16 +260,14 @@ namespace MobileGL::MG_State::GLState { // glUniformBlockBinding entry point speaks THIS space and translates into the block // space before touching any of the block-keyed tables; the backends keep speaking the // block space directly. See LinkArtifacts::glUniformBlockIndexToBlock. - Int GetGlUniformBlockCount() const { - return static_cast(Artifacts().glUniformBlockIndexToBlock.size()); - } + Int GetGlUniformBlockCount() const { return static_cast(Artifacts().glUniformBlockIndexToBlock.size()); } Bool IsActiveGlUniformBlock(Uint glUniformBlockIndex) const { return glUniformBlockIndex < Artifacts().glUniformBlockIndexToBlock.size(); } Int BlockIndexFromGlUniformBlock(Uint glUniformBlockIndex) const { return glUniformBlockIndex < Artifacts().glUniformBlockIndexToBlock.size() - ? Artifacts().glUniformBlockIndexToBlock[glUniformBlockIndex] - : -1; + ? Artifacts().glUniformBlockIndexToBlock[glUniformBlockIndex] + : -1; } Int GlUniformBlockIndexFromBlock(Int blockIndex) const { if (blockIndex < 0 || blockIndex >= static_cast(Artifacts().blockIndexToGlUniformBlock.size())) { @@ -333,9 +326,7 @@ namespace MobileGL::MG_State::GLState { // a block array member it reports 1, so take the count from the TType, which is authoritative // for both. GL 3.3 core uniforms are always sized. Takes a TProgram uniform index (the space // the artifacts' uniformIndexInTProgram stores). - GLint GetUniformArraySizeByTIndex(Int tIndex) const { - return GetUniformArraySizeByTIndex(Artifacts(), tIndex); - } + GLint GetUniformArraySizeByTIndex(Int tIndex) const { return GetUniformArraySizeByTIndex(Artifacts(), tIndex); } GLint GetActiveUniformArraySize(Uint index) const { return GetUniformArraySizeByTIndex(TProgramUniformIndex(index)); @@ -505,9 +496,7 @@ namespace MobileGL::MG_State::GLState { return UniformAt(Artifacts().uniformIndexInTProgram[location]).name; } - const String& GetActiveUniformName(Uint index) const { - return UniformAt(TProgramUniformIndex(index)).name; - } + const String& GetActiveUniformName(Uint index) const { return UniformAt(TProgramUniformIndex(index)).name; } // Sentinel for a uniform location without global-UBO backing storage (should not // survive linking: GenerateBinary falls back to tail-allocated scratch storage). static constexpr Uint kInvalidUniformOffset = MobileGL::MG_State::GLState::kInvalidUniformOffset; @@ -527,8 +516,7 @@ namespace MobileGL::MG_State::GLState { // same value the routing pass itself uses for a uniform the optimizer deleted. Uint GetUniformOffset(Uint location) const { const SpirvArtifacts& spirv = Spirv(); - return location < spirv.uniformOffsets.size() ? spirv.uniformOffsets[location] - : kInvalidUniformOffset; + return location < spirv.uniformOffsets.size() ? spirv.uniformOffsets[location] : kInvalidUniformOffset; } Uint GetUniformSizesInBytes(Uint location) const { return MG_Util::GetGLTypeSize(GetUniformType(location)); } // std140 column stride of a matrix uniform in the global UBO: every column is padded out @@ -663,8 +651,7 @@ namespace MobileGL::MG_State::GLState { Bool IsUniformWrittenAtLocation(Uint location) const { const auto& bits = Artifacts().writtenUniformLocationBits; const SizeT locationWord = location / 64u; - return locationWord < bits.size() && - (bits[locationWord] & (Uint64{1} << (location % 64u))) != 0; + return locationWord < bits.size() && (bits[locationWord] & (Uint64{1} << (location % 64u))) != 0; } // GL active-uniform indices owning at least one written location. Empty for every @@ -708,9 +695,7 @@ namespace MobileGL::MG_State::GLState { } return mask; } - Int GetActiveFragmentOutputCount() const { - return static_cast(Artifacts().pipeOutputReflection.size()); - } + Int GetActiveFragmentOutputCount() const { return static_cast(Artifacts().pipeOutputReflection.size()); } const String& GetActiveFragmentOutputName(Uint index) const { MOBILEGL_ASSERT(index < static_cast(Artifacts().pipeOutputReflection.size()), "ProgramObject::GetActiveFragmentOutputName: index=%u out of range", index); @@ -718,8 +703,7 @@ namespace MobileGL::MG_State::GLState { } Int GetFragmentOutputLocation(Uint index) const { MOBILEGL_ASSERT(index < static_cast(Artifacts().pipeOutputReflection.size()), - "ProgramObject::GetFragmentOutputLocation: index=%u out of range", - index); + "ProgramObject::GetFragmentOutputLocation: index=%u out of range", index); return Artifacts().pipeOutputReflection[index].location; } GLint GetActiveFragmentOutputArraySize(Uint index) const { @@ -729,8 +713,7 @@ namespace MobileGL::MG_State::GLState { } GLenum GetFragmentOutputType(Uint index) const { MOBILEGL_ASSERT(index < static_cast(Artifacts().pipeOutputReflection.size()), - "ProgramObject::GetFragmentOutputType: index=%u out of range", - index); + "ProgramObject::GetFragmentOutputType: index=%u out of range", index); return Artifacts().pipeOutputReflection[index].glDefineType; } GLenum GetAttribType(Uint index) const { return Artifacts().attribTypes[index]; } @@ -855,7 +838,8 @@ namespace MobileGL::MG_State::GLState { } void SetBackendHashMemo(Uint flags, Uint64 hash) const { if (m_backendHashMemoVersion != m_backendStateVersion) { - for (auto& slot : m_backendHashMemoSlots) slot.valid = false; + for (auto& slot : m_backendHashMemoSlots) + slot.valid = false; m_backendHashMemoVersion = m_backendStateVersion; m_backendHashMemoNextSlot = 0; } @@ -990,17 +974,13 @@ namespace MobileGL::MG_State::GLState { // SIGSEGV inside glslang::TProgram::getNumPipeInputs - KHR-GL30.api.coverage does exactly // this after a failed glGetAttribLocation, and reached it as soon as the CopyTexImage2D // throw ahead of it stopped killing the run first. - Int GetActiveAttributesCount() const { - return static_cast(Artifacts().pipeInputReflection.size()); - } + Int GetActiveAttributesCount() const { return static_cast(Artifacts().pipeInputReflection.size()); } // Size of the BLOCK index space - every block the relaxed parse produced except the // synthesized MGL_GLOBAL_UBO, which DoReflection filters out. NOT the answer to // glGetProgramiv(GL_ACTIVE_UNIFORM_BLOCKS): storage blocks and atomic counter blocks // live in here too, and GetGlUniformBlockCount() is the one that excludes them. Int GetActiveUniformBlocksCount() const { return static_cast(Artifacts().glBlockIndexToTProgram.size()); } - GLuint GetComputeLocalSize(Uint dim) const { - return dim < 3u ? Artifacts().computeLocalSize[dim] : 0u; - } + GLuint GetComputeLocalSize(Uint dim) const { return dim < 3u ? Artifacts().computeLocalSize[dim] : 0u; } Int GetActiveAttributesMaxLength() const { return Artifacts().attribInNameMaxLength; } Int GetActiveUniformBlocksMaxNameLength() const { return Artifacts().uniformBlockNameMaxLength; } // Answers in the BLOCK space, so it resolves storage and atomic counter blocks too - @@ -1081,7 +1061,8 @@ namespace MobileGL::MG_State::GLState { // uniformBlocksWithoutBinding. Either way an untouched program already reports what GL // says it should. void SetUniformBlockBinding(Uint index, Uint binding) { - if (index >= Artifacts().uniformBlockBinding.size() || Artifacts().uniformBlockBinding[index] == static_cast(binding)) { + if (index >= Artifacts().uniformBlockBinding.size() || + Artifacts().uniformBlockBinding[index] == static_cast(binding)) { return; } Artifacts().uniformBlockBinding[index] = static_cast(binding); @@ -1185,16 +1166,13 @@ namespace MobileGL::MG_State::GLState { // PUSH-ONLY, so a pull build's symbol set and this class's layout are byte-for-byte // what they were: an inline const-ref accessor adds nothing to a pull build that is // never compiled into one. It joins both phases, like every other Spirv() reader. - const SpirvArtifacts& GetSpirvReflection() const { - return Spirv(); - } + const SpirvArtifacts& GetSpirvReflection() const { return Spirv(); } static Bool IsValidUniformLocation(const LinkArtifacts& artifacts, Int location) { if (location < 0 || location > static_cast(artifacts.maxUniformLocation)) return false; if (static_cast(location) >= artifacts.uniformIndexInTProgram.size()) return false; const Int uniformIndexInProgram = artifacts.uniformIndexInTProgram[location]; - return uniformIndexInProgram != glslang::TQualifier::layoutLocationEnd && - uniformIndexInProgram >= 0 && + return uniformIndexInProgram != glslang::TQualifier::layoutLocationEnd && uniformIndexInProgram >= 0 && uniformIndexInProgram < static_cast(artifacts.tProgramUniformIndexToGl.size()); } diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h index d328c072..afdfbd84 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramPipelineObject.h @@ -105,10 +105,9 @@ namespace MobileGL { // "There is an active program for tessellation control, tessellation evaluation, // or geometry stages with corresponding executable shader, but there is no // active program with an executable vertex shader." - const Bool laterVertexProcessing = - executes(static_cast(ShaderStage::TessControl)) || - executes(static_cast(ShaderStage::TessEval)) || - executes(static_cast(ShaderStage::Geometry)); + const Bool laterVertexProcessing = executes(static_cast(ShaderStage::TessControl)) || + executes(static_cast(ShaderStage::TessEval)) || + executes(static_cast(ShaderStage::Geometry)); if (laterVertexProcessing && !executes(static_cast(ShaderStage::Vertex))) { return "A tessellation or geometry stage is active but no program supplies a vertex shader."; } diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp index 36878336..e1b6a7ea 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.cpp @@ -29,8 +29,7 @@ namespace MobileGL::MG_State::GLState { // First occurrence at WARN - the one a bug report needs - every later one back at // DEBUG, which shipped builds compile out. Int FirstTimeWarnLevel(std::atomic_flag& latch) { - return latch.test_and_set(std::memory_order_relaxed) ? MOBILEGL_LOG_LEVEL_DEBUG - : MOBILEGL_LOG_LEVEL_WARN; + return latch.test_and_set(std::memory_order_relaxed) ? MOBILEGL_LOG_LEVEL_DEBUG : MOBILEGL_LOG_LEVEL_WARN; } std::atomic_flag g_pointSizeDeclineReported; std::atomic_flag g_pointSizeOptimizerFailureReported; @@ -73,7 +72,8 @@ namespace MobileGL::MG_State::GLState { // EnsureSpirvJoined(), which is far worse than a program reported as not drawable. try { MG_Util::Async::ShaderCompilePool::Get().Post(self); - } catch (...) { + } + catch (...) { self->Cancel(); } }); @@ -204,8 +204,7 @@ namespace MobileGL::MG_State::GLState { payload->link.program.reset(); // belt and braces: never memoize a glslang arena payload->spirv = artifacts; const SizeT payloadBytes = ProgramTranslationResultBytes(*payload); - GetProgramTranslationCache().Insert(handoff.spirvCacheKey, - ProgramTranslationResultPtr(Move(payload)), + GetProgramTranslationCache().Insert(handoff.spirvCacheKey, ProgramTranslationResultPtr(Move(payload)), payloadBytes); } MGLOG_D("ProgramObject %u: Binary generation finished (generatedSpirv size=%zu)", externalIndex, @@ -215,8 +214,7 @@ namespace MobileGL::MG_State::GLState { void ProgramSpirvTask::GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, const Uint externalIndex, const Bool deferOutputValidationForDirectVulkan, const Bool enableSpirvValidation, const Bool nativeFloat64, - const Bool demoteTessellationPointSize, - const Bool demoteGeometryPointSize) { + const Bool demoteTessellationPointSize, const Bool demoteGeometryPointSize) { /* As we passed first stage compilation/linking, * we'll assume all the operations here should * pass. We may be able to employ some optimizations @@ -312,9 +310,8 @@ namespace MobileGL::MG_State::GLState { const Bool captureRequestsPointSize = handoff.captureRequestsPointSize; ShaderCompiler::PointSizeDemotionOutcome outcome; if (!ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - artifacts.generatedSpirv, handoff.shaderTypes, demoteTessellationPointSize, - demoteGeometryPointSize, captureRequestsPointSize, outcome, - !deferOutputValidationForDirectVulkan, enableSpirvValidation)) { + artifacts.generatedSpirv, handoff.shaderTypes, demoteTessellationPointSize, demoteGeometryPointSize, + captureRequestsPointSize, outcome, !deferOutputValidationForDirectVulkan, enableSpirvValidation)) { // Optimizer failure: modules untouched, so the capability is still declared // and the backends' existing refusals stay in charge - honest, just slower. DeferLog(std::format("ProgramObject {}: point-size demotion failed in the optimizer; the " @@ -438,8 +435,8 @@ namespace MobileGL::MG_State::GLState { externalIndex, name.c_str(), offset, arrayStride, baseLocation, baseLocation + static_cast(elementCount) - 1); } - MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - finished parsing module %zu metadata", - externalIndex, i); + MGLOG_D("ProgramObject %u: BuildGlobalUboRouting - finished parsing module %zu metadata", externalIndex, + i); } } @@ -453,16 +450,14 @@ namespace MobileGL::MG_State::GLState { for (Uint location = 0; location <= reflection.maxUniformLocation; ++location) { if (artifacts.uniformOffsets[location] != ProgramObject::kInvalidUniformOffset) continue; if (!ProgramObject::IsValidUniformLocation(reflection, static_cast(location))) continue; - const auto& uniform = - ProgramObject::UniformAtIn(reflection, reflection.uniformIndexInTProgram[location]); + const auto& uniform = ProgramObject::UniformAtIn(reflection, reflection.uniformIndexInTProgram[location]); if (uniform.type.isOpaque) continue; // Member of a named uniform block: not settable through glUniform*, so it needs // no global-UBO shadow storage. tProgramBlockIndexToGl[i] >= 0 means block i is // GL-visible, i.e. NOT the synthesized MGL_GLOBAL_UBO - which is exactly what the // strstr(GLOBAL_UBO_NAME) test this replaced was asking, without needing the // TProgram to spell the block name. - if (uniform.index >= 0 && - uniform.index < static_cast(reflection.tProgramBlockIndexToGl.size()) && + if (uniform.index >= 0 && uniform.index < static_cast(reflection.tProgramBlockIndexToGl.size()) && reflection.tProgramBlockIndexToGl[uniform.index] >= 0) { continue; } diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h index d04c9907..c14ea1e4 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramSpirvTask.h @@ -66,9 +66,8 @@ namespace MobileGL::MG_State::GLState { void RunBody() override; void GenerateSpirv(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex, - Bool deferOutputValidationForDirectVulkan, Bool enableSpirvValidation, - Bool nativeFloat64, Bool demoteTessellationPointSize, - Bool demoteGeometryPointSize); + Bool deferOutputValidationForDirectVulkan, Bool enableSpirvValidation, Bool nativeFloat64, + Bool demoteTessellationPointSize, Bool demoteGeometryPointSize); void BuildGlobalUboRouting(const ProgramLinkTask::SpirvHandoff& handoff, Uint externalIndex); // Worker-side MGLOG replacement, replayed by the join on the GL thread. Same reason as diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.cpp index 5749993f..e771f9e4 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.cpp @@ -32,13 +32,15 @@ namespace MobileGL::MG_State::GLState { SizeT StringsBytes(const Vector& values) { SizeT bytes = 0; - for (const String& value : values) bytes += value.size() + sizeof(String); + for (const String& value : values) + bytes += value.size() + sizeof(String); return bytes; } SizeT ResourcesBytes(const Vector& records) { SizeT bytes = records.size() * sizeof(ProgramObject::ResourceReflection); - for (const auto& record : records) bytes += record.name.size(); + for (const auto& record : records) + bytes += record.name.size(); return bytes; } } // namespace @@ -48,7 +50,8 @@ namespace MobileGL::MG_State::GLState { // and ignores per-entry fixed overhead. SizeT ProgramTranslationResultBytes(const ProgramTranslationResult& result) { SizeT bytes = 0; - for (const auto& module : result.spirv.generatedSpirv) bytes += module.size() * sizeof(unsigned); + for (const auto& module : result.spirv.generatedSpirv) + bytes += module.size() * sizeof(unsigned); bytes += result.spirv.uniformOffsets.size() * sizeof(Uint); bytes += result.spirv.globalUboScratch.size(); bytes += ResourcesBytes(result.link.uniformReflection); @@ -61,8 +64,7 @@ namespace MobileGL::MG_State::GLState { return bytes; } - MG_Util::ShaderTranspiler::BoundedTranslationCache& - GetProgramTranslationCache() { + MG_Util::ShaderTranspiler::BoundedTranslationCache& GetProgramTranslationCache() { // DELIBERATELY LEAKED - see the same note on the L2 cache in // MG_Util/ShaderTranspiler/TranslationCache.cpp. A function-local static OBJECT // registers its destructor at first use, and first use here is a ShaderCompilePool @@ -70,13 +72,16 @@ namespace MobileGL::MG_State::GLState { // earlier, and exit handlers run in reverse order - so the cache would be destroyed // while workers were still inserting into it. A function-local static POINTER is // trivially destructible and registers no exit handler at all. - static auto* const kCache = - new MG_Util::ShaderTranspiler::BoundedTranslationCache( - "ShaderTranslationCache L1 (GLSL->front end)", kMaxEntries, kMaxBytes); + static auto* const kCache = new MG_Util::ShaderTranspiler::BoundedTranslationCache( + "ShaderTranslationCache L1 (GLSL->front end)", kMaxEntries, kMaxBytes); return *kCache; } - void ClearProgramTranslationCache() { GetProgramTranslationCache().Clear(); } + void ClearProgramTranslationCache() { + GetProgramTranslationCache().Clear(); + } - void LogProgramTranslationCacheStats() { GetProgramTranslationCache().LogStats(); } + void LogProgramTranslationCacheStats() { + GetProgramTranslationCache().LogStats(); + } } // namespace MobileGL::MG_State::GLState diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.h b/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.h index bbec5c3a..4c6257fb 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.h @@ -61,8 +61,7 @@ namespace MobileGL::MG_State::GLState { // // Global rather than per-context because the producer runs on a ShaderCompilePool worker // and must not reach MG_State::pGLContext. - MG_Util::ShaderTranspiler::BoundedTranslationCache& - GetProgramTranslationCache(); + MG_Util::ShaderTranspiler::BoundedTranslationCache& GetProgramTranslationCache(); void ClearProgramTranslationCache(); void LogProgramTranslationCacheStats(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileAdoptionMap.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileAdoptionMap.cpp index 0b64736e..3fb575db 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileAdoptionMap.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileAdoptionMap.cpp @@ -12,8 +12,8 @@ namespace MobileGL::MG_State::GLState { SharedPtr ShaderCompileAdoptionMap::FindAdoptable(const ShaderStage stage, - const Uint64 sourceHash, const String& source, - const Uint64 envFingerprint) { + const Uint64 sourceHash, const String& source, + const Uint64 envFingerprint) { const ShaderSourceKey key{.stage = stage, .sourceHash = sourceHash, .sourceLength = source.length(), diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.cpp index 6ed64911..eb8ace1a 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.cpp @@ -86,8 +86,7 @@ namespace { ComputeLocalSize localSize; const MobileGL::String uncommentedSource = StripGlslComments(source); - for (std::sregex_iterator it(uncommentedSource.begin(), uncommentedSource.end(), kComputeLocalSizePattern), - end; + for (std::sregex_iterator it(uncommentedSource.begin(), uncommentedSource.end(), kComputeLocalSizePattern), end; it != end; ++it) { const char axis = (*it)[1].str()[0]; // The [0-9]+ capture is unbounded, so `local_size_x = 99999999999999999999999` @@ -97,11 +96,9 @@ namespace { // below rejects anyway - the same verdict a non-overflowing huge value gets. const MobileGL::String digits = (*it)[2].str(); unsigned long long value = 0; - const std::from_chars_result parsed = - std::from_chars(digits.data(), digits.data() + digits.size(), value); - const MobileGL::Uint clampedValue = (parsed.ec != std::errc() || value > UINT_MAX) - ? UINT_MAX - : static_cast(value); + const std::from_chars_result parsed = std::from_chars(digits.data(), digits.data() + digits.size(), value); + const MobileGL::Uint clampedValue = + (parsed.ec != std::errc() || value > UINT_MAX) ? UINT_MAX : static_cast(value); // TODO: Replace this literal layout scanner with parser/AST-backed validation so expressions and // specialization-id layouts are handled consistently with glslang. @@ -152,8 +149,7 @@ namespace { // only survives inside a synthesized block NAME) and re-covers storage blocks as a backstop. // The conformance predicate is compile AND link, so either site satisfies it; the split is // about WHICH error GL reports, not about whether the shader is rejected. - static MobileGL::Int MaxShaderStorageBufferBindings( - const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { + static MobileGL::Int MaxShaderStorageBufferBindings(const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) { return MobileGL::MG_State::GLState::ResolveResourceBindingLimits(env).MaxShaderStorageBufferBindings; } @@ -198,8 +194,8 @@ namespace { return result; } - if (const std::optional bindingError = FindShaderStorageBindingViolation( - result.preprocessedSource, MaxShaderStorageBufferBindings(env))) { + if (const std::optional bindingError = + FindShaderStorageBindingViolation(result.preprocessedSource, MaxShaderStorageBufferBindings(env))) { result.outcome = ShaderPreprocessOutcome::ResourceBindingRejected; result.infoLog = *bindingError; return result; @@ -232,7 +228,9 @@ namespace MobileGL::MG_State::GLState { // thread_local state, and glslang::InitializeProcess() is process-wide, refcounted and // mutex-guarded, so it needs no per-worker counterpart). The pool allocator is the part // that needs undoing; see the declaration in ShaderCompileTask.h. - GlslangThreadAllocatorGuard::~GlslangThreadAllocatorGuard() { glslang::SetThreadPoolAllocator(nullptr); } + GlslangThreadAllocatorGuard::~GlslangThreadAllocatorGuard() { + glslang::SetThreadPoolAllocator(nullptr); + } // Pure CPU work only. Everything this reads is either an input the node owns or a // process-wide constant; everything it writes is `artifacts`. Do not add a GL/EGL call, @@ -248,12 +246,14 @@ namespace MobileGL::MG_State::GLState { // (JobNode still catches: it is the last resort for anything below.) try { RunCompilePipeline(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { artifacts = {}; artifacts.env = env; artifacts.compileStatus = false; artifacts.infoLog = std::format("Error: shader compilation failed: {}", e.what()); - } catch (...) { + } + catch (...) { artifacts = {}; artifacts.env = env; artifacts.compileStatus = false; @@ -304,16 +304,14 @@ namespace MobileGL::MG_State::GLState { // when a link finds no stored parse. A link that hits L1 never asks, so the parse // never happens at all; a link that misses pays exactly one parse, where the CAS // loser has always paid it. See TranslationCache.h's L1c section. - const TranslationCacheKey parseKey = - ShaderTranslationCacheEnabled() - ? BuildShaderParseVerdictKey(ShaderParseVerdictKeyInputs{ - .frontendFingerprint = compileEnv.frontendFingerprint, - .shaderType = glShaderType, - .preprocessedSource = StringView(shared.preprocessedSource), - .shaderCompileFlags = kShaderCompileFlags}) - : TranslationCacheKey{}; - const ShaderParseVerdictPtr verdict = - parseKey.Valid() ? GetShaderParseVerdictCache().Find(parseKey) : nullptr; + const TranslationCacheKey parseKey = ShaderTranslationCacheEnabled() + ? BuildShaderParseVerdictKey(ShaderParseVerdictKeyInputs{ + .frontendFingerprint = compileEnv.frontendFingerprint, + .shaderType = glShaderType, + .preprocessedSource = StringView(shared.preprocessedSource), + .shaderCompileFlags = kShaderCompileFlags}) + : TranslationCacheKey{}; + const ShaderParseVerdictPtr verdict = parseKey.Valid() ? GetShaderParseVerdictCache().Find(parseKey) : nullptr; // The two branches produce exactly one thing between them - a verdict, plus a TShader // only when this task actually parsed - and converge on one publish below. Keeping the @@ -354,8 +352,7 @@ namespace MobileGL::MG_State::GLState { freshVerdict->infoLog = parseLog; freshVerdict->explicitUniformLocations = explicitUniformLocations; const SizeT verdictBytes = ShaderParseVerdictBytes(*freshVerdict); - GetShaderParseVerdictCache().Insert(parseKey, ShaderParseVerdictPtr(Move(freshVerdict)), - verdictBytes); + GetShaderParseVerdictCache().Insert(parseKey, ShaderParseVerdictPtr(Move(freshVerdict)), verdictBytes); } } @@ -386,13 +383,11 @@ namespace MobileGL::MG_State::GLState { const SizeT firstLineEnd = artifacts.infoLog.find('\n'); diagnostics.logLines.push_back( {MOBILEGL_LOG_LEVEL_DEBUG, - std::format( - "ShaderCompileTask: shader {} (stage {}) failed to compile; compileStatus = false. " - "Preprocessed source: {} bytes. First log line: {}", - externalIndex, static_cast(stage), shared.preprocessedSource.length(), - artifacts.infoLog.substr(0, firstLineEnd == String::npos - ? artifacts.infoLog.length() - : firstLineEnd))}); + std::format("ShaderCompileTask: shader {} (stage {}) failed to compile; compileStatus = false. " + "Preprocessed source: {} bytes. First log line: {}", + externalIndex, static_cast(stage), shared.preprocessedSource.length(), + artifacts.infoLog.substr(0, firstLineEnd == String::npos ? artifacts.infoLog.length() + : firstLineEnd))}); if (shouldPopulateCache) { fresh->outcome = ShaderPreprocessOutcome::ParseFailed; fresh->infoLog = artifacts.infoLog; diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.h b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.h index cefc8d66..7115f285 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.h +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderCompileTask.h @@ -45,9 +45,8 @@ namespace MobileGL::MG_State::GLState { // "no backend" arm is not a detail - it is what the GPU-free test binary runs under, and // it has to keep matching what glGetIntegerv answers there. limits.MaxShaderStorageBufferBindings = - env.HasBackend() - ? std::min(bindingPoints, std::max(env.params.MaxShaderStorageBufferBindings, 0)) - : bindingPoints; + env.HasBackend() ? std::min(bindingPoints, std::max(env.params.MaxShaderStorageBufferBindings, 0)) + : bindingPoints; if (!env.HasBackend()) { // The two genuinely per-DEVICE ceilings have nothing to be measured against here, and // zero means "do not enforce this kind" rather than "reject everything". diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp index 346444ba..1617e802 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderObject.cpp @@ -286,8 +286,8 @@ namespace MobileGL::MG_State::GLState { } } - AdoptCompileNode(MakeShared(m_stage, m_source, sourceHash, env, m_preprocessCache, - m_externalIndex)); + AdoptCompileNode( + MakeShared(m_stage, m_source, sourceHash, env, m_preprocessCache, m_externalIndex)); if (!runOnPool) { m_compiled->RunInline(); diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.cpp b/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.cpp index c84e0a4f..0660b72c 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.cpp @@ -10,7 +10,7 @@ namespace MobileGL::MG_State::GLState { ShaderPreprocessResultPtr ShaderPreprocessCache::Find(const ShaderStage stage, const Uint64 sourceHash, - const String& source, const Uint64 envFingerprint) const { + const String& source, const Uint64 envFingerprint) const { const Key key{.stage = stage, .sourceHash = sourceHash, .sourceLength = source.length(), @@ -78,8 +78,7 @@ namespace MobileGL::MG_State::GLState { // FIFO: the oldest insertion goes first. Insert() already refuses entries larger // than the byte budget, so this loop always terminates with at least the entry // that was just added still resident. - while (!m_entries.empty() && - (m_entries.size() > kMaxEntries || m_storedSourceBytes > kMaxStoredSourceBytes)) { + while (!m_entries.empty() && (m_entries.size() > kMaxEntries || m_storedSourceBytes > kMaxStoredSourceBytes)) { EraseEntryLocked(m_entries.begin()); } } diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.h b/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.h index d4f158cf..e74d4d58 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.h +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderPreprocessCache.h @@ -155,7 +155,7 @@ namespace MobileGL::MG_State::GLState { // reading it. Shared ownership of the payload is what closes that hole; the mutex // only protects the containers below. mutable std::mutex m_mutex; - EntryList m_entries; // front = oldest (FIFO victim) + EntryList m_entries; // front = oldest (FIFO victim) UnorderedMap m_index; SizeT m_storedSourceBytes = 0; }; diff --git a/MobileGL/MG_State/GLState/ProgramState/ShaderSourceKey.h b/MobileGL/MG_State/GLState/ProgramState/ShaderSourceKey.h index ec93fce1..0f80786a 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ShaderSourceKey.h +++ b/MobileGL/MG_State/GLState/ProgramState/ShaderSourceKey.h @@ -32,8 +32,8 @@ namespace MobileGL::MG_State::GLState { Uint64 envFingerprint = 0; Bool operator==(const ShaderSourceKey& other) const { - return stage == other.stage && sourceHash == other.sourceHash && - sourceLength == other.sourceLength && envFingerprint == other.envFingerprint; + return stage == other.stage && sourceHash == other.sourceHash && sourceLength == other.sourceLength && + envFingerprint == other.envFingerprint; } }; diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index b939a5c8..aa03f8d1 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -31,10 +31,9 @@ namespace MobileGL { } // Every viewport's scissor-test bit set, i.e. what glEnable(GL_SCISSOR_TEST) writes. - constexpr Uint32 kAllViewportsMask = - RenderStateParameters::MAX_VIEWPORTS >= 32 - ? ~0u - : (1u << RenderStateParameters::MAX_VIEWPORTS) - 1u; + constexpr Uint32 kAllViewportsMask = RenderStateParameters::MAX_VIEWPORTS >= 32 + ? ~0u + : (1u << RenderStateParameters::MAX_VIEWPORTS) - 1u; } // namespace RenderState::RenderState() { @@ -97,7 +96,8 @@ namespace MobileGL { // nearest (GL 4.6 core 22.2), and truncating a 63.5-wide viewport to 63 would // also hand the backends a rectangle one pixel short of what was asked for. return IntVec4(static_cast(std::lround(viewport.x())), static_cast(std::lround(viewport.y())), - static_cast(std::lround(viewport.z())), static_cast(std::lround(viewport.w()))); + static_cast(std::lround(viewport.z())), + static_cast(std::lround(viewport.w()))); } void RenderState::SetViewportIndexed(Uint index, FloatVec4 viewport) { @@ -133,11 +133,20 @@ namespace MobileGL { void RenderState::SetHint(GLenum target, GLenum mode) { GLenum* slot = nullptr; switch (target) { - case GL_LINE_SMOOTH_HINT: slot = &m_parameters.LineSmoothHint; break; - case GL_POLYGON_SMOOTH_HINT: slot = &m_parameters.PolygonSmoothHint; break; - case GL_TEXTURE_COMPRESSION_HINT: slot = &m_parameters.TextureCompressionHint; break; - case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: slot = &m_parameters.FragmentShaderDerivativeHint; break; - default: return; + case GL_LINE_SMOOTH_HINT: + slot = &m_parameters.LineSmoothHint; + break; + case GL_POLYGON_SMOOTH_HINT: + slot = &m_parameters.PolygonSmoothHint; + break; + case GL_TEXTURE_COMPRESSION_HINT: + slot = &m_parameters.TextureCompressionHint; + break; + case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: + slot = &m_parameters.FragmentShaderDerivativeHint; + break; + default: + return; } if (*slot == mode) return; *slot = mode; @@ -146,11 +155,16 @@ namespace MobileGL { GLenum RenderState::GetHint(GLenum target) const { switch (target) { - case GL_LINE_SMOOTH_HINT: return m_parameters.LineSmoothHint; - case GL_POLYGON_SMOOTH_HINT: return m_parameters.PolygonSmoothHint; - case GL_TEXTURE_COMPRESSION_HINT: return m_parameters.TextureCompressionHint; - case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: return m_parameters.FragmentShaderDerivativeHint; - default: return GL_DONT_CARE; + case GL_LINE_SMOOTH_HINT: + return m_parameters.LineSmoothHint; + case GL_POLYGON_SMOOTH_HINT: + return m_parameters.PolygonSmoothHint; + case GL_TEXTURE_COMPRESSION_HINT: + return m_parameters.TextureCompressionHint; + case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: + return m_parameters.FragmentShaderDerivativeHint; + default: + return GL_DONT_CARE; } } @@ -323,7 +337,7 @@ namespace MobileGL { case CapabilityInput::capability: \ if (m_parameters.capability##Enabled == (flag)) break; \ m_parameters.capability##Enabled = (flag); \ - BumpVersions(); \ + BumpVersions(); \ break; switch (cap) { @@ -382,9 +396,8 @@ namespace MobileGL { case CapabilityInput::ClipDistance6: case CapabilityInput::ClipDistance7: { const Uint32 bit = ClipDistanceBit(cap); - const Uint32 updated = - enabled ? (m_parameters.ClipDistanceEnabledMask | bit) - : (m_parameters.ClipDistanceEnabledMask & ~bit); + const Uint32 updated = enabled ? (m_parameters.ClipDistanceEnabledMask | bit) + : (m_parameters.ClipDistanceEnabledMask & ~bit); if (updated == m_parameters.ClipDistanceEnabledMask) break; m_parameters.ClipDistanceEnabledMask = updated; // Deliberately NOT BumpVersions(): no backend bakes a clip-distance enable @@ -673,8 +686,7 @@ namespace MobileGL { void RenderState::SetStencilOp(StencilFace face, StencilOperation fail, StencilOperation depthFail, StencilOperation depthPass) { StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)]; - if (state.FailOp == fail && state.PassDepthFailOp == depthFail && - state.PassDepthPassOp == depthPass) { + if (state.FailOp == fail && state.PassDepthFailOp == depthFail && state.PassDepthPassOp == depthPass) { return; } diff --git a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp index 3dc7c115..e07a2c66 100644 --- a/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp +++ b/MobileGL/MG_State/GLState/RenderbufferState/RenderbufferObject.cpp @@ -23,7 +23,7 @@ namespace MobileGL { // Starts at 1 so a zero-initialized cache slot can never carry a live // renderbuffer's id. std::atomic g_nextRenderbufferLifetimeId{1}; - } + } // namespace Uint64 RenderbufferObject::AllocateLifetimeId() { return g_nextRenderbufferLifetimeId.fetch_add(1, std::memory_order_relaxed); diff --git a/MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp b/MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp index 06e669fa..2cb6c997 100644 --- a/MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp +++ b/MobileGL/MG_State/GLState/SamplerState/SamplerObject.cpp @@ -204,8 +204,8 @@ namespace MobileGL { m_samplerParameters.borderColorForm = BorderColorForm::Float; m_samplerParameters.borderColor = color; m_samplerParameters.borderColorI = - IntVec4(static_cast(color.x()), static_cast(color.y()), - static_cast(color.z()), static_cast(color.w())); + IntVec4(static_cast(color.x()), static_cast(color.y()), static_cast(color.z()), + static_cast(color.w())); m_samplerParameters.borderColorUI = UintVec4(static_cast(color.x()), static_cast(color.y()), static_cast(color.z()), static_cast(color.w())); @@ -238,8 +238,8 @@ namespace MobileGL { m_samplerParameters.borderColorForm = BorderColorForm::Uint; m_samplerParameters.borderColorUI = color; m_samplerParameters.borderColorI = - IntVec4(static_cast(color.x()), static_cast(color.y()), - static_cast(color.z()), static_cast(color.w())); + IntVec4(static_cast(color.x()), static_cast(color.y()), static_cast(color.z()), + static_cast(color.w())); m_samplerParameters.borderColor = FloatVec4(static_cast(color.x()), static_cast(color.y()), static_cast(color.z()), static_cast(color.w())); diff --git a/MobileGL/MG_State/GLState/ShareGroupState.cpp b/MobileGL/MG_State/GLState/ShareGroupState.cpp index a87a2fa1..34616c21 100644 --- a/MobileGL/MG_State/GLState/ShareGroupState.cpp +++ b/MobileGL/MG_State/GLState/ShareGroupState.cpp @@ -115,12 +115,12 @@ namespace MobileGL::MG_State::GLState { } const SharedPtr& ShareGroupState::CreateTextureViewObject( - Uint index, TextureTarget target, const SharedPtr& storageOwner, Uint minLevel, - Uint numLevels, Uint minLayer, Uint numLayers) { + Uint index, TextureTarget target, const SharedPtr& storageOwner, Uint minLevel, Uint numLevels, + Uint minLayer, Uint numLayers) { MOBILEGL_ASSERT(storageOwner != nullptr, "CreateTextureViewObject: storage owner is null"); auto& textureObject = m_textureObjects[index]; - textureObject = MakeShared(index, target, storageOwner, minLevel, numLevels, minLayer, - numLayers); + textureObject = + MakeShared(index, target, storageOwner, minLevel, numLevels, minLayer, numLayers); return textureObject; } diff --git a/MobileGL/MG_State/GLState/StateObjectDeathNotice.h b/MobileGL/MG_State/GLState/StateObjectDeathNotice.h index adfd2fb1..25540052 100644 --- a/MobileGL/MG_State/GLState/StateObjectDeathNotice.h +++ b/MobileGL/MG_State/GLState/StateObjectDeathNotice.h @@ -54,8 +54,7 @@ namespace MobileGL::MG_State::GLState { return g_stateObjectDeathOps; } - inline void NotifyStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, - MG_Pipe::MGPipeHandle handle) { + inline void NotifyStateObjectDestroyed(MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, MG_Pipe::MGPipeHandle handle) { const StateObjectDeathOps* ops = g_stateObjectDeathOps; if (ops == nullptr || ops->OnDestroyed == nullptr) return; ops->OnDestroyed(kind, lifetimeId, handle); diff --git a/MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp b/MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp index aa9c5eaf..e216b9a3 100644 --- a/MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp +++ b/MobileGL/MG_State/GLState/TextureState/MipmapStorage.cpp @@ -123,8 +123,7 @@ namespace MobileGL { } Bool SameFootprintXY(const MipmapDirtyRegion& a, const MipmapDirtyRegion& b) { - return a.lo.x() == b.lo.x() && a.hi.x() == b.hi.x() && a.lo.y() == b.lo.y() && - a.hi.y() == b.hi.y(); + return a.lo.x() == b.lo.x() && a.hi.x() == b.hi.x() && a.lo.y() == b.lo.y() && a.hi.y() == b.hi.y(); } Bool FootprintXYLess(const MipmapDirtyRegion& a, const MipmapDirtyRegion& b) { @@ -148,7 +147,8 @@ namespace MobileGL { Vector runs; for (const auto& box : writes) { for (Int z = box.lo.z(); z < box.hi.z(); ++z) - for (Int y = box.lo.y(); y < box.hi.y(); ++y) runs.push_back({z, y, box.lo.x(), box.hi.x()}); + for (Int y = box.lo.y(); y < box.hi.y(); ++y) + runs.push_back({z, y, box.lo.x(), box.hi.x()}); } std::sort(runs.begin(), runs.end(), [](const Run& a, const Run& b) { if (a.z != b.z) return a.z < b.z; @@ -182,7 +182,8 @@ namespace MobileGL { currentSlice.clear(); SizeT p = 0; for (const auto& box : slice) { - while (p < previousSlice.size() && FootprintXYLess(out[previousSlice[p]], box)) ++p; + while (p < previousSlice.size() && FootprintXYLess(out[previousSlice[p]], box)) + ++p; if (p < previousSlice.size() && SameFootprintXY(out[previousSlice[p]], box)) { out[previousSlice[p]].hi = {box.hi.x(), box.hi.y(), sliceZ + 1}; currentSlice.push_back(previousSlice[p++]); @@ -275,9 +276,8 @@ namespace MobileGL { if (level < m_dirtyRegions.size()) { m_dirtyRegions[level] = m_isDirty[level] - ? MipmapDirtyRegion{IntVec3{0, 0, 0}, - IntVec3{input.texelSize.x(), input.texelSize.y(), - std::max(input.texelSize.z(), 1)}} + ? MipmapDirtyRegion{IntVec3{0, 0, 0}, IntVec3{input.texelSize.x(), input.texelSize.y(), + std::max(input.texelSize.z(), 1)}} : MipmapDirtyRegion{}; } // The rect list mirrors the union box's reset: whatever rects were @@ -426,8 +426,7 @@ namespace MobileGL { if (level < m_dirtyRegions.size()) { if (dirty) { const IntVec3 size = level < m_texelSizes.size() ? m_texelSizes[level] : IntVec3{0, 0, 0}; - m_dirtyRegions[level] = {IntVec3{0, 0, 0}, - IntVec3{size.x(), size.y(), std::max(size.z(), 1)}}; + m_dirtyRegions[level] = {IntVec3{0, 0, 0}, IntVec3{size.x(), size.y(), std::max(size.z(), 1)}}; } else { m_dirtyRegions[level] = {}; } @@ -473,8 +472,7 @@ namespace MobileGL { if (!m_isDirty[level]) { rects.clear(); // stale-safety; MarkDirty(false) already cleared it m_footprintWrites[level].clear(); - } else if (rects.empty() && level < m_dirtyRegions.size() && - !m_dirtyRegions[level].Empty()) { + } else if (rects.empty() && level < m_dirtyRegions.size() && !m_dirtyRegions[level].Empty()) { rects.push_back(m_dirtyRegions[level]); } // Entering the log inside the insert already recorded this write. @@ -487,11 +485,9 @@ namespace MobileGL { if (level < m_dirtyRegions.size()) { MipmapDirtyRegion& region = m_dirtyRegions[level]; if (m_isDirty[level] && !region.Empty()) { - region.lo = {std::min(region.lo.x(), incoming.lo.x()), - std::min(region.lo.y(), incoming.lo.y()), + region.lo = {std::min(region.lo.x(), incoming.lo.x()), std::min(region.lo.y(), incoming.lo.y()), std::min(region.lo.z(), incoming.lo.z())}; - region.hi = {std::max(region.hi.x(), incoming.hi.x()), - std::max(region.hi.y(), incoming.hi.y()), + region.hi = {std::max(region.hi.x(), incoming.hi.x()), std::max(region.hi.y(), incoming.hi.y()), std::max(region.hi.z(), incoming.hi.z())}; } else { region = incoming; diff --git a/MobileGL/MG_State/GLState/TextureState/MipmapUploadTargetArray.h b/MobileGL/MG_State/GLState/TextureState/MipmapUploadTargetArray.h index d1ec8f5d..408781d0 100644 --- a/MobileGL/MG_State/GLState/TextureState/MipmapUploadTargetArray.h +++ b/MobileGL/MG_State/GLState/TextureState/MipmapUploadTargetArray.h @@ -84,8 +84,7 @@ namespace MobileGL { return m_storage[targetIndex].GetDirtyRegion(level); } - SizeT GetDirtyRects(Uint targetIndex, Uint level, MipmapDirtyRegion* outRects, - SizeT maxRects) const { + SizeT GetDirtyRects(Uint targetIndex, Uint level, MipmapDirtyRegion* outRects, SizeT maxRects) const { MOBILEGL_ASSERT(targetIndex < TargetCount, "GetDirtyRects: target invalid"); return m_storage[targetIndex].GetDirtyRects(level, outRects, maxRects); } diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp index edd1486e..eb174301 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.cpp @@ -237,8 +237,7 @@ namespace MobileGL { // make a float (0,0,0,1) and an integer (0,0,0,1) numerically identical, but they are // different GL state and the DirectGLES sync memoises on m_textureParamsVersion. void TextureObjectBase::SetBorderColor(const FloatVec4& color) { - if (color == m_sampler->GetBorderColor() && - m_sampler->GetBorderColorForm() == BorderColorForm::Float) { + if (color == m_sampler->GetBorderColor() && m_sampler->GetBorderColorForm() == BorderColorForm::Float) { return; } @@ -253,8 +252,7 @@ namespace MobileGL { } void TextureObjectBase::SetBorderColorI(const IntVec4& color) { - if (color == m_sampler->GetBorderColorI() && - m_sampler->GetBorderColorForm() == BorderColorForm::Int) { + if (color == m_sampler->GetBorderColorI() && m_sampler->GetBorderColorForm() == BorderColorForm::Int) { return; } @@ -551,8 +549,7 @@ namespace MobileGL { } SizeT TextureObjectWithOneMipmap::GetStorageDirtyRects(TextureUploadTarget uploadTarget, Uint mipmapLevel, - MipmapDirtyRegion* outRects, - SizeT maxRects) const { + MipmapDirtyRegion* outRects, SizeT maxRects) const { return m_textureStorage.GetDirtyRects(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, outRects, maxRects); } @@ -563,35 +560,37 @@ namespace MobileGL { m_textureStorage.GetDirtyFootprint(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, outRects); } - void TextureObjectWithOneMipmap::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, - GLenum internalFormat, const void* data, SizeT size) { + void TextureObjectWithOneMipmap::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, + Uint mipmapLevel, GLenum internalFormat, + const void* data, SizeT size) { m_textureStorage.SetCompressedImage(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, internalFormat, data, size); } GLenum TextureObjectWithOneMipmap::GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.GetCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } SizeT TextureObjectWithOneMipmap::GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.GetCompressedByteSize(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } const void* TextureObjectWithOneMipmap::MapMipmapCompressedImage(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.MapCompressedData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } void TextureObjectWithOneMipmap::SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, - Uint mipmapLevel, GLenum internalFormat) { + Uint mipmapLevel, + GLenum internalFormat) { m_textureStorage.SetRequestedCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, internalFormat); } GLenum TextureObjectWithOneMipmap::GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.GetRequestedCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } @@ -635,88 +634,89 @@ namespace MobileGL { // TODO: add other texture types as needed - Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { - if (texture == nullptr) return true; - return texture->IsMipmapCompleteForFilterCached(mipmapped); - } - - Bool SamplesAsIncompleteTexture(const ITextureObject* texture, const SamplerObject* effectiveSampler) { - // A multisample texture is fetched, never filtered. GL 4.6 core 8.17 gives it exactly - // one level and says its sampler state is not used at all - texelFetch is the only way - // a shader can read it - so 8.14's filter-completeness rules, which is what the - // `mipmapped` branch below asks about, never apply to it. - // - // Deriving `mipmapped` from that unused sampler is what made EVERY multisample texture - // look incomplete: MIN_FILTER's initial value is NEAREST_MIPMAP_LINEAR, and a texture - // that can only ever have one level never satisfies the mip-chain check. Both backends - // treat "samples as incomplete" as "do not bind it" (DirectGLES's per-unit walk in - // ResolveAndBindUnitTextures, DirectVulkan's UniformManager), so the sampler2DMS the - // shader declared was left pointing at nothing and every texelFetch read zero. That is - // the sampler2DMS/sampler2DMSArray half of KHR-GL43.compute_shader.resource-texture, - // which fails at the first data7 element with the multisample texture correctly - // cleared and simply never bound. - // - // IsCopyImageEndpointComplete already spells the same guard as - // CopyImageTargetHasMipmapChain; this was the one place that asked without it. - const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; - const Bool filtered = target != TextureTarget::Texture2DMultisample && - target != TextureTarget::Texture2DMultisampleArray; - const Bool mipmapped = filtered && effectiveSampler != nullptr && - effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None; - return !IsMipmapCompleteForFilter(texture, mipmapped); - } - - static Bool ComputeMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { - if (texture == nullptr) return true; - if (!texture->IsComplete()) return false; - if (!mipmapped) return true; - - const auto* mipmapTexture = AsMipmapTexture(texture); - if (mipmapTexture == nullptr) return true; // no mip chain to be incomplete about - - const UintVec2& levelRange = texture->GetLevelRange(); - const Uint baseLevel = levelRange.x(); - const Uint storedLevels = mipmapTexture->GetMipmapLevelCount(); - if (baseLevel >= storedLevels) return false; - - // An array texture's layer count is not a dimension of the image: it stays put all - // the way down the chain (GL 4.6 core 8.14.3). GetMipmapTexelSize reports it in the - // slot after the image's own dimensions. - const TextureTarget target = texture->GetTarget(); - Int shrinkingComponents = 3; - if (target == TextureTarget::Texture1DArray) { - shrinkingComponents = 1; - } else if (target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray) { - shrinkingComponents = 2; + Bool IsMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { + if (texture == nullptr) return true; + return texture->IsMipmapCompleteForFilterCached(mipmapped); } - for (const auto uploadTarget : texture->GetUploadTargets()) { - const IntVec3 baseSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, baseLevel); - Int largest = 0; - for (Int component = 0; component < shrinkingComponents; ++component) { - largest = std::max(largest, baseSize[component]); + Bool SamplesAsIncompleteTexture(const ITextureObject* texture, const SamplerObject* effectiveSampler) { + // A multisample texture is fetched, never filtered. GL 4.6 core 8.17 gives it exactly + // one level and says its sampler state is not used at all - texelFetch is the only way + // a shader can read it - so 8.14's filter-completeness rules, which is what the + // `mipmapped` branch below asks about, never apply to it. + // + // Deriving `mipmapped` from that unused sampler is what made EVERY multisample texture + // look incomplete: MIN_FILTER's initial value is NEAREST_MIPMAP_LINEAR, and a texture + // that can only ever have one level never satisfies the mip-chain check. Both backends + // treat "samples as incomplete" as "do not bind it" (DirectGLES's per-unit walk in + // ResolveAndBindUnitTextures, DirectVulkan's UniformManager), so the sampler2DMS the + // shader declared was left pointing at nothing and every texelFetch read zero. That is + // the sampler2DMS/sampler2DMSArray half of KHR-GL43.compute_shader.resource-texture, + // which fails at the first data7 element with the multisample texture correctly + // cleared and simply never bound. + // + // IsCopyImageEndpointComplete already spells the same guard as + // CopyImageTargetHasMipmapChain; this was the one place that asked without it. + const TextureTarget target = texture != nullptr ? texture->GetTarget() : TextureTarget::Unknown; + const Bool filtered = + target != TextureTarget::Texture2DMultisample && target != TextureTarget::Texture2DMultisampleArray; + const Bool mipmapped = filtered && effectiveSampler != nullptr && + effectiveSampler->GetMipmapMode() != SamplerMipmapMode::None; + return !IsMipmapCompleteForFilter(texture, mipmapped); + } + + static Bool ComputeMipmapCompleteForFilter(const ITextureObject* texture, Bool mipmapped) { + if (texture == nullptr) return true; + if (!texture->IsComplete()) return false; + if (!mipmapped) return true; + + const auto* mipmapTexture = AsMipmapTexture(texture); + if (mipmapTexture == nullptr) return true; // no mip chain to be incomplete about + + const UintVec2& levelRange = texture->GetLevelRange(); + const Uint baseLevel = levelRange.x(); + const Uint storedLevels = mipmapTexture->GetMipmapLevelCount(); + if (baseLevel >= storedLevels) return false; + + // An array texture's layer count is not a dimension of the image: it stays put all + // the way down the chain (GL 4.6 core 8.14.3). GetMipmapTexelSize reports it in the + // slot after the image's own dimensions. + const TextureTarget target = texture->GetTarget(); + Int shrinkingComponents = 3; + if (target == TextureTarget::Texture1DArray) { + shrinkingComponents = 1; + } else if (target == TextureTarget::Texture2DArray || target == TextureTarget::TextureCubeMapArray) { + shrinkingComponents = 2; } - if (largest <= 0) return false; - // p = log2 of the largest base dimension: the last level the chain needs - // before every dimension has reached 1. TEXTURE_MAX_LEVEL can cut it short. - Uint p = 0; - for (Int extent = largest; extent > 1; extent >>= 1) ++p; - const Uint lastLevel = std::min(baseLevel + p, levelRange.y()); + for (const auto uploadTarget : texture->GetUploadTargets()) { + const IntVec3 baseSize = mipmapTexture->GetMipmapTexelSize(uploadTarget, baseLevel); + Int largest = 0; + for (Int component = 0; component < shrinkingComponents; ++component) { + largest = std::max(largest, baseSize[component]); + } + if (largest <= 0) return false; - for (Uint level = baseLevel; level <= lastLevel; ++level) { - if (level >= storedLevels) return false; - const IntVec3 actual = mipmapTexture->GetMipmapTexelSize(uploadTarget, level); - for (Int component = 0; component < 3; ++component) { - const Int expected = component < shrinkingComponents - ? std::max(1, baseSize[component] >> (level - baseLevel)) - : baseSize[component]; - if (actual[component] != expected) return false; + // p = log2 of the largest base dimension: the last level the chain needs + // before every dimension has reached 1. TEXTURE_MAX_LEVEL can cut it short. + Uint p = 0; + for (Int extent = largest; extent > 1; extent >>= 1) + ++p; + const Uint lastLevel = std::min(baseLevel + p, levelRange.y()); + + for (Uint level = baseLevel; level <= lastLevel; ++level) { + if (level >= storedLevels) return false; + const IntVec3 actual = mipmapTexture->GetMipmapTexelSize(uploadTarget, level); + for (Int component = 0; component < 3; ++component) { + const Int expected = component < shrinkingComponents + ? std::max(1, baseSize[component] >> (level - baseLevel)) + : baseSize[component]; + if (actual[component] != expected) return false; + } } } + return true; } - return true; - } } // namespace GLState } // namespace MG_State diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject.h b/MobileGL/MG_State/GLState/TextureState/TextureObject.h index 91fe371f..47ab98ea 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject.h @@ -89,7 +89,11 @@ namespace MobileGL::MG_State::GLState { virtual Uint64 GetLifetimeId() const = 0; // P15: the pipe client's last handles for this texture, one per kind it is looked up under // (MGPipeSlotAllocator::AcquireHinted). Only hints: checked against the allocator on use. - enum class PipeHintKind : Uint8 { Texture = 0, SamplerView = 1, Count = 2 }; + enum class PipeHintKind : Uint8 { + Texture = 0, + SamplerView = 1, + Count = 2 + }; virtual Uint64& PipeHandleHint(PipeHintKind kind) const = 0; // Which aspect of a packed depth/stencil texture a sampler reads (GL 4.6 core 8.10). // DEPTH_COMPONENT until set, and meaningless for every other format. @@ -352,8 +356,8 @@ namespace MobileGL::MG_State::GLState { // stays uncompressed and correctly sized, so nothing in the backend upload path has to know // these exist; only glGetCompressedTexImage, glGetCompressedTextureImage and the // GL_TEXTURE_COMPRESSED* level queries read them. - virtual void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, - GLenum internalFormat, const void* data, SizeT size) = 0; + virtual void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, GLenum internalFormat, + const void* data, SizeT size) = 0; // GL_NONE when this level is not stored compressed. virtual GLenum GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0; virtual SizeT GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0; @@ -365,8 +369,7 @@ namespace MobileGL::MG_State::GLState { virtual void SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel, GLenum internalFormat) = 0; // GL_NONE when the level was not requested with a compressed internalformat. - virtual GLenum GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const = 0; + virtual GLenum GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const = 0; }; // Cheap replacement for dynamic_cast on the hot path: TextureObjectMipmap is the diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp index 546485a3..c1682a1d 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.cpp @@ -94,7 +94,7 @@ namespace MobileGL { } void TextureObject2DCube::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, - GLenum internalFormat, const void* data, SizeT size) { + GLenum internalFormat, const void* data, SizeT size) { m_textureStorage.SetCompressedImage(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, internalFormat, data, size); } @@ -105,17 +105,17 @@ namespace MobileGL { } SizeT TextureObject2DCube::GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.GetCompressedByteSize(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } const void* TextureObject2DCube::MapMipmapCompressedImage(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const { + Uint mipmapLevel) const { return m_textureStorage.MapCompressedData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel); } void TextureObject2DCube::SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, - Uint mipmapLevel, GLenum internalFormat) { + Uint mipmapLevel, GLenum internalFormat) { m_textureStorage.SetRequestedCompressedFormat(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, internalFormat); } diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.h b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.h index 546d6a04..78f33b58 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.h +++ b/MobileGL/MG_State/GLState/TextureState/TextureObject2DCube.h @@ -35,12 +35,11 @@ namespace MobileGL { MipmapDirtyRegion* outRects, SizeT maxRects) const override; void GetStorageDirtyFootprint(TextureUploadTarget uploadTarget, Uint mipmapLevel, Vector& outRects) const override; - void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, - GLenum internalFormat, const void* data, SizeT size) override; + void SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, GLenum internalFormat, + const void* data, SizeT size) override; GLenum GetMipmapCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override; SizeT GetMipmapCompressedByteSize(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override; - const void* MapMipmapCompressedImage(TextureUploadTarget uploadTarget, - Uint mipmapLevel) const override; + const void* MapMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel) const override; void SetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, Uint mipmapLevel, GLenum internalFormat) override; GLenum GetMipmapRequestedCompressedFormat(TextureUploadTarget uploadTarget, diff --git a/MobileGL/MG_State/GLState/TextureState/TextureObjectView.cpp b/MobileGL/MG_State/GLState/TextureState/TextureObjectView.cpp index 2e5eb237..0cf49969 100644 --- a/MobileGL/MG_State/GLState/TextureState/TextureObjectView.cpp +++ b/MobileGL/MG_State/GLState/TextureState/TextureObjectView.cpp @@ -18,7 +18,11 @@ namespace MobileGL::MG_State::GLState { // TextureObject.cpp's completeness walk assumes); every other layered target keeps them // in z. GL_TEXTURE_3D is deliberately None: its depth is a spatial axis, not layers, and // ARB_texture_view forbids anything but a full-depth 3D->3D view of it. - enum class LayerAxis { None, Y, Z }; + enum class LayerAxis { + None, + Y, + Z + }; LayerAxis LayerAxisOf(TextureTarget target) { switch (target) { @@ -189,10 +193,9 @@ namespace MobileGL::MG_State::GLState { const SizeT bytesPerTexel = ownerBytes / ownerTexels; // One "layer" is a whole x*y slice for a 2D/cube array, and a single row of `width` // texels for a 1D array (whose layer count lives in the state-side height). - const SizeT layerTexels = ownerAxis == LayerAxis::Y - ? static_cast(std::max(ownerSize.x(), 0)) - : static_cast(std::max(ownerSize.x(), 0)) * - static_cast(std::max(ownerSize.y(), 0)); + const SizeT layerTexels = ownerAxis == LayerAxis::Y ? static_cast(std::max(ownerSize.x(), 0)) + : static_cast(std::max(ownerSize.x(), 0)) * + static_cast(std::max(ownerSize.y(), 0)); const SizeT offset = static_cast(layerIndex) * layerTexels * bytesPerTexel; return offset < ownerBytes ? offset : 0; } @@ -337,8 +340,7 @@ namespace MobileGL::MG_State::GLState { Vector& outRects) const { outRects.clear(); if (m_ownerMipmap == nullptr) return; - m_ownerMipmap->GetStorageDirtyFootprint(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), - outRects); + m_ownerMipmap->GetStorageDirtyFootprint(ToOwnerUploadTarget(uploadTarget), ToOwnerLevel(mipmapLevel), outRects); } void TextureObjectView::SetMipmapCompressedImage(TextureUploadTarget uploadTarget, Uint mipmapLevel, diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp index 8bd96b15..74006de1 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.cpp @@ -173,7 +173,6 @@ namespace MobileGL::MG_State::GLState { return m_indexBufferBindingSlot; } - const VertexAttribute& VertexArrayObject::GetAttribute(Uint index) const { static VertexAttribute emptyAttr; if (index >= MAX_VERTEX_ATTRIBS) return emptyAttr; @@ -297,8 +296,7 @@ namespace MobileGL::MG_State::GLState { } void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized, - Bool isInteger, Uint relativeOffset, Bool isBgra, - Bool isLong) { + Bool isInteger, Uint relativeOffset, Bool isBgra, Bool isLong) { if (attribIndex >= MAX_VERTEX_ATTRIBS) return; if (size < 1 || size > 4) return; diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h index 182de730..97b176cb 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayObject.h @@ -90,7 +90,7 @@ namespace MobileGL { } Uint GetAttributeBindingIndex(Uint attribIndex) const { return attribIndex < m_attributeBindingIndex.size() ? m_attributeBindingIndex[attribIndex] - : attribIndex; + : attribIndex; } const VertexBufferBindingPoint& GetBindingPoint(Uint bindingIndex) const { static const VertexBufferBindingPoint kEmpty{}; @@ -109,7 +109,6 @@ namespace MobileGL { // enables, divisors) can see, so the record's emitter keys on it. Uint32 GetElementsVersion() const { return m_elementsVersion; } - private: void BumpAttributeFormatVersion(Uint index); void BumpAttributeBufferVersion(Uint index, Bool presenceChanged); diff --git a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayState.cpp b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayState.cpp index 77fe1922..6a7509d6 100644 --- a/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayState.cpp +++ b/MobileGL/MG_State/GLState/VertexArrayState/VertexArrayState.cpp @@ -52,8 +52,7 @@ namespace MobileGL::MG_State::GLState { // Match the previous semantics exactly: binding an out-of-range name, or a name // whose slot holds no object, left the old SharedPtr member null - resolve that // NOW, so a slot created later does not silently become bound. - m_boundIndex = - (index < m_vertexArrays.size() && m_vertexArrays[index] != nullptr) ? index : kUnboundIndex; + m_boundIndex = (index < m_vertexArrays.size() && m_vertexArrays[index] != nullptr) ? index : kUnboundIndex; } const SharedPtr& VertexArrayState::CreateVertexArrayObject(Uint index) { @@ -80,9 +79,8 @@ namespace MobileGL::MG_State::GLState { // "Deleting the bound VAO rebinds the default VAO" needs the same answer the old // SharedPtr compare gave: either the live bound slot is the one being deleted, or // the bound object is a detached one that carries this external index. - const Bool deletingBound = m_boundDetached - ? m_boundDetached->GetExternalIndex() == index - : (m_boundIndex == index && m_boundIndex != kUnboundIndex); + const Bool deletingBound = m_boundDetached ? m_boundDetached->GetExternalIndex() == index + : (m_boundIndex == index && m_boundIndex != kUnboundIndex); if (deletingBound) { m_boundDetached = nullptr; m_boundIndex = 0; // the default VAO's slot always exists diff --git a/MobileGL/MG_Test/Backend/DirectGLES/BaseInstanceInjectionTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/BaseInstanceInjectionTest.cpp index 36cab57e..692edbad 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/BaseInstanceInjectionTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/BaseInstanceInjectionTest.cpp @@ -37,7 +37,7 @@ namespace { // The capability block is a process-global the backend fills in at init; restore whatever // was there so ordering between this suite and any other that touches it cannot matter. struct ScopedGLESCapabilitiesOverride { - ScopedGLESCapabilitiesOverride(): saved(g_GLESCapabilities) {} + ScopedGLESCapabilitiesOverride() : saved(g_GLESCapabilities) {} ~ScopedGLESCapabilitiesOverride() { g_GLESCapabilities = saved; } ScopedGLESCapabilitiesOverride(const ScopedGLESCapabilitiesOverride&) = delete; ScopedGLESCapabilitiesOverride& operator=(const ScopedGLESCapabilitiesOverride&) = delete; @@ -80,8 +80,7 @@ TEST(BaseInstanceInjectionGate, DriverWithVertexStorageBlocksGetsTheIndirectView g_GLESCapabilities.MaxShaderStorageBufferBindings = 13; g_GLESCapabilities.MaxVertexShaderStorageBlocks = 1; - const String rewritten = - PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); + const String rewritten = PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); EXPECT_TRUE(Contains(rewritten, "layout(std430, binding = 12) readonly buffer mg_IndirectParams")); EXPECT_TRUE(Contains(rewritten, "uniform highp int mg_BaseInstanceWordIndex;")); @@ -99,8 +98,7 @@ TEST(BaseInstanceInjectionGate, DriverWithoutVertexStorageBlocksDeclaresNoBlockA g_GLESCapabilities.MaxShaderStorageBufferBindings = 13; g_GLESCapabilities.MaxVertexShaderStorageBlocks = 0; - const String rewritten = - PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); + const String rewritten = PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); EXPECT_FALSE(Contains(rewritten, "mg_IndirectParams")) << rewritten; EXPECT_FALSE(Contains(rewritten, "buffer")); @@ -118,8 +116,7 @@ TEST(BaseInstanceInjectionGate, WithoutTheBlockBaseInstanceFallsBackToThePlainUn g_GLESCapabilities.MaxShaderStorageBufferBindings = 13; g_GLESCapabilities.MaxVertexShaderStorageBlocks = 0; - const String rewritten = - PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); + const String rewritten = PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); EXPECT_TRUE(Contains(rewritten, "uniform highp int mg_BaseInstance;")) << rewritten; EXPECT_TRUE(Contains(rewritten, "#define mg_BaseInstanceLowered (mg_BaseInstance)")) << rewritten; @@ -136,8 +133,7 @@ TEST(BaseInstanceInjectionGate, WithoutTheBlockInstanceIdRebaseCollapsesToIdenti g_GLESCapabilities.MaxShaderStorageBufferBindings = 13; g_GLESCapabilities.MaxVertexShaderStorageBlocks = 0; - const String rewritten = - PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); + const String rewritten = PromoteDrawParameterGlobalsToUniforms(kLoweredBaseInstanceVertexShader, GL_VERTEX_SHADER); EXPECT_TRUE(Contains(rewritten, "#define mg_ZeroBasedInstanceID gl_InstanceID")) << rewritten; EXPECT_FALSE(Contains(rewritten, "gl_InstanceID - (")); diff --git a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp index 85ca3753..8908ca6a 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/ContextEpochTest.cpp @@ -21,7 +21,7 @@ namespace MobileGL::MG_Backend::DirectGLES { SamplerImpl::BackendSamplerObject* GetRawDepthFetchSampler(); -} +} // namespace MobileGL::MG_Backend::DirectGLES #endif using namespace MobileGL; @@ -31,11 +31,21 @@ namespace { #if MOBILEGL_BUILD_DISAGGREGATED Uint unpackCalls = 0, samplerParameters = 0, nextNativeId = 100, boundXfb = 0; Vector unpackNames; - void GL_APIENTRY PixelStore(GLenum name, GLint) { ++unpackCalls; unpackNames.push_back(name); } - void GL_APIENTRY GenNames(GLsizei count, GLuint* names) { while (count-- > 0) *names++ = ++nextNativeId; } + void GL_APIENTRY PixelStore(GLenum name, GLint) { + ++unpackCalls; + unpackNames.push_back(name); + } + void GL_APIENTRY GenNames(GLsizei count, GLuint* names) { + while (count-- > 0) + *names++ = ++nextNativeId; + } void GL_APIENTRY DeleteNames(GLsizei, const GLuint*) {} - void GL_APIENTRY SamplerParam(GLuint, GLenum, GLint) { ++samplerParameters; } - void GL_APIENTRY BindXfb(GLenum, GLuint id) { boundXfb = id; } + void GL_APIENTRY SamplerParam(GLuint, GLenum, GLint) { + ++samplerParameters; + } + void GL_APIENTRY BindXfb(GLenum, GLuint id) { + boundXfb = id; + } void GL_APIENTRY NoArgs() {} void GL_APIENTRY BeginXfb(GLenum) {} void GL_APIENTRY XfbVaryings(GLuint, GLsizei, const GLchar* const*, GLenum) {} @@ -66,7 +76,7 @@ namespace { } }; #endif -} +} // namespace TEST(ContextEpochTest, UnpackDefaultIsRepublishedForANewNativeContextOrRole) { #if MOBILEGL_BUILD_DISAGGREGATED @@ -110,7 +120,6 @@ TEST(ContextEpochTest, RawDepthSamplerOwnsANativeIdForEachContextGeneration) { #endif } - TEST(ContextEpochTest, SameXfbNameInTwoServedContextsDoesNotAliasThePausedSpan) { #if MOBILEGL_BUILD_DISAGGREGATED DriverScope scope; @@ -187,10 +196,12 @@ TEST(ContextEpochTest, DualBlockApplierDiagnosticIsRoleLocalWhileSharedControlRe Session::NoteApplyThreadEnteredApplier(); serverSawItself = Session::ApplyThreadIsInsideApplier(); entered.store(true, std::memory_order_release); - while (!leave.load(std::memory_order_acquire)) std::this_thread::yield(); + while (!leave.load(std::memory_order_acquire)) + std::this_thread::yield(); Session::NoteApplyThreadLeftApplier(); }); - while (!entered.load(std::memory_order_acquire)) std::this_thread::yield(); + while (!entered.load(std::memory_order_acquire)) + std::this_thread::yield(); const Bool clientSawServer = Session::ApplyThreadIsInsideApplier(); leave.store(true, std::memory_order_release); server.join(); @@ -215,8 +226,12 @@ TEST(ContextEpochTest, DualBlockApplierDiagnosticIsRoleLocalWhileSharedControlRe namespace { Bool fakeScissor = false; Uint isEnabledCalls = 0; - void GL_APIENTRY FakeEnable(GLenum cap) { if (cap == GL_SCISSOR_TEST) fakeScissor = true; } - void GL_APIENTRY FakeDisable(GLenum cap) { if (cap == GL_SCISSOR_TEST) fakeScissor = false; } + void GL_APIENTRY FakeEnable(GLenum cap) { + if (cap == GL_SCISSOR_TEST) fakeScissor = true; + } + void GL_APIENTRY FakeDisable(GLenum cap) { + if (cap == GL_SCISSOR_TEST) fakeScissor = false; + } GLboolean GL_APIENTRY FakeIsEnabled(GLenum cap) { ++isEnabledCalls; return cap == GL_SCISSOR_TEST && fakeScissor ? GL_TRUE : GL_FALSE; @@ -233,8 +248,7 @@ namespace { // The driver's scissor as the copy inside the guard sees it. Bool ScissorDuringGuard() { Bool seen = true; - RenderStateImpl::RunScissorDisabledForTesting([](void* out) { *static_cast(out) = fakeScissor; }, - &seen); + RenderStateImpl::RunScissorDisabledForTesting([](void* out) { *static_cast(out) = fakeScissor; }, &seen); return seen; } } // namespace diff --git a/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp index f4ef6301..836e09d1 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/EsslShaderPassTest.cpp @@ -35,14 +35,14 @@ using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITE_ALIAS_PREFIX; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::IMAGE_WRITEONLY_ALIAS_PREFIX; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::ImageArrayUnitPlan; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::LowerZeroGradCubeArrayShadowLookups; -using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistancesInPerVertexBlock; -using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistanceInputsInPerVertexBlock; -using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemapImageArrayElementUnits; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::PointSizeExtensionName; +using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistanceInputsInPerVertexBlock; +using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RedeclareClipCullDistancesInPerVertexBlock; +using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemapImageArrayElementUnits; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RemoveLayoutBinding; -using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestPointSizeExtension; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestClipCullDistanceExtension; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestExtendedImageFormats; +using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestPointSizeExtension; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::RequestViewportArrayExtension; using MobileGL::MG_Backend::DirectGLES::PrgramImpl::SplitReadWriteImageUniforms; @@ -61,12 +61,22 @@ namespace { // The pass tags the name of every declaration it rewrites with the REPAIR it applied, so the // expectations have to spell the tag that matches how the fixture uses the image. - String RoAlias(const String& name) { return String(IMAGE_READONLY_ALIAS_PREFIX) + name; } - String WoAlias(const String& name) { return String(IMAGE_WRITEONLY_ALIAS_PREFIX) + name; } - String RwAlias(const String& name) { return String(IMAGE_SPLIT_READ_ALIAS_PREFIX) + name; } + String RoAlias(const String& name) { + return String(IMAGE_READONLY_ALIAS_PREFIX) + name; + } + String WoAlias(const String& name) { + return String(IMAGE_WRITEONLY_ALIAS_PREFIX) + name; + } + String RwAlias(const String& name) { + return String(IMAGE_SPLIT_READ_ALIAS_PREFIX) + name; + } // The writeonly half is minted from the ALREADY access-tagged name, so it carries both. - String WriteAlias(const String& name) { return String(IMAGE_WRITE_ALIAS_PREFIX) + name; } - String SplitWriteAlias(const String& name) { return WriteAlias(RwAlias(name)); } + String WriteAlias(const String& name) { + return String(IMAGE_WRITE_ALIAS_PREFIX) + name; + } + String SplitWriteAlias(const String& name) { + return WriteAlias(RwAlias(name)); + } // The scalar RemapImageArrayElementUnits declares for one element of a split image array. String Elem(const String& name, Int element) { return String(IMAGE_ARRAY_ELEMENT_PREFIX) + name + "_" + std::to_string(element); @@ -94,8 +104,8 @@ void main() // Both halves: same binding, same format, same type - which is what makes two image // variables on one image unit legal - and both `coherent`, which is what makes the store // through one of them visible to the load through the other. - EXPECT_TRUE(Contains(out, "layout(binding = 2, rgba8) uniform coherent readonly highp image2D " + - RwAlias("goku") + ";")) + EXPECT_TRUE( + Contains(out, "layout(binding = 2, rgba8) uniform coherent readonly highp image2D " + RwAlias("goku") + ";")) << out; EXPECT_TRUE(Contains(out, "layout(binding = 2, rgba8) uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) @@ -136,8 +146,8 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "layout(binding = 1, rgba16f) uniform readonly highp image2DArray " + - RoAlias("trunks") + ";")) + EXPECT_TRUE( + Contains(out, "layout(binding = 1, rgba16f) uniform readonly highp image2DArray " + RoAlias("trunks") + ";")) << out; EXPECT_TRUE(Contains(out, "imageLoad(" + RoAlias("trunks") + ",")); EXPECT_FALSE(Contains(out, "writeonly")); @@ -154,8 +164,7 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE( - Contains(out, "layout(binding = 3, rgba8) uniform writeonly highp image2D " + WoAlias("gohan") + ";")) + EXPECT_TRUE(Contains(out, "layout(binding = 3, rgba8) uniform writeonly highp image2D " + WoAlias("gohan") + ";")) << out; EXPECT_TRUE(Contains(out, "imageStore(" + WoAlias("gohan") + ",")); EXPECT_FALSE(Contains(out, "readonly")); @@ -189,11 +198,9 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "layout(binding = 0, rgba8) uniform readonly highp image2D " + - RoAlias("reader") + ";")) + EXPECT_TRUE(Contains(out, "layout(binding = 0, rgba8) uniform readonly highp image2D " + RoAlias("reader") + ";")) << out; - EXPECT_TRUE(Contains(out, "layout(binding = 1, rgba8) uniform writeonly highp image2D " + - WoAlias("writer") + ";")) + EXPECT_TRUE(Contains(out, "layout(binding = 1, rgba8) uniform writeonly highp image2D " + WoAlias("writer") + ";")) << out; EXPECT_TRUE(Contains(out, "imageStore(" + WoAlias("writer") + ",")) << out; EXPECT_TRUE(Contains(out, "imageLoad(" + RoAlias("reader") + ",")) << out; @@ -229,8 +236,8 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "layout(binding = 6, rgba8) uniform coherent readonly highp image2D " + - RwAlias("gohan") + "[3];")) + EXPECT_TRUE(Contains(out, "layout(binding = 6, rgba8) uniform coherent readonly highp image2D " + RwAlias("gohan") + + "[3];")) << out; EXPECT_TRUE(Contains(out, "layout(binding = 6, rgba8) uniform coherent writeonly highp image2D " + SplitWriteAlias("gohan") + "[3];")) @@ -256,14 +263,13 @@ void main() // goku is read+write -> split (and coherent with it); goku_hd is write-only -> qualified in // place, not split, and left non-coherent because nothing aliases it. Both are renamed. - EXPECT_TRUE(Contains(out, "layout(binding = 1, rgba8) uniform coherent readonly highp image2D " + - RwAlias("goku") + ";")) + EXPECT_TRUE( + Contains(out, "layout(binding = 1, rgba8) uniform coherent readonly highp image2D " + RwAlias("goku") + ";")) << out; EXPECT_TRUE(Contains(out, "layout(binding = 1, rgba8) uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) << out; - EXPECT_TRUE(Contains(out, "layout(binding = 2, rgba8) uniform writeonly highp image2D " + - WoAlias("goku_hd") + ";")) + EXPECT_TRUE(Contains(out, "layout(binding = 2, rgba8) uniform writeonly highp image2D " + WoAlias("goku_hd") + ";")) << out; EXPECT_TRUE(Contains(out, "imageStore(" + WoAlias("goku_hd") + ",")); EXPECT_FALSE(Contains(out, SplitWriteAlias("goku") + "_hd")); @@ -281,10 +287,8 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "uniform readonly coherent restrict highp image2D " + RwAlias("goku") + ";")) - << out; - EXPECT_TRUE( - Contains(out, "uniform writeonly coherent restrict highp image2D " + SplitWriteAlias("goku") + ";")) + EXPECT_TRUE(Contains(out, "uniform readonly coherent restrict highp image2D " + RwAlias("goku") + ";")) << out; + EXPECT_TRUE(Contains(out, "uniform writeonly coherent restrict highp image2D " + SplitWriteAlias("goku") + ";")) << out; // ...and the coherent the split adds is not a SECOND one: a repeated memory qualifier is a // compile error in ESSL, so the source's own has to be recognized. @@ -311,8 +315,7 @@ void main() )"; const String out = SplitReadWriteImageUniforms(source); EXPECT_TRUE(Contains(out, "uniform coherent readonly highp image2D " + RwAlias("goku") + ";")) << out; - EXPECT_TRUE(Contains(out, "uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) - << out; + EXPECT_TRUE(Contains(out, "uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) << out; // Exactly the two halves of the pair, and nothing else: the store-only image is repaired in // place, has no alias to stay visible to, and must not pay for uncached access. EXPECT_EQ(CountOf(out, "coherent"), 2u); @@ -377,8 +380,8 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "max(imageLoad(" + RwAlias("gohan") + - "[2], ivec2(0)), vec4(0.5))); memoryBarrierImage();")) + EXPECT_TRUE( + Contains(out, "max(imageLoad(" + RwAlias("gohan") + "[2], ivec2(0)), vec4(0.5))); memoryBarrierImage();")) << out; EXPECT_EQ(CountOf(out, "memoryBarrierImage();"), 1u) << out; } @@ -430,8 +433,8 @@ void main() } )"; const String out = SplitReadWriteImageUniforms(source); - EXPECT_TRUE(Contains(out, "layout(binding = 8, rgba8ui) uniform readonly highp uimage2D " + - RoAlias("sizeOnly") + ";")) + EXPECT_TRUE( + Contains(out, "layout(binding = 8, rgba8ui) uniform readonly highp uimage2D " + RoAlias("sizeOnly") + ";")) << out; // The rename has to reach imageSize too, or the declaration and its only use stop agreeing. EXPECT_TRUE(Contains(out, "imageSize(" + RoAlias("sizeOnly") + ")")) << out; @@ -593,8 +596,7 @@ void main() const String first = SplitReadWriteImageUniforms(stage); const String second = SplitReadWriteImageUniforms(stage); EXPECT_EQ(first, second); - EXPECT_TRUE(Contains(first, "uniform coherent readonly highp image2D " + RoAlias("g_image") + ";")) - << first; + EXPECT_TRUE(Contains(first, "uniform coherent readonly highp image2D " + RoAlias("g_image") + ";")) << first; } // The other side of that coin, and the one a per-STAGE tag got wrong. Two stages that use the @@ -630,8 +632,7 @@ void main() // back into the single image uniform the application declared. for (const String& out : {vsOut, fsOut}) { EXPECT_TRUE(Contains(out, "uniform coherent readonly highp image2D " + RwAlias("goku") + ";")) << out; - EXPECT_TRUE(Contains(out, "uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) - << out; + EXPECT_TRUE(Contains(out, "uniform coherent writeonly highp image2D " + SplitWriteAlias("goku") + ";")) << out; EXPECT_TRUE(Contains(out, "imageLoad(" + RwAlias("goku") + ",")) << out; EXPECT_TRUE(Contains(out, "imageStore(" + SplitWriteAlias("goku") + ",")) << out; } @@ -693,8 +694,7 @@ void main() // units 0, 1, 2, 3. TEST(RemapImageArrayElementUnitsTest, NonConsecutiveUnitsSplitIntoOneScalarPerElement) { Vector declined; - const String out = - RemapImageArrayElementUnits(kSsoImageArrayFS, {Plan("g_image", {0, 2, 4, 6})}, &declined); + const String out = RemapImageArrayElementUnits(kSsoImageArrayFS, {Plan("g_image", {0, 2, 4, 6})}, &declined); EXPECT_TRUE(declined.empty()) << (declined.empty() ? String() : declined[0]); const Int units[4] = {0, 2, 4, 6}; @@ -702,8 +702,7 @@ TEST(RemapImageArrayElementUnitsTest, NonConsecutiveUnitsSplitIntoOneScalarPerEl EXPECT_TRUE(Contains(out, "layout(rgba32f, binding = " + std::to_string(units[element]) + ") uniform writeonly highp image2D " + Elem("g_image", element) + ";")) << out; - EXPECT_TRUE(Contains(out, "imageStore(" + Elem("g_image", element) + ", ivec2(gl_FragCoord.xy)")) - << out; + EXPECT_TRUE(Contains(out, "imageStore(" + Elem("g_image", element) + ", ivec2(gl_FragCoord.xy)")) << out; } // The array is gone entirely; nothing may still address units 0,1,2,3 through it. EXPECT_FALSE(Contains(out, "image2D g_image[4];")) << out; @@ -727,8 +726,8 @@ void main() const String out = RemapImageArrayElementUnits(source, {Plan("g_image", {3, 1, 7, 5})}); const Int units[4] = {3, 1, 7, 5}; for (Int element = 0; element < 4; ++element) { - EXPECT_TRUE(Contains(out, "binding = " + std::to_string(units[element]) + - ") uniform writeonly highp image2D " + Elem("g_image", element) + ";")) + EXPECT_TRUE(Contains(out, "binding = " + std::to_string(units[element]) + ") uniform writeonly highp image2D " + + Elem("g_image", element) + ";")) << out; } // Only elements 0 and 3 are ever accessed; elements 1 and 2 are declared and unused, because @@ -783,8 +782,7 @@ void main() } )"; Vector outOfRangeDeclined; - EXPECT_EQ(RemapImageArrayElementUnits(outOfRange, {Plan("g_image", {0, 5})}, &outOfRangeDeclined), - outOfRange); + EXPECT_EQ(RemapImageArrayElementUnits(outOfRange, {Plan("g_image", {0, 5})}, &outOfRangeDeclined), outOfRange); ASSERT_EQ(outOfRangeDeclined.size(), 1u); } @@ -913,8 +911,7 @@ void main() // Element 1 sits on unit 9, and both halves of its split pair say so. EXPECT_EQ(CountOf(out, "binding = 9"), 2u) << out; EXPECT_TRUE(Contains(out, "readonly highp image2D " + RwAlias(Elem("g_image", 1)) + ";")) << out; - EXPECT_TRUE(Contains(out, "writeonly highp image2D " + WriteAlias(RwAlias(Elem("g_image", 1))) + ";")) - << out; + EXPECT_TRUE(Contains(out, "writeonly highp image2D " + WriteAlias(RwAlias(Elem("g_image", 1))) + ";")) << out; EXPECT_EQ(CountOf(out, "binding = 4"), 1u) << out; } @@ -986,9 +983,11 @@ void main() TEST(RetargetTextureBufferExtensionTest, OesOnlyDriverGetsTheOesDirective) { const String out = RetargetTextureBufferExtension(kBufferTextureShader, Tier::ExtensionOES); EXPECT_TRUE(Contains(out, "#extension GL_OES_texture_buffer : require")) - << "the OES driver's own spelling must reach the directive:\n" << out; + << "the OES driver's own spelling must reach the directive:\n" + << out; EXPECT_FALSE(Contains(out, "GL_EXT_texture_buffer")) - << "the EXT spelling this driver does not advertise must be gone:\n" << out; + << "the EXT spelling this driver does not advertise must be gone:\n" + << out; // Only the directive changes; the declaration and the fetch are identical between the two // extensions and must not be touched. EXPECT_TRUE(Contains(out, "uniform highp isamplerBuffer CloudFaces;")); @@ -996,17 +995,14 @@ TEST(RetargetTextureBufferExtensionTest, OesOnlyDriverGetsTheOesDirective) { } TEST(RetargetTextureBufferExtensionTest, ExtDriverKeepsWhatSpirvCrossEmitted) { - EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::ExtensionEXT), - kBufferTextureShader); + EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::ExtensionEXT), kBufferTextureShader); } // ES 3.2 needs no directive at all, and SPIRV-Cross emits none at ESSL 320 - but a shader // that arrived with one anyway must not be rewritten to a name the pass was not asked for. TEST(RetargetTextureBufferExtensionTest, CoreAndUnsupportedTiersAreNoOps) { - EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::CoreEs32), - kBufferTextureShader); - EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::None), - kBufferTextureShader); + EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::CoreEs32), kBufferTextureShader); + EXPECT_EQ(RetargetTextureBufferExtension(kBufferTextureShader, Tier::None), kBufferTextureShader); } // The name is only the subject of a rewrite where it is the subject of an #extension @@ -1065,7 +1061,8 @@ void main() {} const String out = RetargetTextureBufferExtension(source, Tier::ExtensionOES); EXPECT_TRUE(Contains(out, "# extension GL_OES_texture_buffer : require")) << out; EXPECT_TRUE(Contains(out, "#extension GL_EXT_shader_io_blocks : require")) - << "an unrelated extension must survive untouched:\n" << out; + << "an unrelated extension must survive untouched:\n" + << out; EXPECT_EQ(CountOf(out, "GL_OES_texture_buffer"), 1u); } @@ -1368,8 +1365,8 @@ layout(points) in; layout(points, max_vertices = 1) out; void main() { gl_ViewportIndex = 1; gl_PointSize = 2.0; EmitVertex(); } )"; - const String out = RequestPointSizeExtension(RequestViewportArrayExtension(source, true), - "GL_EXT_geometry_point_size"); + const String out = + RequestPointSizeExtension(RequestViewportArrayExtension(source, true), "GL_EXT_geometry_point_size"); EXPECT_EQ(out.find("#version 320 es"), 0u) << out; EXPECT_TRUE(Contains(out, "#extension GL_OES_viewport_array : require\n")) << out; EXPECT_TRUE(Contains(out, "#extension GL_EXT_geometry_point_size : require\n")) << out; @@ -1407,9 +1404,7 @@ TEST(PassthroughTessControlEsslTest, DeclaresThePatchSizeAndWritesEveryTessLevel const String out = BuildPassthroughTessControlEssl(320, 4, "", "", kDefaultOuter, kDefaultInner); EXPECT_EQ(out.find("#version 320 es"), 0u) << out; EXPECT_TRUE(Contains(out, "layout(vertices = 4) out;")) << out; - EXPECT_TRUE(Contains(out, - "gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;")) - << out; + EXPECT_TRUE(Contains(out, "gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;")) << out; // All six, unconditionally: writing a level the evaluation stage's domain does not use is // legal and ignored, and it saves the generator from having to know the domain. for (const char* level : {"gl_TessLevelOuter[0]", "gl_TessLevelOuter[1]", "gl_TessLevelOuter[2]", @@ -1425,8 +1420,8 @@ TEST(PassthroughTessControlEsslTest, DeclaresThePatchSizeAndWritesEveryTessLevel // entry point to forward it to, so a generator that ignored these arguments would tessellate every // control-stage-less program at level 1 whatever the application asked for. TEST(PassthroughTessControlEsslTest, BakesTheDefaultTessLevelsIn) { - const String out = BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(2.0f, 3.0f, 4.0f, 5.0f), - FloatVec2(6.5f, 7.25f)); + const String out = + BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(2.0f, 3.0f, 4.0f, 5.0f), FloatVec2(6.5f, 7.25f)); EXPECT_TRUE(Contains(out, "gl_TessLevelOuter[0] = 2.0;")) << out; EXPECT_TRUE(Contains(out, "gl_TessLevelOuter[1] = 3.0;")) << out; EXPECT_TRUE(Contains(out, "gl_TessLevelOuter[2] = 4.0;")) << out; @@ -1439,8 +1434,8 @@ TEST(PassthroughTessControlEsslTest, BakesTheDefaultTessLevelsIn) { // `gl_TessLevelOuter[0] = 1;` as an int assigned to a float and refuses to compile the stage, // which would take the whole program down with it. TEST(PassthroughTessControlEsslTest, SpellsIntegralLevelsAsFloatLiterals) { - const String out = BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(2.0f, 2.0f, 2.0f, 2.0f), - FloatVec2(2.0f, 2.0f)); + const String out = + BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(2.0f, 2.0f, 2.0f, 2.0f), FloatVec2(2.0f, 2.0f)); EXPECT_FALSE(Contains(out, "= 2;")) << out; } @@ -1452,8 +1447,7 @@ TEST(PassthroughTessControlEsslTest, SpellsIntegralLevelsAsFloatLiterals) { TEST(PassthroughTessControlEsslTest, NonFiniteLevelsFollowTheDiscardRule) { const Float notANumber = std::numeric_limits::quiet_NaN(); const Float infinity = std::numeric_limits::infinity(); - const String out = BuildPassthroughTessControlEssl(320, 4, "", "", - FloatVec4(notANumber, -infinity, infinity, 1.0f), + const String out = BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(notANumber, -infinity, infinity, 1.0f), FloatVec2(notANumber, 1.0f)); EXPECT_TRUE(Contains(out, "gl_TessLevelOuter[0] = 0.0;")) << out; EXPECT_TRUE(Contains(out, "gl_TessLevelOuter[1] = 0.0;")) << out; @@ -1467,9 +1461,8 @@ TEST(PassthroughTessControlEsslTest, NonFiniteLevelsFollowTheDiscardRule) { // A level below the old six-decimal format's resolution is still a POSITIVE level, which GL clamps // to 1 and draws; rendering it as "0.000000" discarded the patch instead. TEST(PassthroughTessControlEsslTest, TinyPositiveLevelsDoNotFlushToZero) { - const String out = BuildPassthroughTessControlEssl(320, 4, "", "", - FloatVec4(1e-7f, 1.0f, 1.0f, 1.0f), - FloatVec2(1.0f, 1.0f)); + const String out = + BuildPassthroughTessControlEssl(320, 4, "", "", FloatVec4(1e-7f, 1.0f, 1.0f, 1.0f), FloatVec2(1.0f, 1.0f)); EXPECT_FALSE(Contains(out, "gl_TessLevelOuter[0] = 0.0;")) << out; EXPECT_FALSE(Contains(out, "gl_TessLevelOuter[0] = 0.000000;")) << out; } diff --git a/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp index 162845b3..17b14d66 100644 --- a/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectGLES/SubsystemDepsTest.cpp @@ -106,30 +106,26 @@ TEST(SubsystemDepsTest, EveryRowNamesExactlyOneFamilyAndNoRowRequiresItself) { } // The families the rule is about. Bit 8 and bit 13 are in the table too, which is the half // MGPipe.h's old "THREE OF THEM" comment was missing. - EXPECT_EQ(seen, kMGPipeSubsystemVertexInput | kMGPipeSubsystemFramebuffer | - kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers | - kMGPipeSubsystemPrograms | kMGPipeSubsystemBufferBindings); + EXPECT_EQ(seen, kMGPipeSubsystemVertexInput | kMGPipeSubsystemFramebuffer | kMGPipeSubsystemTextureResources | + kMGPipeSubsystemSamplers | kMGPipeSubsystemPrograms | kMGPipeSubsystemBufferBindings); } TEST(SubsystemDepsTest, TheFourthRowIsPresentAndTheWithdrawnMirrorPairIsNotFine) { // ID-14/ID-15. This is the row two comments in the tree denied existed, and the reason this // case is written as an assertion rather than a paragraph. - EXPECT_NE(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) & kMGPipeSubsystemSamplers, - 0ull) + EXPECT_NE(MGPipeSubsystemRequires(kMGPipeSubsystemTextureResources) & kMGPipeSubsystemSamplers, 0ull) << "bit 10 no longer requires bit 11. If that is intended, the withdrawal recorded in " "PipeFill.cpp and in BRIEF-P4A's amendment has been reversed and Espryt's " "ResolveTextureResourceSubsystemArm must stop refusing 0x7ff in the same commit"; EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, 0x7ffull)); - EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet( - kMGPipeSubsystemSamplers, - kMGPipeSubsystemsMigratedAtP5e & ~kMGPipeSubsystemTextureResources)) + EXPECT_FALSE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemSamplers, + kMGPipeSubsystemsMigratedAtP5e & ~kMGPipeSubsystemTextureResources)) << "bit 11 without bit 10 must not be live - the symmetric half of the fourth row"; // ...and the mirrors that really are fine. - EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemPrograms, 0ull)) - << "bit 12 depends on nothing"; - EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet( - kMGPipeSubsystemTextureResources, - kMGPipeSubsystemResources | kMGPipeSubsystemSamplers | kMGPipeSubsystemTextureResources)) + EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemPrograms, 0ull)) << "bit 12 depends on nothing"; + EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(kMGPipeSubsystemTextureResources, + kMGPipeSubsystemResources | kMGPipeSubsystemSamplers | + kMGPipeSubsystemTextureResources)) << "bit 10 with 7 and 11 and WITHOUT bit 9 is fine and is not a row"; } @@ -148,16 +144,15 @@ TEST(SubsystemDepsTest, TheFourthRowIsPresentAndTheWithdrawnMirrorPairIsNotFine) // disagreement means records crossing to a server whose matching handle arm did not come up. // --------------------------------------------------------------------------------------------- TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) { - const Uint64 families[] = {kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources, - kMGPipeSubsystemSamplers, kMGPipeSubsystemPrograms}; + const Uint64 families[] = {kMGPipeSubsystemFramebuffer, kMGPipeSubsystemTextureResources, kMGPipeSubsystemSamplers, + kMGPipeSubsystemPrograms}; int emitted = 0; for (const Uint64 mask : InterestingMasks()) { for (const Uint64 family : families) { if (!MGPipeP4aFamilyEmits(family, mask)) continue; ++emitted; EXPECT_TRUE(MGPipeSubsystemDependenciesAreSet(family, mask)) - << "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the CLIENT emits family " - << Describe(family) + << "at MOBILEGL_PIPE_PUSH=" << Describe(mask) << " the CLIENT emits family " << Describe(family) << " while MG_Pipe/SubsystemDeps.def says its dependency bits are not all set. " "Those records reach a server whose matching handle arm refused to come up - " "the half-run subsystem D-K2 exists to make impossible. The two statements of " @@ -172,6 +167,3 @@ TEST(SubsystemDepsTest, TheClientNeverEmitsAFamilyTheTableRefuses) { // to have pulled in a consumer registration. RecordProperty("client_emitting_combinations", emitted); } - - - diff --git a/MobileGL/MG_Test/Backend/DirectVulkan/SanityTest.cpp b/MobileGL/MG_Test/Backend/DirectVulkan/SanityTest.cpp index f72655bc..cadbdb4c 100644 --- a/MobileGL/MG_Test/Backend/DirectVulkan/SanityTest.cpp +++ b/MobileGL/MG_Test/Backend/DirectVulkan/SanityTest.cpp @@ -39,9 +39,8 @@ TEST(DirectVulkanSanity, ExtensionEnumeration) { std::vector extensions(extensionCount); vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); - for(const auto& extension : extensions) { - std::cout << extension.extensionName - << " (r." << extension.specVersion << ")\n"; + for (const auto& extension : extensions) { + std::cout << extension.extensionName << " (r." << extension.specVersion << ")\n"; } } @@ -52,7 +51,7 @@ TEST(DirectVulkanSanity, WindowCreation) { glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); GLFWwindow* window = glfwCreateWindow(800, 600, "MobileGL WindowCreation", nullptr, nullptr); - while(!glfwWindowShouldClose(window)) { + while (!glfwWindowShouldClose(window)) { glfwPollEvents(); } @@ -64,11 +63,11 @@ TEST(DirectVulkanSanity, WindowCreation) { #define EGLAPI #include #ifdef _WIN32 - #define GLFW_EXPOSE_NATIVE_WIN32 +#define GLFW_EXPOSE_NATIVE_WIN32 #elif defined(__linux__) - #define GLFW_EXPOSE_NATIVE_X11 +#define GLFW_EXPOSE_NATIVE_X11 #elif defined(__APPLE__) - #define GLFW_EXPOSE_NATIVE_COCOA +#define GLFW_EXPOSE_NATIVE_COCOA #endif #include #ifdef __APPLE__ @@ -79,12 +78,7 @@ TEST(DirectVulkanSanity, WindowCreation) { TEST(DirectVulkanSanity, ContextCreation) { glfwInit(); - static EGLint const attribute_list[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_NONE - }; + static EGLint const attribute_list[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE}; EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(display, nullptr, nullptr); @@ -122,7 +116,7 @@ TEST(DirectVulkanSanity, ContextCreation) { EGLSurface surface = eglCreateWindowSurface(display, config, nativewindow, nullptr); eglMakeCurrent(display, surface, surface, context); - while(!glfwWindowShouldClose(window)) { + while (!glfwWindowShouldClose(window)) { glfwPollEvents(); eglSwapBuffers(display, surface); } diff --git a/MobileGL/MG_Test/Backend/GpuHangWatchTest.cpp b/MobileGL/MG_Test/Backend/GpuHangWatchTest.cpp index dcd33e75..a1bfdeff 100644 --- a/MobileGL/MG_Test/Backend/GpuHangWatchTest.cpp +++ b/MobileGL/MG_Test/Backend/GpuHangWatchTest.cpp @@ -83,7 +83,8 @@ TEST(GpuHangDetectorTest, TheBudgetRunsFromTheFirstSampleThatSawItRunning) { TEST(GpuHangDetectorTest, AZeroBudgetNamesNothing) { Watch::Detector detector(0); - for (Uint64 now = 0; now <= 60000; now += 1000) EXPECT_EQ(detector.Observe(One(1, true, false), now), 0u); + for (Uint64 now = 0; now <= 60000; now += 1000) + EXPECT_EQ(detector.Observe(One(1, true, false), now), 0u); } namespace { @@ -137,7 +138,7 @@ TEST(GpuHangWatchThreadTest, TheHangingDeviceIsNamedOnceAndTheOneWaitingBehindIt const ScopedWatchTiming timing(20, 200); FakeDevice guilty; FakeDevice innocent; - guilty.started = true; // the GPU runs it and it never ends + guilty.started = true; // the GPU runs it and it never ends innocent.started = false; // queued behind it for the whole stall const Uint64 a = Watch::Register(&guilty, &FakeDevice::Sample, &FakeDevice::Hung); const Uint64 b = Watch::Register(&innocent, &FakeDevice::Sample, &FakeDevice::Hung); @@ -145,7 +146,8 @@ TEST(GpuHangWatchThreadTest, TheHangingDeviceIsNamedOnceAndTheOneWaitingBehindIt ASSERT_NE(b, 0u); const auto start = std::chrono::steady_clock::now(); EXPECT_TRUE(PollUntil([&] { return guilty.named.load() > 0; }, 3000)); - const auto ms = std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); + const auto ms = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); EXPECT_GE(ms, 180) << "named before the budget ran out"; EXPECT_LE(ms, 1500) << "named far past budget + interval"; EXPECT_EQ(guilty.namedId.load(), 42u); diff --git a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp index d10294fd..32fe83ac 100644 --- a/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp +++ b/MobileGL/MG_Test/BackendLoader/BackendLoaderTest.cpp @@ -147,7 +147,9 @@ namespace { FakeDriverState g_fake; - void ResetFakeDriver() { g_fake = FakeDriverState{}; } + void ResetFakeDriver() { + g_fake = FakeDriverState{}; + } std::vector* StoreOfBufferBoundTo(GLenum target) { const auto boundIt = g_fake.boundBuffers.find(target); @@ -473,7 +475,8 @@ namespace { funcs.glIsEnabled = [](GLenum) -> GLboolean { return GL_FALSE; }; funcs.glGetBooleanv = [](GLenum, GLboolean* data) { if (data == nullptr) return; - for (int i = 0; i < 4; ++i) data[i] = GL_TRUE; + for (int i = 0; i < 4; ++i) + data[i] = GL_TRUE; }; funcs.glGetIntegeri_v = [](GLenum pname, GLuint index, GLint* data) { if (data == nullptr) return; @@ -506,9 +509,8 @@ namespace { auto* out = static_cast(pixels); if (out == nullptr) return; const std::size_t index = g_fake.ioBlockReads++; - const bool arrives = index < g_fake.ioBlockPayloadArrives.size() - ? g_fake.ioBlockPayloadArrives[index] - : true; + const bool arrives = + index < g_fake.ioBlockPayloadArrives.size() ? g_fake.ioBlockPayloadArrives[index] : true; // 0.25 and 0.5 as the probe's vertex stage wrote them; zeroes are what a stage that // received nothing reads. out[0] = arrives ? 0x40 : 0x00; @@ -539,18 +541,18 @@ namespace { // Buffer-texture entry points, each present only when its knob says so. A real loader // resolves the suffixed names only on a driver whose support is that extension. - funcs.glTexBuffer = g_fake.hasCoreTexBufferEntryPoint - ? static_cast( - [](GLenum, GLenum, GLuint) {}) - : nullptr; - funcs.glTexBufferEXT = g_fake.hasExtTexBufferEntryPoint - ? static_cast( - [](GLenum, GLenum, GLuint) {}) - : nullptr; - funcs.glTexBufferOES = g_fake.hasOesTexBufferEntryPoint - ? static_cast( - [](GLenum, GLenum, GLuint) {}) - : nullptr; + funcs.glTexBuffer = + g_fake.hasCoreTexBufferEntryPoint + ? static_cast([](GLenum, GLenum, GLuint) {}) + : nullptr; + funcs.glTexBufferEXT = + g_fake.hasExtTexBufferEntryPoint + ? static_cast([](GLenum, GLenum, GLuint) {}) + : nullptr; + funcs.glTexBufferOES = + g_fake.hasOesTexBufferEntryPoint + ? static_cast([](GLenum, GLenum, GLuint) {}) + : nullptr; // The probe's vertex shader writes the gl_InstanceID it observed into the // result SSBO at binding 0. A conforming driver observes 0; a leaking one @@ -1169,21 +1171,23 @@ TEST(FragmentInterpolationCapabilities, QueryErrorIsDrainedAndFallsBackToCoreMin // The extension string is what apps gate on (LWJGL builds GLCapabilities from it), so advertising // it on a driver that cannot filter anisotropically would leave them silently on trilinear. TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSupportsIt) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; - const auto without = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false); + const auto without = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false); EXPECT_FALSE(contains(without, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_FALSE(contains(without, MobileGL::E_GL_ARB_texture_filter_anisotropic)); - const auto with = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false, false, false); + const auto with = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, true, false, false, false, false); EXPECT_TRUE(contains(with, MobileGL::E_GL_EXT_texture_filter_anisotropic)); EXPECT_TRUE(contains(with, MobileGL::E_GL_ARB_texture_filter_anisotropic)); // Same rule on the Vulkan backend, where the gate is the samplerAnisotropy device feature. - const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false); + const auto vkWithout = + MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false); EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_EXT_texture_filter_anisotropic)); const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, true, false, false); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_EXT_texture_filter_anisotropic)); @@ -1196,8 +1200,7 @@ TEST(TextureAnisotropyCapabilities, ExtensionIsAdvertisedOnlyWhenTheHostDriverSu // and it has to BE there when the capability is, because KHR-GL4*.texture_gather.*-cube-array // gates on the string with no core-version fallback. TEST(CubeMapArrayAdvertisement, FollowsTheHostCapabilityOnBothBackends) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; @@ -1208,8 +1211,8 @@ TEST(CubeMapArrayAdvertisement, FollowsTheHostCapabilityOnBothBackends) { MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, true); EXPECT_TRUE(contains(esWith, MobileGL::E_GL_ARB_texture_cube_map_array)); - const auto vkWithout = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, - false); + const auto vkWithout = + MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false); EXPECT_FALSE(contains(vkWithout, MobileGL::E_GL_ARB_texture_cube_map_array)); const auto vkWith = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, true); EXPECT_TRUE(contains(vkWith, MobileGL::E_GL_ARB_texture_cube_map_array)); @@ -1221,22 +1224,26 @@ TEST(CubeMapArrayAdvertisement, FollowsTheHostCapabilityOnBothBackends) { // together so a future edit cannot quietly drop one, and pinned on BOTH backends so the two // cannot disagree about what MobileGL is. TEST(CorePlumbingAdvertisement, IsUnconditionalAndIdenticalOnBothBackends) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; - const MobileGL::GLExtension expected[] = { - MobileGL::E_GL_ARB_sync, MobileGL::E_GL_ARB_shader_atomic_counters, - MobileGL::E_GL_ARB_vertex_array_object, MobileGL::E_GL_ARB_sampler_objects, - MobileGL::E_GL_ARB_map_buffer_range, MobileGL::E_GL_ARB_copy_buffer, - MobileGL::E_GL_ARB_copy_image, MobileGL::E_GL_ARB_texture_swizzle, - MobileGL::E_GL_ARB_vertex_type_2_10_10_10_rev, MobileGL::E_GL_ARB_texture_rg, - MobileGL::E_GL_ARB_depth_buffer_float, MobileGL::E_GL_ARB_texture_float, - MobileGL::E_GL_ARB_viewport_array}; + const MobileGL::GLExtension expected[] = {MobileGL::E_GL_ARB_sync, + MobileGL::E_GL_ARB_shader_atomic_counters, + MobileGL::E_GL_ARB_vertex_array_object, + MobileGL::E_GL_ARB_sampler_objects, + MobileGL::E_GL_ARB_map_buffer_range, + MobileGL::E_GL_ARB_copy_buffer, + MobileGL::E_GL_ARB_copy_image, + MobileGL::E_GL_ARB_texture_swizzle, + MobileGL::E_GL_ARB_vertex_type_2_10_10_10_rev, + MobileGL::E_GL_ARB_texture_rg, + MobileGL::E_GL_ARB_depth_buffer_float, + MobileGL::E_GL_ARB_texture_float, + MobileGL::E_GL_ARB_viewport_array}; // Every gate off: none of these may depend on one. - const auto es = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, - false); + const auto es = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false); const auto vk = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false); for (const auto extension : expected) { EXPECT_TRUE(contains(es, extension)) << "DirectGLES stopped advertising extension " << extension; @@ -1249,18 +1256,17 @@ TEST(CorePlumbingAdvertisement, IsUnconditionalAndIdenticalOnBothBackends) { // the same surface. Unconditional on both backends; only the tessellation string follows a probed // device capability. TEST(CoreSurfaceAdvertisement, BatchIsUnconditionalAndTessellationFollowsTheDevice) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; const MobileGL::GLExtension expected[] = { - MobileGL::E_GL_ARB_transform_feedback2, MobileGL::E_GL_ARB_transform_feedback3, - MobileGL::E_GL_ARB_texture_rgb10_a2ui, MobileGL::E_GL_EXT_texture_shared_exponent, - MobileGL::E_GL_EXT_texture_integer, MobileGL::E_GL_ARB_pipeline_statistics_query, + MobileGL::E_GL_ARB_transform_feedback2, MobileGL::E_GL_ARB_transform_feedback3, + MobileGL::E_GL_ARB_texture_rgb10_a2ui, MobileGL::E_GL_EXT_texture_shared_exponent, + MobileGL::E_GL_EXT_texture_integer, MobileGL::E_GL_ARB_pipeline_statistics_query, MobileGL::E_GL_EXT_shader_integer_mix}; - const auto es = MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, - false); + const auto es = + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false); const auto vk = MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false); for (const auto extension : expected) { EXPECT_TRUE(contains(es, extension)) << "DirectGLES does not advertise extension " << extension; @@ -1273,33 +1279,32 @@ TEST(CoreSurfaceAdvertisement, BatchIsUnconditionalAndTessellationFollowsTheDevi EXPECT_FALSE(contains(es, MobileGL::E_GL_ARB_tessellation_shader)); EXPECT_FALSE(contains(vk, MobileGL::E_GL_ARB_tessellation_shader)); const MobileGL::MG_Backend::AdvertisedFeatureSupport tessellation{.TessellationShader = true}; - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, - false, false, tessellation), - MobileGL::E_GL_ARB_tessellation_shader)); - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, - false, tessellation), + EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, + false, tessellation), MobileGL::E_GL_ARB_tessellation_shader)); + EXPECT_TRUE(contains( + MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false, tessellation), + MobileGL::E_GL_ARB_tessellation_shader)); } // ARB_cull_distance follows the backend's cull-distance capability, never the context version. TEST(CoreSurfaceAdvertisement, CullDistanceFollowsTheDevice) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; - EXPECT_FALSE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, - false, false), - MobileGL::E_GL_ARB_cull_distance)); - EXPECT_FALSE(contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, - false), - MobileGL::E_GL_ARB_cull_distance)); + EXPECT_FALSE( + contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), + MobileGL::E_GL_ARB_cull_distance)); + EXPECT_FALSE( + contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false), + MobileGL::E_GL_ARB_cull_distance)); const MobileGL::MG_Backend::AdvertisedFeatureSupport cull{.CullDistance = true}; - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, - false, false, cull), - MobileGL::E_GL_ARB_cull_distance)); - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, - false, cull), - MobileGL::E_GL_ARB_cull_distance)); + EXPECT_TRUE(contains( + MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false, cull), + MobileGL::E_GL_ARB_cull_distance)); + EXPECT_TRUE( + contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false, cull), + MobileGL::E_GL_ARB_cull_distance)); } // ES 3.2 has tessellation in core; ES 3.1 only through GL_EXT_tessellation_shader. Either way the @@ -1367,28 +1372,26 @@ TEST(ClipControlCapabilities, NeedTheExtensionAndTheEntryPoint) { // DirectGLES advertises ARB_clip_control on the driver's extension; DirectVulkan always, since it // applies clip control in its own position fixup. TEST(CoreSurfaceAdvertisement, ClipControlFollowsTheBackendsRoute) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; - EXPECT_FALSE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, - false, false), - MobileGL::E_GL_ARB_clip_control)); + EXPECT_FALSE( + contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), + MobileGL::E_GL_ARB_clip_control)); const MobileGL::MG_Backend::AdvertisedFeatureSupport clipControl{.ClipControl = true}; - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, - false, false, clipControl), - MobileGL::E_GL_ARB_clip_control)); - EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, - false), + EXPECT_TRUE(contains(MobileGL::MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, + false, clipControl), MobileGL::E_GL_ARB_clip_control)); + EXPECT_TRUE( + contains(MobileGL::MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false), + MobileGL::E_GL_ARB_clip_control)); } // Minecraft 26.3 checks ARB_draw_indirect before it considers the already-advertised // ARB_multi_draw_indirect, then separately requires ARB_base_instance before enabling its terrain // indirect path. Pin both strings and, just as importantly, the non-zero firstInstance gate. TEST(IndirectDrawAdvertisement, MatchesEachBackendsUsableCommandSemantics) { - const auto contains = [](const MobileGL::Vector& extensions, - MobileGL::GLExtension wanted) { + const auto contains = [](const MobileGL::Vector& extensions, MobileGL::GLExtension wanted) { return std::find(extensions.begin(), extensions.end(), wanted) != extensions.end(); }; @@ -1467,8 +1470,8 @@ TEST(MultiDrawCapabilities, PointerAloneNeverCountsAsSupport) { // Simulate the stub hazard: every pointer resolved, no extension advertised. funcs.glMultiDrawArraysIndirectEXT = [](GLenum, const void*, GLsizei, GLsizei) {}; funcs.glMultiDrawElementsIndirectEXT = [](GLenum, GLenum, const void*, GLsizei, GLsizei) {}; - funcs.glMultiDrawElementsBaseVertexEXT = [](GLenum, const GLsizei*, GLenum, const void* const*, - GLsizei, const GLint*) {}; + funcs.glMultiDrawElementsBaseVertexEXT = [](GLenum, const GLsizei*, GLenum, const void* const*, GLsizei, + const GLint*) {}; MobileGL::MG_External::GLESCapabilities stubCaps; ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(stubCaps, funcs)); @@ -1537,8 +1540,7 @@ TEST(DrawIndirectCapabilities, RequiresEs31AndBothCoreEntryPoints) { g_fake.maxVertexSsboBlocks = 0; const auto missingElements = MakeFakeGLESFunctions(); MobileGL::MG_External::GLESCapabilities missingEntryPointCaps; - ASSERT_TRUE( - MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, missingElements)); + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, missingElements)); EXPECT_FALSE(missingEntryPointCaps.SupportsDrawIndirect); } @@ -1547,10 +1549,9 @@ TEST(BaseInstanceCapabilities, RequiresTheExtensionAndAllThreeEntryPoints) { g_fake.maxVertexSsboBlocks = 0; auto funcs = MakeFakeGLESFunctions(); funcs.glDrawArraysInstancedBaseInstanceEXT = [](GLenum, GLint, GLsizei, GLsizei, GLuint) {}; - funcs.glDrawElementsInstancedBaseInstanceEXT = - [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint) {}; - funcs.glDrawElementsInstancedBaseVertexBaseInstanceEXT = - [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, GLuint) {}; + funcs.glDrawElementsInstancedBaseInstanceEXT = [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLuint) {}; + funcs.glDrawElementsInstancedBaseVertexBaseInstanceEXT = [](GLenum, GLsizei, GLenum, const void*, GLsizei, GLint, + GLuint) {}; // Resolved stubs alone must never make the capability true. MobileGL::MG_External::GLESCapabilities pointersOnlyCaps; @@ -1569,8 +1570,7 @@ TEST(BaseInstanceCapabilities, RequiresTheExtensionAndAllThreeEntryPoints) { g_fake.extensions.emplace_back("GL_EXT_base_instance"); funcs.glDrawElementsInstancedBaseInstanceEXT = nullptr; MobileGL::MG_External::GLESCapabilities missingEntryPointCaps; - ASSERT_TRUE( - MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, funcs)); + ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(missingEntryPointCaps, funcs)); EXPECT_FALSE(missingEntryPointCaps.SupportsBaseInstance); } @@ -1749,7 +1749,9 @@ namespace { bool g_damageKhrAvailable = false; std::vector g_damageQueries; - EGLDisplay FakeDamageDisplay() { return g_damageDisplay; } + EGLDisplay FakeDamageDisplay() { + return g_damageDisplay; + } const char* FakeDamageQueryString(EGLDisplay display, EGLint name) { EXPECT_EQ(display, g_damageDisplay); EXPECT_EQ(name, EGL_EXTENSIONS); @@ -1766,8 +1768,8 @@ namespace { __eglMustCastToProperFunctionPointerType FakeDamageGetProcAddress(const char* name) { g_damageQueries.emplace_back(name); - const bool available = std::string(name) == "eglSwapBuffersWithDamageEXT" - ? g_damageExtAvailable : g_damageKhrAvailable; + const bool available = + std::string(name) == "eglSwapBuffersWithDamageEXT" ? g_damageExtAvailable : g_damageKhrAvailable; return available ? reinterpret_cast<__eglMustCastToProperFunctionPointerType>(&FakeSwapBuffersWithDamage) : nullptr; } @@ -1797,7 +1799,8 @@ TEST(SwapBuffersWithDamageResolution, PrefersExtThenKhrThenAnswersNothing) { g_damageExtAvailable = false; g_damageQueries.clear(); EXPECT_EQ(MG_Util::BackendLoader::ResolveSwapBuffersWithDamage(funcs), &FakeSwapBuffersWithDamage); - EXPECT_EQ(g_damageQueries, (std::vector{"eglSwapBuffersWithDamageEXT", "eglSwapBuffersWithDamageKHR"})); + EXPECT_EQ(g_damageQueries, + (std::vector{"eglSwapBuffersWithDamageEXT", "eglSwapBuffersWithDamageKHR"})); g_damageExtensions = "EGL_KHR_swap_buffers_with_damage"; g_damageQueries.clear(); diff --git a/MobileGL/MG_Test/Buffer/BufferTest.cpp b/MobileGL/MG_Test/Buffer/BufferTest.cpp index a4fe3e2b..f3daaf8f 100644 --- a/MobileGL/MG_Test/Buffer/BufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/BufferTest.cpp @@ -23,15 +23,13 @@ using namespace MobileGL; - class BufferTest : public ::testing::Test { protected: // GL error flags are sticky per error code and the context outlives an individual test in this // binary, so drain whatever an earlier test left pending - otherwise an error-code assertion // here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite. static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } // The call under test must raise exactly the expected error and nothing more: a second pending @@ -300,7 +298,8 @@ TEST_F(BufferTest, MappedPointersHonourTheAdvertisedMapBufferAlignment) { const SizeT offset = alignment - 1; bufObj->Resize(bufferSize); Vector initData(bufferSize); - for (SizeT i = 0; i < bufferSize; ++i) initData[i] = static_cast(i); + for (SizeT i = 0; i < bufferSize; ++i) + initData[i] = static_cast(i); bufObj->UploadData(DataPtr{.data = initData.data(), .size = bufferSize}, 0); const auto addressOf = [](const void* pointer) { return reinterpret_cast(pointer); }; @@ -366,8 +365,8 @@ TEST_F(BufferTest, ExplicitFlushOfARangeMapFollowsTheAlignmentBias) { bufObj->UploadData(DataPtr{.data = initData.data(), .size = bufferSize}, 0); const Range1D mapRange{.start = offset, .end = bufferSize}; - Uint8* mapped = static_cast(bufObj->AcquireMemoryRange( - mapRange, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); + Uint8* mapped = static_cast( + bufObj->AcquireMemoryRange(mapRange, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); ASSERT_NE(mapped, nullptr); mapped[4] = 0x5A; mapped[5] = 0x5B; @@ -724,9 +723,9 @@ TEST_F(BufferTest, ClearNamedBufferSubDataAcceptsSignedNormalizedIntPattern) { MobileGL::MG_Impl::GLImpl::CreateBuffers(1, &buffer); const Vector initial(24, 0x7F); - MobileGL::MG_Impl::GLImpl::NamedBufferStorage( - buffer, initial.size(), initial.data(), - GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_DYNAMIC_STORAGE_BIT | GL_MAP_PERSISTENT_BIT); + MobileGL::MG_Impl::GLImpl::NamedBufferStorage(buffer, initial.size(), initial.data(), + GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_DYNAMIC_STORAGE_BIT | + GL_MAP_PERSISTENT_BIT); ASSERT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); const GLint zero = 0; @@ -738,7 +737,8 @@ TEST_F(BufferTest, ClearNamedBufferSubDataAcceptsSignedNormalizedIntPattern) { ASSERT_NE(bufferObject, nullptr); Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size()); Vector expected(initial); - for (SizeT i = 0; i < sizeof(GLint); ++i) expected[i] = 0; + for (SizeT i = 0; i < sizeof(GLint); ++i) + expected[i] = 0; EXPECT_EQ(actual, expected); MobileGL::MG_Impl::GLImpl::DeleteBuffers(1, &buffer); @@ -776,11 +776,10 @@ TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) { MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); Vector initial(32, 0x7F); - MobileGL::MG_Impl::GLImpl::BufferData( - GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); + MobileGL::MG_Impl::GLImpl::BufferData(GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); const GLbyte zero = 0; - const auto clear = reinterpret_cast( - MobileGL::MG_Impl::GetProcAddress("glClearBufferSubData")); + const auto clear = + reinterpret_cast(MobileGL::MG_Impl::GetProcAddress("glClearBufferSubData")); ASSERT_NE(clear, nullptr); clear(GL_SHADER_STORAGE_BUFFER, GL_R8, 4, 24, GL_RED, GL_BYTE, &zero); @@ -788,10 +787,8 @@ TEST_F(BufferTest, ClearBufferSubDataInitializesIrisStaticSsboRange) { auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); ASSERT_NE(bufferObject, nullptr); Memcpy(actual.data(), bufferObject->AcquireMemory(false, true, false), actual.size()); - EXPECT_EQ(actual, (Vector{0x7F, 0x7F, 0x7F, 0x7F, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0x7F, 0x7F, 0x7F, 0x7F})); + EXPECT_EQ(actual, (Vector{0x7F, 0x7F, 0x7F, 0x7F, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0x7F, 0x7F, 0x7F})); EXPECT_EQ(MobileGL::MG_Impl::GLImpl::GetError(), GL_NO_ERROR); MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0); @@ -806,11 +803,10 @@ TEST_F(BufferTest, ClearBufferSubDataInitializesCompleteIrisStaticSsbo) { MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); Vector initial(irisStaticSsboSize, 0x7F); - MobileGL::MG_Impl::GLImpl::BufferData( - GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); + MobileGL::MG_Impl::GLImpl::BufferData(GL_SHADER_STORAGE_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); const GLbyte zero = 0; - MobileGL::MG_Impl::GLImpl::ClearBufferSubData( - GL_SHADER_STORAGE_BUFFER, GL_R8, 0, irisStaticSsboSize, GL_RED, GL_BYTE, &zero); + MobileGL::MG_Impl::GLImpl::ClearBufferSubData(GL_SHADER_STORAGE_BUFFER, GL_R8, 0, irisStaticSsboSize, GL_RED, + GL_BYTE, &zero); Vector actual(irisStaticSsboSize); auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); @@ -831,10 +827,9 @@ TEST_F(BufferTest, ClearBufferDataConvertsOneClientPixelBeforeRepeatingIt) { Vector initial(4, 0u); MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size() * sizeof(Uint32), initial.data(), - GL_STATIC_DRAW); + GL_STATIC_DRAW); const Uint8 value = 0xAB; - MobileGL::MG_Impl::GLImpl::ClearBufferData( - GL_ARRAY_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, &value); + MobileGL::MG_Impl::GLImpl::ClearBufferData(GL_ARRAY_BUFFER, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, &value); Vector actual(initial.size()); auto bufferObject = MobileGL::MG_State::pGLContext->GetBufferObject(buffer); @@ -851,8 +846,7 @@ TEST_F(BufferTest, ClearBufferDataConvertsOneClientPixelBeforeRepeatingIt) { TEST_F(BufferTest, ClearBufferSubDataRejectsUnboundTarget) { MobileGL::MG_Impl::GLImpl::BindBuffer(GL_SHADER_STORAGE_BUFFER, 0); const GLbyte zero = 0; - MobileGL::MG_Impl::GLImpl::ClearBufferSubData( - GL_SHADER_STORAGE_BUFFER, GL_R8, 0, 1, GL_RED, GL_BYTE, &zero); + MobileGL::MG_Impl::GLImpl::ClearBufferSubData(GL_SHADER_STORAGE_BUFFER, GL_R8, 0, 1, GL_RED, GL_BYTE, &zero); ExpectSingleGlError(GL_INVALID_OPERATION); } @@ -864,11 +858,9 @@ TEST_F(BufferTest, ClearBufferDataRejectsInvalidPixelFormatTypePairs) { const Vector initial{0x7F, 0x7F}; MobileGL::MG_Impl::GLImpl::BufferData(GL_ARRAY_BUFFER, initial.size(), initial.data(), GL_STATIC_DRAW); const Uint16 packed = 0; - MobileGL::MG_Impl::GLImpl::ClearBufferData( - GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, &packed); + MobileGL::MG_Impl::GLImpl::ClearBufferData(GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, &packed); ExpectSingleGlError(GL_INVALID_VALUE); - MobileGL::MG_Impl::GLImpl::ClearBufferData( - GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, nullptr); + MobileGL::MG_Impl::GLImpl::ClearBufferData(GL_ARRAY_BUFFER, GL_R16, GL_RED, GL_UNSIGNED_SHORT_5_6_5, nullptr); ExpectSingleGlError(GL_INVALID_VALUE); Vector actual(initial.size()); @@ -882,7 +874,6 @@ TEST_F(BufferTest, ClearBufferDataRejectsInvalidPixelFormatTypePairs) { DrainPendingGlErrors(); } - // GL 4.6 core 6.5: glBufferSubData fails only when the written range OVERLAPS the mapped range. // A second, wrong test used to sit next to the correct one and reject any write whose end reached // the start of the mapping - which killed every legal disjoint update in front of a mapped tail. @@ -1117,8 +1108,7 @@ TEST_F(BufferTest, BindBufferRangeEnforcesUniformAndTransformFeedbackAlignment) // Transform feedback captures 32-bit components: offset and size are both constrained, and the // size half has no analogue on any other target. - auto& feedbackPoint = - MobileGL::MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, 0); + auto& feedbackPoint = MobileGL::MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::TransformFeedback, 0); MobileGL::MG_Impl::GLImpl::BindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer, 2, 4); ExpectSingleGlError(GL_INVALID_VALUE); EXPECT_EQ(feedbackPoint.GetBoundObject(), nullptr); @@ -1394,9 +1384,8 @@ TEST_F(GeneralBufferTest, General_PersistentCoherentWriteDirtyWithoutUnmap) { ASSERT_NE(bufferObject, nullptr); const Uint64 baseSerial = bufferObject->GetChangeSerial(); - auto* mapped = static_cast( - MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); + auto* mapped = static_cast(MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT)); ASSERT_NE(mapped, nullptr); mapped[2] = 1234; @@ -1422,9 +1411,8 @@ TEST_F(GeneralBufferTest, General_PersistentExplicitFlushOnlyDirtiesFlushedRange ASSERT_NE(bufferObject, nullptr); const Uint64 baseSerial = bufferObject->GetChangeSerial(); - auto* mapped = static_cast( - MapBufferRange(GL_ARRAY_BUFFER, 0, sizeof(initial), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); + auto* mapped = static_cast(MapBufferRange( + GL_ARRAY_BUFFER, 0, sizeof(initial), GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); ASSERT_NE(mapped, nullptr); mapped[1] = 200; mapped[3] = 400; @@ -1456,9 +1444,8 @@ TEST_F(GeneralBufferTest, General_NamedBufferStorageMappingWrappers) { ASSERT_NE(bufferObject, nullptr); const Uint64 baseSerial = bufferObject->GetChangeSerial(); - auto* mapped = static_cast( - MapNamedBufferRange(buffer, 0, sizeof(initial), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); + auto* mapped = static_cast(MapNamedBufferRange( + buffer, 0, sizeof(initial), GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); ASSERT_NE(mapped, nullptr); mapped[0] = 99; @@ -1671,8 +1658,6 @@ namespace { }; } // namespace - - // --------------------------------------------------------------------------- // MOBILEGL_COHERENT_AS_FLUSH: persistent FLUSH_EXPLICIT mapping requests are // rewritten to coherent semantics, so apps that bind mapped ranges for GPU reads @@ -1817,9 +1802,9 @@ TEST_F(GeneralBufferTest, General_CoherentAsFlush_PersistentMapAdoptsZeroCopyBac // Flywheel-style map: FLUSH_EXPLICIT and never flushed. Under the feature it becomes // coherent-persistent and takes the zero-copy path: the app writes straight into the // backend's GPU storage, so nothing depends on flush calls. - auto* mapped = static_cast( - MapBufferRange(GL_ARRAY_BUFFER, 0, static_cast(kCount * sizeof(GLint)), - GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); + auto* mapped = + static_cast(MapBufferRange(GL_ARRAY_BUFFER, 0, static_cast(kCount * sizeof(GLint)), + GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)); ASSERT_NE(mapped, nullptr); EXPECT_EQ(mock.acquireMapCalls, 1); @@ -1877,8 +1862,7 @@ TEST_F(BufferTest, RedefiningAnAdoptedBufferHandsTheMappingBack) { // The backend adopts the bytes, exactly as a capture target or an SSBO binding does. ASSERT_TRUE(bufferObject->EnsureGpuResidentStorage()); ASSERT_TRUE(bufferObject->IsBackendPersistentMapped()); - ASSERT_EQ(static_cast(bufferObject->MappedData()), - static_cast(mock.gpu.data())); + ASSERT_EQ(static_cast(bufferObject->MappedData()), static_cast(mock.gpu.data())); mock.respecifyCalls = 0; const GLint after[4] = {10, 20, 30, 40}; @@ -1886,8 +1870,7 @@ TEST_F(BufferTest, RedefiningAnAdoptedBufferHandsTheMappingBack) { ASSERT_EQ(GetError(), GL_NO_ERROR); EXPECT_FALSE(bufferObject->IsBackendPersistentMapped()); - EXPECT_NE(static_cast(bufferObject->MappedData()), - static_cast(mock.gpu.data())); + EXPECT_NE(static_cast(bufferObject->MappedData()), static_cast(mock.gpu.data())); EXPECT_EQ(std::memcmp(bufferObject->MappedData(), after, sizeof(after)), 0); // The backend has a separate copy again, so it must have been told to refresh it. EXPECT_EQ(mock.respecifyCalls, 1); @@ -2052,9 +2035,9 @@ namespace { GenBuffers(1, &buffer); BindBuffer(target, buffer); Vector initial(kAdoptedInts); - for (SizeT i = 0; i < kAdoptedInts; ++i) initial[i] = static_cast(i); - BufferData(target, static_cast(kAdoptedInts * sizeof(GLint)), initial.data(), - GL_DYNAMIC_DRAW); + for (SizeT i = 0; i < kAdoptedInts; ++i) + initial[i] = static_cast(i); + BufferData(target, static_cast(kAdoptedInts * sizeof(GLint)), initial.data(), GL_DYNAMIC_DRAW); EXPECT_EQ(GetError(), GL_NO_ERROR); auto bufferObject = MG_State::pGLContext->GetBufferObject(buffer); EXPECT_NE(bufferObject, nullptr); @@ -2088,8 +2071,10 @@ TEST_F(BufferTest, ANonPersistentReadWriteRangeMapOfAnAdoptedStoreLandsAtUnmap) ASSERT_NE(mapped, nullptr); // A non-persistent map is a staging copy, seeded from the adopted store... EXPECT_NE(static_cast(mapped), static_cast(mock.gpu.data())); - for (SizeT i = 0; i < kAdoptedInts; ++i) EXPECT_EQ(mapped[i], static_cast(i)); - for (SizeT i = 0; i < kAdoptedInts; ++i) mapped[i] = 1000 + static_cast(i); + for (SizeT i = 0; i < kAdoptedInts; ++i) + EXPECT_EQ(mapped[i], static_cast(i)); + for (SizeT i = 0; i < kAdoptedInts; ++i) + mapped[i] = 1000 + static_cast(i); // ...that the store does not see until the unmap. EXPECT_EQ(GpuInts(mock)[0], 0); bufferObject->ReleaseMemory(); @@ -2170,7 +2155,8 @@ TEST_F(BufferTest, AWriteMapInvalidatingAnAdoptedStoreLandsTheWholeRangeAtUnmap) ASSERT_NE(mapped, nullptr); EXPECT_NE(static_cast(mapped), static_cast(mock.gpu.data())); // The whole range is undefined by contract, so the application rewrites all of it. - for (SizeT i = 0; i < kAdoptedInts; ++i) mapped[i] = -static_cast(i) - 1; + for (SizeT i = 0; i < kAdoptedInts; ++i) + mapped[i] = -static_cast(i) - 1; bufferObject->ReleaseMemory(); for (SizeT i = 0; i < kAdoptedInts; ++i) { @@ -2204,14 +2190,16 @@ TEST_F(BufferTest, ARangeMapAtAnUnalignedOffsetOfAnAdoptedStoreLandsInPlace) { auto* mapped = static_cast(bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Write)); ASSERT_NE(mapped, nullptr); // Seeded from the right place... - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(mapped[i], static_cast(kFirst + i)); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 500 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(mapped[i], static_cast(kFirst + i)); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 500 + static_cast(i); bufferObject->ReleaseMemory(); // ...and landed in the right place, with everything outside the range untouched. for (SizeT i = 0; i < kAdoptedInts; ++i) { - const GLint expected = (i >= kFirst && i < kFirst + kCount) ? 500 + static_cast(i - kFirst) - : static_cast(i); + const GLint expected = + (i >= kFirst && i < kFirst + kCount) ? 500 + static_cast(i - kFirst) : static_cast(i); EXPECT_EQ(GpuInts(mock)[i], expected) << "int " << i; } EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial); @@ -2237,10 +2225,11 @@ TEST_F(BufferTest, AnExplicitFlushOfAWriteMapOfAnAdoptedStoreLandsOnlyTheFlushed constexpr SizeT kFirst = 2; constexpr SizeT kCount = 12; const Range1D range{kFirst * sizeof(GLint), (kFirst + kCount) * sizeof(GLint)}; - auto* mapped = static_cast(bufferObject->AcquireMemoryRange( - range, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); + auto* mapped = static_cast( + bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); ASSERT_NE(mapped, nullptr); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 700 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 700 + static_cast(i); // ...but only ints 5..8 (map-relative ints 3..6) flushed. constexpr SizeT kFlushFirst = 3; constexpr SizeT kFlushCount = 4; @@ -2284,10 +2273,11 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesTheUnmappedBytesForAGpuOrd constexpr SizeT kFirst = 3; constexpr SizeT kCount = 5; const Range1D range{kFirst * sizeof(GLint), (kFirst + kCount) * sizeof(GLint)}; - auto* mapped = static_cast(bufferObject->AcquireMemoryRange( - range, BufferMappingAccessBit::Read | BufferMappingAccessBit::Write)); + auto* mapped = static_cast( + bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Read | BufferMappingAccessBit::Write)); ASSERT_NE(mapped, nullptr); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 900 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 900 + static_cast(i); bufferObject->ReleaseMemory(); // The op got exactly the mapped range's bytes at the mapped offset... @@ -2295,9 +2285,11 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesTheUnmappedBytesForAGpuOrd EXPECT_EQ(mock.residentWrites[0].offset, range.start); ASSERT_EQ(mock.residentWrites[0].bytes.size(), kCount * sizeof(GLint)); const auto* handed = reinterpret_cast(mock.residentWrites[0].bytes.data()); - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(handed[i], 900 + static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(handed[i], 900 + static_cast(i)) << "int " << i; // ...the mapping itself was not written through... - for (SizeT i = 0; i < kAdoptedInts; ++i) EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kAdoptedInts; ++i) + EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; EXPECT_GT(bufferObject->GetChangeSerial(), baseSerial); EXPECT_EQ(mock.flushCalls, 0); EXPECT_EQ(mock.subDataCalls, 0); @@ -2308,8 +2300,8 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesTheUnmappedBytesForAGpuOrd EXPECT_EQ(mock.readbackCalls, 1); EXPECT_TRUE(mock.residentWrites.empty()); for (SizeT i = 0; i < kAdoptedInts; ++i) { - const GLint expected = (i >= kFirst && i < kFirst + kCount) ? 900 + static_cast(i - kFirst) - : static_cast(i); + const GLint expected = + (i >= kFirst && i < kFirst + kCount) ? 900 + static_cast(i - kFirst) : static_cast(i); EXPECT_EQ(readBack[i], expected) << "int " << i; } // A second read has nothing outstanding to reconcile. @@ -2331,10 +2323,11 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesAnExplicitlyFlushedRangeTh constexpr SizeT kFirst = 2; constexpr SizeT kCount = 8; const Range1D range{kFirst * sizeof(GLint), (kFirst + kCount) * sizeof(GLint)}; - auto* mapped = static_cast(bufferObject->AcquireMemoryRange( - range, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); + auto* mapped = static_cast( + bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Write | BufferMappingAccessBit::FlushExplicit)); ASSERT_NE(mapped, nullptr); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 800 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 800 + static_cast(i); constexpr SizeT kFlushFirst = 5; constexpr SizeT kFlushCount = 2; bufferObject->FlushMemoryRange(kFlushFirst * sizeof(GLint), kFlushCount * sizeof(GLint)); @@ -2368,12 +2361,13 @@ TEST_F(BufferTest, MapBufferRangeAndUnmapBufferReseedAnAdoptedShaderStorageBuffe ASSERT_NE(bufferObject, nullptr); for (GLint pass = 1; pass <= 3; ++pass) { - auto* mapped = static_cast( - MapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, static_cast(kAdoptedInts * sizeof(GLint)), - GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)); + auto* mapped = static_cast(MapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, + static_cast(kAdoptedInts * sizeof(GLint)), + GL_MAP_READ_BIT | GL_MAP_WRITE_BIT)); ASSERT_NE(mapped, nullptr); ASSERT_EQ(GetError(), GL_NO_ERROR); - for (SizeT i = 0; i < kAdoptedInts; ++i) mapped[i] = pass * 100 + static_cast(i); + for (SizeT i = 0; i < kAdoptedInts; ++i) + mapped[i] = pass * 100 + static_cast(i); EXPECT_TRUE(UnmapBuffer(GL_SHADER_STORAGE_BUFFER)); ASSERT_EQ(GetError(), GL_NO_ERROR); for (SizeT i = 0; i < kAdoptedInts; ++i) { @@ -2436,7 +2430,8 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesAGlBufferSubDataAtItsOffse ASSERT_EQ(mock.residentWrites[0].bytes.size(), sizeof(updated)); EXPECT_EQ(std::memcmp(mock.residentWrites[0].bytes.data(), updated, sizeof(updated)), 0); // The mapping itself is left alone until the backend's ordered copy runs. - for (SizeT i = 0; i < kAdoptedInts; ++i) EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kAdoptedInts; ++i) + EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; const auto* readBack = static_cast(bufferObject->AcquireMemory(false, true, false)); EXPECT_EQ(mock.readbackCalls, 1); @@ -2497,8 +2492,10 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesTheExpandedClearPattern) { EXPECT_EQ(mock.residentWrites[0].offset, kFirst * sizeof(GLint)); ASSERT_EQ(mock.residentWrites[0].bytes.size(), kCount * sizeof(GLint)); const auto* handed = reinterpret_cast(mock.residentWrites[0].bytes.data()); - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(handed[i], value) << "int " << i; - for (SizeT i = 0; i < kAdoptedInts; ++i) EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(handed[i], value) << "int " << i; + for (SizeT i = 0; i < kAdoptedInts; ++i) + EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; const auto* readBack = static_cast(bufferObject->AcquireMemory(false, true, false)); EXPECT_EQ(mock.readbackCalls, 1); @@ -2519,7 +2516,8 @@ namespace { GenBuffers(1, &buffer); BindBuffer(target, buffer); Vector bytes(kAdoptedInts); - for (SizeT i = 0; i < kAdoptedInts; ++i) bytes[i] = bias + static_cast(i); + for (SizeT i = 0; i < kAdoptedInts; ++i) + bytes[i] = bias + static_cast(i); BufferData(target, static_cast(kAdoptedInts * sizeof(GLint)), bytes.data(), GL_STATIC_DRAW); EXPECT_EQ(GetError(), GL_NO_ERROR); return buffer; @@ -2547,8 +2545,8 @@ TEST_F(BufferTest, GlCopyBufferSubDataIntoAnAdoptedStoreLandsAtTheDestinationOff for (SizeT i = 0; i < kAdoptedInts; ++i) { const GLint expected = (i >= kDstFirst && i < kDstFirst + kCount) - ? 900 + static_cast(kSrcFirst + i - kDstFirst) - : static_cast(i); + ? 900 + static_cast(kSrcFirst + i - kDstFirst) + : static_cast(i); EXPECT_EQ(GpuInts(mock)[i], expected) << "int " << i; } EXPECT_EQ(mock.subDataCalls, 0); @@ -2583,7 +2581,8 @@ TEST_F(BufferTest, ABackendWithAResidentSubDataOpTakesACopyAtTheDestinationOffse for (SizeT i = 0; i < kCount; ++i) { EXPECT_EQ(handed[i], 900 + static_cast(kSrcFirst + i)) << "int " << i; } - for (SizeT i = 0; i < kAdoptedInts; ++i) EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kAdoptedInts; ++i) + EXPECT_EQ(GpuInts(mock)[i], static_cast(i)) << "int " << i; GLuint toDelete[] = {destination, source}; DeleteBuffers(2, toDelete); @@ -2606,13 +2605,14 @@ TEST_F(BufferTest, AnExplicitFlushOfAPersistentMapOfAnAdoptedStoreOnlyPublishesT constexpr SizeT kFirst = 2; constexpr SizeT kCount = 8; const Range1D range{kFirst * sizeof(GLint), (kFirst + kCount) * sizeof(GLint)}; - auto* mapped = static_cast(bufferObject->AcquireMemoryRange( - range, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | - BufferMappingAccessBit::FlushExplicit)); + auto* mapped = static_cast( + bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | + BufferMappingAccessBit::FlushExplicit)); ASSERT_NE(mapped, nullptr); // The application writes the store itself: the map IS the adopted memory. EXPECT_EQ(static_cast(mapped), static_cast(mock.gpu.data() + range.start)); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 400 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 400 + static_cast(i); constexpr SizeT kFlushFirst = 3; constexpr SizeT kFlushCount = 2; @@ -2658,8 +2658,7 @@ TEST_F(BufferTest, AZeroLengthExplicitFlushOfAnAdoptedStoreLeavesItUndefined) { const Uint64 baseSerial = bufferObject->GetChangeSerial(); auto* mapped = bufferObject->AcquireMemoryRange({0, kAdoptedInts * sizeof(GLint)}, - BufferMappingAccessBit::Write | - BufferMappingAccessBit::Persistent | + BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | BufferMappingAccessBit::FlushExplicit); ASSERT_NE(mapped, nullptr); bufferObject->FlushMemoryRange(0, 0); @@ -2701,7 +2700,8 @@ TEST_F(BufferTest, AWriteMapThatDiscardsWhatItMapsDoesNotReconcileAnAdoptedStore range, BufferMappingAccessBit::Write | BufferMappingAccessBit::InvalidateRange)); ASSERT_NE(mapped, nullptr); EXPECT_EQ(mock.readbackCalls, 0); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 300 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 300 + static_cast(i); bufferObject->ReleaseMemory(); ASSERT_EQ(mock.residentWrites.size(), 2u); @@ -2709,7 +2709,8 @@ TEST_F(BufferTest, AWriteMapThatDiscardsWhatItMapsDoesNotReconcileAnAdoptedStore const auto* readBack = static_cast(bufferObject->AcquireMemory(false, true, false)); EXPECT_EQ(mock.readbackCalls, 1); EXPECT_EQ(readBack[0], firstInt); - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(readBack[kFirst + i], 300 + static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(readBack[kFirst + i], 300 + static_cast(i)) << "int " << i; // The control: a map that keeps what it maps still reconciles before seeding. BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(firstInt), &firstInt); @@ -2718,7 +2719,8 @@ TEST_F(BufferTest, AWriteMapThatDiscardsWhatItMapsDoesNotReconcileAnAdoptedStore bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Read | BufferMappingAccessBit::Write)); ASSERT_NE(seeded, nullptr); EXPECT_EQ(mock.readbackCalls, 2); - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(seeded[i], 300 + static_cast(i)) << "int " << i; + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(seeded[i], 300 + static_cast(i)) << "int " << i; bufferObject->ReleaseMemory(); DeleteBuffers(1, &buffer); @@ -2738,7 +2740,8 @@ TEST_F(BufferTest, AStorageBindingDoesNotAdoptTheStoreWhileTheApplicationHoldsAM GenBuffers(1, &buffer); BindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); Vector initial(kAdoptedInts); - for (SizeT i = 0; i < kAdoptedInts; ++i) initial[i] = static_cast(i); + for (SizeT i = 0; i < kAdoptedInts; ++i) + initial[i] = static_cast(i); BufferData(GL_SHADER_STORAGE_BUFFER, static_cast(kAdoptedInts * sizeof(GLint)), initial.data(), GL_DYNAMIC_DRAW); ASSERT_EQ(GetError(), GL_NO_ERROR); @@ -2749,9 +2752,9 @@ TEST_F(BufferTest, AStorageBindingDoesNotAdoptTheStoreWhileTheApplicationHoldsAM constexpr SizeT kFirst = 2; constexpr SizeT kCount = 4; const Range1D range{kFirst * sizeof(GLint), (kFirst + kCount) * sizeof(GLint)}; - auto* mapped = static_cast(bufferObject->AcquireMemoryRange( - range, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | - BufferMappingAccessBit::FlushExplicit)); + auto* mapped = static_cast( + bufferObject->AcquireMemoryRange(range, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | + BufferMappingAccessBit::FlushExplicit)); ASSERT_NE(mapped, nullptr); ASSERT_EQ(static_cast(mapped), static_cast(shadowBase + range.start)); @@ -2760,11 +2763,13 @@ TEST_F(BufferTest, AStorageBindingDoesNotAdoptTheStoreWhileTheApplicationHoldsAM EXPECT_EQ(mock.acquireMapCalls, 0); // The application's pointer is still the store's: it survived the binding. EXPECT_EQ(static_cast(bufferObject->MappedData()), static_cast(shadowBase)); - for (SizeT i = 0; i < kCount; ++i) mapped[i] = 250 + static_cast(i); + for (SizeT i = 0; i < kCount; ++i) + mapped[i] = 250 + static_cast(i); bufferObject->FlushMemoryRange(0, kCount * sizeof(GLint)); EXPECT_EQ(mock.flushCalls, 1); const auto* shadowInts = reinterpret_cast(bufferObject->MappedData()); - for (SizeT i = 0; i < kCount; ++i) EXPECT_EQ(shadowInts[kFirst + i], 250 + static_cast(i)); + for (SizeT i = 0; i < kCount; ++i) + EXPECT_EQ(shadowInts[kFirst + i], 250 + static_cast(i)); // Unmapped, the next binding adopts as usual. bufferObject->ReleaseMemory(); @@ -2789,7 +2794,8 @@ TEST_F(BufferTest, RespecifyingAStoreWhileItIsMappedDoesNotLandTheStagedBytesInt auto* mapped = static_cast( bufferObject->AcquireMemoryRange({0, kAdoptedInts * sizeof(GLint)}, BufferMappingAccessBit::Write)); ASSERT_NE(mapped, nullptr); - for (SizeT i = 0; i < kAdoptedInts; ++i) mapped[i] = 1234; + for (SizeT i = 0; i < kAdoptedInts; ++i) + mapped[i] = 1234; bufferObject->Respecify(kAdoptedInts * sizeof(GLint), nullptr); EXPECT_TRUE(mock.residentWrites.empty()); @@ -2803,4 +2809,3 @@ TEST_F(BufferTest, RespecifyingAStoreWhileItIsMappedDoesNotLandTheStagedBytesInt DeleteBuffers(1, &buffer); g_zeroCopyMock = nullptr; } - diff --git a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp index 7f553528..7b7d7ce8 100644 --- a/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp +++ b/MobileGL/MG_Test/Buffer/SplitBufferTest.cpp @@ -38,9 +38,9 @@ using namespace MobileGL; namespace { #if MOBILEGL_BUILD_DISAGGREGATED - using MG_State::GLState::BufferObject; using MG_Record::GpuWriteProducer; using MG_Record::PersistentMapTracker; + using MG_State::GLState::BufferObject; // A backend that MINTS a persistent mapping, for the one case that needs the adopted arm // (TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo): a resource op table (P13: the @@ -48,7 +48,9 @@ namespace { // CASE's, not this table's - AdoptPersistentMap keeps the pointer and never owns it - so the // base travels through a file-scope variable the case sets and clears. void* g_mintedPersistentBase = nullptr; - void* MintPersistentMap(MG_Pipe::MGPipeHandle, Uint64, const void*) { return g_mintedPersistentBase; } + void* MintPersistentMap(MG_Pipe::MGPipeHandle, Uint64, const void*) { + return g_mintedPersistentBase; + } const MG_Pipe::MGPipeResourceOps g_mintingResourceOps = [] { MG_Pipe::MGPipeResourceOps ops{}; ops.MapPersistent = &MintPersistentMap; @@ -126,10 +128,10 @@ namespace { }; #endif -#define MGL_SPLIT_ONLY_OR_SKIP() \ - do { \ - GTEST_SKIP() << "the client-side GPU-write set and the persistent-map push exist only in " \ - "a MOBILEGL_BUILD_DISAGGREGATED build"; \ +#define MGL_SPLIT_ONLY_OR_SKIP() \ + do { \ + GTEST_SKIP() << "the client-side GPU-write set and the persistent-map push exist only in " \ + "a MOBILEGL_BUILD_DISAGGREGATED build"; \ } while (0) } // namespace @@ -170,8 +172,7 @@ TEST_F(SplitBufferSet, Row1EveryBoundAtomicCounterIsMarkedByADraw) { // a byte of it. TEST_F(SplitBufferSet, Row2OnlyAWritableImageBufferTextureCounts) { MG_State::GLState::ImageTextureBinding empty{}; - EXPECT_FALSE(MG_Record::ImageUnitIsAWritableBufferTexture(empty)) - << "an unbound image unit is not a GPU write"; + EXPECT_FALSE(MG_Record::ImageUnitIsAWritableBufferTexture(empty)) << "an unbound image unit is not a GPU write"; auto texture = MakeShared(7u); auto backing = MakeBuffer(13u, 128); @@ -286,7 +287,6 @@ TEST_F(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { EXPECT_EQ(MG_Record::ProducerMarkCount(GpuWriteProducer::EndTransformFeedbackCapture), 1u); } - // ===================================================================================== // The persistent-map push // ===================================================================================== @@ -298,8 +298,7 @@ TEST_F(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { auto buffer = MakeBuffer(20u, 4096); EXPECT_FALSE(PersistentMapTracker::IsLivePersistentMap(*buffer)) << "not mapped"; - buffer->AcquireMemoryRange(Range1D{0, 4096}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + buffer->AcquireMemoryRange(Range1D{0, 4096}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); ASSERT_FALSE(buffer->IsBackendPersistentMapped()) << "the acquisition was minted, so this is the adopted arm and not the one under test"; EXPECT_TRUE(PersistentMapTracker::IsLivePersistentMap(*buffer)); @@ -311,8 +310,7 @@ TEST_F(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { // FLUSH_EXPLICIT is the early-out that is easiest to lose: the application announces its // own writes with glFlushMappedBufferRange, which already crosses as resource_flush_range. - buffer->AcquireMemoryRange(Range1D{0, 4096}, BufferMappingAccessBit::Write | - BufferMappingAccessBit::Persistent | + buffer->AcquireMemoryRange(Range1D{0, 4096}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | BufferMappingAccessBit::FlushExplicit); EXPECT_FALSE(PersistentMapTracker::IsLivePersistentMap(*buffer)); EXPECT_EQ(PersistentMapTracker::Instance().MemberCount(), 0u); @@ -353,8 +351,7 @@ TEST_F(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { auto declined = MakeBuffer(28u, kSize); // The declined twin first, with no backend ops at all: same flags, same size, same call. - declined->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + declined->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); ASSERT_FALSE(declined->IsBackendPersistentMapped()); ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*declined)) << "the emulated arm IS a member - if this fails the two halves are not comparable and the " @@ -413,8 +410,7 @@ TEST_F(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { constexpr SizeT kSize = 4u * 64u * 1024u; // exactly four 64 KiB blocks auto buffer = MakeBuffer(21u, kSize); - buffer->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + buffer->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*buffer)); const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush); @@ -435,8 +431,7 @@ TEST_F(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { TEST_F(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { constexpr SizeT kSize = 64u * 1024u + 7u; auto buffer = MakeBuffer(22u, kSize); - buffer->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + buffer->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); MG_Record::PushPersistentMapsBeforeVerb(); EXPECT_EQ(PersistentMapTracker::Instance().BlocksPushed(), 2u); EXPECT_EQ(PersistentMapTracker::Instance().BytesPushed(), static_cast(kSize)); @@ -454,8 +449,7 @@ TEST_F(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { EXPECT_FALSE(buffer->HasLiveHostWritesForWire()); const Uint64 beforeMap = buffer->GetChangeSerial(); - buffer->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + buffer->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); EXPECT_TRUE(buffer->HasLiveHostWritesForWire()); EXPECT_EQ(buffer->GetChangeSerial(), beforeMap + 1) << "the rising edge published nothing, so the server cannot know a host writer is live " @@ -479,11 +473,9 @@ TEST_F(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) auto buffer = MakeBuffer(23u, kSize); const Uint64 beforeMap = buffer->GetChangeSerial(); - buffer->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + buffer->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); ASSERT_TRUE(PersistentMapTracker::IsLivePersistentMap(*buffer)); - EXPECT_EQ(buffer->GetChangeSerial(), beforeMap) - << "the rising-edge state record went out with the push disabled"; + EXPECT_EQ(buffer->GetChangeSerial(), beforeMap) << "the rising-edge state record went out with the push disabled"; const Uint64 before = MG_Util::PipeStats::TotalBytes(MG_Util::PipeStats::ByteClass::PersistentMapPush); MG_Record::PushPersistentMapsBeforeVerb(); @@ -788,10 +780,9 @@ TEST_F(SplitBufferSet, AFaultedMapTheDrawDidNotBindStillShipsBeforeTheDrawThatDo constexpr SizeT kSize = 4096; // one page, one block each auto a = MakeBuffer(29u, kSize); auto b = MakeBuffer(30u, kSize); - auto* aMapped = static_cast(a->AcquireMemoryRange( - Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent)); - b->AcquireMemoryRange(Range1D{0, kSize}, - BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + auto* aMapped = static_cast( + a->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent)); + b->AcquireMemoryRange(Range1D{0, kSize}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); ASSERT_NE(aMapped, nullptr); ASSERT_NE(PersistentMapTracker::TrackedSlotForTest(*a), nullptr) << "both maps must be on the mprotect arm, or the epoch is not what decides"; @@ -840,32 +831,70 @@ TEST_F(SplitBufferSet, AFaultedMapTheDrawDidNotBindStillShipsBeforeTheDrawThatDo // pull and push name lists line for line and G14 allows build-split to ADD names but never to // remove one, so a case that exists only where it can run would break both gates for a reason // that has nothing to do with what it tests. It skips instead, and says why. -TEST(SplitBufferSet, Row0EverySsboBindingPointIsMarkedByADraw) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row1EveryBoundAtomicCounterIsMarkedByADraw) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row2OnlyAWritableImageBufferTextureCounts) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row2AWritableImageBufferTextureIsMarkedByADraw) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row2TheSweepIsBoundedByTheImageUnitHighWaterMark) { MGL_SPLIT_ONLY_OR_SKIP(); } +TEST(SplitBufferSet, Row0EverySsboBindingPointIsMarkedByADraw) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, Row1EveryBoundAtomicCounterIsMarkedByADraw) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, Row2OnlyAWritableImageBufferTextureCounts) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, Row2AWritableImageBufferTextureIsMarkedByADraw) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, Row2TheSweepIsBoundedByTheImageUnitHighWaterMark) { + MGL_SPLIT_ONLY_OR_SKIP(); +} TEST(SplitBufferSet, Row3TransformFeedbackTargetsAreOnlyMarkedWhileACaptureIsActive) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row4AReadPixelsIntoAPackPboMarksThePbo) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { MGL_SPLIT_ONLY_OR_SKIP(); } +TEST(SplitBufferSet, Row4AReadPixelsIntoAPackPboMarksThePbo) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, Row5EndTransformFeedbackMarksTheCaptureTargets) { + MGL_SPLIT_ONLY_OR_SKIP(); +} -TEST(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, ADestroyedBufferLeavesTheSet) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, UnderSplitTheWritebackClearsThePendingFlagAndNotTheRequest) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, AnOlderWritebackCannotRetireANewerGpuWriteMark) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, TheAdoptTierIsSettledAtTheHandshakeAndNeverFatal) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) { MGL_SPLIT_ONLY_OR_SKIP(); } +TEST(SplitBufferSet, MembershipIsSyncPersistentMappedRangesOwnEarlyOutChain) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, TheAdoptedArmIsNotAMemberAndItIsTheChainRowThatSaysSo) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, ThePushCutsTheMappedSpanIntoBlocksAndMovesPmap) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, TheLastBlockIsTheRemainderAndNotAWholeBlock) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, BothEdgesOfAWriteMapPublishOneStateRecord) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, AZeroBlockSizeTurnsThePushOffRatherThanMakingItUnlimited) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, ADestroyedBufferLeavesTheSet) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, UnderSplitTheWritebackClearsThePendingFlagAndNotTheRequest) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, AnOlderWritebackCannotRetireANewerGpuWriteMark) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, TheAdoptTierIsSettledAtTheHandshakeAndNeverFatal) { + MGL_SPLIT_ONLY_OR_SKIP(); +} +TEST(SplitBufferSet, ASubPageMapIsOneProtectedPageAndAWriteThroughItFaults) { + MGL_SPLIT_ONLY_OR_SKIP(); +} TEST(SplitBufferSet, AShadowThatFailsThePageGranularTestKeepsTheInwardAlignmentAndItsEdges) { MGL_SPLIT_ONLY_OR_SKIP(); } -TEST(SplitBufferSet, AFaultedMapTheDrawDidNotBindStillShipsBeforeTheDrawThatDoes) { MGL_SPLIT_ONLY_OR_SKIP(); } +TEST(SplitBufferSet, AFaultedMapTheDrawDidNotBindStillShipsBeforeTheDrawThatDoes) { + MGL_SPLIT_ONLY_OR_SKIP(); +} #endif // MOBILEGL_BUILD_DISAGGREGATED @@ -916,8 +945,7 @@ TEST(EsprytFlushLadder, AMapWiderThanTheQueuedRangeRefusesTierOne) { "rewrite, and overwrites whatever the GPU put there"; // And narrower, which is the same corruption read the other way round: bytes left // unwritten inside a range that has just been declared dead. - EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, queuedStart, queuedEnd - 8, kStore, kStore, false), - 0u); + EXPECT_EQ(InvalidateFlushAccessFor(queuedStart, queuedEnd, queuedStart, queuedEnd - 8, kStore, kStore, false), 0u); } TEST(EsprytFlushLadder, AnEmptyRangeIsNeverTierOne) { @@ -965,8 +993,8 @@ TEST(EsprytFlushLadder, ARenameThatWouldMoveTooManyUnaskedBytesKeepsItsQueuedRan // The 1.21.5 trace's other shape: 128 bytes into a 53 KiB index store. EXPECT_FALSE(WholeStoreRenameApplies(53824, 53824, 128, true, false, true)) << "rewriting 53 KiB for a 128-byte write moves more bytes than the copy job it saves"; - EXPECT_FALSE(WholeStoreRenameApplies(16384, 16384, 16384 - kEsprytWholeStoreRenameMaxExtraBytes - 1, true, - false, true)); + EXPECT_FALSE( + WholeStoreRenameApplies(16384, 16384, 16384 - kEsprytWholeStoreRenameMaxExtraBytes - 1, true, false, true)); EXPECT_FALSE(WholeStoreRenameApplies(0, 0, 0, true, false, true)); EXPECT_FALSE(WholeStoreRenameApplies(288, 288, 0, true, false, true)) << "nothing queued, nothing to rename"; } diff --git a/MobileGL/MG_Test/EGLState/EGLStateTest.cpp b/MobileGL/MG_Test/EGLState/EGLStateTest.cpp index 99ea664a..f0a7f731 100644 --- a/MobileGL/MG_Test/EGLState/EGLStateTest.cpp +++ b/MobileGL/MG_Test/EGLState/EGLStateTest.cpp @@ -45,7 +45,7 @@ namespace { EXPECT_NE(fixture->Context, EGL_NO_CONTEXT); return fixture; } -} +} // namespace TEST(EGLStateMakeCurrent, ContextCannotBeCurrentOnTwoThreadsAtOnce) { auto fixture = CreateFixture(); @@ -66,19 +66,18 @@ TEST(EGLStateMakeCurrent, ContextCannotBeCurrentOnTwoThreadsAtOnce) { EGLint threadBAttachAfterReleaseError = EGL_SUCCESS; std::thread threadA([&] { - threadAAttachOk = fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, - fixture->Context); + threadAAttachOk = + fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context); threadAReady.set_value(); threadBAttemptedFuture.wait(); - threadAReleaseOk = - fixture->State.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + threadAReleaseOk = fixture->State.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); threadAReleased.set_value(); }); std::thread threadB([&] { threadAReadyFuture.wait(); - threadBDenied = fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, - fixture->Context); + threadBDenied = + fixture->State.MakeCurrent(fixture->Display, fixture->Surface, fixture->Surface, fixture->Context); threadBDeniedError = fixture->State.ConsumeError(); threadBAttempted.set_value(); threadAReleasedFuture.wait(); @@ -276,9 +275,9 @@ TEST(EGLStateCreateContext, NoConfigContextIsAcceptedAndReportsConfigIdZero) { EXPECT_TRUE(fixture->State.MakeCurrent(fixture->Display, EGL_NO_SURFACE, EGL_NO_SURFACE, context)); EXPECT_TRUE(fixture->State.MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)); // A non-null handle that names no config is still EGL_BAD_CONFIG. - EXPECT_EQ(fixture->State.CreateContext(fixture->Display, reinterpret_cast(0xdead), EGL_NO_CONTEXT, - esAttribs), - EGL_NO_CONTEXT); + EXPECT_EQ( + fixture->State.CreateContext(fixture->Display, reinterpret_cast(0xdead), EGL_NO_CONTEXT, esAttribs), + EGL_NO_CONTEXT); EXPECT_EQ(fixture->State.ConsumeError(), EGL_BAD_CONFIG); } @@ -291,8 +290,19 @@ namespace { // A complete single-plane import, as a compositor builds it for a linux-dmabuf wl_buffer. std::vector DmaBufAttribs(EGLAttrib fourcc = kAbgr8888) { - return {EGL_WIDTH, 64, EGL_HEIGHT, 32, EGL_LINUX_DRM_FOURCC_EXT, fourcc, EGL_DMA_BUF_PLANE0_FD_EXT, 7, - EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0, EGL_DMA_BUF_PLANE0_PITCH_EXT, 256, EGL_NONE}; + return {EGL_WIDTH, + 64, + EGL_HEIGHT, + 32, + EGL_LINUX_DRM_FOURCC_EXT, + fourcc, + EGL_DMA_BUF_PLANE0_FD_EXT, + 7, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + 0, + EGL_DMA_BUF_PLANE0_PITCH_EXT, + 256, + EGL_NONE}; } std::vector WithAttrib(std::vector attribs, EGLAttrib name, EGLAttrib value) { @@ -321,9 +331,9 @@ namespace { TEST(EGLStateDmaBufImport, ParsesACompleteSinglePlaneImport) { auto fixture = CreateFixture(); - const auto attribs = WithAttrib(WithAttrib(DmaBufAttribs(kXbgr8888), EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, - static_cast(0xffffffffu)), - EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0x00ffffff); + const auto attribs = WithAttrib( + WithAttrib(DmaBufAttribs(kXbgr8888), EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, static_cast(0xffffffffu)), + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0x00ffffff); StateContext::DmaBufImportAttribs parsed; ASSERT_TRUE(fixture->State.PrepareDmaBufImport(fixture->Display, EGL_NO_CONTEXT, nullptr, attribs.data(), &parsed)); EXPECT_EQ(fixture->State.ConsumeError(), EGL_SUCCESS); @@ -377,7 +387,8 @@ TEST(EGLStateDmaBufImport, RefusesWhatTheExtensionRefuses) { TEST(EGLStateDmaBufImport, SharedImageLifetime) { auto fixture = CreateFixture(); - const StateContext::SharedImageInfo info{.Id = 42, .Width = 64, .Height = 32, .Fourcc = StateContext::kDrmFourccAbgr8888}; + const StateContext::SharedImageInfo info{ + .Id = 42, .Width = 64, .Height = 32, .Fourcc = StateContext::kDrmFourccAbgr8888}; const EGLImage image = fixture->State.CreateSharedImage(fixture->Display, info); ASSERT_NE(image, EGL_NO_IMAGE); @@ -498,7 +509,8 @@ TEST(EGLStateDmaBufImport, ParsesATwoPlaneYuvImportWithItsHints) { auto linear = WithAttrib(WithAttrib(Nv12Attribs(), EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, 0), EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, 0); EXPECT_EQ(PrepareError(*fixture, linear), EGL_BAD_PARAMETER); - linear = WithAttrib(WithAttrib(linear, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, 0), EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, 0); + linear = + WithAttrib(WithAttrib(linear, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, 0), EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, 0); EXPECT_EQ(PrepareError(*fixture, linear), EGL_SUCCESS); auto mixed = WithAttrib(WithAttrib(WithoutAttrib(WithoutAttrib(linear, EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT), EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT), @@ -516,7 +528,8 @@ TEST(EGLStateDmaBufImport, RefusesAnIncompleteYuvImport) { } // NV12 has two planes, not three. EXPECT_EQ(PrepareError(*fixture, WithAttrib(Nv12Attribs(), EGL_DMA_BUF_PLANE2_FD_EXT, 7)), EGL_BAD_ATTRIBUTE); - auto badPitch = WithAttrib(WithoutAttrib(Nv12Attribs(), EGL_DMA_BUF_PLANE1_PITCH_EXT), EGL_DMA_BUF_PLANE1_PITCH_EXT, 0); + auto badPitch = + WithAttrib(WithoutAttrib(Nv12Attribs(), EGL_DMA_BUF_PLANE1_PITCH_EXT), EGL_DMA_BUF_PLANE1_PITCH_EXT, 0); EXPECT_EQ(PrepareError(*fixture, badPitch), EGL_BAD_ACCESS); } @@ -575,8 +588,13 @@ TEST(EGLStateRobustness, ResetNotificationStrategyIsRecordedValidatedAndMatchedA EXPECT_EQ(strategyOf(robustExt), EGL_LOSE_CONTEXT_ON_RESET); // ANGLE's shape: an ES 3.2 request with the EGL 1.5 name. - const EGLint loseCore[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 2, - EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY, EGL_LOSE_CONTEXT_ON_RESET, EGL_NONE}; + const EGLint loseCore[] = {EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 2, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY, + EGL_LOSE_CONTEXT_ON_RESET, + EGL_NONE}; const auto robustCore = state.CreateContext(fixture->Display, fixture->Config, EGL_NO_CONTEXT, loseCore); ASSERT_NE(robustCore, EGL_NO_CONTEXT); EXPECT_EQ(strategyOf(robustCore), EGL_LOSE_CONTEXT_ON_RESET); @@ -630,8 +648,8 @@ TEST(EGLStateRobustness, RobustAccessIsARequestAndNeverAGLContextFlagByItself) { EXPECT_TRUE(requestOf(core, &flags)); EXPECT_EQ(flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT, 0); // KWin's desktop candidate: the KHR flag bit, next to the debug bit that IS a GL flag. - const EGLint khr[] = {EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, - EGL_NONE}; + const EGLint khr[] = {EGL_CONTEXT_FLAGS_KHR, + EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR, EGL_NONE}; EXPECT_TRUE(requestOf(khr, &flags)); EXPECT_EQ(flags & GL_CONTEXT_FLAG_ROBUST_ACCESS_BIT, 0); EXPECT_NE(flags & GL_CONTEXT_FLAG_DEBUG_BIT, 0); diff --git a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp index acc1e9c2..61e033b4 100644 --- a/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp +++ b/MobileGL/MG_Test/Framebuffer/FramebufferTest.cpp @@ -44,8 +44,8 @@ namespace { GLenum g_lastReadPixelsType = GL_NONE; void RecordBlitNamedFramebuffer(const SharedPtr& readFramebuffer, - const SharedPtr& drawFramebuffer, - GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) { + const SharedPtr& drawFramebuffer, GLint, + GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) { g_lastBlitReadFramebuffer = readFramebuffer; g_lastBlitDrawFramebuffer = drawFramebuffer; ++g_blitNamedFramebufferCallCount; @@ -89,8 +89,7 @@ protected: // binary, so drain whatever an earlier test left pending - otherwise an error-code assertion // here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite. static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } // The call under test must raise exactly the expected error and nothing more: a second pending @@ -250,10 +249,8 @@ TEST_F(FramebufferTest, NamedFramebufferTextureAttachesWithoutChangingBindings) MG_State::pGLContext->GenTextureNames(1, textureNames); MG_State::pGLContext->CreateTextureObject(textureNames[0], TextureTarget::Texture2D); - const auto originalDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto originalRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto originalDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto originalRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_COLOR_ATTACHMENT0, textureNames[0], 3); @@ -273,10 +270,8 @@ TEST_F(FramebufferTest, NamedDepthFramebufferTextureStorageIsCompleteWithoutBind MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_2D, 1, &texture); - const auto originalDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto originalRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto originalDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto originalRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_DEPTH_COMPONENT24, 64, 32); MG_Impl::GLImpl::NamedFramebufferTexture(framebuffer, GL_DEPTH_ATTACHMENT, texture, 0); @@ -321,11 +316,10 @@ TEST_F(FramebufferTest, ReadPixelsAllowsPersistentMappedPixelPackBuffer) { MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); MG_Impl::GLImpl::BindBuffer(GL_PIXEL_PACK_BUFFER, pixelPackBuffer); - MG_Impl::GLImpl::BufferStorage(GL_PIXEL_PACK_BUFFER, 4 * 4 * 4, nullptr, - GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT); - ASSERT_NE(MG_Impl::GLImpl::MapBufferRange(GL_PIXEL_PACK_BUFFER, 0, 4 * 4 * 4, - GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT), - nullptr); + MG_Impl::GLImpl::BufferStorage(GL_PIXEL_PACK_BUFFER, 4 * 4 * 4, nullptr, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT); + ASSERT_NE( + MG_Impl::GLImpl::MapBufferRange(GL_PIXEL_PACK_BUFFER, 0, 4 * 4 * 4, GL_MAP_READ_BIT | GL_MAP_PERSISTENT_BIT), + nullptr); MG_Backend::gBackendFunctionsTable.GL.ReadPixels = RecordReadPixels; MG_Impl::GLImpl::ReadPixels(0, 0, 4, 4, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); @@ -411,10 +405,8 @@ TEST_F(FramebufferTest, NamedRenderbufferStorageAndFramebufferAttachDoNotChangeB MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); MG_Impl::GLImpl::CreateRenderbuffers(1, &renderbuffer); - const auto originalDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto originalRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto originalDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto originalRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); const auto originalRenderbuffer = MG_State::pGLContext->GetRenderbufferBindingSlot(RenderbufferTarget::Renderbuffer).GetBoundObject(); @@ -448,10 +440,8 @@ TEST_F(FramebufferTest, NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); - const auto defaultDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto defaultRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto defaultDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); const auto defaultDrawBuffer = defaultDraw->GetDrawBuffers()[0]; const auto defaultReadBuffer = defaultRead->GetReadBuffer(); @@ -471,8 +461,7 @@ TEST_F(FramebufferTest, NamedFramebufferDrawBuffersDoNotModifyDefaultFramebuffer } TEST_F(FramebufferTest, DefaultFramebufferReadBufferAcceptsGLBackAlias) { - const auto defaultRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto defaultRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); MG_Impl::GLImpl::ReadBuffer(GL_BACK); @@ -481,8 +470,7 @@ TEST_F(FramebufferTest, DefaultFramebufferReadBufferAcceptsGLBackAlias) { } TEST_F(FramebufferTest, DefaultFramebufferDrawBufferAcceptsGLFrontAlias) { - const auto defaultDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); MG_Impl::GLImpl::DrawBuffer(GL_FRONT); @@ -514,10 +502,8 @@ TEST_F(FramebufferTest, ClearNamedFramebufferfvUsesNamedObjectWithoutChangingBin GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); - const auto defaultDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto defaultRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto defaultDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); const GLfloat depth[] = {0.25f}; MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfv = RecordClearNamedFramebufferfv; @@ -538,10 +524,8 @@ TEST_F(FramebufferTest, ClearNamedFramebufferfiAllowsDefaultFramebufferZero) { GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); - const auto defaultDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto defaultRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto defaultDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); MG_Backend::gBackendFunctionsTable.GL.ClearNamedFramebufferfi = RecordClearNamedFramebufferfi; MG_Impl::GLImpl::ClearNamedFramebufferfi(0, GL_DEPTH_STENCIL, 0, 0.5f, 7); @@ -569,12 +553,12 @@ TEST_F(FramebufferTest, GetNamedFramebufferAttachmentParameterivReadsTargetObjec GLint objectType = 0; GLint objectName = 0; GLint textureLevel = 0; - MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv( - framebuffer, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType); - MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv( - framebuffer, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName); - MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv( - framebuffer, GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &textureLevel); + MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv(framebuffer, GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &objectType); + MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv(framebuffer, GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &objectName); + MG_Impl::GLImpl::GetNamedFramebufferAttachmentParameteriv(framebuffer, GL_COLOR_ATTACHMENT0, + GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &textureLevel); EXPECT_EQ(objectType, GL_TEXTURE); EXPECT_EQ(objectName, static_cast(textureNames[0])); @@ -586,14 +570,11 @@ TEST_F(FramebufferTest, BlitNamedFramebufferAllowsDefaultFramebufferZero) { GLuint framebuffer = 0; MG_Impl::GLImpl::CreateFramebuffers(1, &framebuffer); - const auto defaultDraw = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); - const auto defaultRead = - MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); + const auto defaultDraw = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + const auto defaultRead = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); MG_Backend::gBackendFunctionsTable.GL.BlitNamedFramebuffer = RecordBlitNamedFramebuffer; - MG_Impl::GLImpl::BlitNamedFramebuffer(framebuffer, 0, 0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, - GL_NEAREST); + MG_Impl::GLImpl::BlitNamedFramebuffer(framebuffer, 0, 0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NEAREST); const auto framebufferObject = MG_State::pGLContext->GetFramebufferObject(framebuffer); EXPECT_EQ(g_blitNamedFramebufferCallCount, 1); @@ -616,8 +597,7 @@ namespace { // Converts a row of wide pixels (4 components of wideType each) into `format`/`type` words. template - Vector ConvertWideRowToPackedWords(const Vector& wide, GLenum wideType, GLenum format, - GLenum type) { + Vector ConvertWideRowToPackedWords(const Vector& wide, GLenum wideType, GLenum format, GLenum type) { ClientReadback::ReadbackChannelMapping mapping{}; EXPECT_TRUE(ClientReadback::GetReadbackChannelMapping(format, mapping)); EXPECT_EQ(ClientReadback::GetReadbackDstPixelSize(mapping, type), sizeof(WordT)); @@ -637,9 +617,9 @@ namespace { // Wide RGBA8 pattern shared by the normalized-encode tests. Expected fields below are // round(v / 255 * (2^bits - 1)), computed by hand per pixel. // R G B A - const Vector kRGBA8Row{255, 0, 128, 64, // P0 - 10, 250, 33, 200, // P1 - 85, 170, 255, 0}; // P2 + const Vector kRGBA8Row{255, 0, 128, 64, // P0 + 10, 250, 33, 200, // P1 + 85, 170, 255, 0}; // P2 } // namespace TEST(PackedReadbackEncodeTest, EncodesUnsignedShort565) { @@ -926,8 +906,8 @@ namespace { class ScopedBackendOverride { public: - explicit ScopedBackendOverride(UniquePtr backend): - m_previous(Move(MG_Backend::pActiveBackendObject)) { + explicit ScopedBackendOverride(UniquePtr backend) + : m_previous(Move(MG_Backend::pActiveBackendObject)) { MG_Backend::pActiveBackendObject = Move(backend); } @@ -1020,7 +1000,8 @@ namespace { RecordedColorMask g_driverUniformColorMask; void ResetRecordedColorMasks() { - for (auto& recorded : g_driverIndexedColorMasks) recorded = {}; + for (auto& recorded : g_driverIndexedColorMasks) + recorded = {}; g_driverUniformColorMask = {}; } @@ -1028,7 +1009,8 @@ namespace { g_driverUniformColorMask = {true, r, g, b, a}; // The non-indexed call sets every draw buffer, so record it as such: a later assertion // about draw buffer 1 must not read a stale indexed record the uniform push overwrote. - for (auto& recorded : g_driverIndexedColorMasks) recorded = {true, r, g, b, a}; + for (auto& recorded : g_driverIndexedColorMasks) + recorded = {true, r, g, b, a}; } void StubColorMaski(GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) { @@ -1047,7 +1029,8 @@ namespace { RecordedBlend g_driverBlend[kRecordedDrawBuffers]; void ResetRecordedBlend() { - for (auto& recorded : g_driverBlend) recorded = {}; + for (auto& recorded : g_driverBlend) + recorded = {}; } void RecordBlendEnable(Bool enabled) { @@ -1138,8 +1121,8 @@ namespace { public: // dualSourceBlendSupported models GL_EXT_blend_func_extended on the ES driver, which is // the one capability in here that a real device is commonly WITHOUT. - explicit ScopedRenderStateDriverStubs(Bool dualSourceBlendSupported = true): - m_funcs(MG_Backend::DirectGLES::g_GLESFuncs), m_caps(MG_Backend::DirectGLES::g_GLESCapabilities) { + explicit ScopedRenderStateDriverStubs(Bool dualSourceBlendSupported = true) + : m_funcs(MG_Backend::DirectGLES::g_GLESFuncs), m_caps(MG_Backend::DirectGLES::g_GLESCapabilities) { auto& gl = MG_Backend::DirectGLES::g_GLESFuncs; gl = MG_External::GLESFunctionsTable{}; gl.glViewport = StubViewport; @@ -1541,21 +1524,21 @@ TEST_F(FramebufferTest, FramebufferTextureRejectsAColourAttachmentPastTheLimit) // The same limit ValidateColorAttachmentInRange reads, so the test cannot disagree with the // implementation about where the boundary is. - const GLint limit = MG_Backend::pActiveBackendObject - ? static_cast( - MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxColorAttachments) - : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); + const GLint limit = + MG_Backend::pActiveBackendObject + ? static_cast(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxColorAttachments) + : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); ASSERT_GT(limit, 0); ASSERT_LT(limit, 32) << "the test needs a colour attachment enum past the limit to exist"; - MG_Impl::GLImpl::FramebufferTexture(GL_DRAW_FRAMEBUFFER, - static_cast(GL_COLOR_ATTACHMENT0 + limit), texture, 0); + MG_Impl::GLImpl::FramebufferTexture(GL_DRAW_FRAMEBUFFER, static_cast(GL_COLOR_ATTACHMENT0 + limit), texture, + 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION); DrainPendingGlErrors(); // The last legal one still attaches, so the boundary is off-by-none. - MG_Impl::GLImpl::FramebufferTexture(GL_DRAW_FRAMEBUFFER, - static_cast(GL_COLOR_ATTACHMENT0 + limit - 1), texture, 0); + MG_Impl::GLImpl::FramebufferTexture(GL_DRAW_FRAMEBUFFER, static_cast(GL_COLOR_ATTACHMENT0 + limit - 1), + texture, 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -1631,13 +1614,12 @@ TEST_F(FramebufferTest, FramebufferTexture2DRejectsTheDefaultFramebufferAndBadAt MG_Impl::GLImpl::BindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - const GLint limit = MG_Backend::pActiveBackendObject - ? static_cast( - MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxColorAttachments) - : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); - MG_Impl::GLImpl::FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, - static_cast(GL_COLOR_ATTACHMENT0 + limit), GL_TEXTURE_2D, - texture, 0); + const GLint limit = + MG_Backend::pActiveBackendObject + ? static_cast(MG_Backend::pActiveBackendObject->GetDynamicParameters().MaxColorAttachments) + : static_cast(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); + MG_Impl::GLImpl::FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, static_cast(GL_COLOR_ATTACHMENT0 + limit), + GL_TEXTURE_2D, texture, 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION); DrainPendingGlErrors(); diff --git a/MobileGL/MG_Test/Gbm/FakeSharedImageLibrary.cpp b/MobileGL/MG_Test/Gbm/FakeSharedImageLibrary.cpp index 18b699b9..fb96c5d3 100644 --- a/MobileGL/MG_Test/Gbm/FakeSharedImageLibrary.cpp +++ b/MobileGL/MG_Test/Gbm/FakeSharedImageLibrary.cpp @@ -41,15 +41,20 @@ namespace { } } // namespace -FAKE_EXPORT uint32_t mobilegl_shared_image_abi_version(void) { return MOBILEGL_SHARED_IMAGE_ABI_VERSION; } +FAKE_EXPORT uint32_t mobilegl_shared_image_abi_version(void) { + return MOBILEGL_SHARED_IMAGE_ABI_VERSION; +} -FAKE_EXPORT int mobilegl_server_available(void) { return 1; } +FAKE_EXPORT int mobilegl_server_available(void) { + return 1; +} FAKE_EXPORT int mobilegl_shared_image_formats(uint32_t* formats, uint32_t capacity) { const uint32_t all[] = {MOBILEGL_SHARED_IMAGE_FOURCC_ABGR8888, MOBILEGL_SHARED_IMAGE_FOURCC_XBGR8888, MOBILEGL_SHARED_IMAGE_FOURCC_ARGB8888, MOBILEGL_SHARED_IMAGE_FOURCC_XRGB8888, MOBILEGL_SHARED_IMAGE_FOURCC_NV12}; - for (uint32_t i = 0; i < 5 && i < capacity; ++i) formats[i] = all[i]; + for (uint32_t i = 0; i < 5 && i < capacity; ++i) + formats[i] = all[i]; return 5; } @@ -58,7 +63,7 @@ FAKE_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t height, if (width == 0 || height == 0 || !Supported(fourcc) || out == nullptr) return -EINVAL; const int fd = ::memfd_create("fake-shared-image", MFD_CLOEXEC); if (fd < 0 || ::ftruncate(fd, static_cast(width) * height * 4) != 0) return -EIO; - struct stat st {}; + struct stat st{}; ::fstat(fd, &st); const std::lock_guard lock(g_mutex); const uint64_t id = g_nextId++; @@ -81,7 +86,7 @@ FAKE_EXPORT int mobilegl_shared_image_allocate(uint32_t width, uint32_t height, FAKE_EXPORT int mobilegl_shared_image_import(int fd, uint32_t width, uint32_t height, uint32_t fourcc, uint64_t* out_id) { - struct stat st {}; + struct stat st{}; if (fd < 0 || ::fstat(fd, &st) != 0 || !Supported(fourcc)) return -EINVAL; const std::lock_guard lock(g_mutex); for (auto& [id, image] : g_images) { @@ -118,7 +123,8 @@ FAKE_EXPORT int fake_shared_image_imports(void) { // A YUV import names an image by its descriptor as an RGBA one does; the planes are not looked at. FAKE_EXPORT int mobilegl_shared_image_import_planes(int fd, uint32_t width, uint32_t height, uint32_t fourcc, - const struct mobilegl_shared_image_planes* planes, uint64_t* out_id) { + const struct mobilegl_shared_image_planes* planes, + uint64_t* out_id) { if (planes == nullptr || planes->plane_count != 2 || fourcc != MOBILEGL_SHARED_IMAGE_FOURCC_NV12) return -EINVAL; return mobilegl_shared_image_import(fd, width, height, fourcc, out_id); } diff --git a/MobileGL/MG_Test/Gbm/GbmGlamorTest.cpp b/MobileGL/MG_Test/Gbm/GbmGlamorTest.cpp index eef31da0..f3f42455 100644 --- a/MobileGL/MG_Test/Gbm/GbmGlamorTest.cpp +++ b/MobileGL/MG_Test/Gbm/GbmGlamorTest.cpp @@ -94,7 +94,7 @@ TEST_F(GbmGlamor, AWindowPixmapIsASharedImageWithOneExportablePlane) { EXPECT_EQ(gbm_bo_get_offset(bo, 0), 0u); // Exports are duplicates of one dma-buf: the same inode every time. const int again = gbm_bo_get_fd(bo); - struct stat a {}, b {}; + struct stat a{}, b{}; ASSERT_EQ(::fstat(fd, &a), 0); ASSERT_EQ(::fstat(again, &b), 0); EXPECT_EQ(a.st_ino, b.st_ino); @@ -137,7 +137,7 @@ TEST_F(GbmGlamor, ADri3ClientsBufferImportsBackAsTheSameImage) { EXPECT_EQ(m_imports(), importsBefore + 1); // ...and re-exported for EGL_LINUX_DMA_BUF_EXT: still that image. const int reexport = gbm_bo_get_fd_for_plane(imported, 0); - struct stat a {}, b {}; + struct stat a{}, b{}; ::fstat(fd, &a); ::fstat(reexport, &b); EXPECT_EQ(a.st_ino, b.st_ino); diff --git a/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp b/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp index b15b3b75..f1303b0f 100644 --- a/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp +++ b/MobileGL/MG_Test/Pipe/CompositeResolverTest.cpp @@ -148,7 +148,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -356,7 +358,6 @@ TEST(CompositeResolver, ASlotAtTheShaderCsoLimitIsRefusedWhileTheLastBandSlotIsN << "an ordinary slot below the band resolved against a record nobody created"; } - namespace { namespace GL = MobileGL::MG_Impl::GLImpl; using GLContext = MG_State::GLState::GLContext; @@ -380,7 +381,9 @@ namespace { } }; - GLContext& Ctx() { return *MG_State::pGLContext; } + GLContext& Ctx() { + return *MG_State::pGLContext; + } const char* kVs = R"(#version 430 core out gl_PerVertex { vec4 gl_Position; }; @@ -589,8 +592,7 @@ void main() { o_color = vec4(0.5); } EXPECT_EQ(MGPipeProgramEmitterInstance().DrawCso(), first) << "the same stage set is the same composite and the same handle"; EXPECT_GE(MGPipeCompositeResolverInstance().GetCounters().Reuses, 1u); - EXPECT_EQ(MGPipeCompositeResolverInstance().GetCounters().Releases, 0u) - << "a reuse releases nothing"; + EXPECT_EQ(MGPipeCompositeResolverInstance().GetCounters().Releases, 0u) << "a reuse releases nothing"; // NOW THE STAGE SET REALLY MOVES: a different fragment stage program is a different // lifetime id, so ComputeDrawProgramSignature moves and the frontend builds a second @@ -712,8 +714,7 @@ void main() { o_color = vec4(0.5); } EXPECT_FALSE(MGPipeHandleIsPublished(MGPipeKind::ShaderCso, handle)) << "and it took the publication latch with the delete"; EXPECT_EQ(MGPipeSlots().LiveCount(MGPipeKind::ShaderCso), liveBefore) << "exactly once"; - EXPECT_EQ(resolver.GetCounters().Releases, releasesBefore) - << "the resolver spoke no release of its own for it"; + EXPECT_EQ(resolver.GetCounters().Releases, releasesBefore) << "the resolver spoke no release of its own for it"; // THE NEXT CONTEXT's FIRST VERB. The stranded entry is dropped, not released. MGPipeProgramEmitterInstance().Reset(); @@ -742,8 +743,7 @@ int main(int argc, char** argv) { // raised by the encoder on THIS thread - the client role. So g_logPath, which is the read // path from here on, becomes the client-derived name; the env keeps the base. The rule is // the library's own, not a copy. - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); MobileGL::Initialize(); ::testing::InitGoogleTest(&argc, argv); const int rc = RUN_ALL_TESTS(); diff --git a/MobileGL/MG_Test/Pipe/CsoCacheTest.cpp b/MobileGL/MG_Test/Pipe/CsoCacheTest.cpp index 8dc4a9db..db64db3c 100644 --- a/MobileGL/MG_Test/Pipe/CsoCacheTest.cpp +++ b/MobileGL/MG_Test/Pipe/CsoCacheTest.cpp @@ -55,7 +55,8 @@ namespace { Uint64 bytes = 0; const RenderStateParameters params = PipelineState(7); const MGPipeHandle first = cache.Acquire(params, bytes); - for (int i = 0; i < 16; ++i) EXPECT_TRUE(cache.Acquire(params, bytes) == first); + for (int i = 0; i < 16; ++i) + EXPECT_TRUE(cache.Acquire(params, bytes) == first); EXPECT_EQ(cache.GetCounters().Mints, 1u); EXPECT_EQ(cache.GetCounters().Hits, 16u); EXPECT_EQ(cache.Size(), 1u); diff --git a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp index 2d3bf248..4f7889a9 100644 --- a/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp +++ b/MobileGL/MG_Test/Pipe/FieldOwnershipTest.cpp @@ -110,7 +110,9 @@ namespace { SharedPtr m_previous; }; - SizeT Index(MGPipeInputField field) { return static_cast(field); } + SizeT Index(MGPipeInputField field) { + return static_cast(field); + } #if MGTEST_HAVE_FORK struct ChildResult { @@ -139,8 +141,12 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } - Bool ExitedWith(const ChildResult& r, int code) { return WIFEXITED(r.Status) && WEXITSTATUS(r.Status) == code; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } + Bool ExitedWith(const ChildResult& r, int code) { + return WIFEXITED(r.Status) && WEXITSTATUS(r.Status) == code; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -150,7 +156,6 @@ namespace { #endif // MGTEST_HAVE_FORK } // namespace - // --------------------------------------------------------------------------------------- // The arithmetic: the table partitions the field set. Runs in push, verify and split. // --------------------------------------------------------------------------------------- @@ -172,12 +177,9 @@ TEST_F(FieldOwnershipTest, TheClassSizesPartitionTheFieldSet) { for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { ++counted[static_cast(kMGPipeFieldOwnership[i])]; } - EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kRecordSupplied)], - kMGPipeRecordSuppliedFieldCount); - EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kApplierDerived)], - kMGPipeApplierDerivedFieldCount); - EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kBarrierPulled)], - kMGPipeBarrierPulledFieldCount); + EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kRecordSupplied)], kMGPipeRecordSuppliedFieldCount); + EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kApplierDerived)], kMGPipeApplierDerivedFieldCount); + EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kBarrierPulled)], kMGPipeBarrierPulledFieldCount); EXPECT_EQ(counted[static_cast(MGPipeFieldOwnership::kFatal)], kMGPipeFatalFieldCount); // The census's own arithmetic (scout-unmigrated-census section 2.1), updated by P5c rv // (CONTRACT-P5C.md §5.3): rv moved the NINE value-class rows to RECORD-SUPPLIED through @@ -186,8 +188,7 @@ TEST_F(FieldOwnershipTest, TheClassSizesPartitionTheFieldSet) { // 15 BARRIER-PULLED (the object class: nine non-sticky rows plus six of the seven sticky // forwards), four APPLIER-DERIVED and three FATAL - and 41 are. EXPECT_EQ(kMGPipeRecordSuppliedFieldCount, SizeT{41}); - EXPECT_EQ(kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount, - SizeT{22}); + EXPECT_EQ(kMGPipeApplierDerivedFieldCount + kMGPipeBarrierPulledFieldCount + kMGPipeFatalFieldCount, SizeT{22}); } TEST_F(FieldOwnershipTest, EveryBarrierPulledRowNamesTheRetiringPhase) { @@ -207,12 +208,18 @@ TEST_F(FieldOwnershipTest, EveryBarrierPulledRowNamesTheRetiringPhase) { TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { // Historical name retained for G14. These are retired legacy getters, not records. const MGPipeInputField retired[] = { - MGPipeInputField::GetBoundVertexArray, MGPipeInputField::GetBufferBindingSlot, - MGPipeInputField::GetBufferBindingPoint, MGPipeInputField::GetFramebufferBindingSlot, - MGPipeInputField::GetImageTextureBinding, MGPipeInputField::GetTextureUnitObject, - MGPipeInputField::GetProgramForDraw, MGPipeInputField::GetProgramForDispatch, - MGPipeInputField::GetTransformFeedbackProgram, MGPipeInputField::GetProgramObject, - MGPipeInputField::GetTextureObject, MGPipeInputField::ValidateProgramName, + MGPipeInputField::GetBoundVertexArray, + MGPipeInputField::GetBufferBindingSlot, + MGPipeInputField::GetBufferBindingPoint, + MGPipeInputField::GetFramebufferBindingSlot, + MGPipeInputField::GetImageTextureBinding, + MGPipeInputField::GetTextureUnitObject, + MGPipeInputField::GetProgramForDraw, + MGPipeInputField::GetProgramForDispatch, + MGPipeInputField::GetTransformFeedbackProgram, + MGPipeInputField::GetProgramObject, + MGPipeInputField::GetTextureObject, + MGPipeInputField::ValidateProgramName, MGPipeInputField::RecordError, }; for (const auto field : retired) @@ -222,23 +229,17 @@ TEST_F(FieldOwnershipTest, TheReducedPathsUnmigratedFieldsAreAllAccountedFor) { EXPECT_EQ(kMGPipeAdmittedPullPairCount, SizeT{0}); EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 0), MGPipeFieldOwnership::kApplierDerived); - EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1), - MGPipeFieldOwnership::kFatal); + EXPECT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetPixelStoreParameters, 1), MGPipeFieldOwnership::kFatal); for (const auto field : {MGPipeInputField::GetBufferBindingPointCount, - MGPipeInputField::HasOpenTransformFeedbackSpan, - MGPipeInputField::GetTextureBindGeneration, - MGPipeInputField::GetSamplingResolutionGeneration, - MGPipeInputField::GetTextureContextId}) + MGPipeInputField::HasOpenTransformFeedbackSpan, MGPipeInputField::GetTextureBindGeneration, + MGPipeInputField::GetSamplingResolutionGeneration, MGPipeInputField::GetTextureContextId}) EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kApplierDerived); - for (const auto field : {MGPipeInputField::GetActiveTextureUnit, - MGPipeInputField::GetMaxTouchedTextureUnit, - MGPipeInputField::GetTouchedBufferBindingPointCount, - MGPipeInputField::IsTransformFeedbackActive, - MGPipeInputField::IsTransformFeedbackPaused, - MGPipeInputField::GetTransformFeedbackGeneration, - MGPipeInputField::GetBoundTransformFeedbackLifetimeId, - MGPipeInputField::GetTransformFeedbackCapturedVertices, - MGPipeInputField::GetCurrentVertexAttribute}) + for (const auto field : + {MGPipeInputField::GetActiveTextureUnit, MGPipeInputField::GetMaxTouchedTextureUnit, + MGPipeInputField::GetTouchedBufferBindingPointCount, MGPipeInputField::IsTransformFeedbackActive, + MGPipeInputField::IsTransformFeedbackPaused, MGPipeInputField::GetTransformFeedbackGeneration, + MGPipeInputField::GetBoundTransformFeedbackLifetimeId, MGPipeInputField::GetTransformFeedbackCapturedVertices, + MGPipeInputField::GetCurrentVertexAttribute}) EXPECT_EQ(MGPipeFieldOwnershipOf(field), MGPipeFieldOwnership::kRecordSupplied); } @@ -271,7 +272,8 @@ TEST_F(FieldOwnershipTest, EveryVerbsRecordOpIsStampedAsAVerbOfTheSameFillClass) const MGPipeVerb stamped = MGPipeVerbForWireOp(op); ASSERT_NE(stamped, MGPipeVerb::kVerbCount) << kMGPipeVerbNames[i] << ": its record op stamps no verb"; EXPECT_EQ(kMGPipeVerbClass[static_cast(stamped)], kMGPipeVerbClass[i]) - << kMGPipeVerbNames[i] << " publishes a record stamped as " << kMGPipeVerbNames[static_cast(stamped)]; + << kMGPipeVerbNames[i] << " publishes a record stamped as " + << kMGPipeVerbNames[static_cast(stamped)]; } // GetIntegeri_v and IsTimerQuerySupported, answered from the caps mirror. EXPECT_EQ(noRecord, 2u); @@ -306,8 +308,7 @@ TEST_F(FieldOwnershipTest, VerbBoundaryOpsCoverEveryVerbShapedCall) { EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::BindShaderImage), MGPipeVerb::BindImageTexture); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::PatchParameter), MGPipeVerb::PatchParameteri); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::BindStreamOutput), MGPipeVerb::BindTransformFeedback); - EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetStorageBlockBinding), - MGPipeVerb::ShaderStorageBlockBinding); + EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::SetStorageBlockBinding), MGPipeVerb::ShaderStorageBlockBinding); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::CopyFramebufferToTexture), MGPipeVerb::CopyTexImage2D); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::FenceCreate), MGPipeVerb::FenceSync); EXPECT_EQ(MGPipeVerbForWireOp(MGPWireOp::FenceStatus), MGPipeVerb::GetSyncStatus); @@ -352,11 +353,9 @@ TEST_F(FieldOwnershipTest, TheAdmittedPullTableIsID84sDerivationAndNotAList) { EXPECT_EQ(kMGPipeAdmittedPullPairCount, SizeT{0}); for (SizeT v = 0; v < kMGPipeVerbCount; ++v) for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) - EXPECT_FALSE(MGPipeBarrierPullAdmitted(static_cast(f), - static_cast(v))) + EXPECT_FALSE(MGPipeBarrierPullAdmitted(static_cast(f), static_cast(v))) << kMGPipeInputFieldNames[f] << "@" << kMGPipeVerbNames[v]; - EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetFramebufferBindingSlot, - MGPipeVerb::kVerbCount)); + EXPECT_FALSE(MGPipeBarrierPullAdmitted(MGPipeInputField::GetFramebufferBindingSlot, MGPipeVerb::kVerbCount)); } // --------------------------------------------------------------------------------------- @@ -395,31 +394,27 @@ TEST_F(FieldOwnershipTest, TheResidualFillsSuppliedMemoReKeysOnEveryInputThatMov // applier writes it without deriving, so at a mask that carries the render-state bit the // fill skips it - and at 0, which is what a unit lane runs at (Features.PipePush defaults // to 0), nothing is emitted and every field is pulled. - EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, - kNoSubsystems, true, false)); - EXPECT_TRUE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, - kEverySubsystem, true, false)); - EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, - kNoSubsystems, true, false)) + EXPECT_FALSE( + MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, kNoSubsystems, true, false)); + EXPECT_TRUE( + MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, kEverySubsystem, true, false)); + EXPECT_FALSE( + MGPipeResidualFillSuppliesField(MGPipeInputField::GetRenderStateParameters, kNoSubsystems, true, false)) << "the memo answered the previous mask: MOBILEGL_PIPE_PUSH is not in its key, and a " "per-subsystem A/B would then fill from the wrong subsystem set"; // 2. THE DERIVATION LATCH. GetViewport reaches PipeInputs only through // MGPipeDeriveRenderStateFields, so a build whose derivation is a stub must keep PULLING // it - skipping it there leaves the mirror unwritten and the backend reading a default. - EXPECT_TRUE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetViewport, kEverySubsystem, - true, false)); - EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetViewport, kEverySubsystem, - false, false)) + EXPECT_TRUE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetViewport, kEverySubsystem, true, false)); + EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetViewport, kEverySubsystem, false, false)) << "the memo answered the previous latch: ApplierDerivesRenderStateFields is not in its key"; // 3. P5c rv's WIRE GATE (CONTRACT-P5C.md §5.3). set_context_values has no producer without a // live wire, so its eight value-class fields keep being pulled under monolith - G1's // byte-for-byte rule - and are skipped only when the record really crosses. - EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetActiveTextureUnit, - kEverySubsystem, true, false)); - EXPECT_TRUE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetActiveTextureUnit, - kEverySubsystem, true, true)) + EXPECT_FALSE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetActiveTextureUnit, kEverySubsystem, true, false)); + EXPECT_TRUE(MGPipeResidualFillSuppliesField(MGPipeInputField::GetActiveTextureUnit, kEverySubsystem, true, true)) << "the memo answered the previous wire state: contextValuesWireLive is not in its key, " "and the emission's half of the gate and the fill's half would then disagree"; @@ -441,8 +436,7 @@ TEST_F(FieldOwnershipTest, TheResidualFillsSuppliedMemoReKeysOnEveryInputThatMov ASSERT_NE(MGPipeGetResourceOps(), nullptr) << "the fixture did not move the signal it names, so this step observes nothing"; for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { - if (MGPipeResidualFillSuppliesField(static_cast(i), kEverySubsystem, - true, true)) { + if (MGPipeResidualFillSuppliesField(static_cast(i), kEverySubsystem, true, true)) { withConsumer.Words[i / 64] |= (Uint64{1} << (i % 64)); } } @@ -450,8 +444,7 @@ TEST_F(FieldOwnershipTest, TheResidualFillsSuppliedMemoReKeysOnEveryInputThatMov MGPipeSetResourceOps(nullptr); ASSERT_EQ(MGPipeGetResourceOps(), nullptr); for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { - if (MGPipeResidualFillSuppliesField(static_cast(i), kEverySubsystem, - true, true)) { + if (MGPipeResidualFillSuppliesField(static_cast(i), kEverySubsystem, true, true)) { withoutConsumer.Words[i / 64] |= (Uint64{1} << (i % 64)); } } @@ -461,13 +454,11 @@ TEST_F(FieldOwnershipTest, TheResidualFillsSuppliedMemoReKeysOnEveryInputThatMov // and so rides the framebuffer family's consumer gate - and is pulled regardless, because // its storage is a BindingSlot no payload can carry. EXPECT_FALSE(MGPipeFieldMaskHas(withConsumer, MGPipeInputField::GetFramebufferBindingSlot)); - EXPECT_FALSE( - MGPipeFieldMaskHas(withoutConsumer, MGPipeInputField::GetFramebufferBindingSlot)); + EXPECT_FALSE(MGPipeFieldMaskHas(withoutConsumer, MGPipeInputField::GetFramebufferBindingSlot)); for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { const auto field = static_cast(i); - EXPECT_EQ(MGPipeFieldMaskHas(withConsumer, field), - MGPipeFieldMaskHas(withoutConsumer, field)) + EXPECT_EQ(MGPipeFieldMaskHas(withConsumer, field), MGPipeFieldMaskHas(withoutConsumer, field)) << kMGPipeInputFieldNames[i] << "'s supplied answer moved with the P4a consumer signal. That is CORRECT " "behaviour and this assertion is a trip wire, not a defect report: the " @@ -488,8 +479,8 @@ TEST_F(FieldOwnershipTest, AServerStampMakesRecordSuppliedFieldsFreshAndWithdraw MGPipeServerStampVerbBoundary(MGPipeVerb::Clear); EXPECT_TRUE(gPipeInputs->ServerStampedVerb()); EXPECT_EQ(gPipeInputs->CurrentVerb(), MGPipeVerb::Clear); - const MGPipeFieldMask& mask = kMGPipeClassFieldMask[static_cast( - kMGPipeVerbClass[static_cast(MGPipeVerb::Clear)])]; + const MGPipeFieldMask& mask = + kMGPipeClassFieldMask[static_cast(kMGPipeVerbClass[static_cast(MGPipeVerb::Clear)])]; SizeT stamped = 0; for (SizeT i = 0; i < kMGPipeInputFieldCount; ++i) { const auto field = static_cast(i); @@ -497,12 +488,12 @@ TEST_F(FieldOwnershipTest, AServerStampMakesRecordSuppliedFieldsFreshAndWithdraw // The stamp respects the verb's own may-read table for the client fill's reason: a // field outside the class was never copied for this verb, so answering it would hand // the server the previous verb's value. - const Bool answerable = MGPipeFieldMaskHas(mask, field) && - (kMGPipeFieldOwnership[i] == MGPipeFieldOwnership::kRecordSupplied || - kMGPipeFieldOwnership[i] == MGPipeFieldOwnership::kApplierDerived); - EXPECT_EQ(fresh, answerable) - << kMGPipeInputFieldNames[i] << " is " << MGPipeFieldOwnershipName(kMGPipeFieldOwnership[i]) - << " and the stamp answered " << (fresh ? "fresh" : "stale"); + const Bool answerable = + MGPipeFieldMaskHas(mask, field) && (kMGPipeFieldOwnership[i] == MGPipeFieldOwnership::kRecordSupplied || + kMGPipeFieldOwnership[i] == MGPipeFieldOwnership::kApplierDerived); + EXPECT_EQ(fresh, answerable) << kMGPipeInputFieldNames[i] << " is " + << MGPipeFieldOwnershipName(kMGPipeFieldOwnership[i]) << " and the stamp answered " + << (fresh ? "fresh" : "stale"); stamped += fresh ? 1 : 0; } // Not vacuous: kClear really does have record-supplied fields to stamp. @@ -513,10 +504,10 @@ TEST_F(FieldOwnershipTest, AServerStampMakesRecordSuppliedFieldsFreshAndWithdraw // with the table - never a table that is wrong. These four say what the stamp must do for // four fields whose class is an argument of this package rather than a lookup. const MGPipeFilledState& filled = gPipeInputs->FilledState(); - EXPECT_TRUE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetClearColor)); // supplied + EXPECT_TRUE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetClearColor)); // supplied EXPECT_TRUE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetRenderStateParameters)); EXPECT_FALSE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::GetFramebufferBindingSlot)); // pulled - EXPECT_FALSE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::RecordError)); // sticky + EXPECT_FALSE(MGPipeInputFieldIsFresh(filled, MGPipeInputField::RecordError)); // sticky } // THE STICKY EXEMPTION, CANCELLED. generated/PipeFilled.inc answers "fresh" for a sticky field @@ -540,8 +531,7 @@ TEST_F(FieldOwnershipTest, TheStickyExemptionIsCancelledByTheServerStamp) { // E4's NEGATIVE CONTROL. Move one field from RECORD-SUPPLIED to FATAL in FieldOwnership.def // and this case goes red by name: the read below aborts instead of completing. TEST_F(FieldOwnershipTest, ARecordSuppliedFieldIsReadableAfterAServerStamp) { - ASSERT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetClearColor), - MGPipeFieldOwnership::kRecordSupplied); + ASSERT_EQ(MGPipeFieldOwnershipOf(MGPipeInputField::GetClearColor), MGPipeFieldOwnership::kRecordSupplied); MGPipeServerStampVerbBoundary(MGPipeVerb::Clear); (void)gPipeInputs->GetClearColor(); (void)gPipeInputs->GetRenderStateParameters(); @@ -610,7 +600,6 @@ TEST_F(FieldOwnershipTest, ABarrierPulledReadAfterAServerStampIsCountedNotFatal) #if MGTEST_HAVE_FORK // Historical name retained for G14: the formerly admitted read is now forbidden. const ChildResult r = RunInChild([] { - MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); (void)gPipeInputs->GetBoundVertexArray(); }); @@ -627,7 +616,6 @@ TEST_F(FieldOwnershipTest, AStickyForwardIsCountedAsAResidualPull) { #if MGTEST_HAVE_FORK // Historical name retained for G14: the formerly admitted read is now forbidden. const ChildResult r = RunInChild([] { - MGPipeServerStampVerbBoundary(MGPipeVerb::Clear); (void)gPipeInputs->ValidateProgramName(1u); }); @@ -680,8 +668,7 @@ TEST_F(FieldOwnershipTest, ABarrierPulledFieldOutsideTheVerbsClassIsStillFatal) (void)gPipeInputs->GetProgramForDraw(); // retired accessor, outside kClear too }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDraw@Clear\"}"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDraw@Clear\"}"), std::string::npos) << r.Log; EXPECT_EQ(r.Log.find("BARRIER-PULLED"), std::string::npos) << r.Log; #else GTEST_SKIP() << "needs fork"; @@ -730,8 +717,7 @@ namespace { } void Arm(Bool arm) { MG_Config::Ipc.RoleSplitState = arm; - MG_Config::Transport = arm ? MG_Config::TransportMode::InProcess - : MG_Config::TransportMode::Monolith; + MG_Config::Transport = arm ? MG_Config::TransportMode::InProcess : MG_Config::TransportMode::Monolith; } MG_Config::TransportMode m_previousTransport; Bool m_previousKnob; @@ -872,8 +858,7 @@ TEST_F(FieldOwnershipTest, RoleSplitGivesTheFillSideADistinctBlockTheStampNeverT // which is CONTRACT-P5E §3.2's "the server's stamp is server-private" as a fact rather // than as a comment. MGPipeClientClearVerbBoundary(); - EXPECT_TRUE(gPipeInputs->ServerStampedVerb()) - << "the client-role clear withdrew the SERVER's stamp"; + EXPECT_TRUE(gPipeInputs->ServerStampedVerb()) << "the client-role clear withdrew the SERVER's stamp"; MGPipeServerClearVerbBoundary(); EXPECT_FALSE(gPipeInputs->ServerStampedVerb()); @@ -914,8 +899,7 @@ TEST_F(FieldOwnershipTest, AClientFreshStampCannotReviveARetiredServerGetter) { (void)gPipeInputs->GetBoundVertexArray(); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), - std::string::npos) << r.Log; + EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetBoundVertexArray@DrawArrays\"}"), std::string::npos) << r.Log; } TEST_F(FieldOwnershipTest, StrictErrorsTurnsABarrierPulledReadIntoANamedAbort) { @@ -1000,12 +984,12 @@ TEST_F(FieldOwnershipTest, AFatalClassReadAbortsEvenWithoutStrictErrors) { TEST_F(FieldOwnershipTest, TheComputeProgramIsServedUnderADispatchStampFromP5bOn) { // Historical name retained for G14: the formerly admitted read is now forbidden. const ChildResult r = RunInChild([] { - MGPipeServerStampVerbBoundary(MGPipeVerb::DispatchCompute); (void)gPipeInputs->GetProgramForDispatch(); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch@DispatchCompute\"}"), std::string::npos) << r.Log; + EXPECT_NE(r.Log.find("Fatal{UnmigratedPipeInput, \"GetProgramForDispatch@DispatchCompute\"}"), std::string::npos) + << r.Log; EXPECT_EQ(r.Log.find("Admitted{"), std::string::npos) << r.Log; } @@ -1019,15 +1003,13 @@ TEST_F(FieldOwnershipTest, TheUnpackHalfOfThePixelStoreAbortsWhileThePackHalfDoe (void)gPipeInputs->GetPixelStoreParameters(true); }); ASSERT_TRUE(DiedOfAbort(fatal)) << DescribeStatus(fatal) << "\n" << fatal.Log; - EXPECT_NE(fatal.Log.find("Fatal{UnmigratedPipeInput, \"GetPixelStoreParameters@ReadPixels\"}"), - std::string::npos) + EXPECT_NE(fatal.Log.find("Fatal{UnmigratedPipeInput, \"GetPixelStoreParameters@ReadPixels\"}"), std::string::npos) << fatal.Log; // The line must say WHICH HALF. Without this the message is byte-identical to what a // genuinely stale read of the pack half would print, and the whole case for narrowing by // argument instead of by a second field id is that the reader is told which half they // asked for. - EXPECT_NE(fatal.Log.find("argument 0 = 1 is FATAL while the field is APPLIER-DERIVED"), - std::string::npos) + EXPECT_NE(fatal.Log.find("argument 0 = 1 is FATAL while the field is APPLIER-DERIVED"), std::string::npos) << fatal.Log; const ChildResult ok = RunInChild([] { @@ -1055,8 +1037,7 @@ TEST_F(FieldOwnershipTest, AnUnmigratedEmulationIsFatalUnderARealTransportAndIne MGPipeUnmigratedEmulation("get-tex-image-shadow"); }); ASSERT_TRUE(DiedOfAbort(split)) << DescribeStatus(split) << "\n" << split.Log; - EXPECT_NE(split.Log.find("Fatal{UnmigratedEmulation, \"get-tex-image-shadow\"}"), std::string::npos) - << split.Log; + EXPECT_NE(split.Log.find("Fatal{UnmigratedEmulation, \"get-tex-image-shadow\"}"), std::string::npos) << split.Log; } // P5f terminal contract; the historical registration name is retained for G14. diff --git a/MobileGL/MG_Test/Pipe/FramebufferEmitTest.cpp b/MobileGL/MG_Test/Pipe/FramebufferEmitTest.cpp index d93e4162..fdc7de2d 100644 --- a/MobileGL/MG_Test/Pipe/FramebufferEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/FramebufferEmitTest.cpp @@ -137,7 +137,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -242,10 +244,8 @@ namespace { // content-addressed cache) and bit 10 requires bit 7 (a buffer texture's // BufferForTexBuffer names a Buffer handle, D-D1). Bit 7 changes nothing else here: // this file constructs no BufferObject. - MG_Config::Features.PipePush |= kMGPipeSubsystemResources | - kMGPipeSubsystemFramebuffer | - kMGPipeSubsystemTextureResources | - kMGPipeSubsystemSamplers; + MG_Config::Features.PipePush |= kMGPipeSubsystemResources | kMGPipeSubsystemFramebuffer | + kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers; m_previousContext = MG_State::pGLContext.Take(); MG_State::pGLContext = MakeUnique(); MGPipeFramebufferEmitterInstance().ResetForTest(); @@ -275,17 +275,21 @@ namespace { Uint64 m_previousPush = 0; }; - MGPipeFramebufferEmitter& Framebuffers() { return MGPipeFramebufferEmitterInstance(); } - GLContext& Ctx() { return *MG_State::pGLContext; } + MGPipeFramebufferEmitter& Framebuffers() { + return MGPipeFramebufferEmitterInstance(); + } + GLContext& Ctx() { + return *MG_State::pGLContext; + } SharedPtr MakeColorTexture(Uint name, Int size, Uint levels = 1) { auto texture = MakeShared(name); texture->SetInternalFormat(TextureInternalFormat::RGBA8); for (Uint level = 0; level < levels; ++level) { const Int extent = std::max(size >> level, 1); - texture->AllocateStorage(TextureUploadTarget::Texture2D, level, - MipmapInput{IntVec3{extent, extent, 1}, - static_cast(extent) * static_cast(extent) * 4}); + texture->AllocateStorage( + TextureUploadTarget::Texture2D, level, + MipmapInput{IntVec3{extent, extent, 1}, static_cast(extent) * static_cast(extent) * 4}); } return texture; } @@ -362,8 +366,7 @@ TEST(FramebufferEmit, OneObjectBoundToBothTargetsEmitsOneRecordWithTargetBoth) { Framebuffers().EmitFramebufferState(Ctx()); EXPECT_EQ(Framebuffers().EmissionCount(), 1u) << "one object on both targets is ONE record"; EXPECT_EQ(Framebuffers().LastDraw().Target, static_cast(MGPipeFramebufferTarget::Both)); - const MGPipeHandle color1Handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, color1->GetLifetimeId()); + const MGPipeHandle color1Handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, color1->GetLifetimeId()); EXPECT_TRUE(Framebuffers().LastDraw().ReadSurface.Res == color1Handle) << "the shared-FBO record's ReadSurface is not that object's own read buffer"; } @@ -445,10 +448,10 @@ TEST(FramebufferEmit, EveryAttachmentFieldSurvivesTheSurfaceConversion) { colors.push_back(MakeColorTexture(static_cast(20 + i), 32, 2)); // Distinct level, layer and layered flag per point, so a conversion that dropped one // field could not be masked by another point's value. - fbo->AttachTexture(static_cast( - static_cast(FramebufferAttachmentType::Color0) + static_cast(i)), - colors.back(), TextureUploadTarget::Texture2D, - static_cast(i % 2), static_cast(i), (i % 2) == 0); + fbo->AttachTexture(static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(i)), + colors.back(), TextureUploadTarget::Texture2D, static_cast(i % 2), static_cast(i), + (i % 2) == 0); } const auto depth = MakeShared(2); depth->SetInternalFormat(TextureInternalFormat::Depth24Stencil8); @@ -463,8 +466,8 @@ TEST(FramebufferEmit, EveryAttachmentFieldSurvivesTheSurfaceConversion) { const MGPFramebufferState& record = Framebuffers().LastDraw(); for (SizeT i = 0; i < kMGPipeMaxColorAttachments; ++i) { - const auto type = static_cast( - static_cast(FramebufferAttachmentType::Color0) + static_cast(i)); + const auto type = static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(i)); const auto& attachment = fbo->GetAttachment(type); const MGPSurface& surface = record.Color[i]; const MGPipeHandle expected = @@ -477,29 +480,25 @@ TEST(FramebufferEmit, EveryAttachmentFieldSurvivesTheSurfaceConversion) { << "MGPSurface::Level at colour point " << i; EXPECT_EQ(surface.Layer, static_cast(attachment.GetTextureLayer())) << "MGPSurface::Layer at colour point " << i; - EXPECT_EQ(surface.Layered, attachment.IsLayered() ? 1 : 0) - << "MGPSurface::Layered at colour point " << i; + EXPECT_EQ(surface.Layered, attachment.IsLayered() ? 1 : 0) << "MGPSurface::Layered at colour point " << i; EXPECT_EQ(surface.UploadTarget, static_cast(TextureUploadTarget::Texture2D)) << "MGPSurface::UploadTarget at colour point " << i; // ID-12 DV-5: three of the four cross-object masks reduce to (format, TEXTURE TARGET) // and no TextureUploadTarget -> TextureTarget inverse exists anywhere in the tree. - EXPECT_EQ(surface.TextureTarget, - static_cast(attachment.GetTexture()->GetTarget())) + EXPECT_EQ(surface.TextureTarget, static_cast(attachment.GetTexture()->GetTarget())) << "MGPSurface::TextureTarget at colour point " << i; } EXPECT_EQ(record.Depth.Kind, kMGPipeSurfaceKindRenderbuffer) << "MGPSurface::Kind on the depth point"; EXPECT_EQ(record.Depth.InternalFormat, static_cast(depth->GetInternalFormat())) << "MGPSurface::InternalFormat on the depth point"; - EXPECT_TRUE(record.Depth.Res == - MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, depth->GetLifetimeId())) + EXPECT_TRUE(record.Depth.Res == MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, depth->GetLifetimeId())) << "MGPSurface::Res on the depth point"; EXPECT_EQ(record.Depth.Layered, 0) << "MGPSurface::Layered on the depth point"; EXPECT_EQ(record.Depth.TextureTarget, kMGPipeSurfaceNoTextureTarget) << "MGPSurface::TextureTarget on the RENDERBUFFER point must be the Unknown sentinel"; EXPECT_EQ(record.Stencil.Kind, kMGPipeSurfaceKindTexture) << "MGPSurface::Kind on the stencil point"; - EXPECT_TRUE(record.Stencil.Res == - MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, stencil->GetLifetimeId())) + EXPECT_TRUE(record.Stencil.Res == MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, stencil->GetLifetimeId())) << "MGPSurface::Res on the stencil point"; EXPECT_EQ(record.Width, 32u) << "MGPFramebufferState::Width"; @@ -520,8 +519,7 @@ TEST(FramebufferEmit, EveryAttachmentFieldSurvivesTheSurfaceConversion) { // BOTH TARGET FIELDS CARRY THE SENTINEL, not 0 (m6 / ID-12 DV-5): a Uint16 zero is // TextureUploadTarget::Texture1D and TextureTarget::Texture1D, so a reader that forgot to // gate on Kind would read a plausible wrong answer instead of a nonsense one. - EXPECT_EQ(Framebuffers().LastDraw().Color[1].UploadTarget, - static_cast(TextureUploadTarget::Unknown)); + EXPECT_EQ(Framebuffers().LastDraw().Color[1].UploadTarget, static_cast(TextureUploadTarget::Unknown)); EXPECT_EQ(Framebuffers().LastDraw().Color[1].TextureTarget, kMGPipeSurfaceNoTextureTarget); } @@ -540,9 +538,8 @@ TEST(FramebufferEmit, AnAttachmentPointAboveTheWireWidthIsRefusedNotTruncated) { // MGPFramebufferState::Color[] is eight wide and MaxColorAttachments is the driver's raw ES // cap, not clamped to eight on the GLES path. Truncating silently is the bug class this // phase is closing, so the record is REFUSED and the legacy arm runs. - fbo->AttachTexture(static_cast( - static_cast(FramebufferAttachmentType::Color0) + - static_cast(kMGPipeMaxColorAttachments)), + fbo->AttachTexture(static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(kMGPipeMaxColorAttachments)), beyond, TextureUploadTarget::Texture2D); Framebuffers().EmitFramebufferState(Ctx()); EXPECT_EQ(Framebuffers().EmissionCount(), 1u) @@ -576,8 +573,8 @@ TEST(FramebufferEmit, TheRecordsElevenSurfacesAreTheWholePointSetTheServerReads) Vector> colors; for (Uint i = 0; i < kMGPipeMaxColorAttachments; ++i) { colors.push_back(MakeColorTexture(70 + i, 32)); - fbo->AttachTexture(static_cast( - static_cast(FramebufferAttachmentType::Color0) + static_cast(i)), + fbo->AttachTexture(static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(i)), colors.back(), TextureUploadTarget::Texture2D); } const auto depth = MakeShared(21); @@ -599,11 +596,9 @@ TEST(FramebufferEmit, TheRecordsElevenSurfacesAreTheWholePointSetTheServerReads) const MGPFramebufferState* const applied = MGPipeApplier().DrawFramebuffer(); ASSERT_NE(applied, nullptr); for (Uint i = 0; i < kMGPipeMaxColorAttachments; ++i) { - const MGPipeHandle expected = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, colors[i]->GetLifetimeId()); + const MGPipeHandle expected = MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, colors[i]->GetLifetimeId()); EXPECT_FALSE(MGPipeHandleIsNull(expected)) << "colour point " << i << " minted no handle"; - EXPECT_TRUE(applied->Color[i].Res == expected) - << "colour point " << i << " did not reach the server's record"; + EXPECT_TRUE(applied->Color[i].Res == expected) << "colour point " << i << " did not reach the server's record"; EXPECT_EQ(applied->Color[i].Kind, kMGPipeSurfaceKindTexture) << "at colour point " << i; } EXPECT_EQ(applied->Depth.Kind, kMGPipeSurfaceKindRenderbuffer) << "the Depth point"; @@ -614,9 +609,8 @@ TEST(FramebufferEmit, TheRecordsElevenSurfacesAreTheWholePointSetTheServerReads) const Uint64 hashBefore = applied->ContentHash; const Uint64 serialBefore = MGPipeApplier().FramebufferSerial; const auto beyond = MakeColorTexture(90, 32); - fbo->AttachTexture(static_cast( - static_cast(FramebufferAttachmentType::Color0) + - static_cast(kMGPipeMaxColorAttachments)), + fbo->AttachTexture(static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(kMGPipeMaxColorAttachments)), beyond, TextureUploadTarget::Texture2D); Framebuffers().EmitFramebufferState(Ctx()); EXPECT_GE(Framebuffers().RefusedCount(), 1u) << "the over-wide framebuffer was not refused"; @@ -730,8 +724,7 @@ TEST(FramebufferEmit, ARestoragedAttachedRenderbufferPublishesItsNewExtent) { BindDrawAndRead(fbo, fbo); Framebuffers().EmitFramebufferState(Ctx()); - const MGPipeHandle handle = - MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, renderbuffer->GetLifetimeId()); + const MGPipeHandle handle = MGPipeSlots().FindByLifetimeId(MGPipeKind::Renderbuffer, renderbuffer->GetLifetimeId()); ASSERT_FALSE(MGPipeHandleIsNull(handle)); const Uint64 respecifiesBefore = MGPipeTextureEmitterInstance().RespecifyCount(); @@ -785,18 +778,15 @@ TEST(FramebufferEmit, AFramebufferHandedOverByNameGetsANamedRecordWithoutMovingA EXPECT_EQ(Framebuffers().LastNamed().Target, static_cast(MGPipeFramebufferTarget::Named)); EXPECT_GT(MGPipeApplier().FramebufferSerial, serialBefore) << "a Named record must publish"; - const MGPFramebufferState* stored = - MGPipeApplier().FramebufferRecordFor(Framebuffers().LastNamed().Fbo); + const MGPFramebufferState* stored = MGPipeApplier().FramebufferRecordFor(Framebuffers().LastNamed().Fbo); ASSERT_NE(stored, nullptr) << "the framebuffer the server is about to receive by name has no record"; EXPECT_EQ(stored->Width, 16u) << "the Named record does not describe the object it names"; // AND IT MOVED NEITHER BINDING. ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr); - EXPECT_TRUE(MGPipeApplier().DrawFramebuffer()->Fbo == boundHandle) - << "a Named record took the draw binding"; + EXPECT_TRUE(MGPipeApplier().DrawFramebuffer()->Fbo == boundHandle) << "a Named record took the draw binding"; ASSERT_NE(MGPipeApplier().ReadFramebuffer(), nullptr); - EXPECT_TRUE(MGPipeApplier().ReadFramebuffer()->Fbo == boundHandle) - << "a Named record took the read binding"; + EXPECT_TRUE(MGPipeApplier().ReadFramebuffer()->Fbo == boundHandle) << "a Named record took the read binding"; } TEST(FramebufferEmit, ANamedRecordIsSuppressedPerObjectAndNeverAgainstABoundRecord) { @@ -842,9 +832,8 @@ TEST(FramebufferEmit, ADrawBufferTokenAboveTheWireWidthIsRefusedNotTruncated) { // A LEGAL STATE THE ATTACHMENT SCAN CANNOT SEE: nothing is attached at the point the token // names, so D-C3's attachment loop passes, and MGPipeDrawBufferIndex would write an index of // 8 or more into an 8-wide array. - fbo->SetDrawBuffer(0, static_cast( - static_cast(FramebufferAttachmentType::Color0) + - static_cast(kMGPipeMaxColorAttachments) + 2)); + fbo->SetDrawBuffer(0, static_cast(static_cast(FramebufferAttachmentType::Color0) + + static_cast(kMGPipeMaxColorAttachments) + 2)); Framebuffers().EmitFramebufferState(Ctx()); EXPECT_EQ(Framebuffers().EmissionCount(), 1u) << "a draw-buffer token naming a colour point at or above the wire width was truncated " @@ -873,12 +862,10 @@ TEST(FramebufferEmit, ALayeredCubeAttachmentDoesNotAssertAFaceItCannotKnow) { Framebuffers().EmitFramebufferState(Ctx()); ASSERT_EQ(Framebuffers().EmissionCount(), 1u); EXPECT_EQ(Framebuffers().LastDraw().Color[0].Layered, 1); - EXPECT_EQ(Framebuffers().LastDraw().Color[0].UploadTarget, - static_cast(TextureUploadTarget::Unknown)) + EXPECT_EQ(Framebuffers().LastDraw().Color[0].UploadTarget, static_cast(TextureUploadTarget::Unknown)) << "a layered cube attachment resolved to one face, so the record asserts a face the " "attachment does not name"; - EXPECT_EQ(Framebuffers().LastDraw().Color[0].TextureTarget, - static_cast(TextureTarget::TextureCubeMap)) + EXPECT_EQ(Framebuffers().LastDraw().Color[0].TextureTarget, static_cast(TextureTarget::TextureCubeMap)) << "MGPSurface::TextureTarget is what a cross-object mask reads instead"; } @@ -958,8 +945,7 @@ TEST(FramebufferEmit, ADrawRecordAndAReadRecordAreKeptApartAndBothWritesBoth) { EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Width, 100u); EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Color[0].InternalFormat, 0x8058u); EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->DrawBuffers[0], 0); - EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr) - << "a Draw record landed in the read binding as well"; + EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr) << "a Draw record landed in the read binding as well"; const Uint64 afterDraw = MGPipeApplier().FramebufferSerial; EXPECT_GT(afterDraw, serialAtStart) << "an applied record must move the serial the twin memoises"; @@ -1011,8 +997,7 @@ TEST(FramebufferEmit, ATargetOutsideTheThreeBindingsIsRefusedNamingTheRecord) { EXPECT_EQ(MGPipeApplier().ReadFramebuffer(), nullptr); EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{7, 4}), nullptr) << "a refused record was written into the per-object table anyway"; - EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore) - << "a refused record must not move the serial"; + EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore) << "a refused record must not move the serial"; } // The draw-buffer array is an INDEX into this record's own Color[], and -1 is NONE. An entry @@ -1163,7 +1148,8 @@ TEST(FramebufferEmit, ANamedRecordDescribesTheFramebufferItNamesWithoutMovingEit "clears into a driver framebuffer with no attachments"; EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{6, 3})->Width, 300u); EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{6, 3})->Color[0].Res, (MGPipeHandle{21, 1})); - EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{6, 3})->Target, static_cast(MGPipeFramebufferTarget::Named)); + EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(MGPipeHandle{6, 3})->Target, + static_cast(MGPipeFramebufferTarget::Named)); // (b) AND NEITHER BINDING MOVED. ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr); @@ -1236,8 +1222,7 @@ TEST(FramebufferEmit, AFramebufferRecordThatNamesNoUsableHandleIsRefusedRatherTh ApplierGuard guard; // The positive control first: the DEFAULT framebuffer is slot 0 at generation 1 and is // perfectly legal, so what follows refuses the null handle and not slot 0. - MGPipeApplySetFramebufferState( - FramebufferRecord(kMGPipeDefaultFramebuffer, MGPipeFramebufferTarget::Both, 128)); + MGPipeApplySetFramebufferState(FramebufferRecord(kMGPipeDefaultFramebuffer, MGPipeFramebufferTarget::Both, 128)); ASSERT_NE(MGPipeApplier().FramebufferRecordFor(kMGPipeDefaultFramebuffer), nullptr); EXPECT_EQ(MGPipeApplier().FramebufferRecordFor(kMGPipeDefaultFramebuffer)->Width, 128u); const Uint64 serialBefore = MGPipeApplier().FramebufferSerial; @@ -1247,20 +1232,18 @@ TEST(FramebufferEmit, AFramebufferRecordThatNamesNoUsableHandleIsRefusedRatherTh "framebuffer handle", [&nullHandle]() { MGPipeApplySetFramebufferState(nullHandle); }); - MGPFramebufferState pastTheBound = FramebufferRecord( - MGPipeHandle{kMGPipeMaxFramebufferSlots, 1}, MGPipeFramebufferTarget::Draw, 400); + MGPFramebufferState pastTheBound = + FramebufferRecord(MGPipeHandle{kMGPipeMaxFramebufferSlots, 1}, MGPipeFramebufferTarget::Draw, 400); ExpectRefusedNaming("set_framebuffer_state {slot=65536, gen=1, target=0}: the framebuffer slot is " "outside the record table's bound", [&pastTheBound]() { MGPipeApplySetFramebufferState(pastTheBound); }); - static_assert(kMGPipeMaxFramebufferSlots == 65536u, - "the refusal line above names the bound; move both together"); + static_assert(kMGPipeMaxFramebufferSlots == 65536u, "the refusal line above names the bound; move both together"); EXPECT_EQ(MGPipeApplier().FramebufferSerial, serialBefore) << "a refused record moved the serial"; ASSERT_NE(MGPipeApplier().DrawFramebuffer(), nullptr); EXPECT_EQ(MGPipeApplier().DrawFramebuffer()->Fbo, kMGPipeDefaultFramebuffer) << "a refused record took the draw binding"; - EXPECT_LT(MGPipeApplier().FramebufferRecords.size(), - static_cast(kMGPipeMaxFramebufferSlots)) + EXPECT_LT(MGPipeApplier().FramebufferRecords.size(), static_cast(kMGPipeMaxFramebufferSlots)) << "an out-of-range slot resized the table instead of being refused"; } diff --git a/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp b/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp index 37c9ffe6..b7cfaba7 100644 --- a/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ImageEmitTest.cpp @@ -140,7 +140,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -324,7 +326,6 @@ TEST(ImageEmit, AMakeCurrentClearsTheImageSetAndAdvancesItsSerial) { EXPECT_GT(MGPipeApplier().ShaderImagesSerial, serialBefore); } - namespace { namespace GL = MobileGL::MG_Impl::GLImpl; using GLContext = MG_State::GLState::GLContext; @@ -343,8 +344,12 @@ namespace { } }; - GLContext& Ctx() { return *MG_State::pGLContext; } - MGPipeImageEmitter& Emitter() { return MGPipeImageEmitterInstance(); } + GLContext& Ctx() { + return *MG_State::pGLContext; + } + MGPipeImageEmitter& Emitter() { + return MGPipeImageEmitterInstance(); + } GLuint MakeComputeProgram(const char* source) { const GLuint shader = GL::CreateShader(GL_COMPUTE_SHADER); @@ -425,8 +430,7 @@ void main() { imageStore(img, ivec2(0), vec4(1.0)); } const auto& resolution = MGPipeProgramOpaqueUnitsShared().For(object.get()); ASSERT_EQ(resolution.MaxImageUnit, 1) << "maxUniformLocation=" << object->GetMaxUniformLocation() - << " unit@0=" << object->GetUniformSamplerOrImageUnitIndex(0) - << " linked=" << object->GetLinkStatus(); + << " unit@0=" << object->GetUniformSamplerOrImageUnitIndex(0) << " linked=" << object->GetLinkStatus(); // ASSERTED ON THE COUNTER, NOT ON THIS CALL'S RETURN VALUE, and the reason is worth // writing down because it surprised this suite: the glBindImageTexture above ALREADY @@ -518,9 +522,8 @@ void main() { imageStore(img, ivec2(0), vec4(1.0)); } ASSERT_GE(Emitter().ImageSetCount(), 1u); ASSERT_GE(Emitter().LastShaderImages().Count, 2u); const MGPImageView& view = Emitter().LastImageViews()[1]; - EXPECT_EQ(view.Access, c.encoded) - << "glBindImageTexture(access=0x" << std::hex << static_cast(c.gl) << std::dec - << ") did not travel as the shared constant"; + EXPECT_EQ(view.Access, c.encoded) << "glBindImageTexture(access=0x" << std::hex << static_cast(c.gl) + << std::dec << ") did not travel as the shared constant"; ASSERT_TRUE(MGPipeImageAccessIsValid(view.Access)); EXPECT_EQ(MGPipeDecodeImageAccess(view.Access), c.decoded) << "the server's decode does not invert the client's encode"; @@ -632,8 +635,7 @@ void main() { imageStore(img, ivec2(0, 0), vec4(1.0)); } GL::BindImageTexture(1, name, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8); Emitter().EmitShaderImages(Ctx()); - EXPECT_EQ(Emitter().LastImageViews()[1].Level, 0u) - << "the base level is base-complete and must stay bound"; + EXPECT_EQ(Emitter().LastImageViews()[1].Level, 0u) << "the base level is base-complete and must stay bound"; GL::UseProgram(0); GL::BindImageTexture(1, 0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8); } @@ -662,8 +664,8 @@ void main() { imageStore(img, ivec2(0, 0), vec4(1.0)); } // And the bind is checked before the mark is, so a REFUSED bind says so rather than // reading as a missing NoteImageUnitTouched. ASSERT_TRUE(Ctx().GetImageTextureBinding(kUnit).Texture != nullptr) - << "glBindImageTexture did not take: this lane's backend advertises fewer than " - << (kUnit + 1) << " image units, so the case cannot observe the mark at all"; + << "glBindImageTexture did not take: this lane's backend advertises fewer than " << (kUnit + 1) + << " image units, so the case cannot observe the mark at all"; EXPECT_GE(Ctx().GetMaxTouchedImageUnit(), kUnit) << "glBindImageTexture did not move the image-unit high-water mark, so every reader " "that bounds a per-draw walk with it now walks past this binding"; @@ -692,8 +694,7 @@ int main(int argc, char** argv) { // raised by the encoder on THIS thread - the client role. So g_logPath, which is the read // path from here on, becomes the client-derived name; the env keeps the base. The rule is // the library's own, not a copy. - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); MobileGL::Initialize(); ::testing::InitGoogleTest(&argc, argv); const int rc = RUN_ALL_TESTS(); diff --git a/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp b/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp index 67d20382..9c68ae56 100644 --- a/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp +++ b/MobileGL/MG_Test/Pipe/MagmaAggregateWaitTest.cpp @@ -18,49 +18,49 @@ #include namespace { -using MobileGL::MG_Backend::DirectVulkan::CollectSubmitFencePrefix; + using MobileGL::MG_Backend::DirectVulkan::CollectSubmitFencePrefix; -struct Submit { - std::uint64_t submitIndex; - std::uint64_t frameSerial; - int fence; -}; + struct Submit { + std::uint64_t submitIndex; + std::uint64_t frameSerial; + int fence; + }; -bool SerialComplete(std::uint64_t serial, const std::vector& pending) { - return std::none_of(pending.begin(), pending.end(), [serial](const Submit& submit) { - return submit.frameSerial == serial; - }); -} + bool SerialComplete(std::uint64_t serial, const std::vector& pending) { + return std::none_of(pending.begin(), pending.end(), + [serial](const Submit& submit) { return submit.frameSerial == serial; }); + } -TEST(MagmaAggregateWaitTest, MissingTargetAfterAnEarlierPrefixIsRejected) { - const std::vector gap{{10, 42, 101}, {12, 43, 103}}; - std::vector fences; + TEST(MagmaAggregateWaitTest, MissingTargetAfterAnEarlierPrefixIsRejected) { + const std::vector gap{{10, 42, 101}, {12, 43, 103}}; + std::vector fences; - EXPECT_FALSE(CollectSubmitFencePrefix(gap, 11, 0, fences)); - EXPECT_TRUE(fences.empty()); -} + EXPECT_FALSE(CollectSubmitFencePrefix(gap, 11, 0, fences)); + EXPECT_TRUE(fences.empty()); + } -TEST(MagmaAggregateWaitTest, SingleFenceControlLeavesTheTargetFrameInFlight) { - const std::vector pending{{10, 42, 101}, {11, 42, 102}, {12, 43, 103}}; + TEST(MagmaAggregateWaitTest, SingleFenceControlLeavesTheTargetFrameInFlight) { + const std::vector pending{{10, 42, 101}, {11, 42, 102}, {12, 43, 103}}; - // The old control waits only the first matching fence. The second submit still carries 42. - auto singleFenceControl = pending; - singleFenceControl.erase(singleFenceControl.begin()); - EXPECT_FALSE(SerialComplete(42, singleFenceControl)); + // The old control waits only the first matching fence. The second submit still carries 42. + auto singleFenceControl = pending; + singleFenceControl.erase(singleFenceControl.begin()); + EXPECT_FALSE(SerialComplete(42, singleFenceControl)); - // The production selector includes both submissions through index 11, but not frame 43. - std::vector fences; - ASSERT_TRUE(CollectSubmitFencePrefix(pending, 11, 0, fences)); - ASSERT_EQ(fences, (std::vector{101, 102})); + // The production selector includes both submissions through index 11, but not frame 43. + std::vector fences; + ASSERT_TRUE(CollectSubmitFencePrefix(pending, 11, 0, fences)); + ASSERT_EQ(fences, (std::vector{101, 102})); - auto aggregateWait = pending; - aggregateWait.erase( - std::remove_if(aggregateWait.begin(), aggregateWait.end(), [&fences](const Submit& submit) { - return std::find(fences.begin(), fences.end(), submit.fence) != fences.end(); - }), - aggregateWait.end()); - EXPECT_TRUE(SerialComplete(42, aggregateWait)); - ASSERT_EQ(aggregateWait.size(), 1u); - EXPECT_EQ(aggregateWait.front().frameSerial, 43u); -} + auto aggregateWait = pending; + aggregateWait.erase(std::remove_if(aggregateWait.begin(), aggregateWait.end(), + [&fences](const Submit& submit) { + return std::find(fences.begin(), fences.end(), submit.fence) != + fences.end(); + }), + aggregateWait.end()); + EXPECT_TRUE(SerialComplete(42, aggregateWait)); + ASSERT_EQ(aggregateWait.size(), 1u); + EXPECT_EQ(aggregateWait.front().frameSerial, 43u); + } } // namespace diff --git a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp index 49e9cd3f..b4e39975 100644 --- a/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp @@ -75,7 +75,7 @@ namespace { // Every payload named in the catalogue must be a memcpy-able POD, and so must every // payload the verify comparator knows about. -#define MGP_ASSERT_CALL_PAYLOAD_POD(Name, Payload, Class, Flags, Wait) \ +#define MGP_ASSERT_CALL_PAYLOAD_POD(Name, Payload, Class, Flags, Wait) \ static_assert(std::is_trivially_copyable_v, #Name "'s payload " #Payload " is not trivially copyable"); MGP_CALL_LIST(MGP_ASSERT_CALL_PAYLOAD_POD) @@ -87,19 +87,14 @@ namespace { // of these and MG_Pipe may not reach it (MG_Remote is above this layer), so the table is // expanded from the catalogue here rather than a name being duplicated per EXPECT. #define MGP_CATALOGUE_NAME_ROW(Name, Payload, Class, Flags, Wait) #Name, - constexpr const char* kCatalogueOpNames[] = { - "", - MGP_CALL_LIST(MGP_CATALOGUE_NAME_ROW) - }; + constexpr const char* kCatalogueOpNames[] = {"", MGP_CALL_LIST(MGP_CATALOGUE_NAME_ROW)}; #undef MGP_CATALOGUE_NAME_ROW - static_assert(sizeof(kCatalogueOpNames) / sizeof(kCatalogueOpNames[0]) == - static_cast(MGPWireOp::kOpCount), + static_assert(sizeof(kCatalogueOpNames) / sizeof(kCatalogueOpNames[0]) == static_cast(MGPWireOp::kOpCount), "the diagnostic name table and the opcode space disagree"); const char* WireOpNameForDiag(MGPWireOp op) { const SizeT index = static_cast(op); - return index < static_cast(MGPWireOp::kOpCount) ? kCatalogueOpNames[index] - : ""; + return index < static_cast(MGPWireOp::kOpCount) ? kCatalogueOpNames[index] : ""; } } // namespace @@ -130,8 +125,8 @@ TEST(PipeCatalogue, EntryCountMatchesTheDocumentedCount) { TEST(PipeCatalogue, GeneratedTablesHoldTheWholeCatalogue) { static_assert(ClassCount() == kMGPipeScreenCallCount); - static_assert(ClassCount() + ClassCount() + ClassCount() + - ClassCount() + ClassCount() + ClassCount() == + static_assert(ClassCount() + ClassCount() + ClassCount() + ClassCount() + + ClassCount() + ClassCount() == kMGPipeCallCount); // The tables ARE their function pointers: a struct that is bigger than its call count // has grown a member no generator knows about. @@ -228,10 +223,16 @@ TEST(PipeCatalogue, ExactlyTheRoutedRowsAreInstalledAndTheRestAreStillNull) { MGPipeInstallMonolithTables(); for (SizeT i = 0; i < kMGPipeScreenCallCount; ++i) { - if (screen[i] != nullptr) ++installed; else ++nulls; + if (screen[i] != nullptr) + ++installed; + else + ++nulls; } for (SizeT i = 0; i < kMGPipeContextCallCount; ++i) { - if (context[i] != nullptr) ++installed; else ++nulls; + if (context[i] != nullptr) + ++installed; + else + ++nulls; } EXPECT_EQ(installed + nulls, static_cast(kMGPipeCallCount)); @@ -407,8 +408,7 @@ TEST(PipeCatalogue, EveryTextureTargetMapsToItsOwnResourceTarget) { const auto target = static_cast(i); EXPECT_NE(MGPipeResourceTargetForTextureTarget(target), kMGPipeResourceTargetUnmapped) << "TextureTarget " << i << " has no MGPResourceDesc::Target row"; - EXPECT_LT(MGPipeResourceTargetForTextureTarget(target), - static_cast(MGPipeResourceTarget::Count)); + EXPECT_LT(MGPipeResourceTargetForTextureTarget(target), static_cast(MGPipeResourceTarget::Count)); } // Buffer is 0 and stays 0: P3a's constant is what a zero-initialised record already says, // and the narrowed ack predicate below compares against it. @@ -451,8 +451,7 @@ TEST(PipeCatalogue, SubDataTargetPacksAResourceTargetAndAnUploadTarget) { static_cast(TextureUploadTarget::CubeMapNegativeZ), static_cast(TextureUploadTarget::TextureUploadTargetCount) - 1u, }; - for (Uint32 resource = 0; resource < static_cast(MGPipeResourceTarget::Count); - ++resource) { + for (Uint32 resource = 0; resource < static_cast(MGPipeResourceTarget::Count); ++resource) { for (const Uint32 upload : uploadTargets) { const Uint16 packed = MGPipePackSubDataTarget(resource, upload); EXPECT_EQ(MGPipeSubDataResourceTargetOf(packed), static_cast(resource)) @@ -465,11 +464,9 @@ TEST(PipeCatalogue, SubDataTargetPacksAResourceTargetAndAnUploadTarget) { // THE BUFFER INVARIANT, at compile time in MGPipeTypes.h and again here so a failure names // itself: a buffer record's Target is exactly kMGPipeResourceTargetBuffer, whole field, // upload byte and all, so P3a's records are unchanged on the wire. - static_assert(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, 0u) == - kMGPipeResourceTargetBuffer); + static_assert(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, 0u) == kMGPipeResourceTargetBuffer); EXPECT_EQ(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, 0u), kMGPipeResourceTargetBuffer); - EXPECT_EQ(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, - static_cast(TextureUploadTarget::Texture1D)), + EXPECT_EQ(MGPipePackSubDataTarget(kMGPipeResourceTargetBuffer, static_cast(TextureUploadTarget::Texture1D)), kMGPipeResourceTargetBuffer); MGPSubData zeroed{}; EXPECT_EQ(zeroed.Target, kMGPipeResourceTargetBuffer); @@ -477,8 +474,7 @@ TEST(PipeCatalogue, SubDataTargetPacksAResourceTargetAndAnUploadTarget) { // ...and the other side of it: a 1D texture's upload target IS 0, and packed it still // cannot be mistaken for a buffer, because no texture's resource target is 0. EXPECT_EQ(static_cast(TextureUploadTarget::Texture1D), 0u); - for (Uint32 resource = 1; resource < static_cast(MGPipeResourceTarget::Count); - ++resource) { + for (Uint32 resource = 1; resource < static_cast(MGPipeResourceTarget::Count); ++resource) { EXPECT_NE(MGPipePackSubDataTarget(resource, 0u), kMGPipeResourceTargetBuffer) << "resource target " << resource << " collides with a buffer record"; } @@ -488,13 +484,10 @@ TEST(PipeCatalogue, SubDataTargetPacksAResourceTargetAndAnUploadTarget) { // What a real cube-face record reads back as, through the field rather than a local. MGPSubData record{}; - record.Target = - MGPipePackSubDataTarget(MGPipeResourceTargetForTextureTarget(TextureTarget::TextureCubeMap), - static_cast(TextureUploadTarget::CubeMapNegativeY)); - EXPECT_EQ(MGPipeSubDataResourceTargetOf(record.Target), - static_cast(MGPipeResourceTarget::TexCube)); - EXPECT_EQ(MGPipeSubDataUploadTargetOf(record.Target), - static_cast(TextureUploadTarget::CubeMapNegativeY)); + record.Target = MGPipePackSubDataTarget(MGPipeResourceTargetForTextureTarget(TextureTarget::TextureCubeMap), + static_cast(TextureUploadTarget::CubeMapNegativeY)); + EXPECT_EQ(MGPipeSubDataResourceTargetOf(record.Target), static_cast(MGPipeResourceTarget::TexCube)); + EXPECT_EQ(MGPipeSubDataUploadTargetOf(record.Target), static_cast(TextureUploadTarget::CubeMapNegativeY)); // Six faces share one resource target: the high byte is the only thing that tells them // apart, which is why it cannot be dropped. EXPECT_EQ(MGPipeSubDataResourceTargetOf( @@ -640,8 +633,7 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) { EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ReadPixelsToBuffer), static_cast(kNone)); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::GetTextureImageToBuffer), static_cast(kNone)); EXPECT_EQ(sizeof(MGPReadbackToBuffer), 104u); - EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetProgramBindings), - static_cast(kVarTail | kHostSpan)); + EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetProgramBindings), static_cast(kVarTail | kHostSpan)); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ApplierReset), static_cast(kNone)); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ObjectDeath), static_cast(kNone)); EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::SetContextValues), static_cast(kNone)); @@ -723,9 +715,8 @@ TEST(PipeCatalogue, LateArrivalsAreAppendedWithoutRenumbering) { // P5e's draw flag: the next free bit, and disjoint from all five before it. EXPECT_EQ(static_cast(kDrawClientArrays), 1u << 6); EXPECT_EQ(static_cast(kDrawClientArrays) & - static_cast(kDrawHasUserIndices | kDrawPrimitiveRestart | - kDrawIndicesAreClient | kDrawHasIndexRange | - kDrawHasXfbCount | kDrawIsIndirect), + static_cast(kDrawHasUserIndices | kDrawPrimitiveRestart | kDrawIndicesAreClient | + kDrawHasIndexRange | kDrawHasXfbCount | kDrawIsIndirect), 0u); // ... and it still fits the one byte MGPDrawInfo::Flags is, which is why the record did not // grow: MGPDrawInfo stays 56 bytes. @@ -757,16 +748,15 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) { // kWaitReply: every row whose answer is not derivable (R-5). Named, all fourteen, because // the flag-to-class agreement is what keeps a waiter from hanging on a slot nobody posts. const MGPWireOp replyRows[] = { - MGPWireOp::GetCaps, MGPWireOp::ResourceCreate, MGPWireOp::ResourceRespecify, - MGPWireOp::MapPersistent, MGPWireOp::FenceStatus, MGPWireOp::FenceWait, - MGPWireOp::QueryAvailable, MGPWireOp::QueryResult, MGPWireOp::QueryTimestamp, + MGPWireOp::GetCaps, MGPWireOp::ResourceCreate, MGPWireOp::ResourceRespecify, + MGPWireOp::MapPersistent, MGPWireOp::FenceStatus, MGPWireOp::FenceWait, + MGPWireOp::QueryAvailable, MGPWireOp::QueryResult, MGPWireOp::QueryTimestamp, MGPWireOp::SetTextureParams, MGPWireOp::ResourceSubData, MGPWireOp::ResourceReadback, - MGPWireOp::GetTextureImage, MGPWireOp::ReadPixels, + MGPWireOp::GetTextureImage, MGPWireOp::ReadPixels, }; for (const MGPWireOp op : replyRows) { EXPECT_EQ(MGPipeWaitClassFor(op), kWaitReply) << WireOpNameForDiag(op); - EXPECT_NE(MGPipeCallFlagsFor(op) & static_cast(kReplySlot), 0u) - << WireOpNameForDiag(op); + EXPECT_NE(MGPipeCallFlagsFor(op) & static_cast(kReplySlot), 0u) << WireOpNameForDiag(op); } // kWaitApplied: only applier_reset is left. P15 (user decision: everything fire-and-forget @@ -788,22 +778,46 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) { // the rows the phase exists for, and then counted for the rest, so a new row cannot join // the set unnoticed. const MGPWireOp noneRows[] = { - MGPWireOp::DrawVbo, MGPWireOp::LaunchGrid, MGPWireOp::Clear, - MGPWireOp::Blit, MGPWireOp::MemoryBarrier, MGPWireOp::Flush, - MGPWireOp::SetSwapInterval, MGPWireOp::BindShaderImage, MGPWireOp::PatchParameter, - MGPWireOp::ObjectDeath, MGPWireOp::ResourceDestroy, MGPWireOp::UnmapPersistent, - MGPWireOp::FenceCreate, MGPWireOp::FenceDestroy, MGPWireOp::FenceWaitServer, - MGPWireOp::QueryCreate, MGPWireOp::QueryBegin, MGPWireOp::QueryEnd, - MGPWireOp::QueryDestroy, MGPWireOp::QueryCounter, MGPWireOp::SetContextValues, - MGPWireOp::SetProgramBindings, MGPWireOp::SetShaderBuffers, - MGPWireOp::SetVertexBuffers, MGPWireOp::SetFramebufferState, - MGPWireOp::CreateShaderState, MGPWireOp::SetDrawProgram, + MGPWireOp::DrawVbo, + MGPWireOp::LaunchGrid, + MGPWireOp::Clear, + MGPWireOp::Blit, + MGPWireOp::MemoryBarrier, + MGPWireOp::Flush, + MGPWireOp::SetSwapInterval, + MGPWireOp::BindShaderImage, + MGPWireOp::PatchParameter, + MGPWireOp::ObjectDeath, + MGPWireOp::ResourceDestroy, + MGPWireOp::UnmapPersistent, + MGPWireOp::FenceCreate, + MGPWireOp::FenceDestroy, + MGPWireOp::FenceWaitServer, + MGPWireOp::QueryCreate, + MGPWireOp::QueryBegin, + MGPWireOp::QueryEnd, + MGPWireOp::QueryDestroy, + MGPWireOp::QueryCounter, + MGPWireOp::SetContextValues, + MGPWireOp::SetProgramBindings, + MGPWireOp::SetShaderBuffers, + MGPWireOp::SetVertexBuffers, + MGPWireOp::SetFramebufferState, + MGPWireOp::CreateShaderState, + MGPWireOp::SetDrawProgram, // P9: the pack-buffer readbacks - the point of the two rows is that nothing waits. - MGPWireOp::ReadPixelsToBuffer, MGPWireOp::GetTextureImageToBuffer, + MGPWireOp::ReadPixelsToBuffer, + MGPWireOp::GetTextureImageToBuffer, // P15: fire-and-forget. - MGPWireOp::GenerateMipmap, MGPWireOp::ResourceCopyRegion, MGPWireOp::CopyFramebufferToTexture, - MGPWireOp::SetStorageBlockBinding, MGPWireOp::BeginStreamOutput, MGPWireOp::EndStreamOutput, - MGPWireOp::PauseStreamOutput, MGPWireOp::ResumeStreamOutput, MGPWireOp::BindStreamOutput, + MGPWireOp::GenerateMipmap, + MGPWireOp::ResourceCopyRegion, + MGPWireOp::CopyFramebufferToTexture, + MGPWireOp::SetStorageBlockBinding, + MGPWireOp::BeginStreamOutput, + MGPWireOp::EndStreamOutput, + MGPWireOp::PauseStreamOutput, + MGPWireOp::ResumeStreamOutput, + MGPWireOp::BindStreamOutput, }; for (const MGPWireOp op : noneRows) { EXPECT_EQ(MGPipeWaitClassFor(op), kWaitNone) << WireOpNameForDiag(op); @@ -817,11 +831,21 @@ TEST(PipeCatalogue, EveryRowCarriesTheWaitClassTheContractGivesIt) { SizeT reply = 0, applied = 0, present = 0, none = 0, other = 0; for (SizeT i = 1; i < static_cast(MGPWireOp::kOpCount); ++i) { switch (MGPipeWaitClassFor(static_cast(i))) { - case kWaitReply: ++reply; break; - case kWaitApplied: ++applied; break; - case kWaitPresent: ++present; break; - case kWaitNone: ++none; break; - default: ++other; break; + case kWaitReply: + ++reply; + break; + case kWaitApplied: + ++applied; + break; + case kWaitPresent: + ++present; + break; + case kWaitNone: + ++none; + break; + default: + ++other; + break; } } EXPECT_EQ(reply, 15u); @@ -888,9 +912,8 @@ TEST(PipeCatalogue, VerifyComparatorNamesTheDifferingField) { TEST(PipeCatalogue, CoverageAccountsForEveryInventoryRow) { EXPECT_EQ(kMGPipeInventoryReadPoints, 477u); EXPECT_EQ(kMGPipeInventoryUnmapped, 0u); - EXPECT_EQ(kMGPipeInventoryMappedToCall + kMGPipeInventoryClientResolved + - kMGPipeInventoryReverseChannel + kMGPipeInventoryStructuralHandle + - kMGPipeInventoryUnmapped, + EXPECT_EQ(kMGPipeInventoryMappedToCall + kMGPipeInventoryClientResolved + kMGPipeInventoryReverseChannel + + kMGPipeInventoryStructuralHandle + kMGPipeInventoryUnmapped, kMGPipeInventoryReadPoints); EXPECT_GT(kMGPipeCoverageEntryCount, 0u); } @@ -947,9 +970,9 @@ TEST(PipeCatalogue, VerbTableIsTheFunctionTable) { // The sticky set is exactly the seven forwarded, argument-keyed accessors (P1 brief D6); no // version or generation accessor is among them. TEST(PipeCatalogue, StickyFieldsAreExactlyTheSeven) { - const char* const expected[] = {"GetBufferBindingPointCount", "GetProgramObject", "GetTextureObject", - "HasOpenTransformFeedbackSpan", "InvalidateCompileEnv", "ValidateProgramName", - "RecordError"}; + const char* const expected[] = { + "GetBufferBindingPointCount", "GetProgramObject", "GetTextureObject", "HasOpenTransformFeedbackSpan", + "InvalidateCompileEnv", "ValidateProgramName", "RecordError"}; SizeT count = 0; for (SizeT f = 0; f < kMGPipeInputFieldCount; ++f) { Bool listed = false; @@ -1104,8 +1127,7 @@ TEST(PipeCatalogue, PipelineSubsetMembersArePinned) { // and its two combiner ops. EXPECT_EQ(kMGPipePipelineStateMemberCount, 47u); EXPECT_STREQ(kMGPipePipelineStateMembers[0], "PatchVertices"); - EXPECT_STREQ(kMGPipePipelineStateMembers[kMGPipePipelineStateMemberCount - 1], - "ScissorTestEnabledMask"); + EXPECT_STREQ(kMGPipePipelineStateMembers[kMGPipePipelineStateMemberCount - 1], "ScissorTestEnabledMask"); } // The reverse channel is exactly SEVEN callbacks: plan B's section 7.1 wrote ten, and the tenth @@ -1163,7 +1185,7 @@ TEST(PipeCatalogue, BufferRangeCarriesNoInlineHostSpan) { // The call still declares the span it may carry, so the transport lays the tail out. Uint32 flags = 0; -#define MGP_FLAGS_OF_SET_SHADER_BUFFERS(Name, Payload, Class, Flags, Wait) \ +#define MGP_FLAGS_OF_SET_SHADER_BUFFERS(Name, Payload, Class, Flags, Wait) \ if (std::strcmp(#Name, "SetShaderBuffers") == 0) flags = static_cast(Flags); MGP_CALL_LIST(MGP_FLAGS_OF_SET_SHADER_BUFFERS) #undef MGP_FLAGS_OF_SET_SHADER_BUFFERS @@ -1301,7 +1323,7 @@ TEST(PipeCatalogue, VertexWireViewsAreFlatAndCarryIsLongSeparately) { // inherits this pin rather than the guess. TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { Uint32 flags = 0; -#define MGP_FLAGS_OF_RESOURCE_RESPECIFY(Name, Payload, Class, Flags, Wait) \ +#define MGP_FLAGS_OF_RESOURCE_RESPECIFY(Name, Payload, Class, Flags, Wait) \ if (std::strcmp(#Name, "ResourceRespecify") == 0) flags = static_cast(Flags); MGP_CALL_LIST(MGP_FLAGS_OF_RESOURCE_RESPECIFY) #undef MGP_FLAGS_OF_RESOURCE_RESPECIFY @@ -1309,7 +1331,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { // And it is the ONLY call that carries it: a second one would be a second decision, and // this predicate answers for exactly one call. Uint32 ackingCalls = 0; -#define MGP_COUNT_ACKING_CALLS(Name, Payload, Class, Flags, Wait) \ +#define MGP_COUNT_ACKING_CALLS(Name, Payload, Class, Flags, Wait) \ if ((static_cast(Flags) & static_cast(kNeedsAck)) != 0) ++ackingCalls; MGP_CALL_LIST(MGP_COUNT_ACKING_CALLS) #undef MGP_COUNT_ACKING_CALLS @@ -1325,7 +1347,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { // carried no flag because in monolith that answer is a direct call's return value. FIFTEEN since // shared_image, whose every operation is answered. Uint32 replySlotCalls = 0; -#define MGP_COUNT_REPLY_SLOT_CALLS(Name, Payload, Class, Flags, Wait) \ +#define MGP_COUNT_REPLY_SLOT_CALLS(Name, Payload, Class, Flags, Wait) \ if ((static_cast(Flags) & static_cast(kReplySlot)) != 0) ++replySlotCalls; MGP_CALL_LIST(MGP_COUNT_REPLY_SLOT_CALLS) #undef MGP_COUNT_REPLY_SLOT_CALLS @@ -1336,8 +1358,8 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { // (ResourceCreate, ResourceRespecify, ResourceSubData, SetTextureParams in PipeApply.h); // map_persistent's void* is the fifth answer and was already declared. Uint32 acceptanceWithSlot = 0; -#define MGP_COUNT_ACCEPTANCE_ROWS(Name, Payload, Class, Flags, Wait) \ - if ((std::strcmp(#Name, "ResourceCreate") == 0 || std::strcmp(#Name, "ResourceRespecify") == 0 || \ +#define MGP_COUNT_ACCEPTANCE_ROWS(Name, Payload, Class, Flags, Wait) \ + if ((std::strcmp(#Name, "ResourceCreate") == 0 || std::strcmp(#Name, "ResourceRespecify") == 0 || \ std::strcmp(#Name, "ResourceSubData") == 0 || std::strcmp(#Name, "SetTextureParams") == 0) && \ (static_cast(Flags) & static_cast(kReplySlot)) != 0) { \ ++acceptanceWithSlot; \ @@ -1374,8 +1396,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { // stopped naming the buffer target would turn these two green-and-wrong. MGPResourceDesc immutableTexture{}; immutableTexture.Immutable = 1; // glTexStorage2D - immutableTexture.Target = - static_cast(MGPipeResourceTargetForTextureTarget(TextureTarget::Texture2D)); + immutableTexture.Target = static_cast(MGPipeResourceTargetForTextureTarget(TextureTarget::Texture2D)); immutableTexture.Width = 256; immutableTexture.Height = 256; immutableTexture.Levels = 9; @@ -1411,8 +1432,7 @@ TEST(PipeCatalogue, ResourceRespecifyAcksOnlyImmutableStorage) { // of the line: glBufferData(size, NULL) at an unchanged size is an orphaning // reallocation, so a record that moves only it must still clear, and must never be read // as a mask change. - EXPECT_NE(offsetof(MGPResourceDesc, HasDefinedContent), - offsetof(MGPResourceDesc, ImageBindableHint)); + EXPECT_NE(offsetof(MGPResourceDesc, HasDefinedContent), offsetof(MGPResourceDesc, ImageBindableHint)); } // G13b, D-M: "emulation 在 split 下显式 Fatal 直到 P8" costs P4a a NAMED, GREPPABLE call site diff --git a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp index eedd8f6b..58dc5b07 100644 --- a/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp +++ b/MobileGL/MG_Test/Pipe/PipeInputsTest.cpp @@ -85,16 +85,19 @@ namespace { }; #if MOBILEGL_PIPE_POISON - Bool Fresh(MGPipeInputField field) { return MGPipeInputFieldIsFresh(gPipeInputs->FilledState(), field); } + Bool Fresh(MGPipeInputField field) { + return MGPipeInputFieldIsFresh(gPipeInputs->FilledState(), field); + } #endif - [[maybe_unused]] constexpr const char* kOmittedFatal ="Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@GenerateMipmap\"}"; + [[maybe_unused]] constexpr const char* kOmittedFatal = + "Fatal{UnmigratedPipeInput, \"GetActiveTextureUnit@GenerateMipmap\"}"; [[maybe_unused]] constexpr const char* kOmissionKnob = "GenerateMipmap:GetActiveTextureUnit"; #if MGTEST_HAVE_FORK struct ChildResult { - int Status = -1; // waitpid's status; -1 when fork or waitpid failed - std::string Log; // the lines the child appended to the log file + int Status = -1; // waitpid's status; -1 when fork or waitpid failed + std::string Log; // the lines the child appended to the log file }; // Runs `body` in a forked child and returns its wait status and log delta. The child @@ -118,8 +121,12 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } - Bool ExitedWith(const ChildResult& r, int code) { return WIFEXITED(r.Status) && WEXITSTATUS(r.Status) == code; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } + Bool ExitedWith(const ChildResult& r, int code) { + return WIFEXITED(r.Status) && WEXITSTATUS(r.Status) == code; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -129,7 +136,6 @@ namespace { #endif // MGTEST_HAVE_FORK } // namespace - // Negative control B, layer 1 (P1 brief D6 / G5): the omitted (verb, field) pair is the // ONLY thing that goes stale - every other bit of the verb class's mask is fresh, every // field outside it is not, and the verb after it neither heals the field (not in kDraw's @@ -190,7 +196,7 @@ TEST_F(PipeInputsTest, ReadingAnOmittedFieldAbortsNamingTheVerb) { MGPipeValidateForVerb(MGPipeVerb::GenerateMipmap); (void)gPipeInputs->GetTextureUnitObject(0); // the sibling field: filled, must not abort (void)gPipeInputs->GetActiveTextureUnit(); // the omitted field: Fatal - ::_exit(3); // reached only if the poison failed + ::_exit(3); // reached only if the poison failed }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find(kOmittedFatal), std::string::npos) << r.Log; @@ -357,8 +363,8 @@ TEST_F(PipeInputsTest, MutatedFieldIsNamedAtRead) { EXPECT_NE(r.Log.find("MGPipe: verify read of GetLineWidth (index 0, 0) differs from the live context"), std::string::npos) << r.Log; - const std::string expected = "Fatal{PipeVerifyDiffer, \"GetLineWidth@DrawArrays\", verb=" + std::to_string(serial) + - ", where=read}"; + const std::string expected = + "Fatal{PipeVerifyDiffer, \"GetLineWidth@DrawArrays\", verb=" + std::to_string(serial) + ", where=read}"; EXPECT_NE(r.Log.find(expected), std::string::npos) << "expected " << expected << " in\n" << r.Log; EXPECT_EQ(r.Log.find("where=entry"), std::string::npos) << r.Log; #endif @@ -385,8 +391,9 @@ TEST_F(PipeInputsTest, VerifyCorruptKnobNamesTheFieldAtEntry) { ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("MGPipe: verify armed - 63 fields, 69 verbs, fatal=1"), std::string::npos) << r.Log; EXPECT_NE(r.Log.find("MGPipe: verify corruption armed - GetRenderStateParameters"), std::string::npos) << r.Log; - const std::string expected = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" + - std::to_string(serial) + ", where=entry}"; + const std::string expected = + "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" + std::to_string(serial) + + ", where=entry}"; EXPECT_NE(r.Log.find(expected), std::string::npos) << "expected " << expected << " in\n" << r.Log; #endif } @@ -442,10 +449,12 @@ TEST_F(PipeInputsTest, VerifyFatalOffLogsTheDivergenceAndContinues) { EXPECT_NE(r.Log.find("MGPipe: verify summary - 2 divergence(s) survived MOBILEGL_PIPE_VERIFY_FATAL=0"), std::string::npos) << r.Log; - const std::string first = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" + - std::to_string(serial) + ", where=entry}"; - const std::string second = "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawElements\", verb=" + - std::to_string(serial + 1) + ", where=entry}"; + const std::string first = + "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawArrays\", verb=" + std::to_string(serial) + + ", where=entry}"; + const std::string second = + "Fatal{PipeVerifyDiffer, \"GetRenderStateParameters@DrawElements\", verb=" + std::to_string(serial + 1) + + ", where=entry}"; EXPECT_NE(r.Log.find(first), std::string::npos) << "expected " << first << " in\n" << r.Log; EXPECT_NE(r.Log.find(second), std::string::npos) << "expected " << second << " in\n" << r.Log; #endif @@ -490,7 +499,7 @@ TEST_F(PipeInputsTest, AFrontendMutationInsideAVerbRefreshesThePushedField) { const Uint64 samplingBefore = ctx.GetSamplingResolutionGeneration(); auto sampler = MakeShared(0u); sampler->SetMinFilter(sampler->GetMinFilter() == SamplerFilterMode::Nearest ? SamplerFilterMode::Linear - : SamplerFilterMode::Nearest); + : SamplerFilterMode::Nearest); ASSERT_NE(ctx.GetSamplingResolutionGeneration(), samplingBefore) << "the mutation did not move the counter"; EXPECT_EQ(gPipeInputs->GetSamplingResolutionGeneration(), ctx.GetSamplingResolutionGeneration()); @@ -568,7 +577,6 @@ TEST_F(PipeInputsTest, AFrontendMutationInsideAVerbDoesNotDivergeAtRead) { #endif } - int main(int argc, char** argv) { // Before anything logs: MG_Util::Debug::InitFile() reads the variable once, on the first // write, and caches the FILE*. The name carries this process's pid (see the file header), diff --git a/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp b/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp index 05f08ec1..97c5f167 100644 --- a/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ProgramEmitTest.cpp @@ -150,7 +150,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -190,8 +192,7 @@ namespace { // See FramebufferEmitTest's twin for why this is a shape pin rather than a placeholder. TEST(ProgramEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) { EXPECT_EQ(&MGPipeProgramEmitterInstance(), &MGPipeProgramEmitterInstance()); - EXPECT_TRUE(kMGPipeWiredProgramSubsystem == 0 || - kMGPipeWiredProgramSubsystem == kMGPipeSubsystemPrograms); + EXPECT_TRUE(kMGPipeWiredProgramSubsystem == 0 || kMGPipeWiredProgramSubsystem == kMGPipeSubsystemPrograms); } // ========================================================================================= @@ -305,11 +306,10 @@ TEST(ProgramEmit, ACreateWithNoArtefactsAnOversizedBlockOrACorruptSlotIsRefusedN EXPECT_TRUE(MGPipeApplier().ShaderCsos.empty()) << "the table was grown by a refused record"; const MGPProgramDesc pastTheBound = ProgramDesc(MGPipeHandle{kMGPipeMaxShaderCsoSlots, 1}, 0x3u, 0); - ExpectRefusedNaming("create_shader_state {slot=1048576, gen=1}: the slot is outside the record table's " - "bound", - [&pastTheBound, &link, &spirv]() { - MGPipeApplyCreateShaderState(pastTheBound, &link, &spirv, nullptr); - }); + ExpectRefusedNaming( + "create_shader_state {slot=1048576, gen=1}: the slot is outside the record table's " + "bound", + [&pastTheBound, &link, &spirv]() { MGPipeApplyCreateShaderState(pastTheBound, &link, &spirv, nullptr); }); EXPECT_TRUE(MGPipeApplier().ShaderCsos.empty()); EXPECT_TRUE(MGPipeApplier().CompositeShaderCsos.empty()); } @@ -469,7 +469,6 @@ TEST(ProgramEmit, TheProgramRecordSurvivesAMakeCurrentWhileTheThreeBindingsDoNot EXPECT_TRUE(MGPipeApplier().ShaderCsos.empty()); } - namespace { namespace GL = MobileGL::MG_Impl::GLImpl; using GLContext = MG_State::GLState::GLContext; @@ -490,8 +489,12 @@ namespace { } }; - GLContext& Ctx() { return *MG_State::pGLContext; } - MGPipeProgramEmitter& Emitter() { return MGPipeProgramEmitterInstance(); } + GLContext& Ctx() { + return *MG_State::pGLContext; + } + MGPipeProgramEmitter& Emitter() { + return MGPipeProgramEmitterInstance(); + } const char* kVs = R"(#version 430 core uniform vec4 u_value; @@ -525,7 +528,9 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); } return program; } - constexpr Uint32 StageBit(ShaderStage stage) { return Uint32{1} << static_cast(stage); } + constexpr Uint32 StageBit(ShaderStage stage) { + return Uint32{1} << static_cast(stage); + } // ProgramObject.h says it in as many words and this is the case that holds it: a stage mask // built from the ATTACH list would describe a program that does not exist yet, because @@ -702,8 +707,7 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); } // Driven to the fixed point rather than assumed to settle in one call: the first // EmitGlobalConstants reads GetUBOContentVersion() BEFORE GetUBOSize() joins phase B, // and that join bumps the counter, so the first key is one behind by construction. - for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) { - } + for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) {} const Uint64 setsBefore = Emitter().GlobalConstantsSetCount(); ASSERT_GT(setsBefore, 0u) << "this program declares a uniform, so a block must have gone out"; const MGPipeHandle cso = Emitter().GlobalConstantsCso(); @@ -731,8 +735,7 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); } EXPECT_EQ(Emitter().GlobalConstantsVersion(), kMGPipeGlobalConstantsNeverUploaded); // And the block really is sent again. - for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) { - } + for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) {} EXPECT_GT(Emitter().GlobalConstantsSetCount(), setsBefore); } // A default uniform block wide enough that one vec4 is less than half of it, so a write to @@ -761,8 +764,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } void DrawWith(GLuint name) { GL::UseProgram(name); Emitter().EmitShaderState(Ctx()); - for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) { - } + for (int i = 0; i < 8 && Emitter().EmitGlobalConstants(Ctx()) > 0u; ++i) {} } // THE set_global_constants KEY IS PER HANDLE, because the applier keeps one block per shader @@ -899,8 +901,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } // is what keeps a program-pipeline composite's per-draw uniform mirror free. const Uint64 latched = Emitter().ProgramBindingsSetCount(); Emitter().EmitProgramBindings(*program, cso); - EXPECT_EQ(Emitter().ProgramBindingsSetCount(), latched) - << "an unchanged binding set was re-sent"; + EXPECT_EQ(Emitter().ProgramBindingsSetCount(), latched) << "an unchanged binding set was re-sent"; // A REAL glUniformBlockBinding, after the link, on a program that declares no block: // the frontend's setter bails out on an out-of-range index, so this drives the other @@ -914,8 +915,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } << "a post-link unit assignment did not move the latch, so it never travels"; const auto& hdr = Emitter().LastProgramBindings(); EXPECT_EQ(hdr.Cso, cso); - EXPECT_GE(hdr.SamplerUnitCount, 1u) - << "the assigned unit is not in the record's second tail"; + EXPECT_GE(hdr.SamplerUnitCount, 1u) << "the assigned unit is not in the record's second tail"; } } @@ -934,8 +934,10 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } EXPECT_TRUE(program->HasUniformLocationTables(0)) << "a linked program must own its tables"; Vector attached; - for (const auto& shader : program->GetAttachedShaders()) attached.push_back(shader->GetExternalIndex()); - for (const GLuint shader : attached) GL::DetachShader(name, shader); + for (const auto& shader : program->GetAttachedShaders()) + attached.push_back(shader->GetExternalIndex()); + for (const GLuint shader : attached) + GL::DetachShader(name, shader); GL::LinkProgram(name); GLint linked = GL_TRUE; GL::GetProgramiv(name, GL_LINK_STATUS, &linked); @@ -1046,8 +1048,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } EXPECT_EQ(Emitter().CreateCount(), creates + 1) << "the failed relink did not re-issue, so the server keeps drawing an executable " "the frontend reports unlinked"; - EXPECT_EQ(Emitter().LastProgramDesc().LinkStatus, 0u) - << "the re-issue claims the program linked"; + EXPECT_EQ(Emitter().LastProgramDesc().LinkStatus, 0u) << "the re-issue claims the program linked"; EXPECT_TRUE(MGPipeSlots().IsLive(MGPipeKind::ShaderCso, cso)) << "a failed relink must never be modelled as an object_death"; } @@ -1064,8 +1065,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } spirv.generatedSpirv.resize(2); spirv.generatedSpirv[0].assign(3, 0x07230203u); spirv.generatedSpirv[1].assign(5, 0x07230204u); - const Vector stages{static_cast(ShaderStage::Vertex), - static_cast(ShaderStage::Vertex)}; + const Vector stages{static_cast(ShaderStage::Vertex), static_cast(ShaderStage::Vertex)}; Vector framed; MG_State::GLState::EncodeProgramArchive(link, spirv, stages, framed); @@ -1082,8 +1082,7 @@ void main() { gl_Position = u_a + u_b + u_c + u_d; } // A TRUNCATED FRAME IS REFUSED, not guessed, and both outputs come back defined. MG_State::GLState::ProgramArchive refused; - EXPECT_FALSE( - MG_State::GLState::DecodeProgramArchive(framed.data(), framed.size() - 1, refused)); + EXPECT_FALSE(MG_State::GLState::DecodeProgramArchive(framed.data(), framed.size() - 1, refused)); EXPECT_TRUE(refused.LinkedStages.empty()); EXPECT_TRUE(refused.Spirv.generatedSpirv.empty()); } @@ -1106,8 +1105,7 @@ int main(int argc, char** argv) { // raised by the encoder on THIS thread - the client role. So g_logPath, which is the read // path from here on, becomes the client-derived name; the env keeps the base. The rule is // the library's own, not a copy. - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); MobileGL::Initialize(); ::testing::InitGoogleTest(&argc, argv); const int rc = RUN_ALL_TESTS(); diff --git a/MobileGL/MG_Test/Pipe/RecordFailSeamTest.cpp b/MobileGL/MG_Test/Pipe/RecordFailSeamTest.cpp index 0ffc371c..24e47196 100644 --- a/MobileGL/MG_Test/Pipe/RecordFailSeamTest.cpp +++ b/MobileGL/MG_Test/Pipe/RecordFailSeamTest.cpp @@ -62,7 +62,9 @@ namespace { g_seenLine = line; return false; } - bool AlwaysLatched() { return true; } + bool AlwaysLatched() { + return true; + } } // namespace // An installed latch hook gets the family and the FORMATTED line, and its answer is the call's. diff --git a/MobileGL/MG_Test/Pipe/RecorderGoldenTest.cpp b/MobileGL/MG_Test/Pipe/RecorderGoldenTest.cpp index ed7885fa..3e521059 100644 --- a/MobileGL/MG_Test/Pipe/RecorderGoldenTest.cpp +++ b/MobileGL/MG_Test/Pipe/RecorderGoldenTest.cpp @@ -118,24 +118,41 @@ namespace { Scrub(p.RendererInfo); } template <> - void Normalize(MGPRenderStateDesc& p) { Scrub(p.Blob); } + void Normalize(MGPRenderStateDesc& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPDynamicState& p) { Scrub(p.Blob); } + void Normalize(MGPDynamicState& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPVertexElements& p) { Scrub(p.Blob); } + void Normalize(MGPVertexElements& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPSamplerDesc& p) { Scrub(p.Parameters); } + void Normalize(MGPSamplerDesc& p) { + Scrub(p.Parameters); + } template <> - void Normalize(MGPGlobalConstants& p) { Scrub(p.Blob); } + void Normalize(MGPGlobalConstants& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPResidualValueState& p) { Scrub(p.Blob); } + void Normalize(MGPResidualValueState& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPSubData& p) { Scrub(p.Blob); } + void Normalize(MGPSubData& p) { + Scrub(p.Blob); + } template <> - void Normalize(MGPStorageBlockBinding& p) { Scrub(p.Name); } + void Normalize(MGPStorageBlockBinding& p) { + Scrub(p.Name); + } template <> void Normalize(MGPProgramDesc& p) { - for (auto& stage : p.Spirv) Scrub(stage); + for (auto& stage : p.Spirv) + Scrub(stage); Scrub(p.Reflection); } @@ -168,8 +185,12 @@ namespace { } // The six argument shapes PipeTables.inc generates after the payload. - Uint64 HashRest(Fnv&, const char*) { return 0; } - Uint64 HashRest(Fnv&, const char*, MGPReplySlot*) { return 0; } + Uint64 HashRest(Fnv&, const char*) { + return 0; + } + Uint64 HashRest(Fnv&, const char*, MGPReplySlot*) { + return 0; + } Uint64 HashRest(Fnv& hash, const char*, const void* blob, Uint64 blobBytes) { hash.AddU64(blobBytes); if (blob != nullptr && blobBytes != 0) hash.Add(blob, static_cast(blobBytes)); @@ -226,13 +247,13 @@ namespace { #undef MGL_REC_NAME void InstallTableRecorders() { -#define MGL_REC_INSTALL(Name, Payload, Class, Flags, Wait) \ +#define MGL_REC_INSTALL(Name, Payload, Class, Flags, Wait) \ Wrap::Install(MGL_REC_TABLE_##Class.Name); MGP_CALL_LIST(MGL_REC_INSTALL) #undef MGL_REC_INSTALL } void UninstallTableRecorders() { -#define MGL_REC_UNINSTALL(Name, Payload, Class, Flags, Wait) \ +#define MGL_REC_UNINSTALL(Name, Payload, Class, Flags, Wait) \ Wrap::Uninstall(MGL_REC_TABLE_##Class.Name); MGP_CALL_LIST(MGL_REC_UNINSTALL) #undef MGL_REC_UNINSTALL @@ -299,8 +320,8 @@ namespace { const char* WireOpName(MGPWireOp op) { switch (op) { -#define MGL_REC_OP_NAME(Name, Payload, Class, Flags, Wait) \ - case MGPWireOp::Name: \ +#define MGL_REC_OP_NAME(Name, Payload, Class, Flags, Wait) \ + case MGPWireOp::Name: \ return #Name; MGP_CALL_LIST(MGL_REC_OP_NAME) #undef MGL_REC_OP_NAME @@ -326,7 +347,8 @@ namespace { Uint64 bytes = payloadBytes; for (Uint32 i = 0; i < tailCount; ++i) { hash.AddU64(tails[i].Size); - if (tails[i].Bytes != nullptr && tails[i].Size != 0) hash.Add(tails[i].Bytes, static_cast(tails[i].Size)); + if (tails[i].Bytes != nullptr && tails[i].Size != 0) + hash.Add(tails[i].Bytes, static_cast(tails[i].Size)); bytes += tails[i].Size; } Note(WireOpName(op), hash, bytes); @@ -363,7 +385,9 @@ namespace { }; RecordingVerbSession* g_session = nullptr; - MG_Record::VerbSession& ResolveRecordingSession(const char*) { return *g_session; } + MG_Record::VerbSession& ResolveRecordingSession(const char*) { + return *g_session; + } // ---- the scope a fixture runs in ------------------------------------------------------------- @@ -599,7 +623,9 @@ void main() { o_color = texture(u_texture, vec2(0.5)); } return stream; } - Bool FirstCaptureInProcess() { return g_captures == 0; } + Bool FirstCaptureInProcess() { + return g_captures == 0; + } // Index of the first record whose op is `op` in a golden, or -1. Int64 FirstGoldenRecordOf(const char* fixture, const char* op) { @@ -617,13 +643,15 @@ void main() { o_color = texture(u_texture, vec2(0.5)); } } // namespace TEST(RecorderGolden, BufferVertexArrayDraw) { - if (!FirstCaptureInProcess()) GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; + if (!FirstCaptureInProcess()) + GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; const std::vector stream = Capture([] { RunBufferVertexArrayDraw(); }); CheckAgainstGolden("BufferVertexArrayDraw", stream); } TEST(RecorderGolden, TextureFramebufferClearBlit) { - if (!FirstCaptureInProcess()) GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; + if (!FirstCaptureInProcess()) + GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; const std::vector stream = Capture(&RunTextureFramebufferClearBlit); CheckAgainstGolden("TextureFramebufferClearBlit", stream); } @@ -644,7 +672,8 @@ TEST(RecorderGolden, TheSameCallsRecordTheSameStreamTwice) { // differently produces - must turn the comparison red AT that record, and nowhere before it. TEST(RecorderGolden, APerturbedRecordFieldIsCaughtAtThatRecord) { if (WriteRequested()) GTEST_SKIP() << "goldens are being rewritten"; - if (!FirstCaptureInProcess()) GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; + if (!FirstCaptureInProcess()) + GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; // The first draw: a verb record, i.e. the session half of the recorder rather than the tables. const Int64 victim = FirstGoldenRecordOf("BufferVertexArrayDraw", "DrawVbo"); ASSERT_GE(victim, 0) << "the golden has no DrawVbo record to perturb"; @@ -661,7 +690,8 @@ TEST(RecorderGolden, APerturbedRecordFieldIsCaughtAtThatRecord) { // enters the stream. TEST(RecorderGolden, AChangedGlInputChangesTheStream) { if (WriteRequested()) GTEST_SKIP() << "goldens are being rewritten"; - if (!FirstCaptureInProcess()) GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; + if (!FirstCaptureInProcess()) + GTEST_SKIP() << "compare the first capture of a process only (ctest runs each case alone)"; const std::vector stream = Capture([] { RunBufferVertexArrayDraw(4); }); const String difference = Compare("BufferVertexArrayDraw", stream); ASSERT_FALSE(difference.empty()) diff --git a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp index 23fe694c..b42b7342 100644 --- a/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp +++ b/MobileGL/MG_Test/Pipe/RenderStateSpansTest.cpp @@ -108,7 +108,7 @@ namespace { SizeT Size; }; -#define MGP_RS_MEMBER_ROW(name) \ +#define MGP_RS_MEMBER_ROW(name) \ Member{#name, offsetof(RenderStateParameters, name), sizeof(RenderStateParameters::name)}, constexpr Member kMembers[] = {MGP_FIELDS_RenderStateParameters(MGP_RS_MEMBER_ROW)}; #undef MGP_RS_MEMBER_ROW @@ -134,7 +134,9 @@ namespace { return OverlapWith(kMGPipeDynamicChunks, kMGPipeDynamicChunkCount, offset, size) == size; } - Bool IsWhollyPipeline(SizeT offset, SizeT size) { return PipelineBytesOf(offset, size) == size; } + Bool IsWhollyPipeline(SizeT offset, SizeT size) { + return PipelineBytesOf(offset, size) == size; + } // --------------------------------------------------------------------------------- // D5's 25 kDraw derivations against the frontend getters they were transcribed from. @@ -280,12 +282,12 @@ namespace { } EXPECT_TRUE(IsWhollyPipeline(member.Offset, member.Size)) << member.Name << " is named as pipeline state but only " << pipelineBytes << " of its " - << member.Size << " bytes are in the pipeline half - the rest have silently left the " - "CSO's identity"; + << member.Size + << " bytes are in the pipeline half - the rest have silently left the " + "CSO's identity"; } else { - EXPECT_EQ(pipelineBytes, SizeT{0}) - << member.Name << " is not named as pipeline state but " << pipelineBytes - << " of its bytes are in the pipeline half"; + EXPECT_EQ(pipelineBytes, SizeT{0}) << member.Name << " is not named as pipeline state but " + << pipelineBytes << " of its bytes are in the pipeline half"; EXPECT_TRUE(IsWhollyDynamic(member.Offset, member.Size)) << member.Name << " is neither wholly pipeline nor wholly dynamic"; } @@ -378,7 +380,7 @@ namespace { // ---- Capabilities: every SET_CAPABILITY name, D3's three new ones included ---- #define MGP_CHECK_CAPABILITY(cap) \ check("SetCapability(" #cap ")", [](RenderState& s) { \ - s.SetCapability(CapabilityInput::cap, !s.IsCapabilityEnabled(CapabilityInput::cap)); \ + s.SetCapability(CapabilityInput::cap, !s.IsCapabilityEnabled(CapabilityInput::cap)); \ }); MGP_CHECK_CAPABILITY(ColorLogicOp) MGP_CHECK_CAPABILITY(DebugOutput) @@ -451,8 +453,8 @@ namespace { BlendFactor::SrcAlpha); }); check("SetBlendFuncIndexed(0)", [](RenderState& s) { - s.SetBlendFuncIndexed(0, BlendFactor::ConstantColor, BlendFactor::ConstantAlpha, BlendFactor::OneMinusDstColor, - BlendFactor::OneMinusDstAlpha); + s.SetBlendFuncIndexed(0, BlendFactor::ConstantColor, BlendFactor::ConstantAlpha, + BlendFactor::OneMinusDstColor, BlendFactor::OneMinusDstAlpha); }); check("SetBlendEquation", [](RenderState& s) { s.SetBlendEquation(BlendEquation::Subtract, BlendEquation::Min); }); @@ -503,7 +505,8 @@ namespace { // ---- Colour mask, clear state, sampling ---- check("SetColorMask", [](RenderState& s) { s.SetColorMask(BoolVec4(true, false, true, false)); }); - check("SetColorMaskIndexed", [](RenderState& s) { s.SetColorMaskIndexed(6, BoolVec4(false, false, true, true)); }); + check("SetColorMaskIndexed", + [](RenderState& s) { s.SetColorMaskIndexed(6, BoolVec4(false, false, true, true)); }); check("SetColorMaskIndexed(0)", [](RenderState& s) { s.SetColorMaskIndexed(0, BoolVec4(false, true, false, true)); }); check("SetClearColor", [](RenderState& s) { s.SetClearColor(FloatVec4(0.1f, 0.2f, 0.3f, 0.4f)); }); @@ -609,68 +612,68 @@ namespace { // below is driven afterwards, so a field that still agrees with the context at the // end can only have got there through the applier's derivation. { - MG_Test::ScopedPipeVerb verb(MGPipeVerb::DrawArrays); + MG_Test::ScopedPipeVerb verb(MGPipeVerb::DrawArrays); - ctx.SetViewportIndexed(0, FloatVec4(1.5f, 2.5f, 63.5f, 32.25f)); - ctx.SetViewportIndexed(7, FloatVec4(8.f, 9.f, 10.f, 11.f)); - ctx.SetLineWidth(3.5f); - ctx.SetPatchVertices(4); - ctx.SetPatchDefaultOuterLevel(FloatVec4(2.f, 3.f, 4.f, 5.f)); - ctx.SetPatchDefaultInnerLevel(FloatVec2(6.f, 7.f)); - ctx.SetPolygonOffsetClamped(1.5f, 2.5f, 0.25f); - ctx.SetClampReadColor(GL_TRUE); - ctx.SetPolygonMode(GL_LINE, GL_POINT); - ctx.SetPrimitiveRestartIndex(0xabcdu); - ctx.SetBlendFuncIndexed(2, BlendFactor::DstColor, BlendFactor::SrcColor, BlendFactor::DstAlpha, - BlendFactor::SrcAlpha); - ctx.SetBlendEquationIndexed(4, BlendEquation::ReverseSubtract, BlendEquation::Max); - ctx.SetCapabilityIndexed(CapabilityInput::Blend, 3, true); - ctx.SetCapabilityIndexed(CapabilityInput::ScissorTest, 5, true); - ctx.SetLogicOp(LogicOperation::Xor); - ctx.SetDepthFunc(DepthTestFunc::GreaterEqual); - ctx.SetDepthMask(false); - ctx.SetStencilFunc(StencilFace::Front, DepthTestFunc::Equal, 7, 0xf0u); - ctx.SetStencilOp(StencilFace::Back, StencilOperation::Replace, StencilOperation::IncrementClamp, - StencilOperation::DecrementWrap); - ctx.SetStencilMask(StencilFace::Back, 0x0fu); - ctx.SetColorMaskIndexed(6, BoolVec4(false, false, true, true)); - ctx.SetClearColor(FloatVec4(0.1f, 0.2f, 0.3f, 0.4f)); - ctx.SetClearDepth(0.75f); - ctx.SetClearStencil(9); - ctx.SetBlendColor(FloatVec4(0.5f, 0.6f, 0.7f, 0.8f)); - ctx.SetDepthRangeIndexed(9, FloatVec2(0.1f, 0.9f)); - ctx.SetMinSampleShadingValue(0.625f); - ctx.SetCullFaceMode(CullFaceMode::Front); - ctx.SetProvokingVertexMode(ProvokingVertexMode::FirstVertex); - ctx.SetScissorBox(IntVec4(1, 2, 3, 4)); - // One capability out of every arm of the 35-way switch: plain bools (the three D3 - // gave storage to among them), the two indexed ones through their non-indexed entry - // point, and a clip distance. - ctx.SetCapability(CapabilityInput::DepthTest, true); - ctx.SetCapability(CapabilityInput::FramebufferSrgb, true); - ctx.SetCapability(CapabilityInput::DepthClamp, true); - ctx.SetCapability(CapabilityInput::TextureCubeMapSeamless, true); - ctx.SetCapability(CapabilityInput::Blend, true); - ctx.SetCapability(CapabilityInput::ClipDistance3, true); + ctx.SetViewportIndexed(0, FloatVec4(1.5f, 2.5f, 63.5f, 32.25f)); + ctx.SetViewportIndexed(7, FloatVec4(8.f, 9.f, 10.f, 11.f)); + ctx.SetLineWidth(3.5f); + ctx.SetPatchVertices(4); + ctx.SetPatchDefaultOuterLevel(FloatVec4(2.f, 3.f, 4.f, 5.f)); + ctx.SetPatchDefaultInnerLevel(FloatVec2(6.f, 7.f)); + ctx.SetPolygonOffsetClamped(1.5f, 2.5f, 0.25f); + ctx.SetClampReadColor(GL_TRUE); + ctx.SetPolygonMode(GL_LINE, GL_POINT); + ctx.SetPrimitiveRestartIndex(0xabcdu); + ctx.SetBlendFuncIndexed(2, BlendFactor::DstColor, BlendFactor::SrcColor, BlendFactor::DstAlpha, + BlendFactor::SrcAlpha); + ctx.SetBlendEquationIndexed(4, BlendEquation::ReverseSubtract, BlendEquation::Max); + ctx.SetCapabilityIndexed(CapabilityInput::Blend, 3, true); + ctx.SetCapabilityIndexed(CapabilityInput::ScissorTest, 5, true); + ctx.SetLogicOp(LogicOperation::Xor); + ctx.SetDepthFunc(DepthTestFunc::GreaterEqual); + ctx.SetDepthMask(false); + ctx.SetStencilFunc(StencilFace::Front, DepthTestFunc::Equal, 7, 0xf0u); + ctx.SetStencilOp(StencilFace::Back, StencilOperation::Replace, StencilOperation::IncrementClamp, + StencilOperation::DecrementWrap); + ctx.SetStencilMask(StencilFace::Back, 0x0fu); + ctx.SetColorMaskIndexed(6, BoolVec4(false, false, true, true)); + ctx.SetClearColor(FloatVec4(0.1f, 0.2f, 0.3f, 0.4f)); + ctx.SetClearDepth(0.75f); + ctx.SetClearStencil(9); + ctx.SetBlendColor(FloatVec4(0.5f, 0.6f, 0.7f, 0.8f)); + ctx.SetDepthRangeIndexed(9, FloatVec2(0.1f, 0.9f)); + ctx.SetMinSampleShadingValue(0.625f); + ctx.SetCullFaceMode(CullFaceMode::Front); + ctx.SetProvokingVertexMode(ProvokingVertexMode::FirstVertex); + ctx.SetScissorBox(IntVec4(1, 2, 3, 4)); + // One capability out of every arm of the 35-way switch: plain bools (the three D3 + // gave storage to among them), the two indexed ones through their non-indexed entry + // point, and a clip distance. + ctx.SetCapability(CapabilityInput::DepthTest, true); + ctx.SetCapability(CapabilityInput::FramebufferSrgb, true); + ctx.SetCapability(CapabilityInput::DepthClamp, true); + ctx.SetCapability(CapabilityInput::TextureCubeMapSeamless, true); + ctx.SetCapability(CapabilityInput::Blend, true); + ctx.SetCapability(CapabilityInput::ClipDistance3, true); - // The vacuity guard: the block still holds what the fill copied out of the DEFAULT - // context, so it must currently DISAGREE with the live one. If this ever passes, the - // comparisons below would be checking the filler against itself. - ASSERT_NE(gPipeInputs->GetLineWidth(), ctx.GetLineWidth()); + // The vacuity guard: the block still holds what the fill copied out of the DEFAULT + // context, so it must currently DISAGREE with the live one. If this ever passes, the + // comparisons below would be checking the filler against itself. + ASSERT_NE(gPipeInputs->GetLineWidth(), ctx.GetLineWidth()); - applyWholeBlock(); + applyWholeBlock(); - // ---- the 25 kDraw fields ---- - ExpectDerivedDrawFieldsMatch(ctx, "whole block, kDraw"); - // ...and the ~25 members that have NO derived field, which only a byte compare of the - // whole block reaches. - ExpectAssembledBlockIsTheLiveBlock(ctx, "whole block, kDraw"); - // The rounding half of GetViewport, exercised on purpose: viewport 0 is - // (1.5, 2.5, 63.5, 32.25), so a transcription that truncated instead of rounding - // would hand the backends a 63-wide rectangle where 64 was asked for. The literal - // is std::lround's answer - round half AWAY FROM ZERO, so 1.5 -> 2 and 2.5 -> 3, - // not the banker's rounding a nearbyint() transcription would give. - EXPECT_EQ(gPipeInputs->GetViewport(), IntVec4(2, 3, 64, 32)); + // ---- the 25 kDraw fields ---- + ExpectDerivedDrawFieldsMatch(ctx, "whole block, kDraw"); + // ...and the ~25 members that have NO derived field, which only a byte compare of the + // whole block reaches. + ExpectAssembledBlockIsTheLiveBlock(ctx, "whole block, kDraw"); + // The rounding half of GetViewport, exercised on purpose: viewport 0 is + // (1.5, 2.5, 63.5, 32.25), so a transcription that truncated instead of rounding + // would hand the backends a 63-wide rectangle where 64 was asked for. The literal + // is std::lround's answer - round half AWAY FROM ZERO, so 1.5 -> 2 and 2.5 -> 3, + // not the banker's rounding a nearbyint() transcription would give. + EXPECT_EQ(gPipeInputs->GetViewport(), IntVec4(2, 3, 64, 32)); } // phase 1, kDraw // Phase 2, kClear: the three clear values. The verb's own fill runs FIRST and copies @@ -792,12 +795,10 @@ namespace { const auto push = [&](const char* tag) { const RenderStateParameters& live = ctx.GetRenderStateParameters(); - const Uint32 movedPipeline = - firstPush ? static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1) - : MGPipePipelineChunksThatMoved(staged, live); - const Uint32 movedDynamic = - firstPush ? static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1) - : MGPipeDynamicChunksThatMoved(staged, live); + const Uint32 movedPipeline = firstPush ? static_cast((Uint64{1} << kMGPipePipelineChunkCount) - 1) + : MGPipePipelineChunksThatMoved(staged, live); + const Uint32 movedDynamic = firstPush ? static_cast((Uint64{1} << kMGPipeDynamicChunkCount) - 1) + : MGPipeDynamicChunksThatMoved(staged, live); if (movedPipeline != 0) { Vector blob(MGPipePipelineChunkBlobBytes(movedPipeline)); @@ -1018,7 +1019,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); diff --git a/MobileGL/MG_Test/Pipe/ResourceEmitTest.cpp b/MobileGL/MG_Test/Pipe/ResourceEmitTest.cpp index 21ba17c6..55a193ba 100644 --- a/MobileGL/MG_Test/Pipe/ResourceEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ResourceEmitTest.cpp @@ -110,8 +110,7 @@ namespace { // It is also the negative control for the registration itself. A Set that did not stick // would leave the family permanently dark, and nothing else in the tree would say so. TEST(ResourceEmit, TheResourceOpTableIsUnregisteredUntilABackendInstallsOne) { - ASSERT_EQ(MGPipeGetResourceOps(), nullptr) - << "something registered a resource op table in a unit-test process"; + ASSERT_EQ(MGPipeGetResourceOps(), nullptr) << "something registered a resource op table in a unit-test process"; static const MGPipeResourceOps ops{}; MGPipeSetResourceOps(&ops); @@ -272,8 +271,8 @@ namespace { g_spy.LastHandle = res; } - const MGPipeResourceOps kSpyOps{SpyCreate, SpyRespecify, SpySubData, SpyResident, SpyFlush, - SpyReadback, SpyDestroy, SpyMap, SpyUnmap}; + const MGPipeResourceOps kSpyOps{SpyCreate, SpyRespecify, SpySubData, SpyResident, SpyFlush, + SpyReadback, SpyDestroy, SpyMap, SpyUnmap}; #if MGTEST_HAVE_FORK struct ChildResult { @@ -304,7 +303,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -579,7 +580,8 @@ namespace { "acquisition"; // Three declines still cost three answers. - for (Uint32 i = 0; i < 3; ++i) EXPECT_EQ(MGPipeApplyMapPersistent(BufferHandle(res), 256, bytes), nullptr); + for (Uint32 i = 0; i < 3; ++i) + EXPECT_EQ(MGPipeApplyMapPersistent(BufferHandle(res), 256, bytes), nullptr); EXPECT_EQ(MGPipeApplier().MapPersistentRoundtrips, 3u); // A mint costs the same one. @@ -643,8 +645,10 @@ namespace { const std::string before = ReadLog(); MGPipeApplyResourceSubData(pastTheEnd, bytes); EXPECT_EQ(RecordOf(res.Slot).Serial, 2u) << "a refused write must not move the serial"; - EXPECT_NE(ReadLog().substr(before.size()).find("ProtocolCorruption resource_subdata {slot=7, gen=3, " - "glName=41}"), + EXPECT_NE(ReadLog() + .substr(before.size()) + .find("ProtocolCorruption resource_subdata {slot=7, gen=3, " + "glName=41}"), std::string::npos) << "the gate refused the write without saying which record it was"; #endif @@ -1002,7 +1006,8 @@ namespace { EXPECT_EQ(ElementsOf(cso.Slot).ContentSerial, 2u) << "a re-create on the same handle counts up"; EXPECT_EQ(ElementsOf(cso.Slot).AttributeCount, 2u); EXPECT_EQ(ElementsOf(cso.Slot).BindingPointCount, 1u); - for (Uint32 i = 0; i < 2; ++i) ExpectAttribEq(ElementsOf(cso.Slot).Attributes[i], AttribAt(i), i); + for (Uint32 i = 0; i < 2; ++i) + ExpectAttribEq(ElementsOf(cso.Slot).Attributes[i], AttribAt(i), i); ExpectBindingEq(ElementsOf(cso.Slot).BindingPoints[0], BindingAt(0), 0); const MGPVertexAttribWire zeroAttrib{}; const MGPVertexBindingPointWire zeroBinding{}; @@ -1054,18 +1059,16 @@ namespace { // And a count above the destination it would be unpacked into. MGPVertexElements tooManyAttributes = declared.Desc; tooManyAttributes.AttributeCount = kMGPipeMaxVertexAttribs + 1; - ExpectRefusedNaming("create_vertex_elements {slot=5, gen=2}: the declared attribute count is above " - "GL's attribute limit", - [&tooManyAttributes, blobBytes]() { - MGPipeApplyCreateVertexElements(tooManyAttributes, blobBytes); - }); + ExpectRefusedNaming( + "create_vertex_elements {slot=5, gen=2}: the declared attribute count is above " + "GL's attribute limit", + [&tooManyAttributes, blobBytes]() { MGPipeApplyCreateVertexElements(tooManyAttributes, blobBytes); }); MGPVertexElements tooManyBindings = declared.Desc; tooManyBindings.BindingPointCount = kMGPipeMaxVertexAttribs + 1; - ExpectRefusedNaming("create_vertex_elements {slot=5, gen=2}: the declared binding-point count is " - "above GL's attribute limit", - [&tooManyBindings, blobBytes]() { - MGPipeApplyCreateVertexElements(tooManyBindings, blobBytes); - }); + ExpectRefusedNaming( + "create_vertex_elements {slot=5, gen=2}: the declared binding-point count is " + "above GL's attribute limit", + [&tooManyBindings, blobBytes]() { MGPipeApplyCreateVertexElements(tooManyBindings, blobBytes); }); EXPECT_EQ(ElementsOf(cso.Slot).ContentSerial, 2u); EXPECT_EQ(ElementsOf(cso.Slot).AttributeCount, 3u); } @@ -1301,9 +1304,8 @@ namespace { // The packed (resource target, upload target) pair a Tex2D level's records carry. 0x0102 - // and it is the very number the split bring-up's Fatal printed, which is how this case is // known to be about the same shape. - const Uint16 kTex2DUpload = MGPipePackSubDataTarget( - static_cast(MGPipeResourceTarget::Tex2D), - static_cast(TextureUploadTarget::Texture2D)); + const Uint16 kTex2DUpload = MGPipePackSubDataTarget(static_cast(MGPipeResourceTarget::Tex2D), + static_cast(TextureUploadTarget::Texture2D)); MGPSubData TextureWrite(MGPipeHandle res, Uint16 uploadTarget, Uint16 level, Uint32 extent) { MGPSubData record{}; @@ -1397,18 +1399,15 @@ namespace { const ChildResult child = RunInChild([&res, &drive]() { MGPResourceDesc perLevel = Tex2DDesc(res, 4, 2, 92); MGPipeSetRespecifiedLevel(perLevel, kTex2DUpload, 1, 2, 2, 1); - const MGPRespecifiedLevel named = MGPipeMakeRespecifiedLevel(drive.PointerTarget, drive.PointerLevel, 2, 2, 1); - MGPipeApplyResourceRespecify(perLevel, nullptr, - drive.HasPointer ? &named : nullptr); + const MGPRespecifiedLevel named = + MGPipeMakeRespecifiedLevel(drive.PointerTarget, drive.PointerLevel, 2, 2, 1); + MGPipeApplyResourceRespecify(perLevel, nullptr, drive.HasPointer ? &named : nullptr); }); - EXPECT_TRUE(DiedOfAbort(child)) - << drive.What << ": " << DescribeStatus(child) << "; log: " << child.Log; - const std::string named = - "Fatal{PipeRespecifyScope} resource_respecify {slot=13, gen=2}"; + EXPECT_TRUE(DiedOfAbort(child)) << drive.What << ": " << DescribeStatus(child) << "; log: " << child.Log; + const std::string named = "Fatal{PipeRespecifyScope} resource_respecify {slot=13, gen=2}"; EXPECT_NE(child.Log.find(named), std::string::npos) << drive.What << ": wanted \"" << named << "\"; log: " << child.Log; - EXPECT_NE(child.Log.find("declares a per-level respecify scope (target=258, level=1)"), - std::string::npos) + EXPECT_NE(child.Log.find("declares a per-level respecify scope (target=258, level=1)"), std::string::npos) << drive.What << ": the refusal must print the DECLARED range; log: " << child.Log; EXPECT_NE(child.Log.find(drive.Wanted), std::string::npos) << drive.What << ": wanted \"" << drive.Wanted << "\"; log: " << child.Log; @@ -1504,8 +1503,7 @@ namespace { // A renderbuffer restorage is the publication D-D2 asks for: the frontend raises no // version for it, so the emission IS the notice, and the applier holds the new extent. - MGPipeApplyResourceRespecify(TargetedDesc(shared, MGPipeResourceTarget::Renderbuffer, 1024, 33), - nullptr); + MGPipeApplyResourceRespecify(TargetedDesc(shared, MGPipeResourceTarget::Renderbuffer, 1024, 33), nullptr); EXPECT_EQ(MGPipeApplier().RenderbufferResources[7].Desc.Width, 1024u); EXPECT_EQ(MGPipeApplier().RenderbufferResources[7].Serial, 1u); @@ -1586,8 +1584,7 @@ namespace { // ---- with no consumer: every one of them declines, and NOTHING is stored ---- EXPECT_FALSE(MGPipeApplyResourceCreate(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 0, 22))); - EXPECT_FALSE( - MGPipeApplyResourceRespecify(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 64, 22), nullptr)); + EXPECT_FALSE(MGPipeApplyResourceRespecify(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 64, 22), nullptr)); EXPECT_FALSE(MGPipeApplyResourceSubData(upload, nullptr)); MGPipeApplySetTextureParams(params); MGPipeApplySetFramebufferState(fboState); @@ -1638,18 +1635,17 @@ namespace { EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().BoundShaderCso)); EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().DrawProgram)); EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().DispatchProgram)); - EXPECT_TRUE(MGPipeHandleIsNull( - MGPipeApplier().BoundFramebuffer[static_cast(MGPipeFramebufferTarget::Draw)])); + EXPECT_TRUE( + MGPipeHandleIsNull(MGPipeApplier().BoundFramebuffer[static_cast(MGPipeFramebufferTarget::Draw)])); // ---- and with one, every one of them lands. Same records, same order ---- { ScopedResourceOps consumer; const Uint64 refusalsBefore = MGPipeApplier().RefusedNoConsumer; + EXPECT_TRUE(MGPipeApplyResourceCreate(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 0, 22))); EXPECT_TRUE( - MGPipeApplyResourceCreate(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 0, 22))); - EXPECT_TRUE(MGPipeApplyResourceRespecify( - TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 64, 22), nullptr)); + MGPipeApplyResourceRespecify(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 64, 22), nullptr)); MGPipeApplySetTextureParams(params); MGPipeApplySetFramebufferState(fboState); MGPipeApplyCreateSamplerState(samplerDesc, &samplerParams); @@ -1670,8 +1666,7 @@ namespace { EXPECT_EQ(MGPipeApplier().TextureResources[7].Params.BuiltinSampler, cso); ASSERT_GT(MGPipeApplier().FramebufferRecords.size(), 4u); EXPECT_TRUE(MGPipeApplier().FramebufferRecords[4].Live); - EXPECT_EQ(MGPipeApplier().BoundFramebuffer[static_cast(MGPipeFramebufferTarget::Draw)], - fbo); + EXPECT_EQ(MGPipeApplier().BoundFramebuffer[static_cast(MGPipeFramebufferTarget::Draw)], fbo); ASSERT_GT(MGPipeApplier().SamplerCsos.size(), 9u); EXPECT_TRUE(MGPipeApplier().SamplerCsos[9].Live); ASSERT_GT(MGPipeApplier().SamplerViewCsos.size(), 9u); @@ -1760,8 +1755,7 @@ namespace { MGPipeApplyResourceCreate(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 0, 55)); MGPipeApplyResourceRespecify(TargetedDesc(texture, MGPipeResourceTarget::Tex2D, 8, 55), nullptr); MGPipeApplyResourceCreate(TargetedDesc(renderbuffer, MGPipeResourceTarget::Renderbuffer, 0, 66)); - MGPipeApplyResourceRespecify(TargetedDesc(renderbuffer, MGPipeResourceTarget::Renderbuffer, 8, 66), - nullptr); + MGPipeApplyResourceRespecify(TargetedDesc(renderbuffer, MGPipeResourceTarget::Renderbuffer, 8, 66), nullptr); MGPSubData upload{}; upload.Res = texture; upload.Target = static_cast(MGPipeResourceTarget::Tex2D); @@ -1827,9 +1821,13 @@ namespace { SharedPtr m_previousContext; }; - GLContext& Ctx() { return *MG_State::pGLContext; } + GLContext& Ctx() { + return *MG_State::pGLContext; + } - const SharedPtr& MakeBuffer(Uint name) { return Ctx().CreateBufferObject(name); } + const SharedPtr& MakeBuffer(Uint name) { + return Ctx().CreateBufferObject(name); + } // Bind `buffer` to `target` the way the GL entry point for that target does. The index // target is the BOUND VAO's element slot, not one of BufferState's, which is why it @@ -1863,23 +1861,23 @@ namespace { Uint32 LiteralBindMaskFor(BufferTarget target) { switch (target) { case BufferTarget::Vertex: - return 1u << 0; // VERTEX + return 1u << 0; // VERTEX case BufferTarget::Index: - return (1u << 1) | (1u << 11); // INDEX | ELEMENT_ARRAY + return (1u << 1) | (1u << 11); // INDEX | ELEMENT_ARRAY case BufferTarget::Uniform: - return 1u << 2; // CONSTANT + return 1u << 2; // CONSTANT case BufferTarget::ShaderStorage: - return 1u << 3; // SHADER_BUFFER + return 1u << 3; // SHADER_BUFFER case BufferTarget::DispatchIndirect: case BufferTarget::DrawIndirect: case BufferTarget::Parameter: - return 1u << 4; // INDIRECT + return 1u << 4; // INDIRECT case BufferTarget::Texture: - return 1u << 5; // SAMPLER + return 1u << 5; // SAMPLER case BufferTarget::TransformFeedback: - return 1u << 9; // STREAM_OUTPUT + return 1u << 9; // STREAM_OUTPUT case BufferTarget::AtomicCounter: - return 1u << 10; // ATOMIC + return 1u << 10; // ATOMIC case BufferTarget::CopyRead: case BufferTarget::CopyWrite: case BufferTarget::PixelPack: @@ -1887,7 +1885,7 @@ namespace { case BufferTarget::Query: case BufferTarget::BufferTargetCount: case BufferTarget::Unknown: - return 0u; // transfer and query targets bind nothing + return 0u; // transfer and query targets bind nothing } return 0xFFFFFFFFu; } @@ -2112,8 +2110,8 @@ namespace { // The same refusal through the reachable cap, which is what a transport will hit // first: a range whose later pieces cross the offset cap is refused whole. pieces.clear(); - EXPECT_FALSE(MGPipeForEachSubDataRecordRange(kMGPipeSubDataMaxRecordOffset - kSegment, - kSegment * 4, collect, kSegment)); + EXPECT_FALSE( + MGPipeForEachSubDataRecordRange(kMGPipeSubDataMaxRecordOffset - kSegment, kSegment * 4, collect, kSegment)); EXPECT_TRUE(pieces.empty()); } @@ -2152,8 +2150,7 @@ namespace { std::vector> pieces; const auto collect = [&](Uint64 at, Uint64 length) { pieces.emplace_back(at, length); }; const auto checkCoverage = [&](Uint64 whole, Uint64 cap) { - EXPECT_GE(pieces.size(), 4u) << "a range of " << whole << " bytes was not cut at a " - << cap << " byte cap"; + EXPECT_GE(pieces.size(), 4u) << "a range of " << whole << " bytes was not cut at a " << cap << " byte cap"; Uint64 covered = 0; Uint64 expectedAt = 0; for (const auto& piece : pieces) { @@ -2165,8 +2162,8 @@ namespace { // And every piece the walk produced has to be encodable by the record builder - // a piece the box refuses is a record the applier's bounds gate would abort on. MGPSubData record{}; - EXPECT_TRUE(MGPipeBuildSubDataRecord(MGPipeHandle{1, 1}, piece.first, piece.second, - record, /*verbatimShadow=*/true)) + EXPECT_TRUE(MGPipeBuildSubDataRecord(MGPipeHandle{1, 1}, piece.first, piece.second, record, + /*verbatimShadow=*/true)) << "a piece the splitter produced does not fit one record"; EXPECT_EQ(MGPipeSubDataBufferOffset(record), piece.first); EXPECT_EQ(MGPipeSubDataBufferSize(record), piece.second); @@ -2250,8 +2247,7 @@ namespace { MG_Pipe::MGPCaps caps{}; caps.CallMask = MG_Remote::MGCapsConsumerBits(MG_Pipe::kMGPipeSubsystemResources); MG_Remote::Client::CapsMirrorInstance().Adopt(caps, MG_Backend::FormatCapabilityCache{}, - MobileGL::RendererInfo{}, String{}, - BackendType::DirectGLES); + MobileGL::RendererInfo{}, String{}, BackendType::DirectGLES); // The spy IS the applier's consumer here, so "no resource_subdata followed" is // counted rather than inferred from a missing log line. g_spy = SpyState{}; @@ -2331,8 +2327,7 @@ namespace { << "the first write after a make-current moved no serial - it was dropped"; EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, 0u) << "the write was refused because the applier had no record for it"; - EXPECT_EQ(tracker.CreateCount(), 1u) - << "the client re-published a create for a record the applier still had"; + EXPECT_EQ(tracker.CreateCount(), 1u) << "the client re-published a create for a record the applier still had"; } // THE DSA HOLE, which is what makes the sampled mask insufficient on its own: an element @@ -2355,8 +2350,7 @@ namespace { // The negative control FIRST, so the assertion below cannot pass because the bit is // set for everything: a respecify with nothing bound publishes an empty mask. indices->Respecify(64, nullptr); - ASSERT_EQ(tracker.LastDesc().BindMask, 0u) - << "a respecify with no binding at all published one"; + ASSERT_EQ(tracker.LastDesc().BindMask, 0u) << "a respecify with no binding at all published one"; // Bind, draw, unbind - the transient the sampler cannot see afterwards. Ctx().GetBoundVertexArray()->GetIndexBufferBindingSlot().Bind(indices); diff --git a/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp b/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp index 13737f8e..3d400911 100644 --- a/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/SamplerEmitTest.cpp @@ -151,7 +151,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -194,8 +196,7 @@ TEST(SamplerEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) { // One bit for the whole sampler family - the CSO, the view and all three unit sets, // including set_shader_images, whose emitter lives in ImageEmit.h. An operator switching // samplers off has to get the whole family's legacy arm, not two thirds of it. - EXPECT_TRUE(kMGPipeWiredSamplerSubsystem == 0 || - kMGPipeWiredSamplerSubsystem == kMGPipeSubsystemSamplers); + EXPECT_TRUE(kMGPipeWiredSamplerSubsystem == 0 || kMGPipeWiredSamplerSubsystem == kMGPipeSubsystemSamplers); } // ========================================================================================= @@ -520,7 +521,6 @@ TEST(SamplerEmit, AMakeCurrentTakesTheUnitSetsAndLeavesTheCsoAndViewRecordsStand EXPECT_GT(MGPipeApplier().SamplerStatesSerial, statesSerial); } - namespace { using GLContext = MG_State::GLState::GLContext; using MG_State::GLState::ITextureObject; @@ -549,9 +549,15 @@ namespace { } }; - GLContext& Ctx() { return *MG_State::pGLContext; } - MGPipeSamplerEmitter& Emitter() { return MGPipeSamplerEmitterInstance(); } - MGPipeSamplerCsoCache& Cache() { return MGPipeSamplerCsoCacheInstance(); } + GLContext& Ctx() { + return *MG_State::pGLContext; + } + MGPipeSamplerEmitter& Emitter() { + return MGPipeSamplerEmitterInstance(); + } + MGPipeSamplerCsoCache& Cache() { + return MGPipeSamplerCsoCacheInstance(); + } // Every field of SamplerParameters set to a value that is not its default, so a conversion // that dropped one is caught by the field's own EXPECT rather than by a count. @@ -591,9 +597,7 @@ namespace { } void BindTextureToUnit(Uint32 unit, const SharedPtr& texture) { - Ctx().GetTextureUnitObject(static_cast(unit)) - .GetBindingSlot(TextureTarget::Texture2D) - .Bind(texture); + Ctx().GetTextureUnitObject(static_cast(unit)).GetBindingSlot(TextureTarget::Texture2D).Bind(texture); Ctx().NoteTextureUnitTouched(static_cast(unit)); } @@ -641,7 +645,8 @@ namespace { auto* bytes = reinterpret_cast(&dirty); // Written THROUGH A BYTE POINTER, past the last member and inside the object, which is // the only way to make the difference this case is about. - for (SizeT i = 97; i < sizeof(SamplerParameters); ++i) bytes[i] = static_cast(0xA5 + i); + for (SizeT i = 97; i < sizeof(SamplerParameters); ++i) + bytes[i] = static_cast(0xA5 + i); SamplerParameters canonClean; SamplerParameters canonDirty; @@ -821,7 +826,8 @@ namespace { EXPECT_EQ(Cache().GetCounters().GroupPublishes, 1u); EXPECT_NE(BoundGroupSamplerRecord(back), nullptr); - for (const MGPipeHandle held : {first, second, again, back}) Cache().Release(held); + for (const MGPipeHandle held : {first, second, again, back}) + Cache().Release(held); EXPECT_EQ(Cache().GetCounters().UnknownReleases + Cache().GetCounters().UnderflowedReleases, 0u); } @@ -934,11 +940,10 @@ namespace { Uint MakeSamplerProgram() { namespace GL = MobileGL::MG_Impl::GLImpl; static const char* kVs = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n"; - static const char* kFs = - "#version 330 core\n" - "uniform sampler2D sampled;\n" - "out vec4 color;\n" - "void main(){ color = texture(sampled, vec2(0.0)); }\n"; + static const char* kFs = "#version 330 core\n" + "uniform sampler2D sampled;\n" + "out vec4 color;\n" + "void main(){ color = texture(sampled, vec2(0.0)); }\n"; const Uint program = GL::CreateProgram(); const Uint vs = GL::CreateShader(GL_VERTEX_SHADER); GL::ShaderSource(vs, 1, &kVs, nullptr); @@ -1098,8 +1103,7 @@ namespace { EXPECT_EQ(Cache().GetCounters().OverCapacityMints, 0u) << "there were unreferenced entries to take, so nothing had to grow"; - EXPECT_TRUE(Cache().RecordIsPublished(pinned)) - << "a handle a standing record names is not the LRU's to take"; + EXPECT_TRUE(Cache().RecordIsPublished(pinned)) << "a handle a standing record names is not the LRU's to take"; Uint64 second = 0; EXPECT_EQ(Cache().Acquire(pinnedParams, second), pinned); EXPECT_EQ(second, 0u) << "and it was a hit, not a re-mint under the same value"; @@ -1239,8 +1243,7 @@ int main(int argc, char** argv) { // raised by the encoder on THIS thread - the client role. So g_logPath, which is the read // path from here on, becomes the client-derived name; the env keeps the base. The rule is // the library's own, not a copy. - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); // ONE process-wide context for the whole suite, because half these cases need a really // linked program and glslang lives behind this call. Each case uses GL names of its own. MobileGL::Initialize(); diff --git a/MobileGL/MG_Test/Pipe/ShaderBufferEmitTest.cpp b/MobileGL/MG_Test/Pipe/ShaderBufferEmitTest.cpp index 1d534631..2ae6eee0 100644 --- a/MobileGL/MG_Test/Pipe/ShaderBufferEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/ShaderBufferEmitTest.cpp @@ -81,7 +81,6 @@ namespace { } } // namespace - namespace { namespace GL = MobileGL::MG_Impl::GLImpl; using GLContext = MG_State::GLState::GLContext; @@ -111,8 +110,12 @@ namespace { } }; - GLContext& Ctx() { return *MG_State::pGLContext; } - MGPipeShaderBufferEmitter& Emitter() { return MGPipeShaderBufferEmitterInstance(); } + GLContext& Ctx() { + return *MG_State::pGLContext; + } + MGPipeShaderBufferEmitter& Emitter() { + return MGPipeShaderBufferEmitterInstance(); + } // ---- driving a refusal, in ImageEmitTest's shape and for its reason ------------------ // @@ -181,8 +184,7 @@ namespace { const ChildResult child = RunInChild(body); EXPECT_TRUE(DiedOfAbort(child)) << DescribeStatus(child) << "; log: " << child.Log; EXPECT_NE(child.Log.find(tagged), std::string::npos) - << "the gate fired without naming what it refused; wanted \"" << tagged - << "\"; log: " << child.Log; + << "the gate fired without naming what it refused; wanted \"" << tagged << "\"; log: " << child.Log; #else (void)needle; (void)body; // no fork on this platform; the verdict here is std::abort() @@ -262,8 +264,7 @@ namespace { // And the record reached the applier through the monolith adapter this route installs. EXPECT_EQ(MGPipeApplier().ShaderBufferCount[kMGPipeShaderBufferClassUniform], header.Count); - EXPECT_EQ(MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassUniform][0].Size, - kMGPipeWholeBuffer); + EXPECT_EQ(MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassUniform][0].Size, kMGPipeWholeBuffer); } // ID-104's case. The mask is what replaces the backend's own GPU-write walk, and until this @@ -288,8 +289,7 @@ namespace { GL::BindBufferBase(GL_UNIFORM_BUFFER, 0, uniform); ASSERT_NE(Emitter().EmitShaderBuffers(Ctx()), 0u); - const MGPShaderBuffers& storageHeader = - Emitter().LastHeader(kMGPipeShaderBufferClassShaderStorage); + const MGPShaderBuffers& storageHeader = Emitter().LastHeader(kMGPipeShaderBufferClassShaderStorage); ASSERT_EQ(storageHeader.Count, top + 1u) << "the window is the touched high-water mark, so binding the top point describes " "every point below it too"; @@ -299,8 +299,7 @@ namespace { << "nothing is bound at storage point 0, so nothing is writable there"; ASSERT_NE(Emitter().EmitConstBuffers(Ctx()), 0u); - const MGPShaderBuffers& uniformHeader = - Emitter().LastHeader(kMGPipeShaderBufferClassUniform); + const MGPShaderBuffers& uniformHeader = Emitter().LastHeader(kMGPipeShaderBufferClassUniform); for (Uint32 w = 0; w < kMGPipeShaderBufferWritableMaskWords; ++w) { EXPECT_EQ(uniformHeader.WritableMask[w], 0u) << "a UBO is read-only to the shader by definition (word " << w << ")"; @@ -308,8 +307,8 @@ namespace { // And the applier carries the mask by class, not by record. EXPECT_TRUE(MGPipeShaderBufferMaskHas( MGPipeApplier().ShaderBufferWritableMask[kMGPipeShaderBufferClassShaderStorage], top)); - EXPECT_FALSE(MGPipeShaderBufferMaskHas( - MGPipeApplier().ShaderBufferWritableMask[kMGPipeShaderBufferClassUniform], top)); + EXPECT_FALSE( + MGPipeShaderBufferMaskHas(MGPipeApplier().ShaderBufferWritableMask[kMGPipeShaderBufferClassUniform], top)); // THE WIRE FIELD's OWN HALF (ID-104): a mask bit at the LAST point the window may // describe survives the record and the applier. A Uint32 field could not have carried @@ -345,8 +344,7 @@ namespace { ASSERT_NE(Emitter().EmitConstBuffers(Ctx()), 0u); ASSERT_NE(Emitter().EmitShaderBuffers(Ctx()), 0u); const Uint64 uniformEmissions = Emitter().EmissionCount(kMGPipeShaderBufferClassUniform); - const Uint64 storageEmissions = - Emitter().EmissionCount(kMGPipeShaderBufferClassShaderStorage); + const Uint64 storageEmissions = Emitter().EmissionCount(kMGPipeShaderBufferClassShaderStorage); // Nothing moved: both classes must be suppressed, and each on its OWN latch. EXPECT_EQ(Emitter().EmitConstBuffers(Ctx()), 0u); @@ -358,8 +356,7 @@ namespace { // re-sent, and the case would pass for the wrong reason if it only asserted the mover. GL::BindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, 0); EXPECT_NE(Emitter().EmitShaderBuffers(Ctx()), 0u); - EXPECT_EQ(Emitter().EmissionCount(kMGPipeShaderBufferClassShaderStorage), - storageEmissions + 1u); + EXPECT_EQ(Emitter().EmissionCount(kMGPipeShaderBufferClassShaderStorage), storageEmissions + 1u); EXPECT_EQ(Emitter().EmitConstBuffers(Ctx()), 0u) << "the uniform class did not move and must not be re-sent on the storage class's " "account"; @@ -386,8 +383,7 @@ namespace { MGPipeApplySetShaderBuffers(header, ranges); EXPECT_EQ(MGPipeApplier().ShaderBufferCount[kMGPipeShaderBufferClassShaderStorage], 2u); - EXPECT_EQ(MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassShaderStorage][1].Offset, - 64u); + EXPECT_EQ(MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassShaderStorage][1].Offset, 64u); EXPECT_EQ(MGPipeApplier().ShaderBuffersSerial, before + 1u); // THE OTHER TWO CLASSES ARE UNTOUCHED, which is the whole reason Class is a field: one // record describes one array and says nothing about the other two. @@ -446,19 +442,17 @@ namespace { applier.ShaderBufferStart[kMGPipeShaderBufferClassUniform] = 0; applier.ShaderBufferCount[kMGPipeShaderBufferClassUniform] = 7; applier.BoundShaderBuffers[kMGPipeShaderBufferClassUniform][0].Res = MGPipeHandle{31, 1}; - MGPipeShaderBufferMaskSet( - applier.ShaderBufferWritableMask[kMGPipeShaderBufferClassShaderStorage], - kMGPipeMaxBufferBindingPoints - 1); + MGPipeShaderBufferMaskSet(applier.ShaderBufferWritableMask[kMGPipeShaderBufferClassShaderStorage], + kMGPipeMaxBufferBindingPoints - 1); applier.ShaderBuffersSerial = 99; MGPipeApplierReset(); EXPECT_EQ(MGPipeApplier().ShaderBufferCount[kMGPipeShaderBufferClassUniform], 0u); - EXPECT_TRUE(MGPipeHandleIsNull( - MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassUniform][0].Res)); - EXPECT_FALSE(MGPipeShaderBufferMaskHas( - MGPipeApplier().ShaderBufferWritableMask[kMGPipeShaderBufferClassShaderStorage], - kMGPipeMaxBufferBindingPoints - 1)); + EXPECT_TRUE(MGPipeHandleIsNull(MGPipeApplier().BoundShaderBuffers[kMGPipeShaderBufferClassUniform][0].Res)); + EXPECT_FALSE( + MGPipeShaderBufferMaskHas(MGPipeApplier().ShaderBufferWritableMask[kMGPipeShaderBufferClassShaderStorage], + kMGPipeMaxBufferBindingPoints - 1)); EXPECT_EQ(MGPipeApplier().ShaderBuffersSerial, 100u); EXPECT_GT(MGPipeApplier().ShaderBuffersSerial, 99u) << "ADVANCED, never zeroed: no value this counter has handed a twin may come back"; @@ -467,8 +461,8 @@ namespace { int main(int argc, char** argv) { namespace fs = std::filesystem; - const fs::path path = fs::temp_directory_path() / - ("mobilegl-shaderbufferemit-test-" + std::to_string(ProcessId()) + ".log"); + const fs::path path = + fs::temp_directory_path() / ("mobilegl-shaderbufferemit-test-" + std::to_string(ProcessId()) + ".log"); std::error_code ec; fs::remove(path, ec); g_logPath = path.string(); @@ -482,8 +476,7 @@ int main(int argc, char** argv) { // raised by the encoder on THIS thread - the client role. So g_logPath, which is the read // path from here on, becomes the client-derived name; the env keeps the base. The rule is // the library's own, not a copy. - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); MobileGL::Initialize(); ::testing::InitGoogleTest(&argc, argv); const int rc = RUN_ALL_TESTS(); diff --git a/MobileGL/MG_Test/Pipe/SlotAllocatorTest.cpp b/MobileGL/MG_Test/Pipe/SlotAllocatorTest.cpp index de64fff8..a67d0dd8 100644 --- a/MobileGL/MG_Test/Pipe/SlotAllocatorTest.cpp +++ b/MobileGL/MG_Test/Pipe/SlotAllocatorTest.cpp @@ -118,7 +118,7 @@ namespace { Uint64 staleHint = MGPipePackHandleHint(minted); const MGPipeHandle successor = allocator.AcquireHinted(MGPipeKind::Buffer, kSecond, staleHint); EXPECT_EQ(successor.Slot, minted.Slot) << "the free list did not hand the slot back"; - Uint64 forged = MGPipePackHandleHint(successor); // the successor's exact slot and generation + Uint64 forged = MGPipePackHandleHint(successor); // the successor's exact slot and generation EXPECT_TRUE(MGPipeHandleIsNull(allocator.FindByLifetimeIdHinted(MGPipeKind::Buffer, kFirst, forged))) << "a hint naming another object's live slot answered for the dead object"; @@ -133,7 +133,8 @@ namespace { MGPipeSlotAllocator allocator; MGPipeHandle handles[8]; - for (MGPipeHandle& handle : handles) handle = allocator.Allocate(MGPipeKind::Framebuffer); + for (MGPipeHandle& handle : handles) + handle = allocator.Allocate(MGPipeKind::Framebuffer); const Uint32 highWater = allocator.HighWater(MGPipeKind::Framebuffer); EXPECT_EQ(allocator.LiveCount(MGPipeKind::Framebuffer), 8u); EXPECT_EQ(allocator.FreeCount(MGPipeKind::Framebuffer), 0u); @@ -148,7 +149,7 @@ namespace { const MGPipeHandle a = allocator.Allocate(MGPipeKind::Framebuffer); const MGPipeHandle b = allocator.Allocate(MGPipeKind::Framebuffer); EXPECT_EQ(allocator.HighWater(MGPipeKind::Framebuffer), highWater) << "the high-water mark grew with two " - "slots waiting on the free list"; + "slots waiting on the free list"; EXPECT_TRUE((a.Slot == handles[2].Slot && b.Slot == handles[5].Slot) || (a.Slot == handles[5].Slot && b.Slot == handles[2].Slot)) << "the reused slots are not the two that were freed"; @@ -247,10 +248,10 @@ namespace { EXPECT_FALSE(MGPipeHandleIsNull(first)); sharp.Free(MGPipeKind::VertexElementsCso, first); const MGPipeHandle second = sharp.Acquire(MGPipeKind::VertexElementsCso, repeatedLifetimeId); - EXPECT_FALSE(second == first) - << "re-acquiring the SAME lifetime id after a free reproduced handle {slot=" << first.Slot - << ", gen=" << first.Gen << "}; an address-keyed or name-keyed memo would then serve the dead " - "object's entry to the live one"; + EXPECT_FALSE(second == first) << "re-acquiring the SAME lifetime id after a free reproduced handle {slot=" + << first.Slot << ", gen=" << first.Gen + << "}; an address-keyed or name-keyed memo would then serve the dead " + "object's entry to the live one"; EXPECT_EQ(second.Slot, first.Slot) << "the freed slot was not the one handed back"; EXPECT_EQ(second.Gen, first.Gen + 1) << "a reused slot must carry a new generation"; // And the dead handle stays dead, which is what makes the ABA detectable rather than diff --git a/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp b/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp index 242bc905..3fdafb7d 100644 --- a/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/TextureEmitTest.cpp @@ -144,7 +144,9 @@ namespace { return result; } - Bool DiedOfAbort(const ChildResult& r) { return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; } + Bool DiedOfAbort(const ChildResult& r) { + return WIFSIGNALED(r.Status) && WTERMSIG(r.Status) == SIGABRT; + } std::string DescribeStatus(const ChildResult& r) { if (r.Status < 0) return "fork/waitpid failed"; if (WIFEXITED(r.Status)) return "exited " + std::to_string(WEXITSTATUS(r.Status)); @@ -186,8 +188,7 @@ namespace { // use-after-free this design closed once already. TEST(TextureEmit, TheEmitterIsOneNeverDestroyedProcessSingleton) { EXPECT_EQ(&MGPipeTextureEmitterInstance(), &MGPipeTextureEmitterInstance()); - EXPECT_TRUE(kMGPipeWiredTextureSubsystem == 0 || - kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources); + EXPECT_TRUE(kMGPipeWiredTextureSubsystem == 0 || kMGPipeWiredTextureSubsystem == kMGPipeSubsystemTextureResources); } // ============================================================================ @@ -228,9 +229,8 @@ namespace { // resource hook has anything to fire on. // Without all three the family gate is FALSE and every case below would be green for // the wrong reason - which is what the dependency cases at the end of this file pin. - MG_Config::Features.PipePush |= kMGPipeSubsystemResources | - kMGPipeSubsystemTextureResources | - kMGPipeSubsystemSamplers; + MG_Config::Features.PipePush |= + kMGPipeSubsystemResources | kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers; m_previousContext = MG_State::pGLContext.Take(); MG_State::pGLContext = MakeUnique(); MGPipeTextureEmitterInstance().ResetForTest(); @@ -273,8 +273,12 @@ namespace { const MGPipeResourceOps* m_saved; }; - MGPipeTextureEmitter& Textures() { return MGPipeTextureEmitterInstance(); } - GLContext& Ctx() { return *MG_State::pGLContext; } + MGPipeTextureEmitter& Textures() { + return MGPipeTextureEmitterInstance(); + } + GLContext& Ctx() { + return *MG_State::pGLContext; + } // The APPLIER's own view of what this emitter sent. With the family's wired constant flipped // the calls actually land, so a case can read the record rather than the emitter's staging @@ -288,8 +292,8 @@ namespace { return &record; } - const MGPipeResourceRecord::PendingUpload* AppliedUpload(const MGPipeResourceRecord& record, - Uint16 target, Uint16 level) { + const MGPipeResourceRecord::PendingUpload* AppliedUpload(const MGPipeResourceRecord& record, Uint16 target, + Uint16 level) { for (const auto& pending : record.PendingUploads) { if (pending.UploadTarget == target && pending.Level == level) return &pending; } @@ -304,9 +308,9 @@ namespace { texture->SetInternalFormat(TextureInternalFormat::RGBA8); for (Uint level = 0; level < levels; ++level) { const Int extent = std::max(size >> level, 1); - texture->AllocateStorage(TextureUploadTarget::Texture2D, level, - MipmapInput{IntVec3{extent, extent, 1}, - static_cast(extent) * static_cast(extent) * 4}); + texture->AllocateStorage( + TextureUploadTarget::Texture2D, level, + MipmapInput{IntVec3{extent, extent, 1}, static_cast(extent) * static_cast(extent) * 4}); } return texture; } @@ -373,8 +377,7 @@ TEST(TextureEmit, ABindMaskBitIsStickyAcrossARespecify) { Textures().NoteTextureBoundAs(handle, kMGPipeBindRenderTarget); // A storage definition REPUBLISHES the mask; it does not rebuild it. - texture->AllocateStorage(TextureUploadTarget::Texture2D, 0, - MipmapInput{IntVec3{16, 16, 1}, 16 * 16 * 4}); + texture->AllocateStorage(TextureUploadTarget::Texture2D, 0, MipmapInput{IntVec3{16, 16, 1}, 16 * 16 * 4}); EXPECT_TRUE(Textures().LastDesc().Resource == handle); EXPECT_NE(Textures().LastDesc().BindMask & kMGPipeBindRenderTarget, 0) << "MGPResourceDesc::BindMask lost the RENDER_TARGET bit across a respecify"; @@ -437,8 +440,7 @@ TEST(TextureEmit, TheUnionBoxAndTheRegionListDescribeTheSameTexels) { TextureScope scope; const auto texture = MakeTexture2D(4, 256); texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{1, 1, 1}); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{255, 255, 0}, - IntVec3{1, 1, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{255, 255, 0}, IntVec3{1, 1, 1}); ASSERT_EQ(Textures().DrainListSize(), 1u); Textures().DrainTextureSubData(Ctx()); @@ -454,11 +456,9 @@ TEST(TextureEmit, TheUnionBoxAndTheRegionListDescribeTheSameTexels) { for (const MGPSubRegion& region : regions) { EXPECT_GE(region.X, record.UnionBox.X) << "a rect starts left of the union box"; EXPECT_GE(region.Y, record.UnionBox.Y) << "a rect starts above the union box"; - EXPECT_LE(region.X + static_cast(region.W), - record.UnionBox.X + static_cast(record.UnionBox.W)) + EXPECT_LE(region.X + static_cast(region.W), record.UnionBox.X + static_cast(record.UnionBox.W)) << "a rect ends right of the union box"; - EXPECT_LE(region.Y + static_cast(region.H), - record.UnionBox.Y + static_cast(record.UnionBox.H)) + EXPECT_LE(region.Y + static_cast(region.H), record.UnionBox.Y + static_cast(record.UnionBox.H)) << "a rect ends below the union box"; loX = std::min(loX, region.X); loY = std::min(loY, region.Y); @@ -475,8 +475,7 @@ TEST(TextureEmit, AScatteredUploadCarriesTheLevelShadowsStridesAndNotZero) { TextureScope scope; const auto texture = MakeTexture2D(5, 256); texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{4, 8, 0}, IntVec3{2, 2, 1}); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{200, 220, 0}, - IntVec3{2, 2, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{200, 220, 0}, IntVec3{2, 2, 1}); Textures().DrainTextureSubData(Ctx()); const Vector regions = Textures().LastRegions(); const auto& record = Textures().LastSubData(); @@ -488,10 +487,8 @@ TEST(TextureEmit, AScatteredUploadCarriesTheLevelShadowsStridesAndNotZero) { // not its own width - or the staging planner on the far side repacks the wrong bytes. for (const MGPSubRegion& region : regions) { EXPECT_EQ(region.SrcRowStride, 256u * 4u) << "SrcRowStride is not the LEVEL's row pitch"; - EXPECT_EQ(region.SrcSliceStride, 256u * 4u * 256u) - << "SrcSliceStride is not the LEVEL's slice pitch"; - const Uint64 expectedOffset = - static_cast(region.Y) * 256u * 4u + static_cast(region.X) * 4u; + EXPECT_EQ(region.SrcSliceStride, 256u * 4u * 256u) << "SrcSliceStride is not the LEVEL's slice pitch"; + const Uint64 expectedOffset = static_cast(region.Y) * 256u * 4u + static_cast(region.X) * 4u; EXPECT_EQ(region.SrcOffset, expectedOffset) << "SrcOffset is not the first texel's byte offset"; } } @@ -499,8 +496,7 @@ TEST(TextureEmit, AScatteredUploadCarriesTheLevelShadowsStridesAndNotZero) { TEST(TextureEmit, AWholeLevelUploadCarriesZeroStrides) { TextureScope scope; const auto texture = MakeTexture2D(6, 64); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{64, 64, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{64, 64, 1}); Textures().DrainTextureSubData(Ctx()); const MGPSubData record = Textures().LastSubData(); EXPECT_EQ(record.RegionCount, 0u) << "a single whole-level rect IS the union box, not a list"; @@ -519,10 +515,8 @@ TEST(TextureEmit, AWholeLevelUploadCarriesZeroStrides) { "whole-level shape stays what it always was"; // The upload target rides in the record's Target byte beside the resource target, which is // the only place a cube face could ever be carried. - EXPECT_EQ(MGPipeSubDataResourceTargetOf(record.Target), - static_cast(MGPipeResourceTarget::Tex2D)); - EXPECT_EQ(MGPipeSubDataUploadTargetOf(record.Target), - static_cast(TextureUploadTarget::Texture2D)); + EXPECT_EQ(MGPipeSubDataResourceTargetOf(record.Target), static_cast(MGPipeResourceTarget::Tex2D)); + EXPECT_EQ(MGPipeSubDataUploadTargetOf(record.Target), static_cast(TextureUploadTarget::Texture2D)); // And the whole-level builder really does say TIGHTLY PACKED. const MGPipeLevelPitch pitch = MGPipeLevelPitchOf(IntVec3{64, 64, 1}, 64 * 64 * 4); const MGPSubRegion whole = MGPipeBuildSubRegion(record.UnionBox, IntVec3{64, 64, 1}, pitch); @@ -543,8 +537,7 @@ TEST(TextureEmit, MoreWritesThanTheRectListKeepsCrossAsTheExactFootprint) { for (Int i = 0; i < 4 * static_cast(MipmapStorage::kMaxDirtyRects); ++i) { const Int x = (i * 7) % 120; const Int y = (i * 11) % 120; - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{x, y, 0}, - IntVec3{2, 2, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{x, y, 0}, IntVec3{2, 2, 1}); } Vector oracle; texture->GetStorageDirtyFootprint(TextureUploadTarget::Texture2D, 0, oracle); @@ -558,8 +551,8 @@ TEST(TextureEmit, MoreWritesThanTheRectListKeepsCrossAsTheExactFootprint) { ASSERT_EQ(regions.size(), oracle.size()); for (SizeT i = 0; i < oracle.size(); ++i) { const MGPBox box = MGPipeBoxOfDirtyRegion(oracle[i]); - EXPECT_TRUE(regions[i].X == box.X && regions[i].Y == box.Y && regions[i].Z == box.Z && - regions[i].W == box.W && regions[i].H == box.H && regions[i].D == box.D) + EXPECT_TRUE(regions[i].X == box.X && regions[i].Y == box.Y && regions[i].Z == box.Z && regions[i].W == box.W && + regions[i].H == box.H && regions[i].D == box.D) << "region " << i << " is not footprint box " << i; } } @@ -575,8 +568,7 @@ TEST(TextureEmit, MoreThanKMaxDirtyRectsCollapsesToTheBoxWithRegionCountZero) { const auto texture = MakeTexture2D(91, 256); for (Int y = 0; y < 256; y += 2) { for (Int x = 0; x < 256; x += 2) { - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{x, y, 0}, - IntVec3{1, 1, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{x, y, 0}, IntVec3{1, 1, 1}); } } Vector footprint; @@ -584,8 +576,8 @@ TEST(TextureEmit, MoreThanKMaxDirtyRectsCollapsesToTheBoxWithRegionCountZero) { ASSERT_GT(footprint.size(), static_cast(kMGPipeMaxPendingUploadRegions)) << "the writes must outgrow one record, or this case pins the footprint path instead"; MipmapDirtyRegion oracle[MipmapStorage::kMaxDirtyRects]; - const SizeT oracleCount = texture->GetStorageDirtyRects(TextureUploadTarget::Texture2D, 0, oracle, - MipmapStorage::kMaxDirtyRects); + const SizeT oracleCount = + texture->GetStorageDirtyRects(TextureUploadTarget::Texture2D, 0, oracle, MipmapStorage::kMaxDirtyRects); EXPECT_LE(oracleCount, MipmapStorage::kMaxDirtyRects); Textures().DrainTextureSubData(Ctx()); EXPECT_EQ(Textures().LastSubData().RegionCount, static_cast(oracleCount)) @@ -648,28 +640,23 @@ TEST(TextureEmit, ALevelTooLargeForTheStageChunkIsCutIntoSlabs) { // exactly once, in order, each within the cap and each exactly its own box's byte extent. const auto cut = [&](const IntVec3& levelSize, Uint64 levelBytes, SizeT chunk) { std::vector slabs; - const MGPipeLevelPitch pitch = - MGPipeLevelPitchOf(levelSize, static_cast(levelBytes)); + const MGPipeLevelPitch pitch = MGPipeLevelPitchOf(levelSize, static_cast(levelBytes)); EXPECT_TRUE(MGPipeForEachTextureSlab(levelSize, pitch, levelBytes, chunk, - [&](const MGPipeTextureSlab& slab) { - slabs.push_back(slab); - })); - EXPECT_GE(slabs.size(), 2u) << "a level of " << levelBytes << " bytes was not cut at a " - << chunk << " byte cap"; + [&](const MGPipeTextureSlab& slab) { slabs.push_back(slab); })); + EXPECT_GE(slabs.size(), 2u) << "a level of " << levelBytes << " bytes was not cut at a " << chunk + << " byte cap"; Uint64 expectedOffset = 0; Uint64 covered = 0; for (const MGPipeTextureSlab& slab : slabs) { EXPECT_GT(slab.RunBytes, 0u); EXPECT_LE(slab.RunBytes, static_cast(chunk)) << "a piece is bigger than the cap"; - EXPECT_EQ(slab.RunOffset, expectedOffset) - << "the pieces are not contiguous and ascending"; + EXPECT_EQ(slab.RunOffset, expectedOffset) << "the pieces are not contiguous and ascending"; expectedOffset += slab.RunBytes; covered += slab.RunBytes; const Uint64 boxBytes = static_cast(slab.Box.D - 1) * pitch.SliceStride + static_cast(slab.Box.H - 1) * pitch.RowStride + static_cast(slab.Box.W) * pitch.BytesPerTexel; - EXPECT_EQ(boxBytes, slab.RunBytes) - << "the box does not describe the run, so the far side cannot place it"; + EXPECT_EQ(boxBytes, slab.RunBytes) << "the box does not describe the run, so the far side cannot place it"; } EXPECT_EQ(covered, levelBytes) << "the pieces do not cover the level exactly once"; return slabs; @@ -735,15 +722,11 @@ TEST(TextureEmit, ALevelTooLargeForTheStageChunkIsCutIntoSlabs) { // split existed: no cap at all, a pitch that does not tile the level's bytes exactly, and a cap // below one texel. std::vector refusals; - const auto refuses = [&](const IntVec3& levelSize, const MGPipeLevelPitch& pitch, - Uint64 levelBytes, SizeT chunk) { + const auto refuses = [&](const IntVec3& levelSize, const MGPipeLevelPitch& pitch, Uint64 levelBytes, SizeT chunk) { return !MGPipeForEachTextureSlab(levelSize, pitch, levelBytes, chunk, - [&](const MGPipeTextureSlab& slab) { - refusals.push_back(slab); - }); + [&](const MGPipeTextureSlab& slab) { refusals.push_back(slab); }); }; - const MGPipeLevelPitch volumePitch = - MGPipeLevelPitchOf(volume, static_cast(volumeBytes)); + const MGPipeLevelPitch volumePitch = MGPipeLevelPitchOf(volume, static_cast(volumeBytes)); const MGPipeLevelPitch linePitch = MGPipeLevelPitchOf(line, static_cast(lineBytes)); EXPECT_TRUE(refuses(volume, volumePitch, volumeBytes, 0)); EXPECT_TRUE(refuses(volume, volumePitch, volumeBytes + 1, cap)) @@ -755,11 +738,11 @@ TEST(TextureEmit, ALevelTooLargeForTheStageChunkIsCutIntoSlabs) { // the piece's own run (MGPipeTypes.h: SrcOffset is "into the blob") and the LEVEL's pitches // kept. A slab the dirty set does not reach still carries its own box as ONE region. const MGPBox dirty[5] = { - {0, 0, 0, 512, 128, 1}, // the whole first slice, inside slab 0 - {0, 0, 4, 64, 64, 1}, // a small rect in the middle of slab 0 - {10, 20, 8, 64, 64, 2}, // a rect spanning the first two slices of slab 1 - {460, 100, 7, 64, 64, 3}, // clipped on X, Y and the slab-0/slab-1 boundary - {0, 0, 32, 512, 128, 1}, // the last slice, inside slab 4 + {0, 0, 0, 512, 128, 1}, // the whole first slice, inside slab 0 + {0, 0, 4, 64, 64, 1}, // a small rect in the middle of slab 0 + {10, 20, 8, 64, 64, 2}, // a rect spanning the first two slices of slab 1 + {460, 100, 7, 64, 64, 3}, // clipped on X, Y and the slab-0/slab-1 boundary + {0, 0, 32, 512, 128, 1}, // the last slice, inside slab 4 }; SizeT regionCounts[5] = {0, 0, 0, 0, 0}; for (SizeT i = 0; i < volumeSlabs.size(); ++i) { @@ -772,21 +755,17 @@ TEST(TextureEmit, ALevelTooLargeForTheStageChunkIsCutIntoSlabs) { const MGPSubRegion& region = regions[r]; EXPECT_EQ(region.SrcRowStride, volumePitch.RowStride); EXPECT_EQ(region.SrcSliceStride, volumePitch.SliceStride); - EXPECT_EQ(region.SrcOffset, - static_cast(region.Z - slab.Box.Z) * volumePitch.SliceStride + - static_cast(region.Y - slab.Box.Y) * volumePitch.RowStride + - static_cast(region.X - slab.Box.X) * volumePitch.BytesPerTexel) + EXPECT_EQ(region.SrcOffset, static_cast(region.Z - slab.Box.Z) * volumePitch.SliceStride + + static_cast(region.Y - slab.Box.Y) * volumePitch.RowStride + + static_cast(region.X - slab.Box.X) * volumePitch.BytesPerTexel) << "SrcOffset is not relative to the piece's own run"; EXPECT_GE(region.X, slab.Box.X); EXPECT_GE(region.Y, slab.Box.Y); EXPECT_GE(region.Z, slab.Box.Z); - EXPECT_LE(region.X + static_cast(region.W), - slab.Box.X + static_cast(slab.Box.W)) + EXPECT_LE(region.X + static_cast(region.W), slab.Box.X + static_cast(slab.Box.W)) << "a clipped rect reaches outside the piece's box"; - EXPECT_LE(region.Y + static_cast(region.H), - slab.Box.Y + static_cast(slab.Box.H)); - EXPECT_LE(region.Z + static_cast(region.D), - slab.Box.Z + static_cast(slab.Box.D)); + EXPECT_LE(region.Y + static_cast(region.H), slab.Box.Y + static_cast(slab.Box.H)); + EXPECT_LE(region.Z + static_cast(region.D), slab.Box.Z + static_cast(slab.Box.D)); } } // Slab 0 sees three rects (one of them clipped at its own z end), slab 1 two, slabs 2 and 3 @@ -819,10 +798,9 @@ TEST(TextureEmit, ALevelTooLargeForTheStageChunkIsCutIntoSlabs) { EXPECT_EQ(regions[i].H, 28u); EXPECT_EQ(regions[i].Z, 7); EXPECT_EQ(regions[i].D, 1u); - EXPECT_EQ(regions[i].SrcOffset, - static_cast(7) * volumePitch.SliceStride + - static_cast(100) * volumePitch.RowStride + - static_cast(460) * volumePitch.BytesPerTexel); + EXPECT_EQ(regions[i].SrcOffset, static_cast(7) * volumePitch.SliceStride + + static_cast(100) * volumePitch.RowStride + + static_cast(460) * volumePitch.BytesPerTexel); } } EXPECT_TRUE(sawClippedRect) << "a rect wider than the level was not clipped to the piece"; @@ -841,8 +819,7 @@ TEST(TextureEmit, AnUploadThroughAViewKeysOnTheStorageOwner) { ASSERT_FALSE(ownerHandle == viewHandle); // ViewOf names the storage owner, and ONE HOP always reaches storage. EXPECT_TRUE(Textures().LastDesc().Resource == viewHandle); - EXPECT_TRUE(Textures().LastDesc().ViewOf == ownerHandle) - << "the view's descriptor does not name its storage owner"; + EXPECT_TRUE(Textures().LastDesc().ViewOf == ownerHandle) << "the view's descriptor does not name its storage owner"; // An upload through the VIEW: TextureObjectView forwards the mark to the OWNER's method // after remapping the level, so the drain list holds exactly one entry and it is the @@ -862,10 +839,8 @@ TEST(TextureEmit, EveryTexturesParamsNameItsBuiltinSamplerCso) { TextureScope scope; const auto texture = MakeTexture2D(10, 8); const MGPipeHandle handle = Textures().FindTexture(*texture); - texture->SetSwizzleParamRGBA(Vec4{TextureSwizzleParam::Alpha, - TextureSwizzleParam::Blue, - TextureSwizzleParam::Green, - TextureSwizzleParam::Red}); + texture->SetSwizzleParamRGBA(Vec4{TextureSwizzleParam::Alpha, TextureSwizzleParam::Blue, + TextureSwizzleParam::Green, TextureSwizzleParam::Red}); const MGPTextureParams params = Textures().LastParams(); EXPECT_TRUE(params.Res == handle); // kMGPipeNullHandle is ILLEGAL here: every texture object owns a sampler object, so a null @@ -883,8 +858,8 @@ TEST(TextureEmit, EveryTexturesParamsNameItsBuiltinSamplerCso) { EXPECT_GE(MGPipeSamplerCsoCacheInstance().RefCountOf(params.BuiltinSampler), 1u) << "the texture holds no reference on its built-in sampler, so an LRU eviction could take " "the handle out from under a standing set_texture_params record"; - EXPECT_TRUE(MGPipeHandleIsNull(MGPipeSlots().FindByLifetimeId( - MGPipeKind::SamplerCso, texture->GetSamplerObject()->GetLifetimeId()))) + EXPECT_TRUE(MGPipeHandleIsNull( + MGPipeSlots().FindByLifetimeId(MGPipeKind::SamplerCso, texture->GetSamplerObject()->GetLifetimeId()))) << "a SamplerCso slot was minted against the SamplerObject's lifetime id; the cache " "deliberately allocates without one, so ~SamplerObject frees nothing for this kind"; EXPECT_EQ(params.Swizzle[0], static_cast(TextureSwizzleParam::Alpha)); @@ -1037,18 +1012,15 @@ TEST(TextureEmit, TheApplierStoresTheRegionListTheEmitterBuiltAndNotAnEmptyOne) const MGPipeResourceRecord* stored = AppliedTexture(handle); ASSERT_NE(stored, nullptr); - const MGPipeResourceRecord::PendingUpload* pending = - AppliedUpload(*stored, Textures().LastSubData().Target, 0); + const MGPipeResourceRecord::PendingUpload* pending = AppliedUpload(*stored, Textures().LastSubData().Target, 0); ASSERT_NE(pending, nullptr) << "the applier accumulated no pending upload for the level"; - ASSERT_EQ(pending->Regions.size(), emitted.size()) - << "the applier's stored region count is not the emitted one"; + ASSERT_EQ(pending->Regions.size(), emitted.size()) << "the applier's stored region count is not the emitted one"; for (SizeT i = 0; i < pending->Regions.size(); ++i) { EXPECT_EQ(pending->Regions[i].X, emitted[i].X) << "MGPSubRegion::X at region " << i; EXPECT_EQ(pending->Regions[i].Y, emitted[i].Y) << "MGPSubRegion::Y at region " << i; EXPECT_EQ(pending->Regions[i].W, emitted[i].W) << "MGPSubRegion::W at region " << i; EXPECT_EQ(pending->Regions[i].H, emitted[i].H) << "MGPSubRegion::H at region " << i; - EXPECT_EQ(pending->Regions[i].SrcOffset, emitted[i].SrcOffset) - << "MGPSubRegion::SrcOffset at region " << i; + EXPECT_EQ(pending->Regions[i].SrcOffset, emitted[i].SrcOffset) << "MGPSubRegion::SrcOffset at region " << i; EXPECT_EQ(pending->Regions[i].SrcRowStride, emitted[i].SrcRowStride) << "MGPSubRegion::SrcRowStride at region " << i; EXPECT_EQ(pending->Regions[i].SrcSliceStride, emitted[i].SrcSliceStride) @@ -1126,8 +1098,7 @@ TEST(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheA // All four families, because all four ride the one signal (D-D1: a texture and a // renderbuffer ARE resource rows, and the other three name texture handles). - EXPECT_FALSE( - MGPipeP4aFamilyEmits(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)); + EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemTextureResources, kMGPipeWiredTextureSubsystem)); EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemFramebuffer, kMGPipeSubsystemFramebuffer)); EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemSamplers, kMGPipeSubsystemSamplers)); EXPECT_FALSE(MGPipeP4aFamilyEmits(kMGPipeSubsystemPrograms, kMGPipeSubsystemPrograms)); @@ -1154,8 +1125,7 @@ TEST(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheA EXPECT_EQ(Textures().RespecifyCount(), 0u); EXPECT_TRUE(MGPipeApplier().TextureResources.empty()); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{8, 8, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{8, 8, 1}); EXPECT_EQ(Textures().DrainListSize(), 0u) << "a level was queued for a drain that has no consumer"; EXPECT_EQ(Textures().SubDataCount(), 0u); // THE ONE THAT MATTERED. The frontend's flag is what Magma's legacy path uploads from, @@ -1176,8 +1146,7 @@ TEST(TextureEmit, WithNoBackendConsumerTheFamilyGateIsFalseAndNothingReachesTheA ASSERT_FALSE(MGPipeHandleIsNull(live)); EXPECT_TRUE(MGPipeHandleIsPublished(MGPipeKind::Texture, live)); EXPECT_GT(Textures().CreateCount(), 0u); - consumed->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{8, 8, 1}); + consumed->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{8, 8, 1}); EXPECT_EQ(Textures().DrainListSize(), 1u); Textures().DrainTextureSubData(Ctx()); EXPECT_EQ(Textures().SubDataCount(), 1u); @@ -1223,8 +1192,7 @@ TEST(TextureEmit, WithTheSamplerBitClearTheTextureFamilyGateIsFalseAndNothingRea EXPECT_EQ(Textures().RespecifyCount(), 0u); EXPECT_TRUE(MGPipeApplier().TextureResources.empty()); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{8, 8, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{8, 8, 1}); EXPECT_EQ(Textures().DrainListSize(), 0u); Textures().DrainTextureSubData(Ctx()); EXPECT_EQ(Textures().SubDataCount(), 0u); @@ -1244,8 +1212,7 @@ TEST(TextureEmit, WithTheSamplerBitClearTheTextureFamilyGateIsFalseAndNothingRea const MGPipeHandle liveHandle = Textures().FindTexture(*live); ASSERT_FALSE(MGPipeHandleIsNull(liveHandle)); EXPECT_TRUE(MGPipeHandleIsPublished(MGPipeKind::Texture, liveHandle)); - live->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{8, 8, 1}); + live->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{8, 8, 1}); EXPECT_EQ(Textures().DrainListSize(), 1u); Textures().DrainTextureSubData(Ctx()); EXPECT_EQ(Textures().SubDataCount(), 1u); @@ -1273,8 +1240,7 @@ TEST(TextureEmit, WithTheBufferResourceBitClearTheTextureFamilyGateIsFalseAndNot EXPECT_EQ(Textures().RespecifyCount(), 0u); EXPECT_TRUE(MGPipeApplier().TextureResources.empty()); - texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, - IntVec3{8, 8, 1}); + texture->MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, IntVec3{0, 0, 0}, IntVec3{8, 8, 1}); EXPECT_EQ(Textures().DrainListSize(), 0u); Textures().DrainTextureSubData(Ctx()); EXPECT_EQ(Textures().SubDataCount(), 0u); @@ -1298,9 +1264,8 @@ TEST(TextureEmit, WithTheBufferResourceBitClearTheTextureFamilyGateIsFalseAndNot TEST(TextureEmit, EveryDKTwoDependencyRowGatesItsOwnFamilyAndTheMirrorPairsStayLive) { TextureScope scope; const auto emits = [](Uint64 family) { return MGPipeP4aFamilyEmits(family, family); }; - const Uint64 kAll = kMGPipeSubsystemResources | kMGPipeSubsystemFramebuffer | - kMGPipeSubsystemTextureResources | kMGPipeSubsystemSamplers | - kMGPipeSubsystemPrograms; + const Uint64 kAll = kMGPipeSubsystemResources | kMGPipeSubsystemFramebuffer | kMGPipeSubsystemTextureResources | + kMGPipeSubsystemSamplers | kMGPipeSubsystemPrograms; // ---- every dependency satisfied: all four families live ---- MG_Config::Features.PipePush = kAll; @@ -1420,8 +1385,7 @@ TEST(TextureEmit, ALodWriteOnTheBuiltinSamplerRepublishesTheParams) { // NOTHING MOVED: the version-first skip reads both counters and emits nothing. MG_Pipe::MGPipeEmitTextureParams(*texture); - EXPECT_EQ(Textures().ParamCount(), paramsBefore) - << "an unchanged texture re-emitted its parameters"; + EXPECT_EQ(Textures().ParamCount(), paramsBefore) << "an unchanged texture re-emitted its parameters"; // A WRITE THAT ONLY THE SAMPLER OBJECT SEES. const Uint16 textureVersionBefore = texture->GetTextureParamsVersion(); @@ -1533,8 +1497,7 @@ TEST(TextureEmit, ALevelMarkedCleanIsCollectedAtTheNextDrain) { EXPECT_EQ(Textures().DrainListSize(), 1u) << "the entry is collected at the drain, not here"; const Uint64 emissionsBefore = Textures().SubDataCount(); Textures().DrainTextureSubData(Ctx()); - EXPECT_EQ(Textures().SubDataCount(), emissionsBefore) - << "a level that is no longer dirty was uploaded anyway"; + EXPECT_EQ(Textures().SubDataCount(), emissionsBefore) << "a level that is no longer dirty was uploaded anyway"; EXPECT_EQ(Textures().DrainListSize(), 0u) << "the clean level was never collected"; // And the key really was dropped from the per-slot list, so a later write re-appends. @@ -1578,7 +1541,8 @@ TEST(TextureEmit, ALevelDefinedAfterAnEmittedButUnconsumedUploadKeepsThatUpload) EXPECT_TRUE(levelZeroPending) << "defining level 1 dropped level 0's accepted-but-unconsumed pending upload (PendingUploads.size()=" << record->PendingUploads.size() << ") while level 0's client dirty flag is " - << (texture->IsStorageDirty(TextureUploadTarget::Texture2D, 0) ? "set" : "CLEAR - the texels are owed by nobody"); + << (texture->IsStorageDirty(TextureUploadTarget::Texture2D, 0) ? "set" + : "CLEAR - the texels are owed by nobody"); // And after the next drain both levels stand in the set. Textures().DrainTextureSubData(Ctx()); record = AppliedTexture(handle); @@ -1913,9 +1877,8 @@ TEST(TextureEmit, ATextureInASecondShareGroupNamesABuiltinSamplerItsOwnGroupHold const MGPipeHandle sampler = record->Params.BuiltinSampler; const auto& samplers = MGPipeApplier().SamplerCsos; ASSERT_FALSE(MGPipeHandleIsNull(sampler)); - ASSERT_LT(sampler.Slot, samplers.size()) - << "the texture names built-in sampler {" << sampler.Slot << ", " << sampler.Gen - << "}, which its share group's applier was never sent"; + ASSERT_LT(sampler.Slot, samplers.size()) << "the texture names built-in sampler {" << sampler.Slot << ", " + << sampler.Gen << "}, which its share group's applier was never sent"; EXPECT_TRUE(samplers[sampler.Slot].Live && samplers[sampler.Slot].Gen == sampler.Gen) << "the texture names built-in sampler {" << sampler.Slot << ", " << sampler.Gen << "}, which its share group's applier was never sent"; @@ -1927,7 +1890,6 @@ TEST(TextureEmit, ATextureInASecondShareGroupNamesABuiltinSamplerItsOwnGroupHold first.reset(); } - // ========================================================================================= // The APPLIER's half of the texture family (the wire commits'): set_texture_params on the // texture's own record, and the sub-data validator plus the pending-upload set that replaces @@ -2158,16 +2120,13 @@ TEST(TextureEmit, TheSubDataValidatorRefusesALevelABoxAndARegionTheRecordCannotD const MGPSubData escapes = TextureUpload(texture, 0, MGPBox{0, 0, 0, 8, 8, 1}, 1); ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: a sub-region is not inside the union " "box the record declares", - [&escapes, &texels, &outside]() { - MGPipeApplyResourceSubData(escapes, texels, outside); - }); + [&escapes, &texels, &outside]() { MGPipeApplyResourceSubData(escapes, texels, outside); }); const MGPSubData nothing = TextureUpload(texture, 0, MGPBox{0, 0, 0, 0, 0, 0}, 0); ExpectRefusedNaming("resource_subdata {slot=5, gen=2, glName=99}: the record describes no texels at all", [¬hing, &texels]() { MGPipeApplyResourceSubData(nothing, texels); }); - EXPECT_EQ(TextureRecordOf(5).PendingUploads.size(), 1u) - << "a refused record was accumulated anyway"; + EXPECT_EQ(TextureRecordOf(5).PendingUploads.size(), 1u) << "a refused record was accumulated anyway"; EXPECT_EQ(TextureRecordOf(5).Serial, serialBefore) << "not one refusal may move the serial"; } @@ -2188,8 +2147,7 @@ TEST(TextureEmit, ARespecifyDropsThePendingUploadsAgainstTheStorageItReplaces) { ASSERT_EQ(TextureRecordOf(3).PendingUploads.size(), 2u); MGPipeApplyResourceRespecify(TextureDesc(texture, 8, 55), nullptr); - EXPECT_TRUE(TextureRecordOf(3).PendingUploads.empty()) - << "a 64-wide box survived onto an 8-wide store"; + EXPECT_TRUE(TextureRecordOf(3).PendingUploads.empty()) << "a 64-wide box survived onto an 8-wide store"; EXPECT_EQ(TextureRecordOf(3).Desc.Width, 8u); } @@ -2301,10 +2259,10 @@ TEST(TextureEmit, ARespecifyOfOneCubeFaceKeepsTheOtherFacesUploadOfTheSameLevel) // The packed Target's two bytes (ID-12): low = the RESOURCE target, high = the per-call // UPLOAD target, which for a cube map is the FACE. Both faces below name the same resource // target and the same level and differ only in the face, which is the whole point. - const Uint16 positiveX = MGPipePackSubDataTarget( - kTexCube, static_cast(TextureUploadTarget::CubeMapPositiveX)); - const Uint16 negativeX = MGPipePackSubDataTarget( - kTexCube, static_cast(TextureUploadTarget::CubeMapNegativeX)); + const Uint16 positiveX = + MGPipePackSubDataTarget(kTexCube, static_cast(TextureUploadTarget::CubeMapPositiveX)); + const Uint16 negativeX = + MGPipePackSubDataTarget(kTexCube, static_cast(TextureUploadTarget::CubeMapNegativeX)); ASSERT_NE(positiveX, negativeX); ASSERT_EQ(MGPipeSubDataResourceTargetOf(positiveX), MGPipeSubDataResourceTargetOf(negativeX)) << "the two faces must differ in the UPLOAD byte alone, or this case would be measuring " @@ -2388,8 +2346,7 @@ TEST(TextureEmit, TheSubDataCallAnswersWhetherTheRecordWasAcceptedSoTheClientCan // A stale generation is the refusal that is NOT a Fatal, so it is the one the answer has to // carry: the record is gone, the texels were never taken, and the flag may not be cleared. const Uint64 refusedBefore = MGPipeApplier().RefusedResourceCalls; - EXPECT_FALSE( - MGPipeApplyResourceSubData(TextureUpload(MGPipeHandle{8, 2}, 0, MGPBox{0, 0, 0, 8, 8, 1}, 0), texels)) + EXPECT_FALSE(MGPipeApplyResourceSubData(TextureUpload(MGPipeHandle{8, 2}, 0, MGPBox{0, 0, 0, 8, 8, 1}, 0), texels)) << "a refused upload answered 'accepted' and the client would clear a flag nothing owes"; EXPECT_EQ(MGPipeApplier().RefusedResourceCalls, refusedBefore + 1); EXPECT_EQ(TextureRecordOf(8).PendingUploads.size(), 1u); @@ -2442,9 +2399,7 @@ TEST(TextureEmit, ASubDataRecordWhoseResourceTargetNamesNoTextureIsRefusedRather pastTheCatalogue.Target = static_cast(MGPipeResourceTarget::Count); ExpectRefusedNaming("resource_subdata {slot=9, gen=1}: the record's resource target names no texture " "to upload into", - [&pastTheCatalogue, &texels]() { - MGPipeApplyResourceSubData(pastTheCatalogue, texels); - }); + [&pastTheCatalogue, &texels]() { MGPipeApplyResourceSubData(pastTheCatalogue, texels); }); MGPSubData packedBuffer = renderbuffer; packedBuffer.Target = static_cast(0x0100u | kMGPipeResourceTargetBuffer); @@ -2508,7 +2463,7 @@ TEST(TextureEmit, ARespecifyThatRedefinesNoStorageCarriesTheStickyMaskAndKeepsTh // glTexStorage2D: an IMMUTABLE store, which is the whole reason this arm exists. MGPResourceDesc allocated = TextureDesc(texture, 64, 151); allocated.Immutable = 1; - allocated.Levels = 2; // level 1 exists for the named-level clause below + allocated.Levels = 2; // level 1 exists for the named-level clause below allocated.InternalFormat = 0x8058u; // GL_RGBA8 allocated.BindMask = static_cast(kMGPipeBindSampler); ASSERT_TRUE(MGPipeApplyResourceRespecify(allocated, nullptr)); diff --git a/MobileGL/MG_Test/Pipe/TrackerTest.cpp b/MobileGL/MG_Test/Pipe/TrackerTest.cpp index 5d53efae..c86ea556 100644 --- a/MobileGL/MG_Test/Pipe/TrackerTest.cpp +++ b/MobileGL/MG_Test/Pipe/TrackerTest.cpp @@ -71,7 +71,7 @@ namespace { } using GLContext = MG_State::GLState::GLContext; - + using MG_State::GLState::TextureObjectBase; using MobileGL::TextureTarget; @@ -128,7 +128,8 @@ namespace { TEST_F(TrackerAggregates, EveryAggregateStartsAtZero) { const Snapshot s = Snap(); - for (SizeT i = 0; i < kAggregateCount; ++i) EXPECT_EQ(s.Values[i], 0ull); + for (SizeT i = 0; i < kAggregateCount; ++i) + EXPECT_EQ(s.Values[i], 0ull); } TEST_F(TrackerAggregates, AVertexArrayAttributeMovesOnlyTheVaoAggregate) { @@ -295,12 +296,10 @@ namespace { ++m_binds; } if (dirty & MGPipeDirtyBit(MGPipeDirty::NewRenderState)) { - m_lastDynamicMask = m_tracker.FreshlyPrimed() - ? ~0u - : MGPipeDynamicChunksThatMoved(live, m_tracker.Staged()); + m_lastDynamicMask = + m_tracker.FreshlyPrimed() ? ~0u : MGPipeDynamicChunksThatMoved(live, m_tracker.Staged()); } - if (dirty & (MGPipeDirtyBit(MGPipeDirty::NewPipelineState) | - MGPipeDirtyBit(MGPipeDirty::NewRenderState))) { + if (dirty & (MGPipeDirtyBit(MGPipeDirty::NewPipelineState) | MGPipeDirtyBit(MGPipeDirty::NewRenderState))) { m_tracker.Staged() = live; } return dirty; @@ -356,22 +355,17 @@ namespace { // escalation, docs/archive/contracts/CONTRACT-P5E.md §5.7) and still names the subsystem, for the // same reason: the bit answers "which A/B switch owns this family's legacy arm", not // "is there a record on the wire for me today". - EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewConstBuffers), - kMGPipeSubsystemBufferBindings); - EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers), - kMGPipeSubsystemBufferBindings); - EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewSoTargets), - kMGPipeSubsystemBufferBindings); + EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewConstBuffers), kMGPipeSubsystemBufferBindings); + EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewShaderBuffers), kMGPipeSubsystemBufferBindings); + EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewSoTargets), kMGPipeSubsystemBufferBindings); // AND THE PHASE CONSTANT IS NOW THE WHOLE BIT SET: after P5e every dirty bit names a // subsystem, so the loop above has no "names none" case left to exercise, and this is // what says so rather than leaving the loop looking stronger than it is. - EXPECT_EQ(kMGPipeDirtyEmittedAtP5e, - static_cast((Uint64{1} << kMGPipeDirtyCount) - 1u)); + EXPECT_EQ(kMGPipeDirtyEmittedAtP5e, static_cast((Uint64{1} << kMGPipeDirtyCount) - 1u)); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewRenderState), kMGPipeSubsystemRenderState); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPixelPack), kMGPipeSubsystemPixelPack); EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewPatchState), kMGPipeSubsystemPatchState); - EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewVertexAttribDefaults), - kMGPipeSubsystemVertexAttribDefaults); + EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewVertexAttribDefaults), kMGPipeSubsystemVertexAttribDefaults); // P3a's three, one subsystem: an operator who clears bit 8 gets the whole legacy // vertex-input arm rather than two thirds of it. EXPECT_EQ(MGPipeSubsystemForDirty(MGPipeDirty::NewVertexElements), kMGPipeSubsystemVertexInput); @@ -393,8 +387,7 @@ namespace { // that started naming it would gate the emission twice and the two gates would // disagree the first time one of them was edited. for (SizeT i = 0; i < kMGPipeDirtyCount; ++i) { - EXPECT_NE(MGPipeSubsystemForDirty(static_cast(i)), - kMGPipeSubsystemTextureResources) + EXPECT_NE(MGPipeSubsystemForDirty(static_cast(i)), kMGPipeSubsystemTextureResources) << kMGPipeDirtyNames[i]; } } @@ -414,7 +407,8 @@ namespace { Walk(); const Uint64 mintsAfterFirst = m_cache.GetCounters().Mints; const Uint64 bindsAfterFirst = m_binds; - for (int i = 0; i < 8; ++i) EXPECT_EQ(Walk(), 0u) << "walk " << i << " fired with nothing moved"; + for (int i = 0; i < 8; ++i) + EXPECT_EQ(Walk(), 0u) << "walk " << i << " fired with nothing moved"; EXPECT_EQ(m_cache.GetCounters().Mints, mintsAfterFirst); EXPECT_EQ(m_binds, bindsAfterFirst); } @@ -466,7 +460,7 @@ namespace { // RenderState's two shutters are Uint16 and the tracker widens them in its OWN state, // never in MG_State. A wrap must cost an extra re-push at worst and never a missed one. TEST_F(TrackerWalk, WrapAroundRePushesButNeverMisses) { - Walk(); // prime + Walk(); // prime constexpr int kMoves = 70000; // past 65535 with room to spare Uint64 fired = 0; for (int i = 0; i < kMoves; ++i) { @@ -475,8 +469,7 @@ namespace { Ctx().SetLineWidth((i & 1) ? 2.0f : 3.0f); if (Walk() & MGPipeDirtyBit(MGPipeDirty::NewRenderState)) ++fired; } - EXPECT_EQ(fired, static_cast(kMoves)) - << "a Uint16 wrap swallowed a render-state change"; + EXPECT_EQ(fired, static_cast(kMoves)) << "a Uint16 wrap swallowed a render-state change"; m_cache.Reset(); } @@ -502,8 +495,7 @@ namespace { // itself, which float equality does not do and a byte compare does. TEST_F(TrackerWalk, ANaNPatchLevelEqualsItselfAndDoesNotFireForever) { Walk(); // prime - Ctx().SetPatchDefaultOuterLevel( - FloatVec4(std::numeric_limits::quiet_NaN(), 1.0f, 1.0f, 1.0f)); + Ctx().SetPatchDefaultOuterLevel(FloatVec4(std::numeric_limits::quiet_NaN(), 1.0f, 1.0f, 1.0f)); EXPECT_NE(Walk() & MGPipeDirtyBit(MGPipeDirty::NewPatchState), 0u); EXPECT_EQ(Walk() & MGPipeDirtyBit(MGPipeDirty::NewPatchState), 0u) << "a NaN patch level re-fired against itself"; @@ -518,8 +510,7 @@ namespace { EXPECT_EQ(Walk() & MGPipeDirtyBit(MGPipeDirty::NewPixelPack), 0u); // The UNPACK half has no carrier at all, so it must not move the pack shutter. Ctx().SetPixelStoreParam(PixelStoreParam::UnpackAlignment, 8); - EXPECT_EQ(Walk() & MGPipeDirtyBit(MGPipeDirty::NewPixelPack), 0u) - << "an unpack write moved the PACK shutter"; + EXPECT_EQ(Walk() & MGPipeDirtyBit(MGPipeDirty::NewPixelPack), 0u) << "an unpack write moved the PACK shutter"; m_cache.Reset(); } @@ -600,8 +591,7 @@ namespace { Walk(); Walk(); const Uint32 family = MGPipeDirtyBit(MGPipeDirty::NewConstBuffers) | - MGPipeDirtyBit(MGPipeDirty::NewShaderBuffers) | - MGPipeDirtyBit(MGPipeDirty::NewSoTargets); + MGPipeDirtyBit(MGPipeDirty::NewShaderBuffers) | MGPipeDirtyBit(MGPipeDirty::NewSoTargets); ASSERT_EQ(m_tracker.LastDirty() & family, 0u) << "the steady state must be quiet before the interesting half of this case"; @@ -656,7 +646,8 @@ namespace { TEST_F(TrackerWalk, TheIndexBufferBitFiresWhenTheSlotVersionWrapsOntoADifferentBuffer) { const SharedPtr objects[3] = { Ctx().CreateBufferObject(1), Ctx().CreateBufferObject(2), Ctx().CreateBufferObject(3)}; - for (const auto& object : objects) object->Respecify(64, nullptr); + for (const auto& object : objects) + object->Respecify(64, nullptr); auto& slot = Ctx().GetBoundVertexArray()->GetIndexBufferBindingSlot(); slot.Bind(objects[0]); @@ -673,7 +664,8 @@ namespace { // the version and the bound object share a parity - 65536 changes always land back on // the object they started from, and the wrap is unobservable. Cycling three lands on // objects[65536 % 3] == objects[1] at exactly the same raw version. - for (Uint32 i = 1; i <= 65536u; ++i) slot.Bind(objects[i % 3]); + for (Uint32 i = 1; i <= 65536u; ++i) + slot.Bind(objects[i % 3]); ASSERT_EQ(slot.GetVersion(), versionAtStart) << "the version did not come back round"; ASSERT_EQ(slot.GetBoundObject(), objects[1]) << "the binding did not land on a different buffer"; @@ -962,8 +954,7 @@ namespace { // frontend's converted int view travels too - 1.5f's int VIEW is 1, and the carrier // that used to send the float bits into all three views would be sending 0x3FC00000 // where the frontend holds 1. - EXPECT_EQ(value.IntView[0], - static_cast(Ctx().GetCurrentVertexAttribute(3).intValue[0])); + EXPECT_EQ(value.IntView[0], static_cast(Ctx().GetCurrentVertexAttribute(3).intValue[0])); EXPECT_NE(value.FloatView[0], value.IntView[0]); } @@ -1039,16 +1030,16 @@ namespace { }; TEST_F(TrackerShippedEmitter, CollidingVaoShuttersStillPublishTheCurrentElementsAndBufferWindow) { - MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP2 | kMGPipeSubsystemResources | - kMGPipeSubsystemVertexInput; + MG_Config::Features.PipePush = + kMGPipeSubsystemsMigratedAtP2 | kMGPipeSubsystemResources | kMGPipeSubsystemVertexInput; using MG_State::GLState::VertexArrayObject; const SharedPtr a = Ctx().CreateVertexArrayObject(1); const SharedPtr b = Ctx().CreateVertexArrayObject(2); const auto buffer = Ctx().CreateBufferObject(1); buffer->Respecify(256, nullptr); for (Uint location : {0u, 1u}) { - a->SetAttributeFormat(location, location == 0 ? 3 : 2, DataType::Float32, false, 20, - location == 0 ? 0 : 12, false); + a->SetAttributeFormat(location, location == 0 ? 3 : 2, DataType::Float32, false, 20, location == 0 ? 0 : 12, + false); a->BindAttributeBuffer(location, buffer); a->EnableAttribute(location); } @@ -1070,8 +1061,8 @@ namespace { const Uint32 firstB = b->GetConfigVersion() + 1; for (Uint32 candidate = firstA; candidate < firstA + 4096; ++candidate) { const Uint64 hash = oldMix(a->GetLifetimeId(), candidate); - const Uint64 other = (hash ^ b->GetLifetimeId()) - kCombine - - (b->GetLifetimeId() << 6) - (b->GetLifetimeId() >> 2); + const Uint64 other = + (hash ^ b->GetLifetimeId()) - kCombine - (b->GetLifetimeId() << 6) - (b->GetLifetimeId() >> 2); if (other < firstB || other >= firstB + 4096) continue; versionA = candidate; versionB = static_cast(other); @@ -1082,8 +1073,7 @@ namespace { // Change a disabled attribute, leaving the enabled 0/1 versus 0 // windows intact. No private lifetime/version field is overwritten. while (vao.GetConfigVersion() < version) { - vao.SetAttributeFormat(31, 4, DataType::Float32, !vao.GetAttribute(31).Normalized, - 16, 0, false); + vao.SetAttributeFormat(31, 4, DataType::Float32, !vao.GetAttribute(31).Normalized, 16, 0, false); } }; advanceConfig(*a, versionA); @@ -1099,8 +1089,7 @@ namespace { // shutter leaves A bound with attribute 1 still enabled: the phone's // exact "buffer-window location=1 window=0+1" mismatch. advanceConfig(*b, versionB); - ASSERT_EQ(oldMix(a->GetLifetimeId(), a->GetConfigVersion()), - oldMix(b->GetLifetimeId(), b->GetConfigVersion())); + ASSERT_EQ(oldMix(a->GetLifetimeId(), a->GetConfigVersion()), oldMix(b->GetLifetimeId(), b->GetConfigVersion())); Ctx().BindVertexArray(2); Draw(); const MGPipeHandle handleB = MGPipeApplier().BoundVertexElements; @@ -1225,9 +1214,8 @@ namespace { MG_State::pGLContext = MakeUnique(); Draw(); const MGPVertexAttribDefaults header = MGPipeVertexAttribDefaultsLastHeader(); - EXPECT_EQ(header.Count, 32u) - << "a fresh context published " << header.Count - << " attribute defaults; the server's mirror still holds the previous context's"; + EXPECT_EQ(header.Count, 32u) << "a fresh context published " << header.Count + << " attribute defaults; the server's mirror still holds the previous context's"; EXPECT_EQ(header.Mask, 0xFFFFFFFFu); } @@ -1273,8 +1261,8 @@ namespace { // BaseInstance in, or the SECOND draw at a different base instance over the same buffer // set would be suppressed as unchanged and the server would keep the first one's shift. TEST_F(TrackerShippedEmitter, ABaseInstancedDrawAfterAMakeCurrentPublishesItsOwnBaseInstance) { - MG_Config::Features.PipePush = kMGPipeSubsystemsMigratedAtP2 | kMGPipeSubsystemResources | - kMGPipeSubsystemVertexInput; + MG_Config::Features.PipePush = + kMGPipeSubsystemsMigratedAtP2 | kMGPipeSubsystemResources | kMGPipeSubsystemVertexInput; Draw(); // prime this context, so the switch below is a real make-current ASSERT_EQ(MGPipeApplier().VertexFetchBaseInstance, 0u); @@ -1296,8 +1284,7 @@ namespace { EXPECT_EQ(MGPipeApplier().VertexBufferCount, 1u); const Uint64 hashAtSeven = MGPipeVertexInputEmitterInstance().LastVertexBuffers().ContentHash; EXPECT_EQ(hashAtSeven, - MGPipeVertexBufferSetContentHash(MGPipeVertexInputEmitterInstance().LastEntries().data(), 0, - 1, 7)) + MGPipeVertexBufferSetContentHash(MGPipeVertexInputEmitterInstance().LastEntries().data(), 0, 1, 7)) << "the emitted set's ContentHash does not include the base instance it went out with"; // CONSUMED by the verb that carried it: the next plain draw sees 0 again, and the diff --git a/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp b/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp index fd4cdf18..3ecf257c 100644 --- a/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp +++ b/MobileGL/MG_Test/Pipe/VertexInputEmitTest.cpp @@ -151,8 +151,12 @@ namespace { SharedPtr m_previousContext; }; - GLContext& Ctx() { return *MG_State::pGLContext; } - MGPipeVertexInputEmitter& Emitter() { return MGPipeVertexInputEmitterInstance(); } + GLContext& Ctx() { + return *MG_State::pGLContext; + } + MGPipeVertexInputEmitter& Emitter() { + return MGPipeVertexInputEmitterInstance(); + } const SharedPtr& MakeVao(Uint name) { Ctx().CreateVertexArrayObject(name); @@ -423,14 +427,12 @@ namespace { ASSERT_GT(Emitter().EmitVertexBuffers(Ctx(), 3), 0u); ASSERT_EQ(Emitter().VertexBufferSetCount(), 1u); - const Uint64 latched = - MGPipeSetHashSuppressorInstance().LastEmitted(MGPipeSuppressorSlot::SetVertexBuffers); + const Uint64 latched = MGPipeSetHashSuppressorInstance().LastEmitted(MGPipeSuppressorSlot::SetVertexBuffers); EXPECT_NE(latched, 0u) << "0 is reserved for 'never emitted'"; EXPECT_EQ(Emitter().EmitVertexBuffers(Ctx(), 3), 0u) << "an unchanged set went out again"; EXPECT_EQ(Emitter().VertexBufferSetCount(), 1u); - EXPECT_EQ(MGPipeSetHashSuppressorInstance().LastEmitted(MGPipeSuppressorSlot::SetVertexBuffers), - latched); + EXPECT_EQ(MGPipeSetHashSuppressorInstance().LastEmitted(MGPipeSuppressorSlot::SetVertexBuffers), latched); // A real change to the SET still goes out with the same base instance, so the // suppression above is not simply "this slot is stuck". @@ -487,20 +489,18 @@ namespace { if (!declared[i].Enabled) continue; ++covered; SCOPED_TRACE(::testing::Message() << "attribute " << i); - EXPECT_GE(i, window.Start) - << "create_vertex_elements declares attribute " << i - << " ENABLED but set_vertex_buffers' window starts at " << window.Start - << ". The backend resolves no binding for it and SKIPS it, leaving the driver " - "array enabled over whatever it last pointed at (ID-95 / ruling 19)"; + EXPECT_GE(i, window.Start) << "create_vertex_elements declares attribute " << i + << " ENABLED but set_vertex_buffers' window starts at " << window.Start + << ". The backend resolves no binding for it and SKIPS it, leaving the driver " + "array enabled over whatever it last pointed at (ID-95 / ruling 19)"; EXPECT_LT(i, window.Start + window.Count) << "create_vertex_elements declares attribute " << i << " ENABLED but set_vertex_buffers' window ends at " << (window.Start + window.Count) << " - the same skip, at the other end. A narrower window is the sink's " "Fatal{ProtocolCorruption, \"SetVertexBuffers.Count\"}"; } - ASSERT_EQ(covered, 2u) - << "the record did not declare the two attributes this case enabled, so the window " - "check above had nothing to be about"; + ASSERT_EQ(covered, 2u) << "the record did not declare the two attributes this case enabled, so the window " + "check above had nothing to be about"; // And the window's own entries agree with the record about WHICH attribute each is, so // "covered" cannot be satisfied by a window of the right LENGTH over the wrong indices. @@ -696,10 +696,11 @@ namespace { } EXPECT_EQ(slots.LiveCount(MGPipeKind::VertexElementsCso), liveBefore) - << kChurn << " vertex arrays were created and destroyed and the client kept their " - "CSO slots; under a backend that installs no death notice consumer " - "that is one SlotState, one map node and a ~1.3 KB applier record per " - "VAO, for the life of the process"; + << kChurn + << " vertex arrays were created and destroyed and the client kept their " + "CSO slots; under a backend that installs no death notice consumer " + "that is one SlotState, one map node and a ~1.3 KB applier record per " + "VAO, for the life of the process"; // The default VAO plus one recycled slot, i.e. the churn recycles instead of growing. EXPECT_LE(slots.HighWater(MGPipeKind::VertexElementsCso) - highWaterBefore, 3u) << "the CSO slot space grew with the churn instead of being recycled"; diff --git a/MobileGL/MG_Test/Pipeline/DriverModuleScreenTest.cpp b/MobileGL/MG_Test/Pipeline/DriverModuleScreenTest.cpp index 81064d1a..2721749c 100644 --- a/MobileGL/MG_Test/Pipeline/DriverModuleScreenTest.cpp +++ b/MobileGL/MG_Test/Pipeline/DriverModuleScreenTest.cpp @@ -39,38 +39,36 @@ namespace { // Shared by both modules: a fragment shader whose default uniform block holds a vec4 and, // in the invalid one, a sampler2D next to it. String UniformBlockFragment(Bool withSampler) { - String text = - "OpCapability Shader\n" - "OpMemoryModel Logical GLSL450\n" - "OpEntryPoint Fragment %main \"main\" %out\n" - "OpExecutionMode %main OriginUpperLeft\n" - "OpDecorate %out Location 0\n" - "OpDecorate %Block Block\n" - "OpMemberDecorate %Block 0 Offset 0\n" - "OpDecorate %ubo DescriptorSet 0\n" - "OpDecorate %ubo Binding 0\n" - "%void = OpTypeVoid\n" - "%fn = OpTypeFunction %void\n" - "%float = OpTypeFloat 32\n" - "%v4 = OpTypeVector %float 4\n" - "%int = OpTypeInt 32 1\n" - "%zero = OpConstant %int 0\n" - "%img = OpTypeImage %float 2D 0 0 0 1 Unknown\n" - "%simg = OpTypeSampledImage %img\n"; + String text = "OpCapability Shader\n" + "OpMemoryModel Logical GLSL450\n" + "OpEntryPoint Fragment %main \"main\" %out\n" + "OpExecutionMode %main OriginUpperLeft\n" + "OpDecorate %out Location 0\n" + "OpDecorate %Block Block\n" + "OpMemberDecorate %Block 0 Offset 0\n" + "OpDecorate %ubo DescriptorSet 0\n" + "OpDecorate %ubo Binding 0\n" + "%void = OpTypeVoid\n" + "%fn = OpTypeFunction %void\n" + "%float = OpTypeFloat 32\n" + "%v4 = OpTypeVector %float 4\n" + "%int = OpTypeInt 32 1\n" + "%zero = OpConstant %int 0\n" + "%img = OpTypeImage %float 2D 0 0 0 1 Unknown\n" + "%simg = OpTypeSampledImage %img\n"; text += withSampler ? "%Block = OpTypeStruct %v4 %simg\n" : "%Block = OpTypeStruct %v4\n"; - text += - "%pBlock = OpTypePointer Uniform %Block\n" - "%ubo = OpVariable %pBlock Uniform\n" - "%pv4u = OpTypePointer Uniform %v4\n" - "%pv4o = OpTypePointer Output %v4\n" - "%out = OpVariable %pv4o Output\n" - "%main = OpFunction %void None %fn\n" - "%entry = OpLabel\n" - "%ptr = OpAccessChain %pv4u %ubo %zero\n" - "%color = OpLoad %v4 %ptr\n" - "OpStore %out %color\n" - "OpReturn\n" - "OpFunctionEnd\n"; + text += "%pBlock = OpTypePointer Uniform %Block\n" + "%ubo = OpVariable %pBlock Uniform\n" + "%pv4u = OpTypePointer Uniform %v4\n" + "%pv4o = OpTypePointer Output %v4\n" + "%out = OpVariable %pv4o Output\n" + "%main = OpFunction %void None %fn\n" + "%entry = OpLabel\n" + "%ptr = OpAccessChain %pv4u %ubo %zero\n" + "%color = OpLoad %v4 %ptr\n" + "OpStore %out %color\n" + "OpReturn\n" + "OpFunctionEnd\n"; return text; } diff --git a/MobileGL/MG_Test/Pipeline/PassthroughTessControlTest.cpp b/MobileGL/MG_Test/Pipeline/PassthroughTessControlTest.cpp index 2f1d6a31..467b044a 100644 --- a/MobileGL/MG_Test/Pipeline/PassthroughTessControlTest.cpp +++ b/MobileGL/MG_Test/Pipeline/PassthroughTessControlTest.cpp @@ -120,8 +120,7 @@ namespace { EXPECT_TRUE(programResult) << (programResult ? String{} : programResult.error().log); if (!programResult) return {}; - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_TESS_CONTROL_SHADER}, - .program = *programResult.value()}; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_TESS_CONTROL_SHADER}, .program = *programResult.value()}; auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); EXPECT_TRUE(binaryResult) << (binaryResult ? String{} : binaryResult.error().log); if (!binaryResult || binaryResult->empty()) return {}; @@ -143,8 +142,7 @@ TEST_F(PassthroughTessControlTest, DeclaresTheRequestedPatchSize) { for (const Uint32 patchVertices : {1u, 2u, 3u, 4u, 16u, 32u}) { const Vector spirv = CompileGeneratedSource(patchVertices); ASSERT_FALSE(spirv.empty()) << "patchVertices=" << patchVertices; - EXPECT_EQ(DeclaredOutputVertices(spirv), static_cast(patchVertices)) - << "patchVertices=" << patchVertices; + EXPECT_EQ(DeclaredOutputVertices(spirv), static_cast(patchVertices)) << "patchVertices=" << patchVertices; } } @@ -167,8 +165,8 @@ TEST_F(PassthroughTessControlTest, ForwardsPositionAndWritesBothLevelArrays) { // user-defined varying, ReflectPassthroughTessControlNeed's "built-ins only" refusal stops being // the right gate and both have to move together. TEST_F(PassthroughTessControlTest, InterfaceIsBuiltInsOnly) { - const String source = ProgramFactory::BuildPassthroughTessControlSource( - 4, kDefaultOuter, kDefaultInner, ProgramFactory::kDefaultPerVertexMembers); + const String source = ProgramFactory::BuildPassthroughTessControlSource(4, kDefaultOuter, kDefaultInner, + ProgramFactory::kDefaultPerVertexMembers); EXPECT_EQ(source.find("layout(location"), String::npos) << source; EXPECT_NE(source.find("layout(vertices = 4) out;"), String::npos) << source; } @@ -181,8 +179,8 @@ TEST_F(PassthroughTessControlTest, BakesTheDefaultTessLevelsInAndKeysOnThem) { constexpr Uint32 kMembers = ProgramFactory::kDefaultPerVertexMembers; const FloatVec4 outer(2.0f, 3.0f, 4.0f, 5.0f); const FloatVec2 inner(6.5f, 7.25f); - const String source = ProgramFactory::BuildPassthroughTessControlSource(4, outer, inner, - ProgramFactory::kDefaultPerVertexMembers); + const String source = + ProgramFactory::BuildPassthroughTessControlSource(4, outer, inner, ProgramFactory::kDefaultPerVertexMembers); EXPECT_NE(source.find("gl_TessLevelOuter[0] = 2.0;"), String::npos) << source; EXPECT_NE(source.find("gl_TessLevelOuter[3] = 5.0;"), String::npos) << source; EXPECT_NE(source.find("gl_TessLevelInner[0] = 6.5;"), String::npos) << source; @@ -197,9 +195,8 @@ TEST_F(PassthroughTessControlTest, BakesTheDefaultTessLevelsInAndKeysOnThem) { // ...and the gl_PerVertex member set is in the same key, for the same reason: two programs at // different GLSL versions need differently-shaped modules, and a pipeline memoised against one // shape must not be handed back for the other. - constexpr Uint32 kMembersWithCull = - ProgramFactory::kDefaultPerVertexMembers | - static_cast(ProgramFactory::PerVertexMemberBit::CullDistance); + constexpr Uint32 kMembersWithCull = ProgramFactory::kDefaultPerVertexMembers | + static_cast(ProgramFactory::PerVertexMemberBit::CullDistance); EXPECT_NE(ProgramFactory::ComputePassthroughTessControlKey(4, kDefaultOuter, kDefaultInner, kMembersWithCull), defaultKey); } @@ -208,10 +205,8 @@ TEST_F(PassthroughTessControlTest, BakesTheDefaultTessLevelsInAndKeysOnThem) { // without a decimal point is an int literal, and `gl_TessLevelOuter[0] = 2;` does not compile. TEST_F(PassthroughTessControlTest, CompilesWithNonDefaultLevels) { using namespace MG_Util::ShaderTranspiler; - const String source = - ProgramFactory::BuildPassthroughTessControlSource(4, FloatVec4(2.0f, 2.0f, 2.0f, 2.0f), - FloatVec2(2.0f, 2.0f), - ProgramFactory::kDefaultPerVertexMembers); + const String source = ProgramFactory::BuildPassthroughTessControlSource( + 4, FloatVec4(2.0f, 2.0f, 2.0f, 2.0f), FloatVec2(2.0f, 2.0f), ProgramFactory::kDefaultPerVertexMembers); ShaderAttrib shaderAttrib{.shaderType = GL_TESS_CONTROL_SHADER, .sourceStr = source}; auto shaderResult = ShaderCompiler::CompileShader(shaderAttrib); EXPECT_TRUE(shaderResult) << (shaderResult ? String{} : shaderResult.error().log) << source; diff --git a/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp b/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp index 164899d6..04cf6bbe 100644 --- a/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp +++ b/MobileGL/MG_Test/Pipeline/PipelineQuirkTest.cpp @@ -28,18 +28,15 @@ namespace { constexpr Uint32 kVendorIdArm = 0x13B5; constexpr VkColorComponentFlags kFullColorWriteMask = - VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | - VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; // Builds non-separate blend state: the alpha channel repeats the color factors/op, which // is what glBlendFunc/glBlendEquation (as opposed to their *Separate forms) produce. // ShouldSuppressDepthWrite deliberately decides on the color channel alone, so these // cases cover its whole input space; SeparateAlphaAccumulationIsNotStripped below pins // the separate-alpha contract. - VkPipelineColorBlendAttachmentState MakeBlendAttachment(Bool blendEnable, - VkBlendFactor srcColor, - VkBlendFactor dstColor, - VkBlendOp colorOp, + VkPipelineColorBlendAttachmentState MakeBlendAttachment(Bool blendEnable, VkBlendFactor srcColor, + VkBlendFactor dstColor, VkBlendOp colorOp, VkColorComponentFlags colorWriteMask) { VkPipelineColorBlendAttachmentState attachment{}; attachment.blendEnable = blendEnable ? VK_TRUE : VK_FALSE; @@ -59,130 +56,103 @@ namespace { // void main() { outColor = vec4(1.0); gl_FragDepth = 0.5; } // Assigning gl_FragDepth makes glslang emit OpExecutionMode ... DepthReplacing. constexpr Uint32 kFragDepthWriterSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000000fu, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000004u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x0000000du, 0x00030010u, - 0x00000004u, 0x00000007u, 0x00030010u, 0x00000004u, 0x0000000cu, 0x00030003u, - 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00050005u, 0x00000009u, 0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00060005u, - 0x0000000du, 0x465f6c67u, 0x44676172u, 0x68747065u, 0x00000000u, 0x00040047u, - 0x00000009u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000000du, 0x0000000bu, - 0x00000016u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, - 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, - 0x00000004u, 0x00040020u, 0x00000008u, 0x00000003u, 0x00000007u, 0x0004003bu, - 0x00000008u, 0x00000009u, 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000000au, - 0x3f800000u, 0x0007002cu, 0x00000007u, 0x0000000bu, 0x0000000au, 0x0000000au, - 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x00000006u, - 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x0004002bu, 0x00000006u, - 0x0000000eu, 0x3f000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, - 0x00000003u, 0x000200f8u, 0x00000005u, 0x0003003eu, 0x00000009u, 0x0000000bu, - 0x0003003eu, 0x0000000du, 0x0000000eu, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000000fu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0007000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x0000000du, 0x00030010u, + 0x00000004u, 0x00000007u, 0x00030010u, 0x00000004u, 0x0000000cu, 0x00030003u, 0x00000002u, 0x000001c2u, + 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00050005u, 0x00000009u, 0x4374756fu, 0x726f6c6fu, + 0x00000000u, 0x00060005u, 0x0000000du, 0x465f6c67u, 0x44676172u, 0x68747065u, 0x00000000u, 0x00040047u, + 0x00000009u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x0000000du, 0x0000000bu, 0x00000016u, 0x00020013u, + 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, + 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, 0x00000008u, 0x00000003u, 0x00000007u, 0x0004003bu, + 0x00000008u, 0x00000009u, 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000000au, 0x3f800000u, 0x0007002cu, + 0x00000007u, 0x0000000bu, 0x0000000au, 0x0000000au, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, + 0x00000003u, 0x00000006u, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x0004002bu, 0x00000006u, + 0x0000000eu, 0x3f000000u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, + 0x00000005u, 0x0003003eu, 0x00000009u, 0x0000000bu, 0x0003003eu, 0x0000000du, 0x0000000eu, 0x000100fdu, + 0x00010038u, }; // Same shader without the gl_FragDepth assignment. constexpr Uint32 kPlainFragmentSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000000cu, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0006000fu, 0x00000004u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x00030010u, 0x00000004u, - 0x00000007u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, - 0x6e69616du, 0x00000000u, 0x00050005u, 0x00000009u, 0x4374756fu, 0x726f6c6fu, - 0x00000000u, 0x00040047u, 0x00000009u, 0x0000001eu, 0x00000000u, 0x00020013u, - 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, - 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, - 0x00000008u, 0x00000003u, 0x00000007u, 0x0004003bu, 0x00000008u, 0x00000009u, - 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000000au, 0x3f800000u, 0x0007002cu, - 0x00000007u, 0x0000000bu, 0x0000000au, 0x0000000au, 0x0000000au, 0x0000000au, - 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, - 0x00000005u, 0x0003003eu, 0x00000009u, 0x0000000bu, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000000cu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0006000fu, 0x00000004u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00000009u, 0x00030010u, 0x00000004u, + 0x00000007u, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, + 0x00050005u, 0x00000009u, 0x4374756fu, 0x726f6c6fu, 0x00000000u, 0x00040047u, 0x00000009u, 0x0000001eu, + 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, + 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040020u, 0x00000008u, 0x00000003u, + 0x00000007u, 0x0004003bu, 0x00000008u, 0x00000009u, 0x00000003u, 0x0004002bu, 0x00000006u, 0x0000000au, + 0x3f800000u, 0x0007002cu, 0x00000007u, 0x0000000bu, 0x0000000au, 0x0000000au, 0x0000000au, 0x0000000au, + 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0003003eu, + 0x00000009u, 0x0000000bu, 0x000100fdu, 0x00010038u, }; - // glslangValidator -V output for a vertex shader reading gl_InstanceIndex: // #version 450 // layout(location = 0) in vec4 inPos; // void main() { gl_Position = inPos + vec4(float(gl_InstanceIndex)); } constexpr Uint32 kInstanceIndexVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u, - 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, - 0x00000000u, 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, - 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, - 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, - 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, - 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, - 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, - 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00070005u, - 0x00000014u, 0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x7865646eu, 0x00000000u, - 0x00030047u, 0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, - 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, - 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, - 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, - 0x00000011u, 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x0000000bu, - 0x0000002bu, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, - 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, - 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, - 0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, - 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, - 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, - 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, - 0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, - 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, - 0x00000001u, 0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu, - 0x00000013u, 0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u, - 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, - 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, - 0x0004003du, 0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u, - 0x00000016u, 0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u, - 0x00000016u, 0x00000016u, 0x00000016u, 0x00050081u, 0x00000007u, 0x00000018u, - 0x00000012u, 0x00000017u, 0x00050041u, 0x00000019u, 0x0000001au, 0x0000000du, - 0x0000000fu, 0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u, + 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, + 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, + 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, + 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, + 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, + 0x00030005u, 0x0000000du, 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00070005u, + 0x00000014u, 0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x7865646eu, 0x00000000u, 0x00030047u, 0x0000000bu, + 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, + 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, + 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, + 0x00000000u, 0x00040047u, 0x00000014u, 0x0000000bu, 0x0000002bu, 0x00020013u, 0x00000002u, 0x00030021u, + 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, + 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, + 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, + 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, + 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu, + 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, + 0x00000010u, 0x00000011u, 0x00000001u, 0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu, + 0x00000013u, 0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u, 0x00000007u, 0x00050036u, + 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, + 0x00000012u, 0x00000011u, 0x0004003du, 0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u, + 0x00000016u, 0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u, 0x00000016u, 0x00000016u, + 0x00000016u, 0x00050081u, 0x00000007u, 0x00000018u, 0x00000012u, 0x00000017u, 0x00050041u, 0x00000019u, + 0x0000001au, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u, }; - // Same, but reading gl_VertexIndex instead: a DIFFERENT input builtin. glslang emits // this for GL's gl_VertexID, so nearly every real vertex shader has one - it is what // separates "declares some builtin" from "declares the InstanceIndex builtin". constexpr Uint32 kVertexIndexVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0008000fu, 0x00000000u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u, - 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, - 0x00000000u, 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, - 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, - 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, - 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, - 0x4470696cu, 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, - 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, - 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00060005u, - 0x00000014u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00030047u, - 0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, - 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, - 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, - 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, - 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x0000000bu, 0x0000002au, - 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, - 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, - 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, - 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, - 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, - 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, - 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, - 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, - 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, - 0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x00000013u, - 0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u, 0x00000007u, - 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, - 0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, 0x0004003du, - 0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u, 0x00000016u, - 0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u, 0x00000016u, - 0x00000016u, 0x00000016u, 0x00050081u, 0x00000007u, 0x00000018u, 0x00000012u, - 0x00000017u, 0x00050041u, 0x00000019u, 0x0000001au, 0x0000000du, 0x0000000fu, - 0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x0000001bu, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0008000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00000014u, + 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, + 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, + 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, + 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, + 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, + 0x00030005u, 0x0000000du, 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00060005u, + 0x00000014u, 0x565f6c67u, 0x65747265u, 0x646e4978u, 0x00007865u, 0x00030047u, 0x0000000bu, 0x00000002u, + 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, 0x00000001u, + 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, + 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, 0x00000000u, + 0x00040047u, 0x00000014u, 0x0000000bu, 0x0000002au, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, + 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, + 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, 0x00000001u, + 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, + 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, + 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu, 0x0000000eu, + 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, + 0x00000011u, 0x00000001u, 0x00040020u, 0x00000013u, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x00000013u, + 0x00000014u, 0x00000001u, 0x00040020u, 0x00000019u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, + 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, + 0x00000011u, 0x0004003du, 0x0000000eu, 0x00000015u, 0x00000014u, 0x0004006fu, 0x00000006u, 0x00000016u, + 0x00000015u, 0x00070050u, 0x00000007u, 0x00000017u, 0x00000016u, 0x00000016u, 0x00000016u, 0x00000016u, + 0x00050081u, 0x00000007u, 0x00000018u, 0x00000012u, 0x00000017u, 0x00050041u, 0x00000019u, 0x0000001au, + 0x0000000du, 0x0000000fu, 0x0003003eu, 0x0000001au, 0x00000018u, 0x000100fdu, 0x00010038u, }; // Owns the reflection module so each test case cleans up after itself. @@ -190,8 +160,7 @@ namespace { public: template explicit ReflectModule(const Uint32 (&spirv)[WordCount]) { - m_created = spvReflectCreateShaderModule(sizeof(spirv), spirv, &m_module) == - SPV_REFLECT_RESULT_SUCCESS; + m_created = spvReflectCreateShaderModule(sizeof(spirv), spirv, &m_module) == SPV_REFLECT_RESULT_SUCCESS; } ~ReflectModule() { if (m_created) { @@ -223,22 +192,17 @@ namespace { // --- Device gate: MOBILEGL_MAGMA_DISABLE_BLENDED_DEPTH_WRITE tri-state --- TEST(PipelineQuirkDeviceGate, ForceOnEnablesOnAnyVendor) { - EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOn, - kVendorIdArm)); - EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOn, - kVendorIdQualcomm)); + EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOn, kVendorIdArm)); + EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOn, kVendorIdQualcomm)); } TEST(PipelineQuirkDeviceGate, ForceOffDisablesEvenOnQualcomm) { - EXPECT_FALSE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOff, - kVendorIdQualcomm)); + EXPECT_FALSE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::ForceOff, kVendorIdQualcomm)); } TEST(PipelineQuirkDeviceGate, AutoDetectsQualcommOnly) { - EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::Auto, - kVendorIdQualcomm)); - EXPECT_FALSE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::Auto, - kVendorIdArm)); + EXPECT_TRUE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::Auto, kVendorIdQualcomm)); + EXPECT_FALSE(PipelineFactory::ShouldSuppressBlendedDepthWriteForDevice(QuirkOverride::Auto, kVendorIdArm)); } TEST(PipelineQuirkDeviceGate, ForceOnRoundTripsThroughTheFactoryFlag) { @@ -253,14 +217,14 @@ TEST(PipelineQuirkDeviceGate, ForceOnRoundTripsThroughTheFactoryFlag) { TEST(PipelineQuirkStripDecision, MaxBlendIsStripped) { // MC 26.3 OIT depth_bounds: GL_MAX accumulation writing depth - the case the quirk fixes. - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_MAX, kFullColorWriteMask)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_MAX, kFullColorWriteMask)); EXPECT_TRUE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } TEST(PipelineQuirkStripDecision, MinBlendIsStripped) { - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_MIN, kFullColorWriteMask)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_MIN, kFullColorWriteMask)); EXPECT_TRUE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -269,8 +233,8 @@ TEST(PipelineQuirkStripDecision, AdditiveOnePlusOneIsNotStripped) { // fixture sweep (transmittance/accumulate disable depth writes themselves); the only // real content with this shape was harmless additive glow effects (Create). A quirk // touches as little unrelated content as possible, so the shape stays exempt. - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, kFullColorWriteMask)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_ADD, kFullColorWriteMask)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -279,16 +243,15 @@ TEST(PipelineQuirkStripDecision, SortedTransparencyOverBlendIsNotStripped) { // draws each surface once and depends on its depth writes to occlude particles, rain, // and clouds drawn later - it must keep them. const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, - kFullColorWriteMask)); + true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, kFullColorWriteMask)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } TEST(PipelineQuirkStripDecision, EffectivelyOpaqueBlendIsNotStripped) { // GL_BLEND left enabled with ONE/ZERO+ADD factors is opaque in effect; stripping its // depth write would break occlusion for plainly opaque geometry. - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, kFullColorWriteMask)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, kFullColorWriteMask)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -296,20 +259,20 @@ TEST(PipelineQuirkStripDecision, FullyMaskedAccumulationBlendIsNotStripped) { // Depth-prepass pattern: colorMask(0,0,0,0) with blending left enabled - blending is // moot, and stripping would delete the entire prepass. MAX so the exemption, not the // blend-op filter, is what keeps the depth write. - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, 0)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, 0)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } TEST(PipelineQuirkStripDecision, DisabledBlendIsNotStripped) { - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } TEST(PipelineQuirkStripDecision, NoDepthWriteMeansNoStrip) { - auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); + auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); payload.depthWriteEnable = false; EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -317,8 +280,8 @@ TEST(PipelineQuirkStripDecision, NoDepthWriteMeansNoStrip) { TEST(PipelineQuirkStripDecision, FragDepthWriterIsExempt) { // gl_FragDepth output does not go through per-pipeline vertex position math, so the // cross-pipeline invariance hazard cannot affect it (e.g. the 26.3 OIT composite). - auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); + auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); payload.fragmentReplacesDepth = true; EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -330,10 +293,10 @@ TEST(PipelineQuirkStripDecision, AccumulationOnSecondaryAttachmentIsStripped) { payload.colorAttachmentCount = 2; payload.depthTestEnable = true; payload.depthWriteEnable = true; - payload.colorBlendAttachments[0] = MakeBlendAttachment( - false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, kFullColorWriteMask); - payload.colorBlendAttachments[1] = MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask); + payload.colorBlendAttachments[0] = + MakeBlendAttachment(false, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ZERO, VK_BLEND_OP_ADD, kFullColorWriteMask); + payload.colorBlendAttachments[1] = + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask); EXPECT_TRUE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -349,15 +312,14 @@ TEST(PipelineQuirkStripDecision, ReverseSubtractIsNotStripped) { // Deliberate narrowing: only the MIN/MAX extremum ops carry the depth-bounds // signature. SUBTRACT-class ops stay outside the quirk until content demands them. const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_REVERSE_SUBTRACT, - kFullColorWriteMask)); + true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_REVERSE_SUBTRACT, kFullColorWriteMask)); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } TEST(PipelineQuirkStripDecision, PartiallyMaskedAccumulationIsStripped) { // Only a fully masked attachment is exempt; a live alpha channel still accumulates. - const auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, VK_COLOR_COMPONENT_A_BIT)); + const auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, VK_COLOR_COMPONENT_A_BIT)); EXPECT_TRUE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -367,8 +329,8 @@ TEST(PipelineQuirkStripDecision, NoColorAttachmentsMeansNoStrip) { payload.colorAttachmentCount = 0; payload.depthTestEnable = true; payload.depthWriteEnable = true; - payload.colorBlendAttachments[0] = MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask); + payload.colorBlendAttachments[0] = + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -376,9 +338,8 @@ TEST(PipelineQuirkStripDecision, SeparateAlphaAccumulationIsNotStripped) { // glBlendEquationSeparate(GL_FUNC_ADD, GL_MAX) over an ordinary color over-blend: the // alpha channel accumulates but the color channel does not. Pins that the decision is // color-channel only - widening it to alpha would re-capture sorted transparency. - auto attachment = MakeBlendAttachment(true, VK_BLEND_FACTOR_SRC_ALPHA, - VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, - kFullColorWriteMask); + auto attachment = MakeBlendAttachment(true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_OP_ADD, kFullColorWriteMask); attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE; attachment.alphaBlendOp = VK_BLEND_OP_MAX; @@ -391,10 +352,9 @@ TEST(PipelineQuirkStripDecision, MixedOverAndMaskedAttachmentsAreNotStripped) { payload.depthTestEnable = true; payload.depthWriteEnable = true; payload.colorBlendAttachments[0] = MakeBlendAttachment( - true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, - kFullColorWriteMask); - payload.colorBlendAttachments[1] = MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, 0); + true, VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, VK_BLEND_OP_ADD, kFullColorWriteMask); + payload.colorBlendAttachments[1] = + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, 0); EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } @@ -426,8 +386,8 @@ TEST(ReflectedFragmentReplacesDepth, ReflectedFlagFlipsTheStripDecision) { ASSERT_TRUE(depthWriter.Created()); ASSERT_TRUE(plain.Created()); - auto payload = MakeDepthWritingPayload(MakeBlendAttachment( - true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); + auto payload = MakeDepthWritingPayload( + MakeBlendAttachment(true, VK_BLEND_FACTOR_ONE, VK_BLEND_FACTOR_ONE, VK_BLEND_OP_MAX, kFullColorWriteMask)); payload.fragmentReplacesDepth = ProgramFactory::ReflectedFragmentReplacesDepth(plain.Get()); EXPECT_TRUE(PipelineFactory::ShouldSuppressDepthWrite(payload)); @@ -436,7 +396,6 @@ TEST(ReflectedFragmentReplacesDepth, ReflectedFlagFlipsTheStripDecision) { EXPECT_FALSE(PipelineFactory::ShouldSuppressDepthWrite(payload)); } - // --- InstanceIndex reflection feeding the shaderDrawParameters diagnostic --- TEST(ReflectedReadsInstanceIndexBuiltin, TrueForAShaderReadingInstanceIndex) { @@ -464,13 +423,12 @@ TEST(ReflectedReadsInstanceIndexBuiltin, FalseForAnEmptyModule) { EXPECT_FALSE(ProgramFactory::ReflectedReadsInstanceIndexBuiltin(emptyModule)); } - TEST(WirePipelineCompatibility, ExactCompatibilitySurvivesNativeObjectTurnover) { #if MOBILEGL_BUILD_DISAGGREGATED using namespace MobileGL::MG_Backend::DirectVulkan; WireRenderPassCompatibilityTable table; - WireRenderPassCompatibilityKey a{{(Uint64(VK_FORMAT_R8G8B8A8_UNORM) << 32) | VK_SAMPLE_COUNT_1_BIT}, {0}, - VK_ATTACHMENT_UNUSED}; + WireRenderPassCompatibilityKey a{ + {(Uint64(VK_FORMAT_R8G8B8A8_UNORM) << 32) | VK_SAMPLE_COUNT_1_BIT}, {0}, VK_ATTACHMENT_UNUSED}; const Uint64 first = table.Intern(a); ASSERT_NE(first, 0u); auto b = a; @@ -606,9 +564,9 @@ TEST(PipelineQuirkTest, WireDepthResolveProbeDetectsARenderPassThatWritesNothing wrong.formats[0].depth.renderPassSentinels = 0; EXPECT_EQ(EvaluateWireDepthResolveProbe(wrong), WireDepthResolveProbeVerdict::RenderPassResolveBroken); // One aspect of one format is enough: the render pass resolves every aspect in one pass. - EXPECT_EQ(EvaluateWireDepthResolveProbe(FakeMeasurement( - {FakeFormat("D24_UNORM_S8_UINT", kBothArmsResolve, kRenderPassWroteNothing), - FakeFormat("D32_SFLOAT", kBothArmsResolve, std::nullopt)})), + EXPECT_EQ(EvaluateWireDepthResolveProbe( + FakeMeasurement({FakeFormat("D24_UNORM_S8_UINT", kBothArmsResolve, kRenderPassWroteNothing), + FakeFormat("D32_SFLOAT", kBothArmsResolve, std::nullopt)})), WireDepthResolveProbeVerdict::RenderPassResolveBroken); EXPECT_NE(DescribeWireDepthResolveProbe(choice.measurement).find("render pass 0/16"), String::npos); #else @@ -849,14 +807,14 @@ TEST(PipelineQuirkTest, WirePlaceholderKeysArePrivatePerShapeAndUnitAndBounded) using MobileGL::MG_Backend::DirectVulkan::WirePlaceholderKey; using MobileGL::MG_Backend::DirectVulkan::WirePlaceholderKeyHash; constexpr Uint32 kUnits = MobileGL::MG_Pipe::kMGPipeMaxImageUnits; - static_assert(kWirePrivateStoragePlaceholderCap >= kUnits, - "one shape on every image unit must stay private"); + static_assert(kWirePrivateStoragePlaceholderCap >= kUnits, "one shape on every image unit must stay private"); UnorderedMap cache; SizeT privateCount = 0; // What ResolveWirePlaceholderImage does with the key: find it, or make (and count) it. const auto take = [&](Uint64 shape, Bool storage, Uint32 unit) { - const WirePlaceholderKey key = ChooseWirePlaceholderKey(shape, storage, unit, privateCount, - [&](const WirePlaceholderKey& candidate) { return cache.count(candidate) != 0; }); + const WirePlaceholderKey key = + ChooseWirePlaceholderKey(shape, storage, unit, privateCount, + [&](const WirePlaceholderKey& candidate) { return cache.count(candidate) != 0; }); if (cache.emplace(key, 0).second && key.unit != WirePlaceholderKey::kShared) ++privateCount; return key; }; @@ -925,9 +883,9 @@ TEST(PipelineQuirkTest, WireColorBlitSamplesNearestWhereTheBlitIsACopy) { #if MOBILEGL_BUILD_DISAGGREGATED // create-instancing's last pass (trace call 530331): glBlitFramebuffer(0,0,854,480 -> 0,0,854,480, // GL_COLOR_BUFFER_BIT, GL_LINEAR) into FB 0, on an identity pbuffer. - EXPECT_EQ(WireColorBlitFilterFor({0, 0, 854, 480}, {0, 0, 854, 480}, GL_LINEAR, - VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR), - VK_FILTER_NEAREST) + EXPECT_EQ( + WireColorBlitFilterFor({0, 0, 854, 480}, {0, 0, 854, 480}, GL_LINEAR, VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR), + VK_FILTER_NEAREST) << "a 1:1 GL_LINEAR blit is a copy (GL 4.6 core 18.3.1); linear sampling of it is not exact on Adreno"; const struct { const char* what; diff --git a/MobileGL/MG_Test/Program/AsyncCompileTest.cpp b/MobileGL/MG_Test/Program/AsyncCompileTest.cpp index b91a6fdc..7b38ba00 100644 --- a/MobileGL/MG_Test/Program/AsyncCompileTest.cpp +++ b/MobileGL/MG_Test/Program/AsyncCompileTest.cpp @@ -37,8 +37,7 @@ namespace { // one mode cannot leak into the next. class AsyncModeScope { public: - explicit AsyncModeScope(const Bool async) - : m_saved(MG_Config::Features.AsyncShaderCompile) { + explicit AsyncModeScope(const Bool async) : m_saved(MG_Config::Features.AsyncShaderCompile) { MG_Config::Features.AsyncShaderCompile = async ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff; } @@ -306,8 +305,9 @@ TEST_F(AsyncCompileTest, LinkDiagnosticsQuoteTheAsyncCompileLog) { const String programLog(buffer.data(), static_cast(written)); const String shaderLog = QueryShaderInfoLog(fs); ASSERT_FALSE(shaderLog.empty()); - EXPECT_NE(programLog.find(shaderLog), String::npos) - << "program log:\n" << programLog << "\nshader log:\n" << shaderLog; + EXPECT_NE(programLog.find(shaderLog), String::npos) << "program log:\n" + << programLog << "\nshader log:\n" + << shaderLog; EXPECT_EQ(GetError(), GL_NO_ERROR); } @@ -507,8 +507,7 @@ TEST_F(AsyncCompileTest, StressCompileQueryResourceDeleteInterleaved) { for (int i = 0; i < kPerRound; ++i) { const GLuint shader = shaders[static_cast(i)]; EXPECT_EQ(QueryCompileStatus(shader), GL_TRUE) << QueryShaderInfoLog(shader); - const String expectedUniform = - "uSeed" + std::to_string(round * 1000 + (i % 2 == 0 ? 500 + i : i)); + const String expectedUniform = "uSeed" + std::to_string(round * 1000 + (i % 2 == 0 ? 500 + i : i)); const GLuint vs = MakeShader(GL_VERTEX_SHADER, kVs); CompileShader(vs); const GLuint program = CreateProgram(); diff --git a/MobileGL/MG_Test/Program/AsyncLinkTest.cpp b/MobileGL/MG_Test/Program/AsyncLinkTest.cpp index f8b3e0a9..3029ddaf 100644 --- a/MobileGL/MG_Test/Program/AsyncLinkTest.cpp +++ b/MobileGL/MG_Test/Program/AsyncLinkTest.cpp @@ -210,8 +210,8 @@ TEST_F(AsyncLinkTest, TwoProgramsSharingAShaderGenerateIdenticalSpirv) { const Vector digestA = SpirvDigest(programA); const Vector digestB = SpirvDigest(programB); ASSERT_EQ(digestA.size(), 2u) << "async=" << async; - EXPECT_EQ(digestA, digestB) - << "the claim winner and the re-parsing loser must produce identical SPIR-V (async=" << async << ")"; + EXPECT_EQ(digestA, digestB) << "the claim winner and the re-parsing loser must produce identical SPIR-V (async=" + << async << ")"; // And the two programs really are usable independently. EXPECT_GE(GetUniformLocation(programA, "uColor"), 0); @@ -803,13 +803,13 @@ TEST_F(AsyncLinkTest, StressLinkQueryRelinkDeleteInterleaved) { const GLuint program = programs[static_cast(i)]; ASSERT_EQ(QueryLinkStatus(program), GL_TRUE) << "round " << round << " program " << i << ": " << QueryProgramInfoLog(program); - const String expected = - "uSeed" + std::to_string(round * 1000 + (i % 2 == 0 ? 700 + i : 300 + i)); + const String expected = "uSeed" + std::to_string(round * 1000 + (i % 2 == 0 ? 700 + i : 300 + i)); EXPECT_GE(GetUniformLocation(program, expected.c_str()), 0) << "round " << round << " program " << i << " expected " << expected; DeleteProgram(program); } - for (const GLuint fs : fragmentShaders) DeleteShader(fs); + for (const GLuint fs : fragmentShaders) + DeleteShader(fs); DeleteShader(vs); EXPECT_EQ(GetError(), GL_NO_ERROR); } diff --git a/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp b/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp index d0df8541..b7296381 100644 --- a/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp +++ b/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp @@ -395,8 +395,9 @@ TEST_F(AsyncSpirvPhaseTest, ALinkedProgramWhoseSpirvJobWasCancelledDegradesInste const GLfloat tint[3] = {1.0f, 2.0f, 3.0f}; GLfloat model[16] = {}; - for (int c = 0; c < 16; ++c) model[c] = static_cast(c); - ProgramUniform3fv(program, locTint, 1, tint); // dropped, not faulted + for (int c = 0; c < 16; ++c) + model[c] = static_cast(c); + ProgramUniform3fv(program, locTint, 1, tint); // dropped, not faulted ProgramUniformMatrix4fv(program, locModel, 1, GL_FALSE, model); // ditto UseProgram(program); Uniform3fv(locTint, 1, tint); // the glUseProgram + glUniform* entry, same verdict @@ -410,7 +411,8 @@ TEST_F(AsyncSpirvPhaseTest, ALinkedProgramWhoseSpirvJobWasCancelledDegradesInste // Reads leave the caller's buffer alone rather than faulting. GLfloat readback[16] = {}; - for (int c = 0; c < 16; ++c) readback[c] = -1.0f; + for (int c = 0; c < 16; ++c) + readback[c] = -1.0f; GetUniformfv(program, locModel, readback); for (int c = 0; c < 16; ++c) { EXPECT_FLOAT_EQ(readback[c], -1.0f) << "a program with no shadow must not write the query buffer"; @@ -489,7 +491,8 @@ TEST_F(AsyncSpirvPhaseTest, UniformWritesInsideTheWindowReplayExactly) { ASSERT_GE(e.locArr, 0); ASSERT_GE(e.locTex, 0); - for (int c = 0; c < 16; ++c) e.model[c] = static_cast(i) + static_cast(c) * 0.25f; + for (int c = 0; c < 16; ++c) + e.model[c] = static_cast(i) + static_cast(c) * 0.25f; e.tint[0] = 0.125f * static_cast(i); e.tint[1] = 0.25f * static_cast(i); e.tint[2] = 0.5f * static_cast(i); @@ -591,7 +594,8 @@ TEST_F(AsyncSpirvPhaseTest, TheBufferedWriteValveFallsThroughToADirectWriteWitho GLfloat model[16] = {}; for (int w = 0; w < kFloodWrites; ++w) { - for (int c = 0; c < 16; ++c) model[c] = static_cast(w) + static_cast(c); + for (int c = 0; c < 16; ++c) + model[c] = static_cast(w) + static_cast(c); ProgramUniformMatrix4fv(program, locModel, 1, GL_FALSE, model); } @@ -852,16 +856,15 @@ TEST_F(AsyncSpirvPhaseTest, DeletingAProgramWithAPendingSpirvJobDoesNotBlock) { } const auto drainEnd = std::chrono::steady_clock::now(); - const auto deleteUs = - std::chrono::duration_cast(deleteEnd - deleteStart).count(); + const auto deleteUs = std::chrono::duration_cast(deleteEnd - deleteStart).count(); const auto drainUs = std::chrono::duration_cast(drainEnd - drainStart).count(); EXPECT_GT(outstandingBeforeDelete, 0) << "no program was deleted inside the A->B window"; EXPECT_GT(outstandingBeforeDrain, 0) << "nothing was left to drain, so the timing bound has no calibration"; if (outstandingBeforeDelete > 0 && outstandingBeforeDrain > 0 && drainUs > 2000) { - EXPECT_LT(deleteUs, drainUs / 4) - << "deleting " << kHalf << " programs with outstanding SPIR-V jobs took " << deleteUs - << " us against " << drainUs << " us to drain the same number - glDeleteProgram is waiting for them"; + EXPECT_LT(deleteUs, drainUs / 4) << "deleting " << kHalf << " programs with outstanding SPIR-V jobs took " + << deleteUs << " us against " << drainUs + << " us to drain the same number - glDeleteProgram is waiting for them"; } EXPECT_EQ(GetError(), GL_NO_ERROR); } @@ -1417,7 +1420,10 @@ discard; GLint location; }; const BoundAttribute kIrisWeatherBindings[] = { - {"iris_Position", 0}, {"iris_Color", 1}, {"iris_UV0", 2}, {"iris_UV2", 3}, + {"iris_Position", 0}, + {"iris_Color", 1}, + {"iris_UV0", 2}, + {"iris_UV2", 3}, }; // Drives Iris's own call sequence and returns the linked program name. @@ -1466,8 +1472,8 @@ TEST_F(AsyncSpirvPhaseTest, IrisWeatherProgramReplaysCleanlyThroughBothPhases) { // ---- phase A: LINK_STATUS is the join, and it must be truthful ---- GLint linkStatus = GL_FALSE; GetProgramiv(program, GL_LINK_STATUS, &linkStatus); - ASSERT_EQ(linkStatus, GL_TRUE) << QueryProgramInfoLog(program) - << " (optimistic quirk " << (optimisticQuirk ? "on" : "off") << ")"; + ASSERT_EQ(linkStatus, GL_TRUE) << QueryProgramInfoLog(program) << " (optimistic quirk " + << (optimisticQuirk ? "on" : "off") << ")"; // Iris queries uniforms straight after the status read; these are phase-A answers. EXPECT_GE(GetUniformLocation(program, "iris_ModelViewMatrix"), -1); @@ -1502,15 +1508,16 @@ TEST_F(AsyncSpirvPhaseTest, IrisWeatherProgramReplaysCleanlyThroughBothPhases) { // which is precisely what the device victim did. spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String validatorMessages; - tools.SetMessageConsumer([&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - if (message != nullptr) validatorMessages += String(message) + "\n"; - }); + tools.SetMessageConsumer( + [&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + if (message != nullptr) validatorMessages += String(message) + "\n"; + }); for (SizeT i = 0; i < modules.size(); ++i) { validatorMessages.clear(); EXPECT_TRUE(tools.Validate(modules[i])) << "module " << i << " is not valid SPIR-V - Adreno rejects the pipeline for this while " - << "lavapipe tolerates it:\n" << validatorMessages; + << "lavapipe tolerates it:\n" + << validatorMessages; } // ---- every vertex input carries a unique Location ---- @@ -1545,8 +1552,7 @@ TEST_F(AsyncSpirvPhaseTest, IrisWeatherProgramReplaysCleanlyThroughBothPhases) { ASSERT_NE(it, locationById.end()) << "a vertex input reached SPIR-V with no Location decoration (optimistic quirk " << (optimisticQuirk ? "on" : "off") << ")"; - EXPECT_TRUE(usedLocations.insert(it->second).second) - << "two vertex inputs share location " << it->second; + EXPECT_TRUE(usedLocations.insert(it->second).second) << "two vertex inputs share location " << it->second; ++checkedInputs; } EXPECT_GT(checkedInputs, 0u) << "no vertex inputs found; the scan proved nothing"; diff --git a/MobileGL/MG_Test/Program/OptimisticStatusTest.cpp b/MobileGL/MG_Test/Program/OptimisticStatusTest.cpp index a1c315cc..4bf42854 100644 --- a/MobileGL/MG_Test/Program/OptimisticStatusTest.cpp +++ b/MobileGL/MG_Test/Program/OptimisticStatusTest.cpp @@ -286,8 +286,8 @@ void main() { fragColor = thisIdentifierWasNeverDeclared; } // then the GL_ACTIVE_UNIFORMS enumeration ProgramUniforms$Builder.buildUniforms does. struct ProgramReflection { GLint linkStatus = GL_FALSE; - Vector> locations; // queried name -> location - Vector> activeUniforms; // name, type, size, location + Vector> locations; // queried name -> location + Vector> activeUniforms; // name, type, size, location }; ProgramReflection RunIrisPhaseTwo(const GLuint program, const Vector& names) { @@ -341,10 +341,10 @@ TEST_F(OptimisticStatusTest, OffByDefaultTheStatusStillJoins) { const GLuint probe = probes[0]; EXPECT_EQ(QueryCompileStatus(probe), GL_TRUE); - EXPECT_EQ(QueryShaderCompletion(probe), GL_TRUE) - << "GL_COMPILE_STATUS with the quirk off must have joined the job"; + EXPECT_EQ(QueryShaderCompletion(probe), GL_TRUE) << "GL_COMPILE_STATUS with the quirk off must have joined the job"; - for (const GLuint shader : saturation) DeleteShader(shader); + for (const GLuint shader : saturation) + DeleteShader(shader); DeleteShader(probe); EXPECT_EQ(GetError(), GL_NO_ERROR); } @@ -371,8 +371,7 @@ TEST_F(OptimisticStatusTest, PendingCompileReportsTrueAndEmptyLogWithoutJoining) EXPECT_EQ(QueryCompileStatus(probe), GL_TRUE) << "an in-flight compile must answer GL_TRUE"; EXPECT_EQ(QueryInfoLogLength(probe), 0) << "an in-flight compile must answer an empty log length"; EXPECT_TRUE(QueryShaderInfoLog(probe).empty()) << "an in-flight compile must answer an empty log"; - EXPECT_EQ(QueryShaderCompletion(probe), GL_FALSE) - << "the three reads above must not have joined the blocked job"; + EXPECT_EQ(QueryShaderCompletion(probe), GL_FALSE) << "the three reads above must not have joined the blocked job"; DeleteShader(probe); EXPECT_EQ(GetError(), GL_NO_ERROR); @@ -511,9 +510,8 @@ TEST_F(OptimisticStatusTest, AFailingCompileStillFailsItsLink) { brokenSource += " fragColor = vec4(acc);\n}\n"; ASSERT_GT(brokenSource.size(), 32768u); - const GLuint vs = MakeShader(GL_VERTEX_SHADER, - "#version 460\nlayout(location = 0) in vec3 aPos;\n" - "void main() { gl_Position = vec4(aPos, 1.0); }\n"); + const GLuint vs = MakeShader(GL_VERTEX_SHADER, "#version 460\nlayout(location = 0) in vec3 aPos;\n" + "void main() { gl_Position = vec4(aPos, 1.0); }\n"); const char* brokenText = brokenSource.c_str(); const GLuint fs = CreateShader(GL_FRAGMENT_SHADER); ShaderSource(fs, 1, &brokenText, nullptr); @@ -592,11 +590,12 @@ TEST_F(OptimisticStatusTest, IrisTwoPhaseReplayProducesIdenticalReflection) { Vector& out = quirkOn ? optimistic : reference; for (int i = 0; i < kPrograms; ++i) { - const Vector names = {"uModel" + std::to_string(i), "uTint", - "uSeed" + std::to_string(i), "uOffset", "uDoesNotExist"}; + const Vector names = {"uModel" + std::to_string(i), "uTint", "uSeed" + std::to_string(i), "uOffset", + "uDoesNotExist"}; out.push_back(RunIrisPhaseTwo(programs[(SizeT)i], names)); } - for (const GLuint program : programs) DeleteProgram(program); + for (const GLuint program : programs) + DeleteProgram(program); ASSERT_EQ(GetError(), GL_NO_ERROR); } @@ -645,7 +644,8 @@ TEST_F(OptimisticStatusTest, PhaseOneIssuesNoCompileJoin) { EXPECT_EQ(QueryShaderCompletion(shader), GL_FALSE) << "a phase-1 read joined a compile the blocked pool could not have run"; } - for (const GLuint shader : shaders) DeleteShader(shader); + for (const GLuint shader : shaders) + DeleteShader(shader); } // Quirk off: every status read joins its shader. @@ -668,7 +668,8 @@ TEST_F(OptimisticStatusTest, PhaseOneIssuesNoCompileJoin) { EXPECT_EQ(QueryShaderCompletion(shader), GL_TRUE) << "with the quirk off every per-shader status read must have joined"; } - for (const GLuint shader : shaders) DeleteShader(shader); + for (const GLuint shader : shaders) + DeleteShader(shader); } EXPECT_EQ(GetError(), GL_NO_ERROR); } diff --git a/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp b/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp index 44b73cc9..7beaa746 100644 --- a/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp +++ b/MobileGL/MG_Test/Program/ParallelShaderCompileTest.cpp @@ -412,9 +412,8 @@ TEST_F(ParallelShaderCompileTest, NonzeroCompilerThreadsRestoresAsynchronousComp MaxShaderCompilerThreadsKHR(1); Vector sources; const Vector shaders = EnqueueBacklog(64, 5000, sources); - const Bool anyOutstanding = std::any_of(shaders.begin(), shaders.end(), [](const GLuint shader) { - return QueryShaderCompletion(shader) == GL_FALSE; - }); + const Bool anyOutstanding = std::any_of( + shaders.begin(), shaders.end(), [](const GLuint shader) { return QueryShaderCompletion(shader) == GL_FALSE; }); if (!anyOutstanding) { GTEST_SKIP() << "the pool drained the backlog before the first query; asynchrony not observable here"; } @@ -516,15 +515,17 @@ TEST_F(ParallelShaderCompileTest, MaxShaderCompilerThreadsIgnoresTheCurrentBudge TEST_F(ParallelShaderCompileTest, BothBackendsAdvertiseTheExtensionIffAsyncIsEnabled) { { const AsyncModeScope async(true); - EXPECT_TRUE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), - E_GL_KHR_parallel_shader_compile)); + EXPECT_TRUE( + Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), + E_GL_KHR_parallel_shader_compile)); EXPECT_TRUE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false), E_GL_KHR_parallel_shader_compile)); } { const AsyncModeScope async(false); - EXPECT_FALSE(Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), - E_GL_KHR_parallel_shader_compile)) + EXPECT_FALSE( + Advertises(MG_Backend::DirectGLES::BuildAdvertisedExtensions(false, false, false, false, false, false), + E_GL_KHR_parallel_shader_compile)) << "MOBILEGL_ASYNC_SHADER_COMPILE=0 must withdraw the extension, not only the threading"; EXPECT_FALSE(Advertises(MG_Backend::DirectVulkan::BuildAdvertisedExtensions(false, false, false, false, false), E_GL_KHR_parallel_shader_compile)) diff --git a/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp b/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp index 2c043fa4..76fbe7e1 100644 --- a/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramArtifactsCodecTest.cpp @@ -365,8 +365,8 @@ TEST(ProgramArtifactsCodec, ACompactArchiveOfTenEmptySpirvModulesRoundTrips) { spirv.generatedSpirv.resize(10); Vector bytes; EncodeProgramArtifacts(LinkArtifacts{}, spirv, bytes); - const SizeT remaining = BytesAfterTheCount(bytes, spirv.generatedSpirv.size(), - Vector(10 * sizeof(Uint64), 0)); + const SizeT remaining = + BytesAfterTheCount(bytes, spirv.generatedSpirv.size(), Vector(10 * sizeof(Uint64), 0)); ASSERT_LT(remaining, spirv.generatedSpirv.size() * sizeof(Vector)) << "fixture: a sizeof(value_type) charge would admit this count, so the case could not go red"; @@ -375,7 +375,8 @@ TEST(ProgramArtifactsCodec, ACompactArchiveOfTenEmptySpirvModulesRoundTrips) { ASSERT_TRUE(DecodeProgramArtifacts(bytes.data(), bytes.size(), decodedLink, decodedSpirv)) << "a SpirvArtifacts with ten empty modules, which this encoder wrote, was refused"; ASSERT_EQ(decodedSpirv.generatedSpirv.size(), 10u); - for (const auto& module : decodedSpirv.generatedSpirv) EXPECT_TRUE(module.empty()); + for (const auto& module : decodedSpirv.generatedSpirv) + EXPECT_TRUE(module.empty()); Vector reencoded; EncodeProgramArtifacts(decodedLink, decodedSpirv, reencoded); EXPECT_EQ(reencoded, bytes); @@ -392,8 +393,7 @@ TEST(ProgramArtifactsCodec, ATruncatedStreamIsRefusedNotGuessed) { // Cut at a spread of points rather than one: the header, a length prefix, the middle of a // string and the last byte all fail through different branches. - for (const SizeT cut : {SizeT{0}, SizeT{4}, SizeT{9}, bytes.size() / 3, bytes.size() / 2, - bytes.size() - 1}) { + for (const SizeT cut : {SizeT{0}, SizeT{4}, SizeT{9}, bytes.size() / 3, bytes.size() / 2, bytes.size() - 1}) { LinkArtifacts link; SpirvArtifacts spirv; link.infoLog = "must be cleared"; @@ -432,8 +432,7 @@ TEST(ProgramArtifactsCodec, AVectorCountCannotReservePastTheRemainingArchiveByte ASSERT_GT(perElement, 1u); ASSERT_LT(perElement, sizeof(UniformReflection)); const Uint64 forgedCount = static_cast(available / perElement) + 1; - ASSERT_LT(forgedCount, static_cast(available)) - << "fixture must pass the old count <= remaining check"; + ASSERT_LT(forgedCount, static_cast(available)) << "fixture must pass the old count <= remaining check"; std::memcpy(bytes.data() + countOffset, &forgedCount, sizeof(forgedCount)); LinkArtifacts link; diff --git a/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp b/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp index 1e87cbe3..d3076696 100644 --- a/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp @@ -121,9 +121,10 @@ namespace { RecordProperty("sizeof_XfbVarying", static_cast(sizeof(XfbVarying))); RecordProperty("sizeof_LinkArtifacts", static_cast(sizeof(LinkArtifacts))); RecordProperty("sizeof_SpirvArtifacts", static_cast(sizeof(SpirvArtifacts))); - std::printf("sizeof: TypeFacts=%zu ResourceReflection=%zu XfbVarying=%zu LinkArtifacts=%zu SpirvArtifacts=%zu\n", - sizeof(TypeFacts), sizeof(ResourceReflection), sizeof(XfbVarying), sizeof(LinkArtifacts), - sizeof(SpirvArtifacts)); + std::printf( + "sizeof: TypeFacts=%zu ResourceReflection=%zu XfbVarying=%zu LinkArtifacts=%zu SpirvArtifacts=%zu\n", + sizeof(TypeFacts), sizeof(ResourceReflection), sizeof(XfbVarying), sizeof(LinkArtifacts), + sizeof(SpirvArtifacts)); #ifdef MGL_LINKARTIFACTS_SIZE // whichever STL branch the header pinned, this twin follows it EXPECT_EQ(sizeof(ResourceReflection), static_cast(MGL_RESOURCEREFLECTION_SIZE)); EXPECT_EQ(sizeof(XfbVarying), static_cast(MGL_XFBVARYING_SIZE)); diff --git a/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp b/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp index 1e9f75b7..3b383faa 100644 --- a/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp @@ -97,10 +97,11 @@ namespace { return Props(program, iface, index, props); } - GLenum TakeError() { return GetError(); } + GLenum TakeError() { + return GetError(); + } void ClearErrors() { - for (int i = 0; i < 32 && TakeError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && TakeError() != GL_NO_ERROR; ++i) {} } const char* kSimpleVs = R"(#version 430 @@ -221,12 +222,9 @@ void main(void) GL_REFERENCED_BY_VERTEX_SHADER, GL_LOCATION, GL_IS_PER_PATCH}; - EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "a", props), - (std::vector{2, GL_FLOAT_MAT4, 1, 0, 0, 1, 0, 0})); - EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "c[0]", props), - (std::vector{5, GL_FLOAT, 2, 0, 0, 1, 5, 0})); - EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "d", props), - (std::vector{5, GL_FLOAT_MAT2x3, 2, 0, 0, 1, 7, 0})); + EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "a", props), (std::vector{2, GL_FLOAT_MAT4, 1, 0, 0, 1, 0, 0})); + EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "c[0]", props), (std::vector{5, GL_FLOAT, 2, 0, 0, 1, 5, 0})); + EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "d", props), (std::vector{5, GL_FLOAT_MAT2x3, 2, 0, 0, 1, 7, 0})); EXPECT_EQ(TakeError(), GL_NO_ERROR); } @@ -271,10 +269,10 @@ void main() { EXPECT_EQ(GetProgramResourceLocationIndex(p, GL_PROGRAM_OUTPUT, name), 0) << name; } - const std::vector props = {GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE, GL_REFERENCED_BY_FRAGMENT_SHADER, - GL_LOCATION, GL_IS_PER_PATCH, GL_LOCATION_INDEX}; - EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "a", props), - (std::vector{5, GL_FLOAT_VEC3, 2, 1, 0, 0, 0})); + const std::vector props = { + GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE, GL_REFERENCED_BY_FRAGMENT_SHADER, + GL_LOCATION, GL_IS_PER_PATCH, GL_LOCATION_INDEX}; + EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "a", props), (std::vector{5, GL_FLOAT_VEC3, 2, 1, 0, 0, 0})); EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "d", props), (std::vector{5, GL_INT, 2, 1, 5, 0, 0})); EXPECT_EQ(TakeError(), GL_NO_ERROR); } @@ -299,8 +297,8 @@ void main(void) { gl_FragDepth = 0.1; gl_SampleMask[0] = 1; } EXPECT_EQ(GetProgramResourceLocationIndex(p, GL_PROGRAM_OUTPUT, "gl_FragDepth"), -1); EXPECT_EQ(GetProgramResourceLocationIndex(p, GL_PROGRAM_OUTPUT, "gl_SampleMask[0]"), -1); - const std::vector props = {GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE, GL_REFERENCED_BY_FRAGMENT_SHADER, - GL_LOCATION, GL_LOCATION_INDEX}; + const std::vector props = { + GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE, GL_REFERENCED_BY_FRAGMENT_SHADER, GL_LOCATION, GL_LOCATION_INDEX}; EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_FragDepth", props), (std::vector{13, GL_FLOAT, 1, 1, -1, -1})); EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_SampleMask[0]", props), @@ -377,9 +375,9 @@ void main() { color = vec4(0, 1, 0, 1) + recolor; } GL_REFERENCED_BY_FRAGMENT_SHADER, GL_REFERENCED_BY_VERTEX_SHADER, GL_LOCATION}; - EXPECT_EQ(PropsOf(p, GL_UNIFORM, "repos", props), - (std::vector{6, GL_FLOAT_VEC4, 1, -1, -1, -1, -1, 0, -1, 0, 0, 1, - GetUniformLocation(p, "repos")})); + EXPECT_EQ( + PropsOf(p, GL_UNIFORM, "repos", props), + (std::vector{6, GL_FLOAT_VEC4, 1, -1, -1, -1, -1, 0, -1, 0, 0, 1, GetUniformLocation(p, "repos")})); EXPECT_EQ(PropsOf(p, GL_UNIFORM, "recolor", props), (std::vector{8, GL_FLOAT_VEC4, 1, -1, -1, -1, -1, 0, -1, 0, 1, 0, GetUniformLocation(p, "recolor")})); @@ -439,9 +437,9 @@ void main(void) { GL_ATOMIC_COUNTER_BUFFER_INDEX, GL_REFERENCED_BY_VERTEX_SHADER, GL_LOCATION}; - EXPECT_EQ(PropsOf(p, GL_UNIFORM, "a[2][1]", props), - (std::vector{11, GL_FLOAT_VEC4, 5, -1, -1, -1, -1, 0, -1, 1, - GetUniformLocation(p, "a[2][1]")})); + EXPECT_EQ( + PropsOf(p, GL_UNIFORM, "a[2][1]", props), + (std::vector{11, GL_FLOAT_VEC4, 5, -1, -1, -1, -1, 0, -1, 1, GetUniformLocation(p, "a[2][1]")})); EXPECT_EQ(TakeError(), GL_NO_ERROR); } @@ -1089,7 +1087,8 @@ void main() { const std::string n = std::to_string(count); const std::string source = std::string(R"(#version 430 out vec4 color; -layout(r32i) uniform iimage2D u_image[)") + n + R"(]; +layout(r32i) uniform iimage2D u_image[)") + + n + R"(]; void main() { int value = 1; for (int i = 0; i < )" + n + R"(; ++i) { @@ -1245,7 +1244,8 @@ void main() { )"; const std::string tes = std::string(R"(#version 430 layout(triangles) in; -layout(r32i) uniform iimage2D u_tesImage[)") + n + R"(]; +layout(r32i) uniform iimage2D u_tesImage[)") + + n + R"(]; void main() { int value = 1; for (int i = 0; i < )" + n + R"(; ++i) { @@ -1358,8 +1358,8 @@ void main() { for (GLuint index = 0; index < static_cast(bufferCount); ++index) { const std::vector viaInterface = Props(p, GL_ATOMIC_COUNTER_BUFFER, index, - {GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE, GL_NUM_ACTIVE_VARIABLES, - GL_REFERENCED_BY_VERTEX_SHADER, GL_REFERENCED_BY_FRAGMENT_SHADER}); + {GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE, GL_NUM_ACTIVE_VARIABLES, GL_REFERENCED_BY_VERTEX_SHADER, + GL_REFERENCED_BY_FRAGMENT_SHADER}); ASSERT_EQ(viaInterface.size(), 5u); EXPECT_EQ(activeBufferiv(index, GL_ATOMIC_COUNTER_BUFFER_BINDING), viaInterface[0]); EXPECT_EQ(activeBufferiv(index, GL_ATOMIC_COUNTER_BUFFER_DATA_SIZE), viaInterface[1]); @@ -1412,16 +1412,15 @@ void main(void) { EXPECT_EQ(Interfaceiv(p, GL_TRANSFORM_FEEDBACK_VARYING, GL_ACTIVE_RESOURCES), 6); EXPECT_EQ(Interfaceiv(p, GL_TRANSFORM_FEEDBACK_VARYING, GL_MAX_NAME_LENGTH), 5); - for (const char* name : varyings) ExpectResource(p, GL_TRANSFORM_FEEDBACK_VARYING, name, name); + for (const char* name : varyings) + ExpectResource(p, GL_TRANSFORM_FEEDBACK_VARYING, name, name); const std::vector props = {GL_NAME_LENGTH, GL_TYPE, GL_ARRAY_SIZE}; - EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "a", props), - (std::vector{2, GL_INT_VEC4, 1})); + EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "a", props), (std::vector{2, GL_INT_VEC4, 1})); EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "b[0]", props), (std::vector{5, GL_FLOAT, 1})); EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "c", props), (std::vector{2, GL_UNSIGNED_INT_VEC2, 1})); - EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "e", props), - (std::vector{2, GL_FLOAT_VEC3, 2})); + EXPECT_EQ(PropsOf(p, GL_TRANSFORM_FEEDBACK_VARYING, "e", props), (std::vector{2, GL_FLOAT_VEC3, 2})); EXPECT_EQ(TakeError(), GL_NO_ERROR); } @@ -1447,7 +1446,8 @@ void main(void) { EXPECT_EQ(Interfaceiv(p, GL_TRANSFORM_FEEDBACK_VARYING, GL_MAX_NAME_LENGTH), 19); std::vector names; - for (GLuint i = 0; i < 11; ++i) names.push_back(ResourceName(p, GL_TRANSFORM_FEEDBACK_VARYING, i)); + for (GLuint i = 0; i < 11; ++i) + names.push_back(ResourceName(p, GL_TRANSFORM_FEEDBACK_VARYING, i)); const auto indexOf = [&names](const std::string& name) -> GLuint { for (GLuint i = 0; i < names.size(); ++i) { if (names[i] == name) return i; @@ -1608,17 +1608,16 @@ void main() { // A non-fragment stage's outputs are varyings: no color number, so no color index. EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_Position", {GL_LOCATION_INDEX}), (std::vector{-1})); - const std::vector stageProps = { - GL_NAME_LENGTH, - GL_TYPE, - GL_ARRAY_SIZE, - GL_REFERENCED_BY_COMPUTE_SHADER, - GL_REFERENCED_BY_FRAGMENT_SHADER, - GL_REFERENCED_BY_GEOMETRY_SHADER, - GL_REFERENCED_BY_TESS_CONTROL_SHADER, - GL_REFERENCED_BY_TESS_EVALUATION_SHADER, - GL_REFERENCED_BY_VERTEX_SHADER, - GL_IS_PER_PATCH}; + const std::vector stageProps = {GL_NAME_LENGTH, + GL_TYPE, + GL_ARRAY_SIZE, + GL_REFERENCED_BY_COMPUTE_SHADER, + GL_REFERENCED_BY_FRAGMENT_SHADER, + GL_REFERENCED_BY_GEOMETRY_SHADER, + GL_REFERENCED_BY_TESS_CONTROL_SHADER, + GL_REFERENCED_BY_TESS_EVALUATION_SHADER, + GL_REFERENCED_BY_VERTEX_SHADER, + GL_IS_PER_PATCH}; EXPECT_EQ(PropsOf(p, GL_PROGRAM_INPUT, "gl_PerVertex.gl_Position", stageProps), (std::vector{25, GL_FLOAT_VEC4, 1, 0, 0, 1, 0, 0, 0, 0})); EXPECT_EQ(PropsOf(p, GL_PROGRAM_OUTPUT, "gl_Position", stageProps), @@ -1937,9 +1936,9 @@ void main() { color = u + v; } ASSERT_NE(neverLinked, 0u); ClearErrors(); - for (const GLenum pname : {GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, GL_ACTIVE_UNIFORMS, - GL_ACTIVE_UNIFORM_MAX_LENGTH, GL_ACTIVE_UNIFORM_BLOCKS, - GL_ACTIVE_ATOMIC_COUNTER_BUFFERS}) { + for (const GLenum pname : + {GL_ACTIVE_ATTRIBUTES, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, GL_ACTIVE_UNIFORMS, GL_ACTIVE_UNIFORM_MAX_LENGTH, + GL_ACTIVE_UNIFORM_BLOCKS, GL_ACTIVE_ATOMIC_COUNTER_BUFFERS}) { GLint value = -1; GetProgramiv(neverLinked, pname, &value); ClearErrors(); @@ -1967,8 +1966,8 @@ void main() { color = u + v; } GetProgramiv(brokenSource, GL_COMPUTE_WORK_GROUP_SIZE, localSize); const GLenum computeError = TakeError(); ClearErrors(); - EXPECT_TRUE(computeError == GL_INVALID_OPERATION || (localSize[0] == 0 && localSize[1] == 0 && - localSize[2] == 0)) + EXPECT_TRUE(computeError == GL_INVALID_OPERATION || + (localSize[0] == 0 && localSize[1] == 0 && localSize[2] == 0)) << "either the query is refused, or it answers the initial value - never both untouched " "and unreported"; } @@ -1998,8 +1997,8 @@ void main() { color = u + v; } const GLenum compatibleSubroutinesProp = GL_COMPATIBLE_SUBROUTINES; const GLenum notAProp = GL_TEXTURE_2D; - const auto lengthAfter = [&](GLuint program, GLenum iface, GLuint index, GLsizei propCount, - const GLenum* props, GLsizei bufSize, GLint* out) { + const auto lengthAfter = [&](GLuint program, GLenum iface, GLuint index, GLsizei propCount, const GLenum* props, + GLsizei bufSize, GLint* out) { GLsizei length = kPoison; GetProgramResourceiv(program, iface, index, propCount, props, bufSize, &length, out); ClearErrors(); diff --git a/MobileGL/MG_Test/Program/ProgramPipelineCompositeTest.cpp b/MobileGL/MG_Test/Program/ProgramPipelineCompositeTest.cpp index 76fbda59..0881bc49 100644 --- a/MobileGL/MG_Test/Program/ProgramPipelineCompositeTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramPipelineCompositeTest.cpp @@ -283,8 +283,7 @@ void main() { gl_Position = u_vsOnly; } UseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fs); ASSERT_EQ(GetError(), GL_NO_ERROR); UseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vs); - ASSERT_EQ(GetError(), GL_INVALID_OPERATION) - << "a program not linked as separable is not a legal pipeline stage"; + ASSERT_EQ(GetError(), GL_INVALID_OPERATION) << "a program not linked as separable is not a legal pipeline stage"; { const auto& pipelineObject = MG_State::pGLContext->MaterializeProgramPipelineObject(pipeline); ASSERT_NE(pipelineObject, nullptr); @@ -454,8 +453,7 @@ TEST_F(ProgramPipelineCompositeTest, ASamplerWritePerDrawDoesNotRebuildTheCompos Uniform1i(sampler, unit); const auto composite = DrawProgram(); ASSERT_NE(composite, nullptr); - EXPECT_EQ(composite.get(), first.get()) - << "the composite was rebuilt by a sampler-unit write at unit " << unit; + EXPECT_EQ(composite.get(), first.get()) << "the composite was rebuilt by a sampler-unit write at unit " << unit; EXPECT_EQ(composite->GetLifetimeId(), firstLifetime) << "the composite's identity changed at unit " << unit; // The value still has to ARRIVE - the whole point is that the mirror carries it now that // the rebuild no longer does. @@ -555,20 +553,17 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); EndPrimitive(); } ASSERT_FALSE(composite->HasLinkedShaderStage(ShaderStage::Vertex)); DrawArrays(GL_POINTS, 0, 1); - EXPECT_EQ(GetError(), GL_INVALID_OPERATION) - << "a geometry stage with no vertex stage must refuse the draw"; + EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "a geometry stage with no vertex stage must refuse the draw"; // A dispatch shares the same "is there a program, did it link" helper and legitimately has no // vertex stage; the rule must not have leaked onto it. There is no compute stage here, so the // error is the compute check's own - what matters is that the draw rule did not fire first // with a different meaning. - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} BindProgramPipeline(0); DeleteProgramPipelines(1, &pipeline); - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} } // GL 4.6 core 7.3: only a SEPARABLE program may have a geometry (or tessellation) stage without a @@ -616,7 +611,8 @@ void main() { gl_Position = vec4(0.0); } // One separable program carrying BOTH the vertex and the fragment stage. const GLuint both = CreateProgram(); ProgramParameteri(both, GL_PROGRAM_SEPARABLE, GL_TRUE); - for (const auto& [stage, source] : {std::pair{GL_VERTEX_SHADER, kVf}, std::pair{GL_FRAGMENT_SHADER, kSharedUniformFs}}) { + for (const auto& [stage, source] : + {std::pair{GL_VERTEX_SHADER, kVf}, std::pair{GL_FRAGMENT_SHADER, kSharedUniformFs}}) { const GLuint shader = CreateShader(stage); ShaderSource(shader, 1, &source, nullptr); CompileShader(shader); @@ -669,8 +665,7 @@ void main() { gl_Position = vec4(0.0); } BindProgramPipeline(0); DeleteProgramPipelines(1, &pipeline); - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} } // KHR-GL4x.geometry_shader.api.program_pipeline_vs_gs_capture: a vertex-only separable program @@ -710,8 +705,7 @@ void main() { color = vec4(vertexID[0]); gl_Position = vec4(0.0); EmitVertex(); BindProgramPipeline(0); DeleteProgramPipelines(1, &pipeline); - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} } // --------------------------------------------------------------------------------------------- diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index b7d04644..4893a0af 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -38,8 +38,7 @@ protected: // GL error flags are sticky per code and the context outlives an individual test in this // binary, so a pending error would be handed to whoever runs next. static void DrainProgramTestErrors() { - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} } }; @@ -474,7 +473,7 @@ namespace { class ScopedLimitsBackend { public: - ScopedLimitsBackend(): m_previous(Move(MG_Backend::pActiveBackendObject)) { + ScopedLimitsBackend() : m_previous(Move(MG_Backend::pActiveBackendObject)) { MG_Backend::pActiveBackendObject = MakeUnique(); } ~ScopedLimitsBackend() { MG_Backend::pActiveBackendObject = Move(m_previous); } @@ -564,8 +563,7 @@ void main() { ASSERT_EQ(spvReflectCreateShaderModule(spirv.size() * sizeof(Uint), spirv.data(), &module), SPV_REFLECT_RESULT_SUCCESS); uint32_t count = 0; - ASSERT_EQ(spvReflectEnumerateDescriptorBindings(&module, &count, nullptr), - SPV_REFLECT_RESULT_SUCCESS); + ASSERT_EQ(spvReflectEnumerateDescriptorBindings(&module, &count, nullptr), SPV_REFLECT_RESULT_SUCCESS); std::vector bindings(count); if (count != 0) { ASSERT_EQ(spvReflectEnumerateDescriptorBindings(&module, &count, bindings.data()), @@ -579,8 +577,7 @@ void main() { return lhs->binding < rhs->binding; }); for (const SpvReflectDescriptorBinding* binding : bindings) { - if (binding == nullptr || - binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { + if (binding == nullptr || binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) { continue; } const String name = normalise(*binding); @@ -613,9 +610,7 @@ void main() { << "the arrayed SSBO must collapse to one entry named without its subscript"; // The atomic counter is in the BACKEND's space even though GL's own enumeration excludes it. EXPECT_NE(std::find_if(archived.begin(), archived.end(), - [](const String& name) { - return name.rfind("gl_AtomicCounterBlock", 0) == 0; - }), + [](const String& name) { return name.rfind("gl_AtomicCounterBlock", 0) == 0; }), archived.end()) << "the synthesized atomic-counter block is a STORAGE_BUFFER descriptor in SPIR-V, so " "DirectVulkan's index space contains it and the archive has to as well"; @@ -741,8 +736,7 @@ TEST_F(ProgramTest, CompileAndLink) { // glGetnUniform* (GL 4.5 / KHR_robustness): the same answers, bounded by bufSize. A // destination too small for the uniform's components is GL_INVALID_OPERATION and untouched. - while (GetError() != GL_NO_ERROR) { - } + while (GetError() != GL_NO_ERROR) {} float boundedRed[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; GetnUniformfv(program, locRed, static_cast(3 * sizeof(float)), boundedRed); EXPECT_EQ(GetError(), static_cast(GL_NO_ERROR)); @@ -769,7 +763,8 @@ TEST_F(ProgramTest, CompileAndLink) { String source; auto& spirvCode = shaderSpirvs[index]; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -1613,7 +1608,8 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitVertexIn) { char* pSrcVertIn = nullptr; const char* needle = "layout(location = 2) in vec2 UV0;"; for (auto spirv : spirvs) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirv, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -1838,7 +1834,8 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) { char* pSrcfragOut = nullptr; const char* needle = "layout(location = 7) out vec4 fragColor;"; // for (auto spirv: spirvs) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -2131,7 +2128,8 @@ TEST_F(ProgramTest, CompileShaderWithSamplerAsVarName) { auto programObject = MG_State::pGLContext->GetCurrentProgram(); auto& spirvs = programObject->GetGeneratedSpirv(); auto& fragSpirv = spirvs[programObject->GetShaderIndexByStage(ShaderStage::Fragment)]; - MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(fragSpirv, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); @@ -2155,17 +2153,11 @@ namespace { const char* vsSrc = R"(#version 330 core void main() { gl_Position = vec4(0.0); } )"; - std::string fsSrc = std::string("#version 330 core\n") + - "layout(std140" + matrixLayout + ") uniform Block {\n" + - " float uScalar;\n" + - " vec4 uArray[3];\n" + - " mat4 uMatrix;\n" + - "};\n" + - "uniform sampler2D uTex;\n" + - "out vec4 fragColor;\n" + + std::string fsSrc = std::string("#version 330 core\n") + "layout(std140" + matrixLayout + + ") uniform Block {\n" + " float uScalar;\n" + " vec4 uArray[3];\n" + + " mat4 uMatrix;\n" + "};\n" + "uniform sampler2D uTex;\n" + "out vec4 fragColor;\n" + "void main() {\n" + - " fragColor = texture(uTex, uArray[0].xy) * uScalar * uMatrix[0];\n" + - "}\n"; + " fragColor = texture(uTex, uArray[0].xy) * uScalar * uMatrix[0];\n" + "}\n"; const char* fsPtr = fsSrc.c_str(); GLuint vs = CreateShader(GL_VERTEX_SHADER); @@ -2265,8 +2257,7 @@ TEST_F(ProgramTest, GetActiveUniformsivStd140Block) { GLint size = 0; GLenum type = 0; GetActiveUniform(program, m, sizeof(nameBuf), &nameLen, &size, &type, nameBuf); - EXPECT_EQ(QueryUniformiv(program, m, GL_UNIFORM_NAME_LENGTH), - static_cast(std::strlen(nameBuf) + 1)); + EXPECT_EQ(QueryUniformiv(program, m, GL_UNIFORM_NAME_LENGTH), static_cast(std::strlen(nameBuf) + 1)); EXPECT_EQ(GetError(), GL_NO_ERROR); } @@ -2405,7 +2396,7 @@ void main() { ASSERT_GE(locUiOne, 0) << "body: '" << body << "'"; UseProgram(program); - Uniform1i(locOne, 1); // crashed with a null MapUBO() before the fallback storage + Uniform1i(locOne, 1); // crashed with a null MapUBO() before the fallback storage Uniform1i(locUiOne, 2); EXPECT_EQ(GetError(), GL_NO_ERROR) << "body: '" << body << "'"; @@ -2458,8 +2449,7 @@ void main() { // Distinct scalar leaves must land at distinct UBO offsets (they all aliased // offset 0 before the fix). - const char* scalarLeaves[] = {"s[0].b[0].a", "s[0].b[1].a", "s[0].b[2].a", "s[1].a", "s[1].b[1].a", - "s[1].b[2].a"}; + const char* scalarLeaves[] = {"s[0].b[0].a", "s[0].b[1].a", "s[0].b[2].a", "s[1].a", "s[1].b[1].a", "s[1].b[2].a"}; const GLfloat scalarValues[] = {0.5f, 0.25f, 0.125f, 7.0f, 3.0f, 4.0f}; for (SizeT i = 0; i < std::size(scalarLeaves); ++i) { const GLint loc = GetUniformLocation(program, scalarLeaves[i]); @@ -2588,8 +2578,7 @@ void main() { Uint firstOffset = MG_State::GLState::ProgramObject::kInvalidUniformOffset; for (int i = 0; i < outer; ++i) { for (int j = 0; j < inner; ++j) { - const std::string name = - std::string(base) + "[" + std::to_string(i) + "][" + std::to_string(j) + "]"; + const std::string name = std::string(base) + "[" + std::to_string(i) + "][" + std::to_string(j) + "]"; const GLint location = GetUniformLocation(program, name.c_str()); ASSERT_GE(location, 0) << name; const Uint offset = programObject->GetUniformOffset(static_cast(location)); @@ -2929,8 +2918,7 @@ TEST_F(ProgramTest, UniformBlockProgramInterfaceMatchesTheUniformBlockList) { GetProgramInterfaceiv(program, GL_SHADER_STORAGE_BLOCK, GL_ACTIVE_RESOURCES, &storageBlocks); EXPECT_EQ(storageBlocks, 1); EXPECT_EQ(GetProgramResourceIndex(program, GL_UNIFORM_BLOCK, "AVeryLongStorageBlockName"), GL_INVALID_INDEX); - EXPECT_NE(GetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "AVeryLongStorageBlockName"), - GL_INVALID_INDEX); + EXPECT_NE(GetProgramResourceIndex(program, GL_SHADER_STORAGE_BLOCK, "AVeryLongStorageBlockName"), GL_INVALID_INDEX); // ... and the buffer variable by GL_BUFFER_VARIABLE, not GL_UNIFORM. EXPECT_NE(GetProgramResourceIndex(program, GL_BUFFER_VARIABLE, "storageVec"), GL_INVALID_INDEX); EXPECT_EQ(GetProgramResourceIndex(program, GL_UNIFORM, "storageVec"), GL_INVALID_INDEX); @@ -2948,8 +2936,7 @@ TEST_F(ProgramTest, UniformBlockProgramInterfaceMatchesTheUniformBlockList) { ASSERT_EQ(memberCount, 1) << blockName; const GLenum activeVariables = GL_ACTIVE_VARIABLES; GLint memberIndex = -1; - GetProgramResourceiv(program, GL_UNIFORM_BLOCK, interfaceIndex, 1, &activeVariables, 1, nullptr, - &memberIndex); + GetProgramResourceiv(program, GL_UNIFORM_BLOCK, interfaceIndex, 1, &activeVariables, 1, nullptr, &memberIndex); GLint viaBlockiv = -1; GetActiveUniformBlockiv(program, interfaceIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &viaBlockiv); EXPECT_EQ(memberIndex, viaBlockiv) << blockName; @@ -2966,8 +2953,7 @@ TEST_F(ProgramTest, ActiveUniformsExcludeBufferVariablesButKeepAtomicCounters) { GLint activeUniforms = -1; GetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms); - ASSERT_EQ(activeUniforms, 4) - << "uboVec, uboVec2[0], uScale and counter - storageVec is a buffer variable"; + ASSERT_EQ(activeUniforms, 4) << "uboVec, uboVec2[0], uScale and counter - storageVec is a buffer variable"; // Neither spelling of the buffer variable is a uniform index. EXPECT_EQ(UniformIndexByName(program, "storageVec"), GL_INVALID_INDEX); @@ -2989,8 +2975,7 @@ TEST_F(ProgramTest, ActiveUniformsExcludeBufferVariablesButKeepAtomicCounters) { // uniform block, and still points at its atomic counter BUFFER. const GLuint counter = UniformIndexByName(program, "counter"); ASSERT_NE(counter, GL_INVALID_INDEX); - EXPECT_EQ(QueryUniformiv(program, counter, GL_UNIFORM_TYPE), - static_cast(GL_UNSIGNED_INT_ATOMIC_COUNTER)); + EXPECT_EQ(QueryUniformiv(program, counter, GL_UNIFORM_TYPE), static_cast(GL_UNSIGNED_INT_ATOMIC_COUNTER)); EXPECT_EQ(QueryUniformiv(program, counter, GL_UNIFORM_BLOCK_INDEX), -1); EXPECT_EQ(QueryUniformiv(program, counter, GL_UNIFORM_ATOMIC_COUNTER_BUFFER_INDEX), 0); EXPECT_EQ(QueryUniformiv(program, counter, GL_UNIFORM_OFFSET), 0); @@ -3005,8 +2990,7 @@ TEST_F(ProgramTest, ActiveUniformsExcludeBufferVariablesButKeepAtomicCounters) { GetActiveAtomicCounterBufferiv(program, 0, GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTERS, &counterCount); ASSERT_EQ(counterCount, 1); GLint counterIndex = -1; - GetActiveAtomicCounterBufferiv(program, 0, GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES, - &counterIndex); + GetActiveAtomicCounterBufferiv(program, 0, GL_ATOMIC_COUNTER_BUFFER_ACTIVE_ATOMIC_COUNTER_INDICES, &counterIndex); EXPECT_EQ(static_cast(counterIndex), counter); GLint counterBinding = -1; GetActiveAtomicCounterBufferiv(program, 0, GL_ATOMIC_COUNTER_BUFFER_BINDING, &counterBinding); @@ -3134,8 +3118,7 @@ void main() { fragColor = uUsedColor; } GLint size = 0; GLenum type = 0; GetActiveUniform(program, static_cast(i), sizeof(nameBuf), &nameLen, &size, &type, nameBuf); - EXPECT_TRUE(std::strcmp(nameBuf, "uDeadVec") != 0 && std::strcmp(nameBuf, "uDeadFloat") != 0) - << nameBuf; + EXPECT_TRUE(std::strcmp(nameBuf, "uDeadVec") != 0 && std::strcmp(nameBuf, "uDeadFloat") != 0) << nameBuf; } // No named blocks are declared, so GL must see zero uniform blocks - the global @@ -3334,8 +3317,8 @@ namespace { Vector esslModules; auto programObj = MG_State::pGLContext->GetProgramObject(program); for (auto& spirvCode : programObj->GetGeneratedSpirv()) { - MG_Util::ShaderTranspiler::SpvcSession spvcSession( - spirvCode, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode, + MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); spvc_compiler_options options; spvcSession.CreateOptions(&options); spvc_compiler_options_set_uint(options, SPVC_COMPILER_OPTION_GLSL_VERSION, 320); @@ -3434,8 +3417,7 @@ namespace { const MobileGL::String needle = name + "("; for (SizeT pos = essl.find(needle); pos != String::npos; pos = essl.find(needle, pos + 1)) { const char before = pos == 0 ? ' ' : essl[pos - 1]; - const bool isIdentifierChar = - std::isalnum(static_cast(before)) != 0 || before == '_'; + const bool isIdentifierChar = std::isalnum(static_cast(before)) != 0 || before == '_'; if (!isIdentifierChar) return true; } return false; @@ -3538,7 +3520,6 @@ void main() { fragColor = vec4(pow(uBase, 2.2), 1.0); } EXPECT_EQ(GetError(), GL_NO_ERROR); } - // --------------------------------------------------------------------------- // P0b: source-hash dedupe for shader recompiles. // Layer 1 - the same shader object re-sourced with byte-identical text keeps its @@ -3911,8 +3892,7 @@ void main() { fragColor = vec4(1.0); } // GL 4.6 core 7.1: shaderType is an enum, so an unrecognised one is INVALID_ENUM - it used to be // reported as INVALID_VALUE. glCreateShaderProgramv adds a count < 0 gate ahead of everything. TEST_F(ProgramTest, CreateShaderAndCreateShaderProgramvReportTheRightErrorClasses) { - while (GetError() != GL_NO_ERROR) { - } + while (GetError() != GL_NO_ERROR) {} EXPECT_EQ(CreateShader(GL_FLOAT), 0u); EXPECT_EQ(GetError(), GL_INVALID_ENUM); @@ -3988,8 +3968,7 @@ void main() { fragColor = vec4(1.0); } const GLuint fs = CompileShaderChecked(GL_FRAGMENT_SHADER, fsSource); { - const String source = String("#version 430 core\nlayout(location = ") + - std::to_string(maxLocations - 1) + + const String source = String("#version 430 core\nlayout(location = ") + std::to_string(maxLocations - 1) + ") uniform vec4 uAtLimit;\nvoid main() { gl_Position = uAtLimit; }\n"; const GLuint vs = CompileShaderChecked(GL_VERTEX_SHADER, source.c_str()); const GLuint program = LinkVsFs(vs, fs, GL_TRUE); @@ -3997,8 +3976,7 @@ void main() { fragColor = vec4(1.0); } << "the last location in the pool is legal and must come back verbatim"; } { - const String source = String("#version 430 core\nlayout(location = ") + - std::to_string(maxLocations - 4) + + const String source = String("#version 430 core\nlayout(location = ") + std::to_string(maxLocations - 4) + ") uniform vec4 uSpill[8];\nvoid main() { gl_Position = uSpill[0]; }\n"; const GLuint vs = CompileShaderChecked(GL_VERTEX_SHADER, source.c_str()); (void)LinkVsFs(vs, fs, GL_FALSE); @@ -4052,8 +4030,8 @@ void main() { GLint arrayStride; }; const Expectation expectations[] = { - {"ac_counter0", 1, 0, 0}, {"ac_counter1", 1, 4, 0}, {"ac_counter2", 1, 8, 0}, - {"ac_counter67[0]", 2, 12, 4}, {"ac_counter3", 1, 20, 0}, + {"ac_counter0", 1, 0, 0}, {"ac_counter1", 1, 4, 0}, {"ac_counter2", 1, 8, 0}, + {"ac_counter67[0]", 2, 12, 4}, {"ac_counter3", 1, 20, 0}, }; for (const auto& expected : expectations) { @@ -4171,8 +4149,8 @@ TEST_F(ProgramTest, ImplicitLocationStaysInRangeWhenABufferBlockSharesTheProgram const GLint location = GetUniformLocation(program, implicitName.c_str()); EXPECT_EQ(location, holeBase) << "the implicit uniform must take the one free span left"; EXPECT_LT(location + implicitCount, maxLocations + 1) - << "locations " << location << ".." << (location + implicitCount - 1) - << " must stay inside 0.." << (maxLocations - 1); + << "locations " << location << ".." << (location + implicitCount - 1) << " must stay inside 0.." + << (maxLocations - 1); EXPECT_EQ(GetUniformLocation(program, "cs_result"), -1); }; @@ -4248,8 +4226,7 @@ void main() { }; const auto atTheLimit = buildWith(maxPatchVertices); - EXPECT_EQ(atTheLimit.first, GL_TRUE) - << "exactly GL_MAX_PATCH_VERTICES is legal: " << atTheLimit.second; + EXPECT_EQ(atTheLimit.first, GL_TRUE) << "exactly GL_MAX_PATCH_VERTICES is legal: " << atTheLimit.second; const auto pastTheLimit = buildWith(maxPatchVertices + 1); EXPECT_EQ(pastTheLimit.first, GL_FALSE) << "one past GL_MAX_PATCH_VERTICES must not link"; @@ -4459,8 +4436,7 @@ void main() { fragColorA = vec4(1.0); fragColorB = vec4(0.5); } GetProgramInfoLog(program, sizeof(log), nullptr, log); return std::string(log); }(); - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } // An API colour index of ZERO is "no override", not "index 0". glBindFragDataLocation is @@ -4510,8 +4486,7 @@ void main() { fragColor0 = vec4(1.0); fragColor1 = vec4(0.5); } GetProgramiv(program, GL_LINK_STATUS, &linkStatus); EXPECT_EQ(linkStatus, GL_TRUE); EXPECT_EQ(GetFragDataIndex(program, "fragColor1"), 1); - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } TEST_F(ProgramTest, GetProgramivReportsTheGeometryStageLinkProperties) { @@ -4523,8 +4498,8 @@ void main() { EndPrimitive(); } )"; - const GLuint program = - LinkStages({{GL_VERTEX_SHADER, kPassthroughVs}, {GL_GEOMETRY_SHADER, gs}, {GL_FRAGMENT_SHADER, kPassthroughFs}}); + const GLuint program = LinkStages( + {{GL_VERTEX_SHADER, kPassthroughVs}, {GL_GEOMETRY_SHADER, gs}, {GL_FRAGMENT_SHADER, kPassthroughFs}}); GLint value = -1; GetProgramiv(program, GL_GEOMETRY_INPUT_TYPE, &value); @@ -4541,8 +4516,8 @@ void main() { // says "a linked program object with a geometry shader", which the conformance suite checks // from both sides. const GLuint noGeometry = LinkStages({{GL_VERTEX_SHADER, kPassthroughVs}, {GL_FRAGMENT_SHADER, kPassthroughFs}}); - for (const GLenum pname : {GL_GEOMETRY_INPUT_TYPE, GL_GEOMETRY_OUTPUT_TYPE, GL_GEOMETRY_VERTICES_OUT, - GL_GEOMETRY_SHADER_INVOCATIONS}) { + for (const GLenum pname : + {GL_GEOMETRY_INPUT_TYPE, GL_GEOMETRY_OUTPUT_TYPE, GL_GEOMETRY_VERTICES_OUT, GL_GEOMETRY_SHADER_INVOCATIONS}) { GetProgramiv(noGeometry, pname, &value); EXPECT_EQ(GetError(), static_cast(GL_INVALID_OPERATION)) << "pname " << pname; } @@ -4613,15 +4588,18 @@ void main() { gl_Position = gl_in[0].gl_Position; } TEST_F(ProgramTest, ContextWideTessellationPropertiesAnswerEveryWidth) { GLfloat outer[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; GetFloatv(GL_PATCH_DEFAULT_OUTER_LEVEL, outer); - for (const GLfloat level : outer) EXPECT_FLOAT_EQ(level, 1.0f); + for (const GLfloat level : outer) + EXPECT_FLOAT_EQ(level, 1.0f); GLfloat inner[2] = {-1.0f, -1.0f}; GetFloatv(GL_PATCH_DEFAULT_INNER_LEVEL, inner); - for (const GLfloat level : inner) EXPECT_FLOAT_EQ(level, 1.0f); + for (const GLfloat level : inner) + EXPECT_FLOAT_EQ(level, 1.0f); GLboolean outerBools[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE}; GetBooleanv(GL_PATCH_DEFAULT_OUTER_LEVEL, outerBools); - for (const GLboolean level : outerBools) EXPECT_EQ(level, GL_TRUE); + for (const GLboolean level : outerBools) + EXPECT_EQ(level, GL_TRUE); GLint restart = -1; GetIntegerv(GL_PRIMITIVE_RESTART_FOR_PATCHES_SUPPORTED, &restart); @@ -4702,16 +4680,16 @@ TEST_F(ProgramTest, AtomicCounterBindingAtTheLimitIsRejected) { GetIntegerv(GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, &maxAtomicBindings); ASSERT_GT(maxAtomicBindings, 0); - const String legal = "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + - std::to_string(maxAtomicBindings - 1) + - ") uniform atomic_uint counter;\nvoid main() { atomicCounterIncrement(counter); }\n"; + const String legal = + "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + std::to_string(maxAtomicBindings - 1) + + ") uniform atomic_uint counter;\nvoid main() { atomicCounterIncrement(counter); }\n"; EXPECT_EQ(LinkComputeProgramStatus(legal.c_str()), GL_TRUE) << "the last counter binding in the range is legal and must still link"; String infoLog; - const String overRange = "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + - std::to_string(maxAtomicBindings) + - ") uniform atomic_uint counter;\nvoid main() { atomicCounterIncrement(counter); }\n"; + const String overRange = + "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + std::to_string(maxAtomicBindings) + + ") uniform atomic_uint counter;\nvoid main() { atomicCounterIncrement(counter); }\n"; EXPECT_EQ(LinkComputeProgramStatus(overRange.c_str(), &infoLog), GL_FALSE) << "an atomic_uint binding at GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS must be rejected"; @@ -4754,9 +4732,9 @@ TEST_F(ProgramTest, StorageBlockBindingAtTheLimitIsStillRejected) { GTEST_SKIP() << "no storage-buffer binding points advertised in this configuration"; } - const String overRange = "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + - std::to_string(maxStorageBindings) + - ", std430) buffer Blk { vec4 v; } blk;\nvoid main() { blk.v = vec4(0.0); }\n"; + const String overRange = + "#version 430 core\nlayout(local_size_x = 1) in;\nlayout(binding = " + std::to_string(maxStorageBindings) + + ", std430) buffer Blk { vec4 v; } blk;\nvoid main() { blk.v = vec4(0.0); }\n"; EXPECT_EQ(LinkComputeProgramStatus(overRange.c_str()), GL_FALSE); DrainProgramTestErrors(); @@ -4846,8 +4824,7 @@ TEST_F(ProgramTest, UniformEntryPointsRejectAnUnlinkedProgram) { const GLfloat value = 1.0f; ProgramUniform1fv(program, -1, 1, &value); - EXPECT_EQ(GetError(), GL_INVALID_OPERATION) - << "the link check has to run BEFORE the location == -1 early-out"; + EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "the link check has to run BEFORE the location == -1 early-out"; ProgramUniform1f(program, 0, 1.0f); EXPECT_EQ(GetError(), GL_INVALID_OPERATION); @@ -4925,41 +4902,41 @@ namespace { EXPECT_EQ(GetError(), GL_NO_ERROR) << "the call recorded more than one error"; } - // 255 words - const unsigned int kVertexModule[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000020u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, - 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, - 0x0009000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000012u, 0x0000001eu, - 0x0000001fu, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, - 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, - 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, - 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, - 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u, 0x00040005u, 0x00000012u, 0x736f5061u, 0x00000000u, - 0x00040005u, 0x00000014u, 0x61635375u, 0x0000656cu, 0x00050005u, 0x0000001eu, 0x565f6c67u, 0x65747265u, - 0x00444978u, 0x00060005u, 0x0000001fu, 0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x00000044u, 0x00030047u, - 0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, - 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, - 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000012u, - 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x00000001u, 0x00000003u, 0x00040047u, 0x0000001eu, - 0x0000000bu, 0x00000005u, 0x00040047u, 0x0000001fu, 0x0000000bu, 0x00000006u, 0x00020013u, 0x00000002u, - 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, - 0x00000006u, 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, - 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, - 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, - 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, - 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040017u, 0x00000010u, 0x00000006u, 0x00000002u, - 0x00040020u, 0x00000011u, 0x00000001u, 0x00000010u, 0x0004003bu, 0x00000011u, 0x00000012u, 0x00000001u, - 0x00040032u, 0x00000006u, 0x00000014u, 0x3f800000u, 0x0004002bu, 0x00000006u, 0x00000016u, 0x00000000u, - 0x0004002bu, 0x00000006u, 0x00000017u, 0x3f800000u, 0x00040020u, 0x0000001bu, 0x00000003u, 0x00000007u, - 0x00040020u, 0x0000001du, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x0000001du, 0x0000001eu, 0x00000001u, - 0x0004003bu, 0x0000001du, 0x0000001fu, 0x00000001u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, - 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000010u, 0x00000013u, 0x00000012u, 0x0005008eu, - 0x00000010u, 0x00000015u, 0x00000013u, 0x00000014u, 0x00050051u, 0x00000006u, 0x00000018u, 0x00000015u, - 0x00000000u, 0x00050051u, 0x00000006u, 0x00000019u, 0x00000015u, 0x00000001u, 0x00070050u, 0x00000007u, - 0x0000001au, 0x00000018u, 0x00000019u, 0x00000016u, 0x00000017u, 0x00050041u, 0x0000001bu, 0x0000001cu, - 0x0000000du, 0x0000000fu, 0x0003003eu, 0x0000001cu, 0x0000001au, 0x000100fdu, 0x00010038u, - }; + // 255 words + const unsigned int kVertexModule[] = { + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000020u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0009000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000012u, 0x0000001eu, + 0x0000001fu, 0x00030003u, 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, + 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, + 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, + 0x746e696fu, 0x657a6953u, 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, + 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, + 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u, 0x00040005u, 0x00000012u, 0x736f5061u, 0x00000000u, + 0x00040005u, 0x00000014u, 0x61635375u, 0x0000656cu, 0x00050005u, 0x0000001eu, 0x565f6c67u, 0x65747265u, + 0x00444978u, 0x00060005u, 0x0000001fu, 0x495f6c67u, 0x6174736eu, 0x4965636eu, 0x00000044u, 0x00030047u, + 0x0000000bu, 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, + 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, + 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000012u, + 0x0000001eu, 0x00000000u, 0x00040047u, 0x00000014u, 0x00000001u, 0x00000003u, 0x00040047u, 0x0000001eu, + 0x0000000bu, 0x00000005u, 0x00040047u, 0x0000001fu, 0x0000000bu, 0x00000006u, 0x00020013u, 0x00000002u, + 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, + 0x00000006u, 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, + 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, + 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, + 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, + 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040017u, 0x00000010u, 0x00000006u, 0x00000002u, + 0x00040020u, 0x00000011u, 0x00000001u, 0x00000010u, 0x0004003bu, 0x00000011u, 0x00000012u, 0x00000001u, + 0x00040032u, 0x00000006u, 0x00000014u, 0x3f800000u, 0x0004002bu, 0x00000006u, 0x00000016u, 0x00000000u, + 0x0004002bu, 0x00000006u, 0x00000017u, 0x3f800000u, 0x00040020u, 0x0000001bu, 0x00000003u, 0x00000007u, + 0x00040020u, 0x0000001du, 0x00000001u, 0x0000000eu, 0x0004003bu, 0x0000001du, 0x0000001eu, 0x00000001u, + 0x0004003bu, 0x0000001du, 0x0000001fu, 0x00000001u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, + 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000010u, 0x00000013u, 0x00000012u, 0x0005008eu, + 0x00000010u, 0x00000015u, 0x00000013u, 0x00000014u, 0x00050051u, 0x00000006u, 0x00000018u, 0x00000015u, + 0x00000000u, 0x00050051u, 0x00000006u, 0x00000019u, 0x00000015u, 0x00000001u, 0x00070050u, 0x00000007u, + 0x0000001au, 0x00000018u, 0x00000019u, 0x00000016u, 0x00000017u, 0x00050041u, 0x0000001bu, 0x0000001cu, + 0x0000000du, 0x0000000fu, 0x0003003eu, 0x0000001cu, 0x0000001au, 0x000100fdu, 0x00010038u, + }; } // namespace TEST_F(ProgramTest, ShaderBinaryStoresASpirvModuleAndTheStateQueryReportsIt) { diff --git a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp index 35f9d150..212f9889 100644 --- a/MobileGL/MG_Test/Program/ProgramUtilTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramUtilTest.cpp @@ -170,16 +170,13 @@ void main() { const auto countOccurrences = [](const String& text, const String& needle) { SizeT count = 0; - for (SizeT offset = 0; (offset = text.find(needle, offset)) != String::npos; - offset += needle.size()) { + for (SizeT offset = 0; (offset = text.find(needle, offset)) != String::npos; offset += needle.size()) { ++count; } return count; }; - EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageReadWithoutFormat"), 1u) - << outputText; - EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageWriteWithoutFormat"), 1u) - << outputText; + EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageReadWithoutFormat"), 1u) << outputText; + EXPECT_EQ(countOccurrences(outputText, "OpCapability StorageImageWriteWithoutFormat"), 1u) << outputText; EXPECT_EQ(countOccurrences(outputText, "2D 0 0 0 2 Unknown"), 1u) << outputText; EXPECT_NE(outputText.find("Binding 0"), String::npos) << outputText; EXPECT_NE(outputText.find("Binding 1"), String::npos) << outputText; @@ -296,10 +293,8 @@ TEST_F(ProgramUtilTest, UnformattedFloatStorageImagesKeepFloatAtomicImageTypesTy EXPECT_EQ(outputText.find("StorageImageReadWithoutFormat"), String::npos) << outputText; EXPECT_EQ(outputText.find("StorageImageWriteWithoutFormat"), String::npos) << outputText; String validationDiagnostics; - tools.SetMessageConsumer([&validationDiagnostics](spv_message_level_t, const char*, - const spv_position_t&, const char* message) { - validationDiagnostics += message; - }); + tools.SetMessageConsumer([&validationDiagnostics](spv_message_level_t, const char*, const spv_position_t&, + const char* message) { validationDiagnostics += message; }); EXPECT_TRUE(tools.Validate(outputBinary)) << validationDiagnostics; } @@ -506,8 +501,8 @@ void main() { ShaderAttrib vertexAttrib{.shaderType = GL_VERTEX_SHADER, .sourceStr = vertexSource}; auto vertexResult = ShaderCompiler::CompileShader(vertexAttrib); if (!vertexResult) { - FAIL() << "errc: " << vertexResult.error().errc << "\nlog: " << vertexResult.error().log - << "\nsource:\n" << vertexSource; + FAIL() << "errc: " << vertexResult.error().errc << "\nlog: " << vertexResult.error().log << "\nsource:\n" + << vertexSource; } String fragmentSource = R"(#version 150 @@ -527,8 +522,8 @@ void main() { ShaderAttrib fragmentAttrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fragmentSource}; auto fragmentResult = ShaderCompiler::CompileShader(fragmentAttrib); if (!fragmentResult) { - FAIL() << "errc: " << fragmentResult.error().errc << "\nlog: " << fragmentResult.error().log - << "\nsource:\n" << fragmentSource; + FAIL() << "errc: " << fragmentResult.error().errc << "\nlog: " << fragmentResult.error().log << "\nsource:\n" + << fragmentSource; } } @@ -609,25 +604,24 @@ TEST_F(ProgramUtilTest, PreprocessRejectsMalformedVersionDirectives) { PreprocessShaderSource(ShaderStage::Fragment, src); ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = src}; auto res = ShaderCompiler::CompileShader(attrib); - return res ? false : true; // "rejects" == compile failed + return res ? false : true; // "rejects" == compile failed }; // Silently legalized today - the five this fix must flip to rejection: - EXPECT_TRUE(rejects(String("#version 329") + body)) << "329 is not a real version"; - EXPECT_TRUE(rejects(String("#version 331") + body)) << "331 is not a real version"; - EXPECT_TRUE(rejects(String("#version 330 foo") + body)) << "unknown profile keyword"; - EXPECT_TRUE(rejects(String("#version 330.0") + body)) << "float literal, not an int token"; + EXPECT_TRUE(rejects(String("#version 329") + body)) << "329 is not a real version"; + EXPECT_TRUE(rejects(String("#version 331") + body)) << "331 is not a real version"; + EXPECT_TRUE(rejects(String("#version 330 foo") + body)) << "unknown profile keyword"; + EXPECT_TRUE(rejects(String("#version 330.0") + body)) << "float literal, not an int token"; EXPECT_TRUE(rejects(String("#version 330 foobar") + body)) << "trailing tokens after a valid decl"; // Already rejected (no leading integer, or #version is not the first token) - pinned so a future // change to the normalizer cannot start legalizing them either: - EXPECT_TRUE(rejects(String("#version") + body)) << "missing version number"; - EXPECT_TRUE(rejects(String("#version foobar") + body)) << "identifier where the int belongs"; - EXPECT_TRUE(rejects(String("#version AAA") + body)) << "identifier where the int belongs"; + EXPECT_TRUE(rejects(String("#version") + body)) << "missing version number"; + EXPECT_TRUE(rejects(String("#version foobar") + body)) << "identifier where the int belongs"; + EXPECT_TRUE(rejects(String("#version AAA") + body)) << "identifier where the int belongs"; EXPECT_TRUE(rejects(String("precision mediump float;\n#version 330") + body)) << "#version must be the first statement"; - EXPECT_TRUE(rejects(String("#define FOO BAR\n#version 330") + body)) - << "#version must precede a #define"; + EXPECT_TRUE(rejects(String("#define FOO BAR\n#version 330") + body)) << "#version must precede a #define"; } // The PASS half of the same CTS group: a valid decl, and #version preceded only by whitespace or a @@ -645,10 +639,8 @@ TEST_F(ProgramUtilTest, PreprocessKeepsValidVersionDirectivesCompiling) { }; EXPECT_TRUE(compiles(String("#version 330 core") + body)); - EXPECT_TRUE(compiles(String("\n#version 330 core") + body)) - << "leading whitespace is legal before #version"; - EXPECT_TRUE(compiles(String("// test\n#version 330 core") + body)) - << "a leading comment is legal before #version"; + EXPECT_TRUE(compiles(String("\n#version 330 core") + body)) << "leading whitespace is legal before #version"; + EXPECT_TRUE(compiles(String("// test\n#version 330 core") + body)) << "a leading comment is legal before #version"; } TEST_F(ProgramUtilTest, PreprocessUsesRealSpacedVersionDirectiveForInjectedOutput) { @@ -921,7 +913,6 @@ void main() { } } - const char* vs = R"(#version 150 in vec4 Position; @@ -1253,10 +1244,11 @@ void main() { fragColor = vColor; } auto essl = ShaderCompiler::DecompileShader(session); if (!essl) FAIL() << "decompile errc: " << essl.error().errc << "\nlog: " << essl.error().log; - EXPECT_NE(essl.value().find("noperspective"), String::npos) - << "noperspective was lost before it reached SPIR-V:\n" << essl.value(); + EXPECT_NE(essl.value().find("noperspective"), String::npos) << "noperspective was lost before it reached SPIR-V:\n" + << essl.value(); EXPECT_NE(essl.value().find("GL_NV_shader_noperspective_interpolation"), String::npos) - << "SPIRV-Cross must require the NV extension for ES noperspective:\n" << essl.value(); + << "SPIRV-Cross must require the NV extension for ES noperspective:\n" + << essl.value(); } // The old handling was a naked substring erase of "noperspective", so any identifier that merely @@ -1271,8 +1263,8 @@ out vec4 fragColor; void main() { fragColor = vec4(noperspectiveBlend); } )"; PreprocessShaderSource(ShaderStage::Fragment, source); - EXPECT_NE(source.find("noperspectiveBlend"), String::npos) - << "identifier was corrupted by substring stripping:\n" << source; + EXPECT_NE(source.find("noperspectiveBlend"), String::npos) << "identifier was corrupted by substring stripping:\n" + << source; } // The DirectGLES fallback for devices without GL_NV_shader_noperspective_interpolation: stripping the @@ -1315,10 +1307,10 @@ void main() { fragColor = vColor; } SpvcSession session(stripped, SessionUsageBit::Transpile); auto essl = ShaderCompiler::DecompileShader(session); if (!essl) FAIL() << "decompile errc: " << essl.error().errc << "\nlog: " << essl.error().log; - EXPECT_EQ(essl.value().find("noperspective"), String::npos) - << "the decoration should be gone:\n" << essl.value(); + EXPECT_EQ(essl.value().find("noperspective"), String::npos) << "the decoration should be gone:\n" << essl.value(); EXPECT_EQ(essl.value().find("GL_NV_shader_noperspective_interpolation"), String::npos) - << "no extension require without the decoration:\n" << essl.value(); + << "no extension require without the decoration:\n" + << essl.value(); } // Directly exercises BOTH decoration forms StripNoPerspectivePass handles: a plain-variable @@ -1374,8 +1366,8 @@ TEST_F(ProgramUtilTest, StripNoPerspectivePassRemovesBothDecorateForms) { String inputText; ASSERT_TRUE(tools.Disassemble(inputBinary, &inputText)); - ASSERT_EQ(countNoPerspective(inputText), 2u) - << "fixture must carry both a plain and a member NoPerspective:\n" << inputText; + ASSERT_EQ(countNoPerspective(inputText), 2u) << "fixture must carry both a plain and a member NoPerspective:\n" + << inputText; Vector outputBinary; ASSERT_TRUE(ShaderCompiler::StripNoPerspectiveForEssl(inputBinary, outputBinary)); @@ -1384,11 +1376,10 @@ TEST_F(ProgramUtilTest, StripNoPerspectivePassRemovesBothDecorateForms) { String outputText; ASSERT_TRUE(tools.Disassemble(outputBinary, &outputText)); EXPECT_EQ(countNoPerspective(outputText), 0u) - << "both NoPerspective decorations (OpDecorate and OpMemberDecorate) must be stripped:\n" << outputText; - EXPECT_NE(outputText.find("Flat"), String::npos) - << "the unrelated Flat decoration must survive:\n" << outputText; - EXPECT_NE(outputText.find("Location"), String::npos) - << "Location decorations must survive:\n" << outputText; + << "both NoPerspective decorations (OpDecorate and OpMemberDecorate) must be stripped:\n" + << outputText; + EXPECT_NE(outputText.find("Flat"), String::npos) << "the unrelated Flat decoration must survive:\n" << outputText; + EXPECT_NE(outputText.find("Location"), String::npos) << "Location decorations must survive:\n" << outputText; } // Phase 2 emulation - fragment side. On a device without the NV extension the NoPerspective input is @@ -1431,7 +1422,8 @@ void main() { f = vColor; } if (!essl) FAIL() << "decompile: " << essl.error().log; EXPECT_EQ(essl.value().find("noperspective"), String::npos) << essl.value(); EXPECT_EQ(essl.value().find("GL_NV_shader_noperspective_interpolation"), String::npos) << essl.value(); - EXPECT_NE(essl.value().find("gl_FragCoord"), String::npos) << "recovery must reference gl_FragCoord:\n" << essl.value(); + EXPECT_NE(essl.value().find("gl_FragCoord"), String::npos) << "recovery must reference gl_FragCoord:\n" + << essl.value(); } // Phase 2 emulation - vertex side. The NoPerspective output is pre-multiplied by gl_Position.w before @@ -1470,27 +1462,28 @@ void main() { gl_Position = pos; vColor = pos; } auto essl = ShaderCompiler::DecompileShader(session); if (!essl) FAIL() << "decompile: " << essl.error().log; EXPECT_EQ(essl.value().find("noperspective"), String::npos) << essl.value(); - EXPECT_NE(essl.value().find("gl_Position"), String::npos) << "pre-multiply must reference gl_Position:\n" << essl.value(); + EXPECT_NE(essl.value().find("gl_Position"), String::npos) << "pre-multiply must reference gl_Position:\n" + << essl.value(); } namespace { -// Compiles one shader stage through the full pipeline and returns its SPIR-V, or fails the test. -MobileGL::Vector CompileStageSpirv(GLenum type, const char* src) { - using namespace MG_Util::ShaderTranspiler; - ShaderAttrib attrib{.shaderType = type, .sourceStr = src}; - auto res = ShaderCompiler::CompileShader(attrib); - EXPECT_TRUE(static_cast(res)) << (res ? "" : res.error().log); - if (!res) return {}; - ProgramAttrib pa{.shaders = {res.value()}}; - auto pr = ShaderCompiler::LinkProgram(pa); - EXPECT_TRUE(static_cast(pr)) << (pr ? "" : pr.error().log); - if (!pr) return {}; - ProgramBinaryAttrib ba{.shaderTypes = {type}, .program = *pr.value()}; - auto br = ShaderCompiler::GetSpirvBinaryFromProgram(ba); - EXPECT_TRUE(static_cast(br)) << (br ? "" : br.error().log); - if (!br || br.value().empty()) return {}; - return br.value()[0]; -} + // Compiles one shader stage through the full pipeline and returns its SPIR-V, or fails the test. + MobileGL::Vector CompileStageSpirv(GLenum type, const char* src) { + using namespace MG_Util::ShaderTranspiler; + ShaderAttrib attrib{.shaderType = type, .sourceStr = src}; + auto res = ShaderCompiler::CompileShader(attrib); + EXPECT_TRUE(static_cast(res)) << (res ? "" : res.error().log); + if (!res) return {}; + ProgramAttrib pa{.shaders = {res.value()}}; + auto pr = ShaderCompiler::LinkProgram(pa); + EXPECT_TRUE(static_cast(pr)) << (pr ? "" : pr.error().log); + if (!pr) return {}; + ProgramBinaryAttrib ba{.shaderTypes = {type}, .program = *pr.value()}; + auto br = ShaderCompiler::GetSpirvBinaryFromProgram(ba); + EXPECT_TRUE(static_cast(br)) << (br ? "" : br.error().log); + if (!br || br.value().empty()) return {}; + return br.value()[0]; + } } // namespace // Regression: the vertex pre-multiply must be applied exactly once (in main), not once per function. @@ -1542,8 +1535,8 @@ void main() { f = vec4(vColor.x); } ASSERT_TRUE(tools.Disassemble(emulated, &dis)); ASSERT_TRUE(tools.Validate(emulated)) << dis; EXPECT_EQ(dis.find("NoPerspective"), String::npos) << dis; - EXPECT_NE(dis.find("FragCoord"), String::npos) - << "the component read must still be recovered via gl_FragCoord.w:\n" << dis; + EXPECT_NE(dis.find("FragCoord"), String::npos) << "the component read must still be recovered via gl_FragCoord.w:\n" + << dis; } // Coverage: a scalar float varying exercises the OpFMul path; a vector varying the OpVectorTimesScalar @@ -1567,7 +1560,8 @@ void main() { f = vec4(a, b, 1.0); } EXPECT_EQ(dis.find("NoPerspective"), String::npos) << dis; EXPECT_NE(dis.find("OpFMul"), String::npos) << "the scalar varying must scale with OpFMul:\n" << dis; EXPECT_NE(dis.find("OpVectorTimesScalar"), String::npos) - << "the vector varying must scale with OpVectorTimesScalar:\n" << dis; + << "the vector varying must scale with OpVectorTimesScalar:\n" + << dis; } const char* vs_location = R"(#version 460 @@ -2032,8 +2026,7 @@ TEST_F(ProgramUtilTest, DecomposeWorkgroupVec3InSpirvPass) { SpvcSession session(optimized, SessionUsageBit::Transpile); auto sourceRes = ShaderCompiler::DecompileShader(session); - ASSERT_TRUE(sourceRes.has_value()) << "errc: " << sourceRes.error().errc - << "\nlog: " << sourceRes.error().log; + ASSERT_TRUE(sourceRes.has_value()) << "errc: " << sourceRes.error().errc << "\nlog: " << sourceRes.error().log; const String& source = sourceRes.value(); // The decomposed output must not contain a `shared vec3` declaration. @@ -2042,9 +2035,8 @@ TEST_F(ProgramUtilTest, DecomposeWorkgroupVec3InSpirvPass) { << source; // It should now use a scalar array form (shared float ...). - EXPECT_NE(source.find("shared float"), std::string::npos) - << "Expected `shared float` in decomposed output:\n" - << source; + EXPECT_NE(source.find("shared float"), std::string::npos) << "Expected `shared float` in decomposed output:\n" + << source; EXPECT_EQ(source.find("= shared_memory[0]"), std::string::npos) << "Decomposed output kept an invalid whole-row shared-memory load:\n" @@ -2094,7 +2086,6 @@ void main() { ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(binRes->at(0), optimized)); } - TEST_F(ProgramUtilTest, PreprocessCoercesBlockPackingQualifiersToStd140) { using namespace MG_Util::ShaderTranspiler; @@ -2121,9 +2112,8 @@ void main() { EXPECT_NE(source.find("layout ( std140 ) uniform SpacedBlock"), String::npos); EXPECT_NE(source.find("layout(std140) uniform KeptBlock"), String::npos); - ShaderAttrib attrib{.shaderType = GL_VERTEX_SHADER, - .sourceStr = source, - .flags = ShaderCompileBits::CompileForOpenGL}; + ShaderAttrib attrib{ + .shaderType = GL_VERTEX_SHADER, .sourceStr = source, .flags = ShaderCompileBits::CompileForOpenGL}; auto res = ShaderCompiler::CompileShader(attrib); if (!res) { FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source; @@ -2378,7 +2368,6 @@ TEST_F(ProgramUtilTest, CompileEnvFingerprintTracksEveryInput) { CompileEnv otherExtensions = base; otherExtensions.advertisedExtensions.push_back(MobileGL::E_GL_ARB_gpu_shader_int64); EXPECT_NE(ComputeCompileEnvFingerprint(otherExtensions), baseline); - } // The no-backend fallback must stay exactly what the pipeline used to do inline: @@ -2639,8 +2628,7 @@ void main() { explicitVertexIns["a_TexCoord"] = 2; explicitVertexIns["iris_Normal"] = 10; explicitVertexIns["mc_Entity"] = 11; - ProgramAttrib programAttrib{.shaders = {shaderResult.value()}, - .explicitVertexInLocations = explicitVertexIns}; + ProgramAttrib programAttrib{.shaders = {shaderResult.value()}, .explicitVertexInLocations = explicitVertexIns}; auto programResult = ShaderCompiler::LinkProgram(programAttrib); ASSERT_TRUE(programResult) << programResult.error().log; @@ -2653,10 +2641,10 @@ void main() { // The authoritative check - this is the same validator whose VUID the driver enforces. spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String validatorMessages; - tools.SetMessageConsumer([&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - if (message != nullptr) validatorMessages += String(message) + "\n"; - }); + tools.SetMessageConsumer( + [&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + if (message != nullptr) validatorMessages += String(message) + "\n"; + }); EXPECT_TRUE(tools.Validate(vertexBinary)) << "the raw vertex module is not valid SPIR-V; Adreno rejects the whole pipeline for this " << "while lavapipe tolerates it:\n" @@ -2689,8 +2677,7 @@ void main() { for (const unsigned id : inputIds) { if (builtInIds.count(id) != 0) continue; const auto it = locationById.find(id); - ASSERT_NE(it, locationById.end()) - << "vertex input id " << id << " reached SPIR-V with no Location decoration"; + ASSERT_NE(it, locationById.end()) << "vertex input id " << id << " reached SPIR-V with no Location decoration"; EXPECT_TRUE(usedLocations.insert(it->second).second) << "two vertex inputs were assigned location " << it->second; ++checked; @@ -2718,10 +2705,17 @@ namespace { if (wordCount == 0 || i + wordCount > spirv.size()) break; if (opcode == kOpVariable && wordCount >= 4) { switch (spirv[i + 3]) { - case kStorageClassInput: ++census.inputCount; break; - case kStorageClassOutput: ++census.outputCount; break; - case kStorageClassPrivate: ++census.privateCount; break; - default: break; + case kStorageClassInput: + ++census.inputCount; + break; + case kStorageClassOutput: + ++census.outputCount; + break; + case kStorageClassPrivate: + ++census.privateCount; + break; + default: + break; } } else if (opcode == kOpFunctionCall) { ++census.functionCallCount; @@ -2762,12 +2756,10 @@ vec4 helperTint() { return vec4(1.0); } ADD_FAILURE() << programResult.error().log; return {}; } - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER}, - .program = *programResult.value()}; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_VERTEX_SHADER}, .program = *programResult.value()}; auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); if (!binaryResult || binaryResult->size() != 1u) { - ADD_FAILURE() << (binaryResult ? "unexpected module count" - : binaryResult.error().log); + ADD_FAILURE() << (binaryResult ? "unexpected module count" : binaryResult.error().log); return {}; } return binaryResult->front(); @@ -2802,10 +2794,10 @@ TEST_F(ProgramUtilTest, DeadPrivateChainVertexInputIsEliminatedFromOptimizedBina spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String validatorMessages; - tools.SetMessageConsumer([&validatorMessages](spv_message_level_t, const char*, - const spv_position_t&, const char* message) { - if (message != nullptr) validatorMessages += String(message) + "\n"; - }); + tools.SetMessageConsumer( + [&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + if (message != nullptr) validatorMessages += String(message) + "\n"; + }); EXPECT_TRUE(tools.Validate(optimized)) << validatorMessages; } @@ -3026,8 +3018,7 @@ namespace { ADD_FAILURE() << programResult.error().log; return {}; } - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, - .program = *programResult.value()}; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, .program = *programResult.value()}; auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); if (!binaryResult || binaryResult->size() != 1u) { ADD_FAILURE() << (binaryResult ? "unexpected module count" : binaryResult.error().log); @@ -3103,16 +3094,19 @@ void main() { const String disassembly = DisassembleSpirv(legalized); EXPECT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(legalized)) - << "no fragment output may be left indexed by anything but a constant:\n" << disassembly; + << "no fragment output may be left indexed by anything but a constant:\n" + << disassembly; EXPECT_EQ(disassembly.find("OpSwitch"), String::npos) - << "a loop-derived index must fold, not fall back to the switch lowering:\n" << disassembly; + << "a loop-derived index must fold, not fall back to the switch lowering:\n" + << disassembly; EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "the legalized module must stay validator-clean"; const String essl = DecompileToEssl(legalized); ASSERT_FALSE(essl.empty()); EXPECT_TRUE(AllArrayIndicesAreLiterals(essl, "coeff")) - << "the generated ESSL still indexes a fragment output with a non-constant:\n" << essl; + << "the generated ESSL still indexes a fragment output with a non-constant:\n" + << essl; } // Marking a loop for unrolling means marking every loop enclosing it - SPIRV-Tools only unrolls @@ -3173,8 +3167,7 @@ void main() { } )"); ASSERT_FALSE(raw.empty()); - ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)) - << DisassembleSpirv(raw); + ASSERT_TRUE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)) << DisassembleSpirv(raw); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); @@ -3184,18 +3177,23 @@ void main() { const String disassembly = DisassembleSpirv(legalized); EXPECT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(legalized)) - << "the uniform-driven index must be lowered away:\n" << disassembly; + << "the uniform-driven index must be lowered away:\n" + << disassembly; EXPECT_NE(disassembly.find("OpSwitch"), String::npos) - << "the dynamic write must become a switch over the array range:\n" << disassembly; + << "the dynamic write must become a switch over the array range:\n" + << disassembly; EXPECT_NE(disassembly.find("OpSelect"), String::npos) - << "the dynamic read must become constant-indexed loads and a select:\n" << disassembly; + << "the dynamic read must become constant-indexed loads and a select:\n" + << disassembly; EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) - << "the lowered module must stay validator-clean:\n" << disassembly; + << "the lowered module must stay validator-clean:\n" + << disassembly; const String essl = DecompileToEssl(legalized); ASSERT_FALSE(essl.empty()); EXPECT_TRUE(AllArrayIndicesAreLiterals(essl, "coeff")) - << "the generated ESSL still indexes a fragment output with a non-constant:\n" << essl; + << "the generated ESSL still indexes a fragment output with a non-constant:\n" + << essl; } // The bound on the folding half. The index here IS loop-derived, so unrolling would fold it - @@ -3227,19 +3225,23 @@ void main() { const String disassembly = DisassembleSpirv(legalized); EXPECT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(legalized)) - << "the index must be legalized even when the loop is left standing:\n" << disassembly; + << "the index must be legalized even when the loop is left standing:\n" + << disassembly; EXPECT_NE(disassembly.find("OpLoopMerge"), String::npos) << "a 512-trip loop must NOT be unrolled - that is the whole point of the cap:\n" << disassembly; EXPECT_NE(disassembly.find("OpSwitch"), String::npos) - << "with the loop standing, the write must go through the switch lowering:\n" << disassembly; + << "with the loop standing, the write must go through the switch lowering:\n" + << disassembly; EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) - << "lowering inside a loop body must stay validator-clean:\n" << disassembly; + << "lowering inside a loop body must stay validator-clean:\n" + << disassembly; const String essl = DecompileToEssl(legalized); ASSERT_FALSE(essl.empty()); EXPECT_TRUE(AllArrayIndicesAreLiterals(essl, "coeff")) - << "the generated ESSL still indexes a fragment output with a non-constant:\n" << essl; + << "the generated ESSL still indexes a fragment output with a non-constant:\n" + << essl; } // The gate: a fragment shader that never indexes an output array dynamically must come back byte @@ -3284,7 +3286,8 @@ void main() { )"); ASSERT_FALSE(raw.empty()); EXPECT_FALSE(LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(raw)) - << "only fragment outputs carry the constant-index rule:\n" << DisassembleSpirv(raw); + << "only fragment outputs carry the constant-index rule:\n" + << DisassembleSpirv(raw); } // --------------------------------------------------------------------------------------- @@ -3426,8 +3429,7 @@ void main() { fragColor = vec4(texelFetch(Data, 3)); } const String essl310 = emitAt(310); ASSERT_FALSE(essl310.empty()) << "ESSL 310 emission failed outright"; - EXPECT_NE(essl310.find("isamplerBuffer"), String::npos) - << "the buffer sampler must survive to ESSL:\n" << essl310; + EXPECT_NE(essl310.find("isamplerBuffer"), String::npos) << "the buffer sampler must survive to ESSL:\n" << essl310; EXPECT_NE(essl310.find("GL_EXT_texture_buffer"), String::npos) << "SPIRV-Cross requires EXT_texture_buffer below ESSL 320, and hardcodes that spelling - " "which is the whole reason an OES-only driver needs the emitted directive retargeted:\n" @@ -3441,7 +3443,6 @@ void main() { fragColor = vec4(texelFetch(Data, 3)); } EXPECT_EQ(essl320.find("GL_EXT_texture_buffer"), String::npos) << "ES 3.2 has buffer textures in core; requiring the extension there would be wrong:\n" << essl320; - } namespace { @@ -3544,18 +3545,17 @@ TEST_F(ProgramUtilTest, Lower1DArrayImagesRewritesTheTypeAndWidensTheCoordinate) ASSERT_TRUE(ShaderCompiler::Lower1DArrayImagesForEssl(spirv, lowered, true)); ASSERT_FALSE(lowered.empty()); - EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) - << "no 1D-array storage image type may survive the pass:\n" - << DisassembleSpirv(lowered); + EXPECT_EQ(Count1DArrayStorageImageTypes(lowered), 0u) << "no 1D-array storage image type may survive the pass:\n" + << DisassembleSpirv(lowered); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "the lowered module must stay validator-clean"; const String essl = DecompileToEssl(lowered); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("uimage2DArray"), String::npos) - << "the image must be declared as the 2D array the texture is stored as:\n" << essl; - EXPECT_EQ(essl.find("ivec2(ivec2("), String::npos) - << "the malformed constructor must be gone:\n" << essl; + << "the image must be declared as the 2D array the texture is stored as:\n" + << essl; + EXPECT_EQ(essl.find("ivec2(ivec2("), String::npos) << "the malformed constructor must be gone:\n" << essl; // The ORDER is the whole point, and it is what a widening that merely appended the 0 would // get wrong while still producing a three-component constructor that compiles. The fixture // reads (u=2, layer=3), and the ES 2D array holds height 1 with the layers in depth @@ -3620,7 +3620,7 @@ layout (location = 0, r32ui) readonly uniform uimage1D i0; layout (std430, binding = 0) buffer SSB { uint sum; } ssb; void main() { ssb.sum = imageLoad(i0, 2).r; } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); Vector lowered; @@ -3630,7 +3630,8 @@ void main() { ssb.sum = imageLoad(i0, 2).r; } const String essl = DecompileToEssl(lowered); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("uimage2D "), String::npos) - << "SPIRV-Cross's own 1D-as-2D emulation must still be what handles this:\n" << essl; + << "SPIRV-Cross's own 1D-as-2D emulation must still be what handles this:\n" + << essl; } // The negative control for the ATOMIC half, and the reason the non-arrayed case is in scope at @@ -3650,14 +3651,16 @@ TEST_F(ProgramUtilTest, SpirvCrossEmitsAScalarCoordinateForA1DImageAtomic) { const String essl = DecompileToEssl(spirv); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("uimage2D"), String::npos) - << "SPIRV-Cross declares the 1D image as 2D on ES; that half it does do:\n" << essl; + << "SPIRV-Cross declares the 1D image as 2D on ES; that half it does do:\n" + << essl; EXPECT_NE(essl.find("imageAtomicAdd(i0, 2"), String::npos) << "SPIRV-Cross is expected to pass the SCALAR coordinate straight through to the atomic. " "If this no longer happens, the non-arrayed half of Lower1DArrayImagesForEssl may no " "longer be needed:\n" << essl; EXPECT_EQ(essl.find("ivec2("), String::npos) - << "nothing else in this fixture builds an ivec2, so its absence is the defect:\n" << essl; + << "nothing else in this fixture builds an ivec2, so its absence is the defect:\n" + << essl; } // The fix: the type becomes a plain 2D image - which is what MobileGL stores a GL_TEXTURE_1D in, @@ -3688,8 +3691,8 @@ TEST_F(ProgramUtilTest, Lower1DArrayImagesWidensThe1DAtomicCoordinate) { const String essl = DecompileToEssl(lowered); ASSERT_FALSE(essl.empty()); - EXPECT_NE(essl.find("uimage2D"), String::npos) - << "the declaration must still be the 2D one the ES texture is:\n" << essl; + EXPECT_NE(essl.find("uimage2D"), String::npos) << "the declaration must still be the 2D one the ES texture is:\n" + << essl; EXPECT_NE(essl.find("imageAtomicAdd(i0, ivec2(2, 0)"), String::npos) << "the atomic must address the image with the same (u, 0) SPIRV-Cross writes for a read " "or a write:\n" @@ -3708,7 +3711,7 @@ layout (r32ui) coherent uniform uimage1D i0; layout (std430, binding = 0) buffer SSB { uint sum; } ssb; void main() { ssb.sum = imageAtomicAdd(i0, 2, 7u) + uint(imageSize(i0)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); const auto traits = Lower1DArrayImagesPass::InspectBinary(spirv); ASSERT_TRUE(traits.declaresImage && traits.queriesImageSize) @@ -3732,7 +3735,7 @@ in vec2 vUv; out vec4 fragColor; void main() { fragColor = texture(uTex, vUv); } )", - GL_FRAGMENT_SHADER); + GL_FRAGMENT_SHADER); ASSERT_FALSE(spirv.empty()); Vector lowered; @@ -3753,7 +3756,7 @@ layout (location = 0, r32ui) readonly uniform uimage1DArray i0; layout (std430, binding = 0) buffer SSB { uint sum; } ssb; void main() { ssb.sum = uint(imageSize(i0).x) + imageLoad(i0, ivec2(0, 0)).r; } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); const auto traits = Lower1DArrayImagesPass::InspectBinary(spirv); ASSERT_TRUE(traits.declaresImage && traits.queriesImageSize) @@ -3782,8 +3785,7 @@ namespace { const unsigned wordCount = spirv[i] >> 16; const unsigned opcode = spirv[i] & 0xFFFFu; if (wordCount == 0 || i + wordCount > spirv.size()) break; - if (opcode == kOpTypeImage && wordCount >= 8 && spirv[i + 3] == kDim1D && - spirv[i + 7] == 1u) { + if (opcode == kOpTypeImage && wordCount >= 8 && spirv[i + 3] == kDim1D && spirv[i + 7] == 1u) { ++count; } i += wordCount; @@ -3816,7 +3818,8 @@ TEST_F(ProgramUtilTest, SpirvCrossEmitsAScalarOffsetFor1DSamplers) { const String essl = DecompileToEssl(spirv); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("sampler2DArray"), String::npos) - << "SPIRV-Cross declares the 1D array sampler as 2D on ES; that half it does do:\n" << essl; + << "SPIRV-Cross declares the 1D array sampler as 2D on ES; that half it does do:\n" + << essl; EXPECT_EQ(essl.find("ivec2"), String::npos) << "SPIRV-Cross is expected to pass the SCALAR offset straight through, so nothing in this " "fixture builds an ivec2 - its absence IS the defect, because ESSL has no " @@ -3848,9 +3851,8 @@ TEST_F(ProgramUtilTest, Lower1DSampledImagesWidensTheOffsetOfA1DArrayLookup) { ASSERT_TRUE(ShaderCompiler::Lower1DSampledImagesForEssl(spirv, lowered, true)); ASSERT_FALSE(lowered.empty()); - EXPECT_EQ(Count1DSampledImageTypes(lowered), 0u) - << "no 1D sampled image type may survive the pass:\n" - << DisassembleSpirv(lowered); + EXPECT_EQ(Count1DSampledImageTypes(lowered), 0u) << "no 1D sampled image type may survive the pass:\n" + << DisassembleSpirv(lowered); // The point of moving the TYPE rather than only the operand: an ivec2 offset against a type // still declared Dim1D is an invalid module, and the validator would say so. EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) @@ -3860,7 +3862,8 @@ TEST_F(ProgramUtilTest, Lower1DSampledImagesWidensTheOffsetOfA1DArrayLookup) { const String essl = DecompileToEssl(lowered); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("sampler2DArray"), String::npos) - << "the sampler must still be declared as the 2D array the texture is stored as:\n" << essl; + << "the sampler must still be declared as the 2D array the texture is stored as:\n" + << essl; EXPECT_NE(essl.find("ivec2"), String::npos) << "the offset must now be the two-component one ESSL's sampler2DArray overload takes:\n" << essl; @@ -3883,8 +3886,7 @@ void main() { ssb.data = textureGrad(g_sampler0, 0.5, 0.25, 0.125); } Vector spirv; ASSERT_TRUE(ShaderCompiler::SanitizeAndOptimizeBinary(raw, spirv)); - ASSERT_TRUE(Lower1DSampledImagesPass::BinaryHasOffsetOrGrad1DSampledImage(spirv)) - << DisassembleSpirv(spirv); + ASSERT_TRUE(Lower1DSampledImagesPass::BinaryHasOffsetOrGrad1DSampledImage(spirv)) << DisassembleSpirv(spirv); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); @@ -3901,10 +3903,8 @@ void main() { ssb.data = textureGrad(g_sampler0, 0.5, 0.25, 0.125); } ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("textureGrad"), String::npos) << essl; // Both derivatives have to be widened, not just the first: ESSL's overload takes two vec2s. - EXPECT_NE(essl.find("vec2(0.25, 0.0)"), String::npos) - << "dPdx must be widened to two components:\n" << essl; - EXPECT_NE(essl.find("vec2(0.125, 0.0)"), String::npos) - << "dPdy must be widened too:\n" << essl; + EXPECT_NE(essl.find("vec2(0.25, 0.0)"), String::npos) << "dPdx must be widened to two components:\n" << essl; + EXPECT_NE(essl.find("vec2(0.125, 0.0)"), String::npos) << "dPdy must be widened too:\n" << essl; } // Scope: a 1D sampler that is only SAMPLED or FETCHED is emitted correctly by the very same @@ -3924,7 +3924,7 @@ void main() { ssb.data = texelFetch(g_sampler0, 2, 0) + texture(g_sampler4, vec2(0.5, 1.0)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_EQ(Count1DSampledImageTypes(spirv), 2u); EXPECT_FALSE(Lower1DSampledImagesPass::BinaryHasOffsetOrGrad1DSampledImage(spirv)) @@ -3977,8 +3977,7 @@ void main() { // was rewritten and the stage that was not declare the same ESSL type. const String essl = DecompileToEssl(lowered); ASSERT_FALSE(essl.empty()); - EXPECT_EQ(essl.find("sampler1D"), String::npos) - << "nothing may reach the driver still spelled 1D:\n" << essl; + EXPECT_EQ(essl.find("sampler1D"), String::npos) << "nothing may reach the driver still spelled 1D:\n" << essl; } // The shape that would emit INVALID SPIR-V without the deduplication, and the shape the @@ -4044,8 +4043,7 @@ void main() { Vector lowered; ASSERT_TRUE(ShaderCompiler::Lower1DSampledImagesForEssl(spirv, lowered, true)); - EXPECT_EQ(lowered, spirv) - << "a declined module must be handed back untouched, not partly rewritten"; + EXPECT_EQ(lowered, spirv) << "a declined module must be handed back untouched, not partly rewritten"; EXPECT_EQ(Count1DSampledImageTypes(lowered), 1u) << "declining means the 1D type is still there for the driver to reject"; } @@ -4090,7 +4088,7 @@ layout (local_size_x = 1) in; writeonly uniform uimage2D uni_image; void main() { imageStore(uni_image, ivec2(gl_GlobalInvocationID.xy), uvec4(15u, 0u, 0u, 0u)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)) << "the fixture must reproduce the defect before the fix is asked to remove it:\n" @@ -4111,9 +4109,11 @@ void main() { imageStore(uni_image, ivec2(gl_GlobalInvocationID.xy), uvec4(15u, const String essl = DecompileToEssl(baked); ASSERT_FALSE(essl.empty()); EXPECT_NE(essl.find("r32ui"), String::npos) - << "the bound format must reach the declaration as a layout qualifier:\n" << essl; + << "the bound format must reach the declaration as a layout qualifier:\n" + << essl; EXPECT_NE(essl.find("writeonly"), String::npos) - << "the access qualifier the declaration already had must survive:\n" << essl; + << "the access qualifier the declaration already had must survive:\n" + << essl; } // SPIRV-Cross THROWS rather than printing the formats it calls desktop-only when it targets ESSL @@ -4155,16 +4155,16 @@ layout (local_size_x = 1) in; writeonly uniform uimage2D uni_image; void main() { imageStore(uni_image, ivec2(0), uvec4(15u)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); - { // Unprintable AND uncarriable: declined, module untouched, and the stage still transpiles. + { // Unprintable AND uncarriable: declined, module untouched, and the stage still transpiles. Vector baked; ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlRgb8}}, baked)); EXPECT_EQ(baked, spirv) << "a format nothing can carry must leave the module untouched"; EXPECT_FALSE(DecompileToEssl(baked).empty()); } - { // Unprintable but carriable: baked narrow here, then widened into the carrier, which is + { // Unprintable but carriable: baked narrow here, then widened into the carrier, which is // what finally gives the declaration a qualifier ES accepts. Vector baked; ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_image", kGlR8ui}}, baked, true)); @@ -4178,8 +4178,7 @@ void main() { imageStore(uni_image, ivec2(0), uvec4(15u)); } ASSERT_FALSE(widened.empty()); const String essl = DecompileToEssl(widened); ASSERT_FALSE(essl.empty()); - EXPECT_NE(essl.find("rgba8ui"), String::npos) - << "the baked r8ui must come out as the core carrier:\n" << essl; + EXPECT_NE(essl.find("rgba8ui"), String::npos) << "the baked r8ui must come out as the core carrier:\n" << essl; } } @@ -4194,7 +4193,7 @@ layout (local_size_x = 1) in; layout (binding = 0, rgba32ui) writeonly uniform uimage2D uni_image; void main() { imageStore(uni_image, ivec2(0), uvec4(1u)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)); @@ -4218,7 +4217,7 @@ writeonly uniform uimage2D uni_image; void writeIt(writeonly uimage2D img) { imageStore(img, ivec2(0), uvec4(1u)); } void main() { writeIt(uni_image); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)); @@ -4244,7 +4243,7 @@ layout (local_size_x = 1) in; writeonly uniform image2D uni_image; void main() { imageStore(uni_image, ivec2(0), vec4(1.0)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); @@ -4276,16 +4275,17 @@ void main() { imageStore(imgB, ivec2(0), uvec4(2u)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 1u) - << "the fixture must have the two images sharing one type:\n" << DisassembleSpirv(spirv); + << "the fixture must have the two images sharing one type:\n" + << DisassembleSpirv(spirv); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); Vector baked; - ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl( - spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked, true)); + ASSERT_TRUE( + ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"imgA", kGlR32ui}, {"imgB", kGlRgba32ui}}, baked, true)); ASSERT_FALSE(baked.empty()); EXPECT_EQ(ShaderCompiler::SpirvValidationFailureCount(), failuresBefore) << "splitting the shared type must not leave a dangling or duplicate declaration:\n" @@ -4313,10 +4313,11 @@ void main() { imageStore(declared, ivec2(0), uvec4(2u)); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_EQ(CountSpirvOpcode(DisassembleSpirv(spirv), "OpTypeImage"), 2u) - << "the fixture needs one Unknown-format and one r32ui image type:\n" << DisassembleSpirv(spirv); + << "the fixture needs one Unknown-format and one r32ui image type:\n" + << DisassembleSpirv(spirv); const Uint64 failuresBefore = ShaderCompiler::SpirvValidationFailureCount(); @@ -4327,7 +4328,8 @@ void main() { << "the baked image collided with the module's own r32ui image and left a duplicate type:\n" << DisassembleSpirv(baked); EXPECT_EQ(CountSpirvOpcode(DisassembleSpirv(baked), "OpTypeImage"), 1u) - << "the two identical image types must be the same declaration:\n" << DisassembleSpirv(baked); + << "the two identical image types must be the same declaration:\n" + << DisassembleSpirv(baked); } // An ARRAY of format-less images: the variable's type is a pointer to an array, every use goes @@ -4342,7 +4344,7 @@ void main() { for (int i = 0; i < 2; ++i) imageStore(imgs[i], ivec2(0), uvec4(uint(i))); } )", - GL_COMPUTE_SHADER); + GL_COMPUTE_SHADER); ASSERT_FALSE(spirv.empty()); ASSERT_TRUE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)); @@ -4369,10 +4371,11 @@ out uvec4 fragColor; in vec2 vUv; void main() { fragColor = texture(uni_sampler, vUv); } )", - GL_FRAGMENT_SHADER); + GL_FRAGMENT_SHADER); ASSERT_FALSE(spirv.empty()); EXPECT_FALSE(ShaderCompiler::DeclaresFormatlessStorageImage(spirv)) - << "a sampled image must not read as a format-less STORAGE image:\n" << DisassembleSpirv(spirv); + << "a sampled image must not read as a format-less STORAGE image:\n" + << DisassembleSpirv(spirv); Vector baked; ASSERT_TRUE(ShaderCompiler::BakeImageFormatsForEssl(spirv, {{"uni_sampler", kGlR32ui}}, baked, true)); @@ -4486,8 +4489,7 @@ TEST_F(ProgramUtilTest, StorageBlockBindingCeilingIsCheckedAtItsExactBoundary) { EXPECT_TRUE(violation("layout(binding = 34) buffer Buffer { int x; } g_array[4];\n").has_value()); // Qualifiers and a second layout list may sit between the binding and the keyword. - EXPECT_TRUE(violation("layout(std430) layout(binding = 36) coherent restrict buffer B { int x; };\n") - .has_value()); + EXPECT_TRUE(violation("layout(std430) layout(binding = 36) coherent restrict buffer B { int x; };\n").has_value()); // Things the scanner must NOT judge: a uniform block (a different ceiling), a storage block // with no explicit binding, the bare default-qualifier form, and an instance array whose size @@ -4497,8 +4499,7 @@ TEST_F(ProgramUtilTest, StorageBlockBindingCeilingIsCheckedAtItsExactBoundary) { .has_value()); EXPECT_FALSE(violation("buffer Buffer { int x; };\nconst int binding = 40;\n").has_value()); EXPECT_FALSE(violation("layout(binding = 1) buffer;\nbuffer Buffer { int x; };\n").has_value()); - EXPECT_FALSE(violation("const int kCount = 4;\nlayout(binding = 34) buffer B { int x; } g[kCount];\n") - .has_value()); + EXPECT_FALSE(violation("const int kCount = 4;\nlayout(binding = 34) buffer B { int x; } g[kCount];\n").has_value()); // A backend that advertises no binding points has no ceiling to enforce. EXPECT_FALSE(FindShaderStorageBindingViolation("layout(binding = 36) buffer B { int x; };\n", 0).has_value()); @@ -4560,14 +4561,21 @@ TEST_F(ProgramUtilTest, PreprocessSubroutineKeepsTheTypeNameVisibleToTheCompiler const auto lowerAndCompile = [](const String& typeName) { String source = "#version 400 core\n" "out vec4 fragColor;\n" - "subroutine void " + typeName + "(void);\n" - "subroutine(" + typeName + ") void Impl(void) { fragColor = vec4(1.0); }\n" - "subroutine uniform " + typeName + " g_routine;\n" + "subroutine void " + + typeName + + "(void);\n" + "subroutine(" + + typeName + + ") void Impl(void) { fragColor = vec4(1.0); }\n" + "subroutine uniform " + + typeName + + " g_routine;\n" "void main() { g_routine(); }\n"; PreprocessShaderSource(ShaderStage::Fragment, source); EXPECT_EQ(source.find("subroutine"), String::npos) << source; EXPECT_NE(source.find("void " + typeName + "(void);"), String::npos) - << "the type declaration must survive as a prototype:\n" << source; + << "the type declaration must survive as a prototype:\n" + << source; ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source}; return Bool(ShaderCompiler::CompileShader(attrib)); }; @@ -4671,7 +4679,6 @@ subroutine(FuncType) void Func0(int coord) { fragColor = vec4(float(coord)); } << "an inactive #if arm must not have an unconditional forwarding body appended for it"; } - // --------------------------------------------------------------------------------------------- // gl_NumSamples: glslang declares the built-in only when it is NOT targeting SPIR-V, and MobileGL // always targets SPIR-V, so every fragment shader that reads it used to die at compile time with @@ -4797,33 +4804,25 @@ TEST_F(ProgramUtilTest, NumSamplesShimHonoursTheVersionAndExtensionGate) { {"desktop 460 core", "#version 460 core\n", true}, {"desktop 400 core", "#version 400 core\n", true}, {"desktop 330 core, no extension", "#version 330 core\n", false}, - {"desktop 330 core + ARB_sample_shading", - "#version 330 core\n#extension GL_ARB_sample_shading : require\n", true}, - {"desktop 330 core + all : warn", - "#version 330 core\n#extension all : warn\n", true}, + {"desktop 330 core + ARB_sample_shading", "#version 330 core\n#extension GL_ARB_sample_shading : require\n", + true}, + {"desktop 330 core + all : warn", "#version 330 core\n#extension all : warn\n", true}, {"desktop 120, no extension", "#version 120\n", false}, - {"desktop 120 + all : warn (below the 1.30 floor)", - "#version 120\n#extension all : warn\n", false}, + {"desktop 120 + all : warn (below the 1.30 floor)", "#version 120\n#extension all : warn\n", false}, {"ESSL 320", "#version 320 es\n", true}, {"ESSL 310, no extension", "#version 310 es\n", false}, - {"ESSL 310 + OES_sample_variables", - "#version 310 es\n#extension GL_OES_sample_variables : require\n", true}, + {"ESSL 310 + OES_sample_variables", "#version 310 es\n#extension GL_OES_sample_variables : require\n", true}, // The AEP spellings. glslang applies the directive's behavior to all twelve AEP members, // GL_OES_sample_variables among them, so these are legal ES 3.1 shaders. - {"ESSL 310 + AEP : require", - "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : require\n", true}, - {"ESSL 310 + AEP : enable", - "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : enable\n", true}, - {"ESSL 310 + AEP : warn", - "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : warn\n", true}, + {"ESSL 310 + AEP : require", "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : require\n", true}, + {"ESSL 310 + AEP : enable", "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : enable\n", true}, + {"ESSL 310 + AEP : warn", "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : warn\n", true}, // ...but `disable` is not an opt-in, and the implication carries the behavior with it. - {"ESSL 310 + AEP : disable", - "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : disable\n", false}, - {"ESSL 310 + all : warn", - "#version 310 es\n#extension all : warn\n", true}, + {"ESSL 310 + AEP : disable", "#version 310 es\n#extension GL_ANDROID_extension_pack_es31a : disable\n", false}, + {"ESSL 310 + all : warn", "#version 310 es\n#extension all : warn\n", true}, // An AEP member that does NOT imply sample variables must not open the gate. - {"ESSL 310 + EXT_geometry_shader only", - "#version 310 es\n#extension GL_EXT_geometry_shader : require\n", false}, + {"ESSL 310 + EXT_geometry_shader only", "#version 310 es\n#extension GL_EXT_geometry_shader : require\n", + false}, {"ESSL 300", "#version 300 es\n", false}, {"ESSL 300 + AEP (below the 3.10 floor)", "#version 300 es\n#extension GL_ANDROID_extension_pack_es31a : require\n", false}, @@ -4948,10 +4947,9 @@ TEST_F(ProgramUtilTest, RepeatedIdenticalVersionDirectiveIsElided) { // Byte-for-byte the concatenation the CTS produces: kGLSLPrec ends without a newline, so the // subcase's own "#version 310 es" lands mid-line. - String source = - "#version 310 es\n\nprecision highp float;\nprecision highp uimage2DArray;#version 310 es\n" - "layout(location = 0) in vec4 i_position;\n" - "void main() { gl_Position = i_position; }\n"; + String source = "#version 310 es\n\nprecision highp float;\nprecision highp uimage2DArray;#version 310 es\n" + "layout(location = 0) in vec4 i_position;\n" + "void main() { gl_Position = i_position; }\n"; const SizeT lineCountBefore = static_cast(std::count(source.begin(), source.end(), '\n')); PreprocessShaderSource(ShaderStage::Vertex, source); @@ -4978,8 +4976,7 @@ TEST_F(ProgramUtilTest, OnlyAnExactVersionRepeatIsElided) { SCOPED_TRACE("a lone non-first #version is still a lone non-first #version"); // KHR-GL33.shaders.preprocessor.directive.version_not_first_statement_1 requires this to // fail to compile, and it only does so because the directive is left where it was. - String source = - "precision mediump float;\n#version 330\nout vec4 c;\nvoid main() { c = vec4(1.0); }\n"; + String source = "precision mediump float;\n#version 330\nout vec4 c;\nvoid main() { c = vec4(1.0); }\n"; PreprocessShaderSource(ShaderStage::Fragment, source); const SizeT versionPos = source.find("#version"); ASSERT_NE(versionPos, String::npos) << source; @@ -4992,8 +4989,7 @@ TEST_F(ProgramUtilTest, OnlyAnExactVersionRepeatIsElided) { { SCOPED_TRACE("a MALFORMED repeat is left alone"); - String source = - "#version 330 core\nout vec4 c;\n#version 330 foobar\nvoid main() { c = vec4(1.0); }\n"; + String source = "#version 330 core\nout vec4 c;\n#version 330 foobar\nvoid main() { c = vec4(1.0); }\n"; PreprocessShaderSource(ShaderStage::Fragment, source); EXPECT_NE(source.find("#version 330 foobar"), String::npos) << source; } @@ -5024,14 +5020,13 @@ void main() { fragColor = vec4(float(gl_NumSamples)); } // Both halves of the AEP path: the built-in shim AND the restored member macros. EXPECT_TRUE(HasNumSamplesShim(source)) << source; const String defines = CollectEsPreambleMacroDefines(source); - for (const char* member : {"GL_ANDROID_extension_pack_es31a", "GL_OES_sample_variables", - "GL_OES_shader_image_atomic", "GL_OES_shader_multisample_interpolation", - "GL_OES_texture_storage_multisample_2d_array", "GL_EXT_geometry_shader", - "GL_EXT_gpu_shader5", "GL_EXT_primitive_bounding_box", - "GL_EXT_shader_io_blocks", "GL_EXT_tessellation_shader", - "GL_EXT_texture_buffer", "GL_EXT_texture_cube_map_array"}) { - EXPECT_NE(defines.find(String("#define ") + member + " 1\n"), String::npos) - << member << " missing from:\n" << defines; + for (const char* member : + {"GL_ANDROID_extension_pack_es31a", "GL_OES_sample_variables", "GL_OES_shader_image_atomic", + "GL_OES_shader_multisample_interpolation", "GL_OES_texture_storage_multisample_2d_array", + "GL_EXT_geometry_shader", "GL_EXT_gpu_shader5", "GL_EXT_primitive_bounding_box", "GL_EXT_shader_io_blocks", + "GL_EXT_tessellation_shader", "GL_EXT_texture_buffer", "GL_EXT_texture_cube_map_array"}) { + EXPECT_NE(defines.find(String("#define ") + member + " 1\n"), String::npos) << member << " missing from:\n" + << defines; } // GL_KHR_blend_equation_advanced is an AEP member glslang propagates to, but its macro is in // the DESKTOP preamble too - so the rewrite never took it away and it must not be restored. diff --git a/MobileGL/MG_Test/Program/ShaderCompileAdoptionTest.cpp b/MobileGL/MG_Test/Program/ShaderCompileAdoptionTest.cpp index 342d4ff0..3a44089e 100644 --- a/MobileGL/MG_Test/Program/ShaderCompileAdoptionTest.cpp +++ b/MobileGL/MG_Test/Program/ShaderCompileAdoptionTest.cpp @@ -613,8 +613,7 @@ TEST_F(ShaderCompileAdoptionTest, AfterEverySharerIsGoneTheNextCompileIsStillCor ShaderSource(c, 1, &text, nullptr); CompileShader(c); ASSERT_EQ(QueryCompileStatus(c), GL_TRUE) - << "a cancelled node must never be adopted - it can only ever report GL_FALSE. " - << QueryShaderInfoLog(c); + << "a cancelled node must never be adopted - it can only ever report GL_FALSE. " << QueryShaderInfoLog(c); const GLuint program = LinkWith(c); ASSERT_EQ(QueryLinkStatus(program), GL_TRUE); EXPECT_GE(GetUniformLocation(program, "uSeed240"), 0); @@ -781,8 +780,7 @@ TEST_F(ShaderCompileAdoptionTest, StressSharedNodesUnderResourceAndDelete) { << "round " << round << " shader " << i << ": " << QueryShaderInfoLog(shader); const GLuint program = LinkWith(shader); ASSERT_EQ(QueryLinkStatus(program), GL_TRUE) << "round " << round << " shader " << i; - const String expected = - "uSeed" + std::to_string(i % 3 == 0 ? round * 100 + 50 : round * 100 + (i % 4)); + const String expected = "uSeed" + std::to_string(i % 3 == 0 ? round * 100 + 50 : round * 100 + (i % 4)); EXPECT_GE(GetUniformLocation(program, expected.c_str()), 0) << "round " << round << " shader " << i << " expected " << expected; DeleteProgram(program); @@ -818,19 +816,17 @@ TEST(ShaderCompileAdoptionMapTest, RegisteredNodeIsAdoptedOnAnExactMatch) { const SharedPtr node = MakeNode(text, ShaderStage::Fragment, env); map.Register(node); - EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, - env->fingerprint), + EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, env->fingerprint), node); EXPECT_EQ(map.GetAdoptionCount(), 1u); // Every discriminator in the key is load-bearing. - EXPECT_EQ(map.FindAdoptable(ShaderStage::Vertex, ShaderPreprocessCache::HashSource(text), text, - env->fingerprint), + EXPECT_EQ(map.FindAdoptable(ShaderStage::Vertex, ShaderPreprocessCache::HashSource(text), text, env->fingerprint), nullptr); const String other = text + "\n"; - EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(other), other, - env->fingerprint), - nullptr); + EXPECT_EQ( + map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(other), other, env->fingerprint), + nullptr); EXPECT_EQ(map.GetAdoptionCount(), 1u) << "a miss must not count as an adoption"; } @@ -851,9 +847,9 @@ TEST(ShaderCompileAdoptionMapTest, EnvFingerprintMismatchIsNotAdopted) { otherEnv->fingerprint = MG_Util::ShaderTranspiler::ComputeCompileEnvFingerprint(*otherEnv); ASSERT_NE(otherEnv->fingerprint, env->fingerprint); - EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, - otherEnv->fingerprint), - nullptr); + EXPECT_EQ( + map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, otherEnv->fingerprint), + nullptr); EXPECT_EQ(map.GetAdoptionCount(), 0u); } @@ -871,8 +867,7 @@ TEST(ShaderCompileAdoptionMapTest, CancelledNodeIsNotAdoptedAndIsPruned) { ASSERT_TRUE(node->IsCancelled()); ASSERT_EQ(map.GetEntryCount(), 1u); - EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, - env->fingerprint), + EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, env->fingerprint), nullptr); EXPECT_EQ(map.GetEntryCount(), 0u) << "the dead entry must be pruned on the lookup that found it"; EXPECT_EQ(map.GetAdoptionCount(), 0u); @@ -884,13 +879,10 @@ TEST(ShaderCompileAdoptionMapTest, ExpiredNodeIsNotAdoptedAndIsPruned) { const auto& env = MG_Util::ShaderTranspiler::GetDefaultCompileEnv(); ShaderCompileAdoptionMap map; const String text = "#version 460\nvoid main() {}\n"; - { - map.Register(MakeNode(text, ShaderStage::Fragment, env)); - } + { map.Register(MakeNode(text, ShaderStage::Fragment, env)); } ASSERT_EQ(map.GetEntryCount(), 1u); - EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, - env->fingerprint), + EXPECT_EQ(map.FindAdoptable(ShaderStage::Fragment, ShaderPreprocessCache::HashSource(text), text, env->fingerprint), nullptr); EXPECT_EQ(map.GetEntryCount(), 0u); } diff --git a/MobileGL/MG_Test/Program/TessellationLinkTest.cpp b/MobileGL/MG_Test/Program/TessellationLinkTest.cpp index dc121e9c..82e094ee 100644 --- a/MobileGL/MG_Test/Program/TessellationLinkTest.cpp +++ b/MobileGL/MG_Test/Program/TessellationLinkTest.cpp @@ -43,12 +43,11 @@ using namespace MobileGL; using namespace MobileGL::MG_Impl::GLImpl; namespace { - class TessellationLinkTest: public ::testing::Test { + class TessellationLinkTest : public ::testing::Test { protected: void SetUp() override { MobileGL::Initialize(); - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } }; @@ -77,7 +76,7 @@ namespace { if (separable) { ProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE); } - for (const auto& [type, source]: stages) { + for (const auto& [type, source] : stages) { const GLuint shader = CreateShader(type); ShaderSource(shader, 1, &source, nullptr); CompileShader(shader); @@ -157,8 +156,7 @@ void main() { color = vec4(1.0); } LinkProgram(program); EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE); EXPECT_NE(LinkLog(program).find("no vertex-processing stage"), std::string::npos) << LinkLog(program); - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } constexpr const char* kPassthroughVs = R"(#version 460 core @@ -217,8 +215,7 @@ void main() { LinkProgram(program); EXPECT_EQ(Programiv(program, GL_LINK_STATUS), GL_FALSE) << LinkLog(program); } - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } // A patch-qualified SCALAR crosses the TCS/TES boundary today. It is the control for the @@ -270,10 +267,9 @@ void main() { gl_Position = gl_in[0].gl_Position; } )"; - const GLuint program = MakeProgram({{GL_VERTEX_SHADER, kPassthroughVs}, - {GL_TESS_CONTROL_SHADER, tcs}, - {GL_TESS_EVALUATION_SHADER, tes}}, - /*separable=*/true); + const GLuint program = MakeProgram( + {{GL_VERTEX_SHADER, kPassthroughVs}, {GL_TESS_CONTROL_SHADER, tcs}, {GL_TESS_EVALUATION_SHADER, tes}}, + /*separable=*/true); LinkProgram(program); const std::string log = LinkLog(program); ASSERT_EQ(Programiv(program, GL_LINK_STATUS), GL_TRUE) diff --git a/MobileGL/MG_Test/Program/XfbBlockVaryingTest.cpp b/MobileGL/MG_Test/Program/XfbBlockVaryingTest.cpp index 1f5a5cbf..d284a780 100644 --- a/MobileGL/MG_Test/Program/XfbBlockVaryingTest.cpp +++ b/MobileGL/MG_Test/Program/XfbBlockVaryingTest.cpp @@ -82,8 +82,7 @@ namespace { } void ClearErrors() { - for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 32 && GetError() != GL_NO_ERROR; ++i) {} } // The CTS shader, narrowed to two elements so the expectations stay readable. diff --git a/MobileGL/MG_Test/Program/XfbFrontendOrderInvarianceTest.cpp b/MobileGL/MG_Test/Program/XfbFrontendOrderInvarianceTest.cpp index ac1e5d52..537873e1 100644 --- a/MobileGL/MG_Test/Program/XfbFrontendOrderInvarianceTest.cpp +++ b/MobileGL/MG_Test/Program/XfbFrontendOrderInvarianceTest.cpp @@ -73,8 +73,7 @@ namespace { // --------------------------------------------------------------------------------- class AsyncModeScope { public: - explicit AsyncModeScope(const Bool async) - : m_saved(MG_Config::Features.AsyncShaderCompile) { + explicit AsyncModeScope(const Bool async) : m_saved(MG_Config::Features.AsyncShaderCompile) { MG_Config::Features.AsyncShaderCompile = async ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff; } @@ -98,7 +97,9 @@ namespace { return String(version) + "\n" "\n" - "out float gl_ClipDistance[" + n + "];\n" + "out float gl_ClipDistance[" + + n + + "];\n" "\n" "float f(int i)\n" "{\n" @@ -109,7 +110,9 @@ namespace { "\n" "void main()\n" "{\n" - " for(int i = 0; i < " + n + "; i++)\n" + " for(int i = 0; i < " + + n + + "; i++)\n" " {\n" " gl_ClipDistance[i] = f(i);\n" " }\n" @@ -119,16 +122,15 @@ namespace { } String ClipDistanceFs(const char* version) { - return String(version) + - "\n" - "\n" - "\n" - "out highp vec4 color;\n" - "\n" - "void main()\n" - "{\n" - " color = vec4(1.0, 0.0, 0.0, 1.0);\n" - "}\n"; + return String(version) + "\n" + "\n" + "\n" + "out highp vec4 color;\n" + "\n" + "void main()\n" + "{\n" + " color = vec4(1.0, 0.0, 0.0, 1.0);\n" + "}\n"; } // --------------------------------------------------------------------------------- @@ -160,18 +162,25 @@ namespace { } String SkipComponentsFs(const char* version) { - return String(version) + - "\n" - " out vec4 fragColor;\n" - " void main (void)\n" - " {\n" - " fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n" - " }\n"; + return String(version) + "\n" + " out vec4 fragColor;\n" + " void main (void)\n" + " {\n" + " fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n" + " }\n"; } Vector SkipComponentsVaryings() { - return {"gl_SkipComponents1", "value1", "gl_SkipComponents2", "gl_SkipComponents1", "value2", - "gl_SkipComponents3", "gl_SkipComponents2", "value3", "gl_SkipComponents4", "value4"}; + return {"gl_SkipComponents1", + "value1", + "gl_SkipComponents2", + "gl_SkipComponents1", + "value2", + "gl_SkipComponents3", + "gl_SkipComponents2", + "value3", + "gl_SkipComponents4", + "value4"}; } // --------------------------------------------------------------------------------- @@ -187,9 +196,8 @@ namespace { for (int i = 0; i < userVaryings; ++i) { const String name = "result_" + std::to_string(i); declarations += "out vec4 " + name + ";\n"; - setters += " " + name + " = vec4(" + std::to_string(i * 4) + ".0, " + - std::to_string(i * 4 + 1) + ".0, " + std::to_string(i * 4 + 2) + ".0, " + - std::to_string(i * 4 + 3) + ".0);\n"; + setters += " " + name + " = vec4(" + std::to_string(i * 4) + ".0, " + std::to_string(i * 4 + 1) + + ".0, " + std::to_string(i * 4 + 2) + ".0, " + std::to_string(i * 4 + 3) + ".0);\n"; } return String(version) + "\n\n" + declarations + "\n" + "void main()\n" @@ -219,20 +227,20 @@ namespace { } String CaptureInterleavedFs(const char* version) { - return String(version) + - "\n" - "\n" - "out vec4 color;\n" - "\n" - "void main()\n" - "{\n" - " color = vec4(0.5);\n" - "}\n"; + return String(version) + "\n" + "\n" + "out vec4 color;\n" + "\n" + "void main()\n" + "{\n" + " color = vec4(0.5);\n" + "}\n"; } Vector CaptureInterleavedVaryings(const int userVaryings) { Vector names; - for (int i = 0; i < userVaryings; ++i) names.push_back("result_" + std::to_string(i)); + for (int i = 0; i < userVaryings; ++i) + names.push_back("result_" + std::to_string(i)); names.push_back("gl_Position"); return names; } @@ -254,9 +262,8 @@ namespace { for (int i = 0; i < userVaryings; ++i) { const String name = "result_" + std::to_string(i); declarations += "out vec4 " + name + ";\n"; - setters += " " + name + " = vec4(" + std::to_string(i * 4) + ".0, " + - std::to_string(i * 4 + 1) + ".0, " + std::to_string(i * 4 + 2) + ".0, " + - std::to_string(i * 4 + 3) + ".0);\n"; + setters += " " + name + " = vec4(" + std::to_string(i * 4) + ".0, " + std::to_string(i * 4 + 1) + + ".0, " + std::to_string(i * 4 + 2) + ".0, " + std::to_string(i * 4 + 3) + ".0);\n"; } String source = "#version 150\n" "\n" @@ -268,10 +275,9 @@ namespace { declarations + "\n" + "void main()\n" "{\n"; - const char* positions[] = {"vec4(-1.0 + 0.0625, 1.0 - 0.0625, 0.0, 1.0)", - "vec4( 1.0 - 0.0625, 1.0 - 0.0625, 0.0, 1.0)", - "vec4(-1.0 + 0.0625, -1.0 + 0.0625, 0.0, 1.0)", - "vec4( 1.0 - 0.0625, -1.0 + 0.0625, 0.0, 1.0)"}; + const char* positions[] = { + "vec4(-1.0 + 0.0625, 1.0 - 0.0625, 0.0, 1.0)", "vec4( 1.0 - 0.0625, 1.0 - 0.0625, 0.0, 1.0)", + "vec4(-1.0 + 0.0625, -1.0 + 0.0625, 0.0, 1.0)", "vec4( 1.0 - 0.0625, -1.0 + 0.0625, 0.0, 1.0)"}; for (const char* position : positions) { source += String("\n gl_Position = ") + position + ";\n"; source += setters; @@ -296,29 +302,47 @@ namespace { const char* DecorationName(const Uint32 decoration) { switch (decoration) { - case 11: return "BuiltIn"; - case 30: return "Location"; - case 31: return "Component"; - case 32: return "Index"; - case 33: return "Binding"; - case 34: return "DescriptorSet"; - case 35: return "Offset"; - case 36: return "XfbBuffer"; - case 37: return "XfbStride"; - case 38: return "FuncParamAttr"; - default: return nullptr; + case 11: + return "BuiltIn"; + case 30: + return "Location"; + case 31: + return "Component"; + case 32: + return "Index"; + case 33: + return "Binding"; + case 34: + return "DescriptorSet"; + case 35: + return "Offset"; + case 36: + return "XfbBuffer"; + case 37: + return "XfbStride"; + case 38: + return "FuncParamAttr"; + default: + return nullptr; } } const char* BuiltInName(const Uint32 builtIn) { switch (builtIn) { - case 0: return "Position"; - case 1: return "PointSize"; - case 3: return "ClipDistance"; - case 4: return "CullDistance"; - case 5: return "VertexId"; - case 42: return "VertexIndex"; - default: return nullptr; + case 0: + return "Position"; + case 1: + return "PointSize"; + case 3: + return "ClipDistance"; + case 4: + return "CullDistance"; + case 5: + return "VertexId"; + case 42: + return "VertexIndex"; + default: + return nullptr; } } @@ -401,14 +425,17 @@ namespace { } else if (opcode == kOpEntryPoint && wordCount >= 4) { SizeT next = 0; (void)ReadSpirvString(words, w + 3, w + wordCount, next); - for (SizeT i = next; i < w + wordCount; ++i) interfaceIds.push_back(words[i]); + for (SizeT i = next; i < w + wordCount; ++i) + interfaceIds.push_back(words[i]); } else if (opcode == kOpDecorate && wordCount >= 3) { PendingDecoration entry{words[w + 1], -1, words[w + 2], {}}; - for (SizeT i = w + 3; i < w + wordCount; ++i) entry.operands.push_back(words[i]); + for (SizeT i = w + 3; i < w + wordCount; ++i) + entry.operands.push_back(words[i]); pending.push_back(Move(entry)); } else if (opcode == kOpMemberDecorate && wordCount >= 4) { PendingDecoration entry{words[w + 1], static_cast(words[w + 2]), words[w + 3], {}}; - for (SizeT i = w + 4; i < w + wordCount; ++i) entry.operands.push_back(words[i]); + for (SizeT i = w + 4; i < w + wordCount; ++i) + entry.operands.push_back(words[i]); pending.push_back(Move(entry)); } w += wordCount; @@ -440,7 +467,8 @@ namespace { } std::sort(digest.decorations.begin(), digest.decorations.end()); - for (const Uint32 id : interfaceIds) digest.interfaceNames.push_back(label(id)); + for (const Uint32 id : interfaceIds) + digest.interfaceNames.push_back(label(id)); std::sort(digest.interfaceNames.begin(), digest.interfaceNames.end()); return digest; } @@ -508,14 +536,16 @@ namespace { snapshot.strides.push_back(object->GetTransformFeedbackStride(static_cast(i))); } for (const auto& varying : object->GetTransformFeedbackVaryings()) { - snapshot.varyings.push_back(varying.name + " type=0x" + [&] { - char buffer[16]; - std::snprintf(buffer, sizeof(buffer), "%04X", static_cast(varying.type)); - return String(buffer); - }() + " size=" + std::to_string(varying.size) + " buf=" + std::to_string(varying.bufferIndex) + - " off=" + std::to_string(varying.offsetBytes) + - " bytes=" + std::to_string(varying.byteSize) + - " packedOff=" + std::to_string(varying.packedOffsetBytes)); + snapshot.varyings.push_back( + varying.name + " type=0x" + + [&] { + char buffer[16]; + std::snprintf(buffer, sizeof(buffer), "%04X", static_cast(varying.type)); + return String(buffer); + }() + + " size=" + std::to_string(varying.size) + " buf=" + std::to_string(varying.bufferIndex) + + " off=" + std::to_string(varying.offsetBytes) + " bytes=" + std::to_string(varying.byteSize) + + " packedOff=" + std::to_string(varying.packedOffsetBytes)); } for (const auto& module : object->GetGeneratedSpirv()) { snapshot.spirv.push_back(DigestSpirv(module)); @@ -533,17 +563,21 @@ namespace { text += "xfbNeedsScatter = " + std::to_string(static_cast(snapshot.needsScattered)) + "\n"; text += "xfbNameMaxLength = " + std::to_string(snapshot.varyingNameMaxLength) + "\n"; text += "xfbStrides ="; - for (const Uint32 stride : snapshot.strides) text += " " + std::to_string(stride); + for (const Uint32 stride : snapshot.strides) + text += " " + std::to_string(stride); text += "\n"; text += "xfbVaryings (" + std::to_string(snapshot.varyings.size()) + "):\n"; - for (const String& varying : snapshot.varyings) text += " " + varying + "\n"; + for (const String& varying : snapshot.varyings) + text += " " + varying + "\n"; text += "gsInputPrimitive = " + std::to_string(snapshot.gsInputPrimitive) + "\n"; text += "gsStripFixup = " + std::to_string(static_cast(snapshot.gsStripCaptureFixup)) + "\n"; text += "gsStripTriangles ="; - for (const Uint32 triangle : snapshot.gsStripTriangles) text += " " + std::to_string(triangle); + for (const Uint32 triangle : snapshot.gsStripTriangles) + text += " " + std::to_string(triangle); text += "\n"; text += "uniformBlocks = " + std::to_string(snapshot.uniformBlockCount) + " bindings:"; - for (const Uint binding : snapshot.uniformBlockBindings) text += " " + std::to_string(binding); + for (const Uint binding : snapshot.uniformBlockBindings) + text += " " + std::to_string(binding); text += "\n"; text += "maxUniformLoc = " + std::to_string(snapshot.maxUniformLocation) + "\n"; text += "activeAttribs = " + std::to_string(snapshot.activeAttributes) + "\n"; @@ -553,15 +587,16 @@ namespace { text += "spirv[" + std::to_string(i) + "] words=" + std::to_string(digest.wordCount); if (includeSpirvHash) { char buffer[32]; - std::snprintf(buffer, sizeof(buffer), " hash=%016llX", - static_cast(digest.hash)); + std::snprintf(buffer, sizeof(buffer), " hash=%016llX", static_cast(digest.hash)); text += buffer; } text += "\n"; text += " interface:"; - for (const String& name : digest.interfaceNames) text += " " + name; + for (const String& name : digest.interfaceNames) + text += " " + name; text += "\n"; - for (const String& decoration : digest.decorations) text += " " + decoration + "\n"; + for (const String& decoration : digest.decorations) + text += " " + decoration + "\n"; } return text; } @@ -569,8 +604,8 @@ namespace { // --------------------------------------------------------------------------------- // Program construction through the real GL entry points // --------------------------------------------------------------------------------- - GLuint BuildProgram(const String& vertexSource, const String& fragmentSource, - const Vector& xfbVaryings, const GLenum bufferMode) { + GLuint BuildProgram(const String& vertexSource, const String& fragmentSource, const Vector& xfbVaryings, + const GLenum bufferMode) { const GLuint vertexShader = CreateShader(GL_VERTEX_SHADER); const char* vertexText = vertexSource.c_str(); ShaderSource(vertexShader, 1, &vertexText, nullptr); @@ -587,7 +622,8 @@ namespace { if (!xfbVaryings.empty()) { std::vector names; names.reserve(xfbVaryings.size()); - for (const String& name : xfbVaryings) names.push_back(name.c_str()); + for (const String& name : xfbVaryings) + names.push_back(name.c_str()); TransformFeedbackVaryings(program, static_cast(names.size()), names.data(), bufferMode); } LinkProgram(program); @@ -600,13 +636,12 @@ namespace { // setter, clip function 0). Returns the program so the caller can keep it alive, which // is what the CTS does too (it holds m_program across the whole case). GLuint LinkClipDistanceProgram(const int clipCount, const char* version) { - return BuildProgram(ClipDistanceVs(clipCount, version), ClipDistanceFs(version), {}, - GL_INTERLEAVED_ATTRIBS); + return BuildProgram(ClipDistanceVs(clipCount, version), ClipDistanceFs(version), {}, GL_INTERLEAVED_ATTRIBS); } GLuint LinkSkipComponentsProgram(const char* version, const String& salt = String()) { - return BuildProgram(SkipComponentsVs(version, salt), SkipComponentsFs(version), - SkipComponentsVaryings(), GL_INTERLEAVED_ATTRIBS); + return BuildProgram(SkipComponentsVs(version, salt), SkipComponentsFs(version), SkipComponentsVaryings(), + GL_INTERLEAVED_ATTRIBS); } GLuint LinkCaptureInterleavedProgram(const int userVaryings, const char* version) { @@ -633,9 +668,9 @@ namespace { AttachShader(program, fragmentShader); std::vector names; names.reserve(xfbVaryings.size()); - for (const String& name : xfbVaryings) names.push_back(name.c_str()); - TransformFeedbackVaryings(program, static_cast(names.size()), names.data(), - GL_INTERLEAVED_ATTRIBS); + for (const String& name : xfbVaryings) + names.push_back(name.c_str()); + TransformFeedbackVaryings(program, static_cast(names.size()), names.data(), GL_INTERLEAVED_ATTRIBS); LinkProgram(program); DeleteShader(vertexShader); DeleteShader(geometryShader); @@ -645,13 +680,14 @@ namespace { GLuint LinkCaptureGeometryProgram(const int userVaryings, const char* outPrimitive) { return BuildProgramWithGeometry(kGeometryBlankVs, CaptureGeometryGs(userVaryings, outPrimitive), - CaptureInterleavedFs("#version 130"), - CaptureInterleavedVaryings(userVaryings)); + CaptureInterleavedFs("#version 130"), CaptureInterleavedVaryings(userVaryings)); } // Reinstalls pGLContext: new ProgramState, new P0b preprocess cache, new stage-6 // adoption map. glslang's process globals are untouched on purpose. - void FreshContext() { MG_State::Init(); } + void FreshContext() { + MG_State::Init(); + } class XfbFrontendOrderInvarianceTest : public ::testing::Test { protected: @@ -665,13 +701,25 @@ namespace { GLuint (*link)(); }; - GLuint LinkSkip150() { return LinkSkipComponentsProgram("#version 150"); } - GLuint LinkCapture130() { return LinkCaptureInterleavedProgram(15, "#version 130"); } - GLuint LinkSkip400() { return LinkSkipComponentsProgram("#version 400"); } - GLuint LinkCapture400() { return LinkCaptureInterleavedProgram(15, "#version 400"); } + GLuint LinkSkip150() { + return LinkSkipComponentsProgram("#version 150"); + } + GLuint LinkCapture130() { + return LinkCaptureInterleavedProgram(15, "#version 130"); + } + GLuint LinkSkip400() { + return LinkSkipComponentsProgram("#version 400"); + } + GLuint LinkCapture400() { + return LinkCaptureInterleavedProgram(15, "#version 400"); + } - GLuint LinkGeometryPoints() { return LinkCaptureGeometryProgram(15, "points"); } - GLuint LinkGeometryTriangleStrip() { return LinkCaptureGeometryProgram(15, "triangle_strip"); } + GLuint LinkGeometryPoints() { + return LinkCaptureGeometryProgram(15, "points"); + } + GLuint LinkGeometryTriangleStrip() { + return LinkCaptureGeometryProgram(15, "triangle_strip"); + } const BCase kBCases[] = { {"skip_components@150", &LinkSkip150}, @@ -690,8 +738,8 @@ namespace { String poisoned; }; - AbResult RunAb(const BCase& bCase, const int clipCount, const char* clipVersion, - const Bool freshContextForControl, const Bool includeSpirvHash) { + AbResult RunAb(const BCase& bCase, const int clipCount, const char* clipVersion, const Bool freshContextForControl, + const Bool includeSpirvHash) { AbResult result; // CONTROL: B alone, in a context that has never seen A. @@ -735,8 +783,7 @@ TEST_F(XfbFrontendOrderInvarianceTest, AsyncOn_ClipDistanceBeforeXfbChangesNothi for (const char* clipVersion : {"#version 400", "#version 150", "#version 130"}) { const AbResult result = RunAb(bCase, clipCount, clipVersion, true, true); EXPECT_EQ(result.control, result.poisoned) - << "async=1 B=" << bCase.label << " clipCount=" << clipCount - << " clipVersion=" << clipVersion; + << "async=1 B=" << bCase.label << " clipCount=" << clipCount << " clipVersion=" << clipVersion; } } } @@ -750,8 +797,7 @@ TEST_F(XfbFrontendOrderInvarianceTest, AsyncOff_ClipDistanceBeforeXfbChangesNoth for (const char* clipVersion : {"#version 400", "#version 150", "#version 130"}) { const AbResult result = RunAb(bCase, clipCount, clipVersion, true, true); EXPECT_EQ(result.control, result.poisoned) - << "async=0 B=" << bCase.label << " clipCount=" << clipCount - << " clipVersion=" << clipVersion; + << "async=0 B=" << bCase.label << " clipCount=" << clipCount << " clipVersion=" << clipVersion; } } } @@ -798,7 +844,8 @@ TEST_F(XfbFrontendOrderInvarianceTest, NoMemoHitPossibleForB) { DeleteProgram(poisonedProgram); DeleteProgram(clipProgram); - EXPECT_EQ(control, poisoned) << "async=" << async << " (SPIR-V hash excluded: the salt comment " + EXPECT_EQ(control, poisoned) << "async=" << async + << " (SPIR-V hash excluded: the salt comment " "is stripped by the preprocessor but ids can still renumber)"; } } @@ -863,7 +910,8 @@ TEST_F(XfbFrontendOrderInvarianceTest, LongCaseSequenceLikeGlcts) { } keepAlive.push_back(program); } - for (const GLuint program : keepAlive) DeleteProgram(program); + for (const GLuint program : keepAlive) + DeleteProgram(program); } } @@ -902,7 +950,8 @@ TEST_F(XfbFrontendOrderInvarianceTest, BLinksWhileAIsStillInFlight) { const String poisoned = Render(Capture(program), true); EXPECT_EQ(control, poisoned) << "repetition " << repetition; DeleteProgram(program); - for (const GLuint clipProgram : clipPrograms) DeleteProgram(clipProgram); + for (const GLuint clipProgram : clipPrograms) + DeleteProgram(clipProgram); } } @@ -953,8 +1002,7 @@ TEST_F(XfbFrontendOrderInvarianceTest, DumpBSpirvForDisassembly) { const auto& object = MG_State::pGLContext->GetProgramObject(program); const auto& modules = object->GetGeneratedSpirv(); for (SizeT i = 0; i < modules.size(); ++i) { - String path = String(directory) + "/" + dump.tag + "-" + bCase.label + "-" + - std::to_string(i) + ".spv"; + String path = String(directory) + "/" + dump.tag + "-" + bCase.label + "-" + std::to_string(i) + ".spv"; std::replace(path.begin() + std::strlen(directory) + 1, path.end(), '@', '_'); std::FILE* file = std::fopen(path.c_str(), "wb"); ASSERT_NE(file, nullptr) << path; diff --git a/MobileGL/MG_Test/Query/QueryTest.cpp b/MobileGL/MG_Test/Query/QueryTest.cpp index 1c84f5b6..126a0785 100644 --- a/MobileGL/MG_Test/Query/QueryTest.cpp +++ b/MobileGL/MG_Test/Query/QueryTest.cpp @@ -41,7 +41,7 @@ namespace { // setting into later tests even if an assertion unwinds the test body // early (same pattern as SanityTest's ScopedGLESCapabilitiesOverride). struct ScopedFeaturesOverride { - ScopedFeaturesOverride(): m_snapshot(MG_Config::Features) {} + ScopedFeaturesOverride() : m_snapshot(MG_Config::Features) {} ~ScopedFeaturesOverride() { MG_Config::Features = m_snapshot; } ScopedFeaturesOverride(const ScopedFeaturesOverride&) = delete; ScopedFeaturesOverride& operator=(const ScopedFeaturesOverride&) = delete; @@ -54,7 +54,7 @@ namespace { // it on destruction, so stub timer-query pointers cannot leak into later // tests. struct ScopedBackendFunctionsOverride { - ScopedBackendFunctionsOverride(): m_snapshot(MG_Backend::gBackendFunctionsTable) {} + ScopedBackendFunctionsOverride() : m_snapshot(MG_Backend::gBackendFunctionsTable) {} ~ScopedBackendFunctionsOverride() { MG_Backend::gBackendFunctionsTable = m_snapshot; } ScopedBackendFunctionsOverride(const ScopedBackendFunctionsOverride&) = delete; ScopedBackendFunctionsOverride& operator=(const ScopedBackendFunctionsOverride&) = delete; @@ -77,21 +77,27 @@ namespace { Bool g_stubResultObtainable = true; Uint64 g_stubResultNs = 0; - Bool StubIsTimerQuerySupported() { return g_stubTimerQuerySupported; } + Bool StubIsTimerQuerySupported() { + return g_stubTimerQuerySupported; + } MG_Backend::BackendQueryHandle StubBeginTimeElapsedQuery() { ++g_stubBeginCount; return reinterpret_cast(static_cast(0x51)); } - void StubEndTimeElapsedQuery(MG_Backend::BackendQueryHandle) { ++g_stubEndCount; } + void StubEndTimeElapsedQuery(MG_Backend::BackendQueryHandle) { + ++g_stubEndCount; + } MG_Backend::BackendQueryHandle StubQueryCounterTimestamp() { ++g_stubCounterCount; return reinterpret_cast(static_cast(0x52)); } - Bool StubIsQueryResultAvailable(MG_Backend::BackendQueryHandle) { return g_stubResultAvailable; } + Bool StubIsQueryResultAvailable(MG_Backend::BackendQueryHandle) { + return g_stubResultAvailable; + } Bool StubGetQueryResult64(MG_Backend::BackendQueryHandle, Bool, Uint64* outNanoseconds) { if (!g_stubResultObtainable) { @@ -101,7 +107,9 @@ namespace { return true; } - void StubDeleteBackendQuery(MG_Backend::BackendQueryHandle) { ++g_stubDeleteCount; } + void StubDeleteBackendQuery(MG_Backend::BackendQueryHandle) { + ++g_stubDeleteCount; + } void InstallStubBackendTimerQueries() { auto& backendGL = MG_Backend::gBackendFunctionsTable.GL; @@ -139,7 +147,9 @@ namespace { return reinterpret_cast(static_cast(0x53)); } - void StubEndXfbPrimitivesQuery(MG_Backend::BackendQueryHandle) { ++g_stubXfbEndCount; } + void StubEndXfbPrimitivesQuery(MG_Backend::BackendQueryHandle) { + ++g_stubXfbEndCount; + } // Stub backend occlusion queries. The host has no ES context, and BeginQuery refuses the // occlusion targets outright when the backend advertises no hook - so a conditional-render @@ -203,8 +213,7 @@ protected: // Drain errors recorded by earlier tests so assertions here are // attributable to this test alone (GetError pops one queued error // per call). - while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) { - } + while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) {} } }; @@ -830,14 +839,18 @@ TEST_F(QueryTest, PipelineStatisticsTargetsAreAcceptedAndReportZeroCounterBits) // The NINE unconditional targets. The two tessellation ones are conditional on tessellation // support and have their own test below. static constexpr GLenum kTargets[] = { - GL_VERTICES_SUBMITTED, GL_PRIMITIVES_SUBMITTED, - GL_VERTEX_SHADER_INVOCATIONS, GL_GEOMETRY_SHADER_INVOCATIONS, - GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED, GL_FRAGMENT_SHADER_INVOCATIONS, - GL_COMPUTE_SHADER_INVOCATIONS, GL_CLIPPING_INPUT_PRIMITIVES, + GL_VERTICES_SUBMITTED, + GL_PRIMITIVES_SUBMITTED, + GL_VERTEX_SHADER_INVOCATIONS, + GL_GEOMETRY_SHADER_INVOCATIONS, + GL_GEOMETRY_SHADER_PRIMITIVES_EMITTED, + GL_FRAGMENT_SHADER_INVOCATIONS, + GL_COMPUTE_SHADER_INVOCATIONS, + GL_CLIPPING_INPUT_PRIMITIVES, GL_CLIPPING_OUTPUT_PRIMITIVES, }; - for (const GLenum target: kTargets) { + for (const GLenum target : kTargets) { GLuint id = 0; MG_Impl::GLImpl::GenQueries(1, &id); ASSERT_NE(id, 0u); @@ -887,8 +900,7 @@ TEST_F(QueryTest, PipelineStatisticsTargetsAreAcceptedAndReportZeroCounterBits) // backend starts emitting GL_ARB_tessellation_shader this test keeps passing and keeps pinning // the coupling, and it fails loudly if only one of the two halves moves. TEST_F(QueryTest, TessellationPipelineStatisticsTargetsFollowTheTessellationShaderAdvertisement) { - const auto* extensionsString = - reinterpret_cast(MG_Impl::GLImpl::GetString(GL_EXTENSIONS)); + const auto* extensionsString = reinterpret_cast(MG_Impl::GLImpl::GetString(GL_EXTENSIONS)); ASSERT_NE(extensionsString, nullptr); const Bool advertised = String(extensionsString).find("GL_ARB_tessellation_shader") != String::npos; const GLenum expectedError = advertised ? GL_NO_ERROR : GL_INVALID_ENUM; @@ -898,7 +910,7 @@ TEST_F(QueryTest, TessellationPipelineStatisticsTargetsFollowTheTessellationShad GL_TESS_EVALUATION_SHADER_INVOCATIONS, }; - for (const GLenum target: kTessTargets) { + for (const GLenum target : kTessTargets) { GLuint id = 0; MG_Impl::GLImpl::GenQueries(1, &id); ASSERT_NE(id, 0u); @@ -935,8 +947,7 @@ TEST_F(QueryTest, TessellationPipelineStatisticsTargetsFollowTheTessellationShad } MG_Impl::GLImpl::DeleteQueries(1, &id); - while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) { - } + while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) {} } } @@ -983,8 +994,7 @@ TEST_F(QueryTest, PipelineStatisticsQueryObjectRefusesASecondTargetAndTargetsAre MG_Impl::GLImpl::EndQuery(GL_PRIMITIVES_SUBMITTED); MG_Impl::GLImpl::DeleteQueries(1, &second); MG_Impl::GLImpl::DeleteQueries(1, &id); - while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) { - } + while (MG_Impl::GLImpl::GetError() != GL_NO_ERROR) {} } // glCreateQueries keeps its own, shorter accepted-target list on purpose: it is unchanged here, diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index c4da86f5..67454eb6 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -132,8 +132,8 @@ namespace { // while the getter bounds it by "a backend object exists" - two spellings of the same // thing in production, and only in production. explicit DynamicParameterBackend(MobileGL::MG_Backend::DynamicBackendParameters params, - MobileGL::BackendType type = MobileGL::BackendType::Unknown): - m_params(params), m_type(type) {} + MobileGL::BackendType type = MobileGL::BackendType::Unknown) + : m_params(params), m_type(type) {} void Initialize() override {} MobileGL::Bool InitCapabilities() override { return true; } @@ -143,24 +143,21 @@ namespace { const MobileGL::MG_Backend::GlobalBackendFunctionsTable& GetBackendFunctions() const override { return m_functions; } - const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { - return m_params; - } + const MobileGL::MG_Backend::DynamicBackendParameters& GetDynamicParameters() const override { return m_params; } MobileGL::BackendType GetBackendType() const override { return m_type; } private: MobileGL::MG_Backend::DynamicBackendParameters m_params; MobileGL::BackendType m_type = MobileGL::BackendType::Unknown; MobileGL::MG_Backend::GlobalBackendFunctionsTable m_functions{}; - MobileGL::RendererInfo m_info{ - .RendererName = "Test", - .BackendName = "DynamicParameterBackend", - .ExtraVendor = MobileGL::Nullopt, - .RendererGLInfo = {.TargetGLVersion = {3, 3, 0}, - .TargetGLSLVersion = {4, 6, 0}, - .Extensions = {}, - .IsCompatibilityProfile = false}, - .StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; + MobileGL::RendererInfo m_info{.RendererName = "Test", + .BackendName = "DynamicParameterBackend", + .ExtraVendor = MobileGL::Nullopt, + .RendererGLInfo = {.TargetGLVersion = {3, 3, 0}, + .TargetGLSLVersion = {4, 6, 0}, + .Extensions = {}, + .IsCompatibilityProfile = false}, + .StaticBackendCapability = {.AllowVSOnlyPrograms = false}}; }; void SetEnvVar(const char* name, const char* value) { @@ -192,11 +189,8 @@ namespace { // destruction, so tests that mutate g_GLESCapabilities cannot leak state into later // tests even if an assertion or exception unwinds the test body early. struct ScopedGLESCapabilitiesOverride { - ScopedGLESCapabilitiesOverride(): - m_snapshot(MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities) {} - ~ScopedGLESCapabilitiesOverride() { - MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities = m_snapshot; - } + ScopedGLESCapabilitiesOverride() : m_snapshot(MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities) {} + ~ScopedGLESCapabilitiesOverride() { MobileGL::MG_Backend::DirectGLES::g_GLESCapabilities = m_snapshot; } ScopedGLESCapabilitiesOverride(const ScopedGLESCapabilitiesOverride&) = delete; ScopedGLESCapabilitiesOverride& operator=(const ScopedGLESCapabilitiesOverride&) = delete; @@ -234,12 +228,12 @@ namespace { // installs only the native ES entry points needed to construct/bind a backend texture and // restores the process-wide state even when a gtest assertion unwinds the test body. struct ScopedDirectGLESTextureBindings { - ScopedDirectGLESTextureBindings(): - previousContext(MobileGL::MG_State::pGLContext.Take()), - previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs), - previousActiveUnit(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit), - previousCache(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache), - previousRegistry(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects) { + ScopedDirectGLESTextureBindings() + : previousContext(MobileGL::MG_State::pGLContext.Take()), + previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs), + previousActiveUnit(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit), + previousCache(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache), + previousRegistry(MobileGL::MG_Backend::DirectGLES::TextureImpl::g_backendTextureObjects) { MobileGL::MG_State::pGLContext = MobileGL::MakeUnique(); MobileGL::MG_Backend::DirectGLES::TextureImpl::g_activeTextureUnit = 0; MobileGL::MG_Backend::DirectGLES::TextureImpl::g_boundTexturesCache = {}; @@ -310,8 +304,7 @@ TEST(DirectGLESSanity, AdvertisesUniformBufferObjectAndStencilTexturing) { EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_uniform_buffer_object), extensions.end()); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing), extensions.end()); } TEST(DirectVulkanSanity, AdvertisesUniformBufferObjectAndStencilTexturing) { @@ -320,8 +313,7 @@ TEST(DirectVulkanSanity, AdvertisesUniformBufferObjectAndStencilTexturing) { EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_uniform_buffer_object), extensions.end()); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_stencil_texturing), extensions.end()); } // P11 M: THE SERVER HALF OF A SPLIT eglSwapInterval. The client's emit-table slot forwards the @@ -351,12 +343,10 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) { EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 6); EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader), extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_storage_buffer_object), extensions.end()); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_texture_storage), extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_direct_state_access), extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_multi_draw_indirect), @@ -365,14 +355,10 @@ TEST(DirectGLESSanity, AdvertisesVoxyRequiredRenderingExtensions) { extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_draw_parameters), extensions.end()); - EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64), - extensions.end()); - EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_KHR_shader_subgroup), - extensions.end()); + EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64), extensions.end()); + EXPECT_EQ(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_KHR_shader_subgroup), extensions.end()); } - - TEST(DirectGLESSanity, ProvidesNamedFramebufferBlitForDirectStateAccess) { MobileGL::MG_Backend::DirectGLES::BackendObject_DirectGLES backend; // P13 W6: the backend applies a blit (BlitFramebuffer, which the record sink calls with the @@ -399,8 +385,8 @@ void main() { } )"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader( - source, GL_VERTEX_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader(source, GL_VERTEX_SHADER); EXPECT_NE(rewritten.find("uniform highp int mg_BaseInstance;"), MobileGL::String::npos); EXPECT_NE(rewritten.find("uint drawId = mg_BaseInstance;"), MobileGL::String::npos); @@ -411,8 +397,8 @@ void main() { TEST(DirectGLESSanity, LeavesBaseInstanceBuiltinAloneOutsideVertexShaders) { const MobileGL::String source = "#version 320 es\nuint value = gl_BaseInstance;\n"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader( - source, GL_FRAGMENT_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::EmulateBaseInstanceInVertexShader(source, GL_FRAGMENT_SHADER); EXPECT_EQ(rewritten, source); } @@ -441,8 +427,8 @@ void main() { } )"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms( - source, GL_VERTEX_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(source, GL_VERTEX_SHADER); EXPECT_NE(rewritten.find("int instance = mg_ZeroBasedInstanceID + mg_BaseInstanceLowered;"), MobileGL::String::npos); @@ -481,8 +467,8 @@ void main() { } )"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms( - source, GL_VERTEX_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(source, GL_VERTEX_SHADER); EXPECT_NE(rewritten.find("#define mg_BaseInstanceLowered ((mg_BaseInstanceWordIndex > 0) ? " "int(mg_indirectWords[uint(mg_BaseInstanceWordIndex - 1)]) : mg_BaseInstance)"), @@ -511,8 +497,8 @@ void main() { } )"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms( - source, GL_VERTEX_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(source, GL_VERTEX_SHADER); EXPECT_EQ(rewritten.find("mg_ZeroBasedInstanceID"), MobileGL::String::npos); EXPECT_NE(rewritten.find("int instance = gl_InstanceID + mg_BaseInstanceLowered;"), MobileGL::String::npos); @@ -529,8 +515,8 @@ TEST(DirectGLESSanity, LeavesDrawParameterGlobalsAloneOutsideVertexShaders) { const MobileGL::String source = "#version 310 es\nhighp int mg_BaseInstanceLowered;\nint value = gl_InstanceID + mg_BaseInstanceLowered;\n"; - const auto rewritten = MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms( - source, GL_FRAGMENT_SHADER); + const auto rewritten = + MobileGL::MG_Backend::DirectGLES::PromoteDrawParameterGlobalsToUniforms(source, GL_FRAGMENT_SHADER); EXPECT_EQ(rewritten, source); } @@ -592,8 +578,7 @@ TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensions) { EXPECT_EQ(rendererInfo.TargetGLVersion.Minor, 6); EXPECT_EQ(rendererInfo.TargetGLVersion.Patch, 0); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_compute_shader), extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_storage_buffer_object), extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_multi_draw_indirect), @@ -602,8 +587,7 @@ TEST(DirectVulkanSanity, AdvertisesVoxyRequiredRenderingExtensions) { extensions.end()); EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_shader_draw_parameters), extensions.end()); - EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64), - extensions.end()); + EXPECT_NE(std::find(extensions.begin(), extensions.end(), MobileGL::E_GL_ARB_gpu_shader_int64), extensions.end()); } TEST(DirectVulkanSanity, ClampsAdvertisedTextureAndDrawBufferLimitsToFrontendState) { @@ -625,8 +609,10 @@ TEST(DirectVulkanSanity, ClampsAdvertisedTextureAndDrawBufferLimitsToFrontendSta // Blaze3D's 128-entry TEXTURES[] so Iris' unbind loop cannot index out of bounds); the combined // limit clamps to the full texture-unit array capacity. EXPECT_EQ(highParams.MaxTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS); - EXPECT_EQ(highParams.MaxVertexTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS); - EXPECT_EQ(highParams.MaxComputeTextureImageUnits, MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS); + EXPECT_EQ(highParams.MaxVertexTextureImageUnits, + MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS); + EXPECT_EQ(highParams.MaxComputeTextureImageUnits, + MG_State::GLState::TextureState::MAX_PER_STAGE_TEXTURE_IMAGE_UNITS); EXPECT_LE(highParams.MaxTextureImageUnits, 128); EXPECT_EQ(highParams.MaxCombinedTextureImageUnits, MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS); EXPECT_EQ(highParams.MaxDrawBuffers, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS); @@ -802,8 +788,7 @@ TEST(ProvokingVertexConventions, EachBackendReportsWhatItActuallyPins) { glesCaps.LayerProvokingVertex = GL_FIRST_VERTEX_CONVENTION; glesCaps.ViewportIndexProvokingVertex = GL_UNDEFINED_VERTEX; glesBackend.ApplyGLESCapabilitiesForTesting(glesCaps); - EXPECT_EQ(glesBackend.GetDynamicParameters().LayerProvokingVertex, - static_cast(GL_FIRST_VERTEX_CONVENTION)); + EXPECT_EQ(glesBackend.GetDynamicParameters().LayerProvokingVertex, static_cast(GL_FIRST_VERTEX_CONVENTION)); EXPECT_EQ(glesBackend.GetDynamicParameters().ViewportIndexProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); @@ -811,8 +796,7 @@ TEST(ProvokingVertexConventions, EachBackendReportsWhatItActuallyPins) { MG_External::VulkanCapabilities vkCaps; vkBackend.ApplyVulkanCapabilitiesForTesting(vkCaps); EXPECT_EQ(vkBackend.GetDynamicParameters().LayerProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); - EXPECT_EQ(vkBackend.GetDynamicParameters().ViewportIndexProvokingVertex, - static_cast(GL_UNDEFINED_VERTEX)); + EXPECT_EQ(vkBackend.GetDynamicParameters().ViewportIndexProvokingVertex, static_cast(GL_UNDEFINED_VERTEX)); } TEST(FragmentInterpolationCapabilities, PlumbsGLESAndBothVulkanPropertyPaths) { @@ -908,8 +892,7 @@ TEST(DirectVulkanSanity, AdvertisesNativeSubgroupsOrTheEmulation) { EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedStages, static_cast(GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT)); EXPECT_EQ(backend.GetDynamicParameters().SubgroupSupportedFeatures, - static_cast(GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | - GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | + static_cast(GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | GL_SUBGROUP_FEATURE_QUAD_BIT_KHR)); EXPECT_TRUE(backend.GetDynamicParameters().SubgroupQuadOperationsInAllStages); } @@ -1188,8 +1171,7 @@ TEST(GetterSanity, AtomicCounterQueriesMatchShaderCompilerLimits) { GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS}) { reported = -1; MG_Impl::GLImpl::GetIntegerv(pname, &reported); - EXPECT_EQ(reported, static_cast(Transpiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE)) - << "pname " << pname; + EXPECT_EQ(reported, static_cast(Transpiler::MAX_ATOMIC_COUNTER_BUFFERS_PER_STAGE)) << "pname " << pname; } EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -1229,10 +1211,12 @@ out vec4 color; int mgBindings[gl_MaxAtomicCounterBindings]; int mgCombinedBuffers[gl_MaxCombinedAtomicCounterBuffers]; int mgFragmentBuffers[gl_MaxFragmentAtomicCounterBuffers]; -layout(binding = )" + lastBinding + R"(, offset = 0) uniform atomic_uint mgCounter; +layout(binding = )" + lastBinding + + R"(, offset = 0) uniform atomic_uint mgCounter; void main() { - color = vec4(float(mgBindings[)" + lastBinding + R"(] + mgCombinedBuffers[)" + lastBuffer + - R"(] + mgFragmentBuffers[)" + lastBuffer + R"(] + int(atomicCounterIncrement(mgCounter)))); + color = vec4(float(mgBindings[)" + + lastBinding + R"(] + mgCombinedBuffers[)" + lastBuffer + R"(] + mgFragmentBuffers[)" + + lastBuffer + R"(] + int(atomicCounterIncrement(mgCounter)))); } )"; auto compiled = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({ @@ -1357,14 +1341,13 @@ TEST(GetterSanity, ComputeWorkGroupQueriesMatchShaderCompilerLimits) { // A negative array size is a compile error, so the stage only compiles when EVERY component // of both built-in constants equals what the query above reported. Two-sided by construction: // a resource table that is too permissive fails it exactly like one that is too tight. - const String source = R"(#version 430 core + const String source = + R"(#version 430 core layout(local_size_x = 1) in; const int mgAgree = (gl_MaxComputeWorkGroupSize == ivec3()" + - std::to_string(size[0]) + ", " + std::to_string(size[1]) + ", " + - std::to_string(size[2]) + R"() && + std::to_string(size[0]) + ", " + std::to_string(size[1]) + ", " + std::to_string(size[2]) + R"() && gl_MaxComputeWorkGroupCount == ivec3()" + - std::to_string(count[0]) + ", " + std::to_string(count[1]) + ", " + - std::to_string(count[2]) + R"()) ? 1 : -1; + std::to_string(count[0]) + ", " + std::to_string(count[1]) + ", " + std::to_string(count[2]) + R"()) ? 1 : -1; int mgProbe[mgAgree]; void main() { mgProbe[0] = 0; @@ -1379,10 +1362,10 @@ void main() { // The z ceiling is also what glslang checks a declared local_size_z against, so it has to // reject one invocation past the advertised limit and accept the limit itself. - const String atLimit = "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2]) + - ") in;\nvoid main() {}\n"; - const String pastLimit = "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2] + 1) + - ") in;\nvoid main() {}\n"; + const String atLimit = + "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2]) + ") in;\nvoid main() {}\n"; + const String pastLimit = + "#version 430 core\nlayout(local_size_z = " + std::to_string(size[2] + 1) + ") in;\nvoid main() {}\n"; EXPECT_TRUE(MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({ .shaderType = GL_COMPUTE_SHADER, .sourceStr = atLimit, @@ -1495,15 +1478,14 @@ TEST(GetterSanity, EveryLimitWithABuiltinAgreesWithItsQuery) { for (const LimitPair& pair : pairs) { GLint reported = -424242; MG_Impl::GLImpl::GetIntegerv(pair.pname, &reported); - EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) - << pair.builtin << "'s pname is not answerable at all"; + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << pair.builtin << "'s pname is not answerable at all"; // A negative array size is a compile error, so the stage only compiles when the built-in // equals what the query just reported. Two-sided by construction: a resource table that // is too permissive fails it exactly like one that is too tight. One shader per pair, so // a failure names the limit instead of reporting "something disagreed". - const String source = String("#version 460 core\nout vec4 mgColor;\nconst int mgAgree = (") + - pair.builtin + " == " + std::to_string(reported) + + const String source = String("#version 460 core\nout vec4 mgColor;\nconst int mgAgree = (") + pair.builtin + + " == " + std::to_string(reported) + ") ? 1 : -1;\nint mgProbe[mgAgree];\nvoid main() { mgProbe[0] = 0; mgColor = " "vec4(float(mgProbe[0])); }\n"; auto compiled = MG_Util::ShaderTranspiler::ShaderCompiler::CompileShader({ @@ -1723,10 +1705,8 @@ TEST(GetterSanity, ReportsKhrSubgroupDynamicParameters) { MG_Backend::DynamicBackendParameters params; params.SubgroupSize = 32; params.SubgroupSupportedStages = GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT | GL_COMPUTE_SHADER_BIT; - params.SubgroupSupportedFeatures = GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | - GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | - GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR | - GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; + params.SubgroupSupportedFeatures = GL_SUBGROUP_FEATURE_BASIC_BIT_KHR | GL_SUBGROUP_FEATURE_ARITHMETIC_BIT_KHR | + GL_SUBGROUP_FEATURE_CLUSTERED_BIT_KHR | GL_SUBGROUP_FEATURE_QUAD_BIT_KHR; params.SubgroupQuadOperationsInAllStages = true; MG_Backend::pActiveBackendObject = MakeUnique(params); @@ -1771,22 +1751,20 @@ TEST(DirectVulkanSanity, ReadbackUsesTheSourceFormatTexelSize) { } TEST(DirectVulkanSanity, DefaultFramebufferQuarterTurnReadbackMapsRectAndPixels) { - using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer; using MobileGL::Uint8; + using MobileGL::MG_Backend::DirectVulkan::VulkanRenderer; VkOffset2D offset{}; VkExtent2D copyExtent{}; ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect( - 1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, - &offset, ©Extent)); + 1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, &offset, ©Extent)); EXPECT_EQ(offset.x, 0); EXPECT_EQ(offset.y, 1); EXPECT_EQ(copyExtent.width, 1u); EXPECT_EQ(copyExtent.height, 2u); ASSERT_TRUE(VulkanRenderer::MapDefaultFramebufferReadbackRect( - 1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, - &offset, ©Extent)); + 1, 0, 2, 1, VkExtent2D{2, 3}, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, &offset, ©Extent)); EXPECT_EQ(offset.x, 1); EXPECT_EQ(offset.y, 0); EXPECT_EQ(copyExtent.width, 1u); @@ -1799,13 +1777,13 @@ TEST(DirectVulkanSanity, DefaultFramebufferQuarterTurnReadbackMapsRectAndPixels) const Uint8 expected[] = {'a', 'b', 'c', 'd', 'e', 'f'}; Uint8 result[sizeof(expected)]{}; - ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback( - raw90, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, 1, result)); + ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(raw90, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR, 1, + result)); EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result))); std::fill(std::begin(result), std::end(result), 0); - ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback( - raw270, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, 1, result)); + ASSERT_TRUE(VulkanRenderer::RemapDefaultFramebufferReadback(raw270, 3, 2, VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR, + 1, result)); EXPECT_TRUE(std::equal(std::begin(expected), std::end(expected), std::begin(result))); } @@ -1815,48 +1793,43 @@ TEST(DirectVulkanSanity, ReadbackConvertsRgba8AndRgba16fPixels) { const MobileGL::Uint8 rgba8[] = {17, 34, 51, 68, 85, 102, 119, 136}; MobileGL::Uint8 rgba8Result[sizeof(rgba8)]{}; - ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - rgba8, VK_FORMAT_R8G8B8A8_UNORM, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(rgba8Result), rgba8Result)); + ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(rgba8, VK_FORMAT_R8G8B8A8_UNORM, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, + sizeof(rgba8Result), rgba8Result)); EXPECT_TRUE(std::equal(std::begin(rgba8), std::end(rgba8), std::begin(rgba8Result))); const MobileGL::Uint8 bgra8[] = {51, 34, 17, 68}; MobileGL::Uint8 bgra8Result[4]{}; - ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - bgra8, VK_FORMAT_B8G8R8A8_UNORM, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(bgra8Result), bgra8Result)); + ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(bgra8, VK_FORMAT_B8G8R8A8_UNORM, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, + sizeof(bgra8Result), bgra8Result)); const MobileGL::Uint8 expectedBgra8[] = {17, 34, 51, 68}; EXPECT_TRUE(std::equal(std::begin(expectedBgra8), std::end(expectedBgra8), std::begin(bgra8Result))); const MobileGL::Uint16 rgba16f[] = { - EncodeFloatToHalfBits(-0.25f), EncodeFloatToHalfBits(0.5f), EncodeFloatToHalfBits(1.5f), - EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.25f), EncodeFloatToHalfBits(0.0f), - EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.5f), - EncodeFloatToHalfBits(0.75f), EncodeFloatToHalfBits(0.125f), EncodeFloatToHalfBits(-1.0f), - EncodeFloatToHalfBits(2.0f), EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.75f), - EncodeFloatToHalfBits(0.25f), EncodeFloatToHalfBits(0.0f), + EncodeFloatToHalfBits(-0.25f), EncodeFloatToHalfBits(0.5f), EncodeFloatToHalfBits(1.5f), + EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.25f), EncodeFloatToHalfBits(0.0f), + EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.5f), EncodeFloatToHalfBits(0.75f), + EncodeFloatToHalfBits(0.125f), EncodeFloatToHalfBits(-1.0f), EncodeFloatToHalfBits(2.0f), + EncodeFloatToHalfBits(1.0f), EncodeFloatToHalfBits(0.75f), EncodeFloatToHalfBits(0.25f), + EncodeFloatToHalfBits(0.0f), }; constexpr MobileGL::SizeT kDestinationRowStride = 12; MobileGL::Uint8 rgba16fResult[kDestinationRowStride * 2]; std::fill(std::begin(rgba16fResult), std::end(rgba16fResult), 0xCD); - ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - reinterpret_cast(rgba16f), VK_FORMAT_R16G16B16A16_SFLOAT, - 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, kDestinationRowStride, rgba16fResult)); + ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels(reinterpret_cast(rgba16f), + VK_FORMAT_R16G16B16A16_SFLOAT, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, + kDestinationRowStride, rgba16fResult)); const MobileGL::Uint8 expectedRgba16fRow0[] = {0, 128, 255, 255, 64, 0, 255, 128}; const MobileGL::Uint8 expectedRgba16fRow1[] = {191, 32, 0, 255, 255, 191, 64, 0}; - EXPECT_TRUE(std::equal(std::begin(expectedRgba16fRow0), std::end(expectedRgba16fRow0), - std::begin(rgba16fResult))); + EXPECT_TRUE(std::equal(std::begin(expectedRgba16fRow0), std::end(expectedRgba16fRow0), std::begin(rgba16fResult))); EXPECT_TRUE(std::equal(std::begin(expectedRgba16fRow1), std::end(expectedRgba16fRow1), std::begin(rgba16fResult) + kDestinationRowStride)); - EXPECT_TRUE(std::all_of(std::begin(rgba16fResult) + 8, - std::begin(rgba16fResult) + kDestinationRowStride, + EXPECT_TRUE(std::all_of(std::begin(rgba16fResult) + 8, std::begin(rgba16fResult) + kDestinationRowStride, [](MobileGL::Uint8 value) { return value == 0xCD; })); MobileGL::Float rgba16fFloatResult[16]{}; ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - reinterpret_cast(rgba16f), VK_FORMAT_R16G16B16A16_SFLOAT, - 2, 2, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 8, - reinterpret_cast(rgba16fFloatResult))); + reinterpret_cast(rgba16f), VK_FORMAT_R16G16B16A16_SFLOAT, 2, 2, GL_RGBA, GL_FLOAT, + sizeof(MobileGL::Float) * 8, reinterpret_cast(rgba16fFloatResult))); EXPECT_FLOAT_EQ(rgba16fFloatResult[0], -0.25f); EXPECT_FLOAT_EQ(rgba16fFloatResult[1], 0.5f); EXPECT_FLOAT_EQ(rgba16fFloatResult[2], 1.5f); @@ -1871,9 +1844,8 @@ TEST(DirectVulkanSanity, ReadbackDecodesSingleChannel32BitFormats) { const MobileGL::Float r32f[] = {0.75f, -2.0f}; MobileGL::Float r32fResult[8]{}; ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - reinterpret_cast(r32f), VK_FORMAT_R32_SFLOAT, - 2, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 8, - reinterpret_cast(r32fResult))); + reinterpret_cast(r32f), VK_FORMAT_R32_SFLOAT, 2, 1, GL_RGBA, GL_FLOAT, + sizeof(MobileGL::Float) * 8, reinterpret_cast(r32fResult))); EXPECT_FLOAT_EQ(r32fResult[0], 0.75f); EXPECT_FLOAT_EQ(r32fResult[1], 0.0f); EXPECT_FLOAT_EQ(r32fResult[2], 0.0f); @@ -1883,9 +1855,8 @@ TEST(DirectVulkanSanity, ReadbackDecodesSingleChannel32BitFormats) { const MobileGL::Uint32 r32ui[] = {12345u}; MobileGL::Float r32uiResult[4]{}; ASSERT_TRUE(VulkanRenderer::ConvertReadbackPixels( - reinterpret_cast(r32ui), VK_FORMAT_R32_UINT, - 1, 1, GL_RGBA, GL_FLOAT, sizeof(MobileGL::Float) * 4, - reinterpret_cast(r32uiResult))); + reinterpret_cast(r32ui), VK_FORMAT_R32_UINT, 1, 1, GL_RGBA, GL_FLOAT, + sizeof(MobileGL::Float) * 4, reinterpret_cast(r32uiResult))); EXPECT_FLOAT_EQ(r32uiResult[0], 12345.0f); EXPECT_FLOAT_EQ(r32uiResult[3], 1.0f); } @@ -1915,8 +1886,8 @@ TEST(DirectVulkanSanity, UndefinedDepthStencilLayoutUsesDontCareForUnclearedAspe EXPECT_EQ(depthOnlyClear.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_DONT_CARE); EXPECT_EQ(depthOnlyClear.initialLayout, VK_IMAGE_LAYOUT_UNDEFINED); - auto knownLayout = ResolveDepthStencilAttachmentLoadInfo( - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, false); + auto knownLayout = + ResolveDepthStencilAttachmentLoadInfo(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, false); EXPECT_EQ(knownLayout.depthLoadOp, VK_ATTACHMENT_LOAD_OP_LOAD); EXPECT_EQ(knownLayout.stencilLoadOp, VK_ATTACHMENT_LOAD_OP_LOAD); EXPECT_EQ(knownLayout.initialLayout, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); @@ -1931,9 +1902,9 @@ TEST(DirectVulkanSanity, SampledDepthStencilViewUsesSingleDepthAspect) { VK_IMAGE_ASPECT_DEPTH_BIT); EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_STENCIL_BIT), VK_IMAGE_ASPECT_STENCIL_BIT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewAspectMask( - VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT), - VK_IMAGE_ASPECT_DEPTH_BIT); + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewAspectMask(VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT), + VK_IMAGE_ASPECT_DEPTH_BIT); } TEST(DirectVulkanSanity, SpirvStorageImageFormatsMapToVulkanFormats) { @@ -2000,20 +1971,20 @@ TEST(DirectVulkanSanity, SpirvStorageImageFormatsMapToVulkanFormats) { TEST(DirectVulkanSanity, StorageImageViewFormatUsesBindingOnlyForFormatlessFloatPolicy) { using MobileGL::MG_Backend::DirectVulkan::UniformManager; - EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat( - VK_FORMAT_UNDEFINED, GL_RGBA16F, VK_FORMAT_R16G16B16A16_UNORM, true), + EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(VK_FORMAT_UNDEFINED, GL_RGBA16F, + VK_FORMAT_R16G16B16A16_UNORM, true), VK_FORMAT_R16G16B16A16_SFLOAT); - EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat( - VK_FORMAT_UNDEFINED, GL_RGBA16, VK_FORMAT_R16G16B16A16_SFLOAT, true), + EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(VK_FORMAT_UNDEFINED, GL_RGBA16, + VK_FORMAT_R16G16B16A16_SFLOAT, true), VK_FORMAT_R16G16B16A16_UNORM); - EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat( - VK_FORMAT_R32_UINT, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false), - VK_FORMAT_R32_UINT); - EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat( - VK_FORMAT_UNDEFINED, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false), - VK_FORMAT_R32_SFLOAT); - EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat( - VK_FORMAT_UNDEFINED, GL_NONE, VK_FORMAT_R16G16B16A16_SFLOAT, true), + EXPECT_EQ( + UniformManager::ResolveStorageImageViewFormat(VK_FORMAT_R32_UINT, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false), + VK_FORMAT_R32_UINT); + EXPECT_EQ( + UniformManager::ResolveStorageImageViewFormat(VK_FORMAT_UNDEFINED, GL_RGBA16F, VK_FORMAT_R32_SFLOAT, false), + VK_FORMAT_R32_SFLOAT); + EXPECT_EQ(UniformManager::ResolveStorageImageViewFormat(VK_FORMAT_UNDEFINED, GL_NONE, VK_FORMAT_R16G16B16A16_SFLOAT, + true), VK_FORMAT_UNDEFINED); } @@ -2043,16 +2014,14 @@ TEST(DirectVulkanSanity, ProgramObjectMovePreservesStorageImageFormatPolicy) { TEST(DirectVulkanSanity, SamplerUniformTypesPreserveTheirNumericDomain) { using namespace MobileGL::MG_Backend::DirectVulkan; - EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_SAMPLER_2D), - SamplerNumericDomain::Float); + EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_SAMPLER_2D), SamplerNumericDomain::Float); EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW), SamplerNumericDomain::Float); EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_INT_SAMPLER_2D_ARRAY), SamplerNumericDomain::SignedInteger); EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_UNSIGNED_INT_SAMPLER_2D), SamplerNumericDomain::UnsignedInteger); - EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_IMAGE_2D), - SamplerNumericDomain::Unknown); + EXPECT_EQ(ProgramFactory::UniformTypeToSamplerNumericDomain(GL_IMAGE_2D), SamplerNumericDomain::Unknown); } // The image half of the same question, which the sampler form above deliberately answers @@ -2064,8 +2033,7 @@ TEST(DirectVulkanSanity, ImageUniformTypesPreserveTheirNumericDomain) { EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_IMAGE_2D), SamplerNumericDomain::Float); EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_IMAGE_BUFFER), SamplerNumericDomain::Float); - EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_IMAGE_CUBE_MAP_ARRAY), - SamplerNumericDomain::Float); + EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_IMAGE_CUBE_MAP_ARRAY), SamplerNumericDomain::Float); EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_INT_IMAGE_2D_ARRAY), SamplerNumericDomain::SignedInteger); EXPECT_EQ(ProgramFactory::UniformTypeToImageNumericDomain(GL_INT_IMAGE_BUFFER), @@ -2082,26 +2050,25 @@ TEST(DirectVulkanSanity, ImageUniformTypesPreserveTheirNumericDomain) { TEST(DirectVulkanSanity, SampledViewFormatMatchesSamplerNumericDomainWithoutChangingComponentLayout) { using namespace MobileGL::MG_Backend::DirectVulkan; - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::UnsignedInteger), - VK_FORMAT_R32_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::SignedInteger), - VK_FORMAT_R32_SINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R32_UINT, SamplerNumericDomain::Float), + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::UnsignedInteger), + VK_FORMAT_R32_UINT); + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R32_SFLOAT, SamplerNumericDomain::SignedInteger), + VK_FORMAT_R32_SINT); + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R32_UINT, SamplerNumericDomain::Float), VK_FORMAT_R32_SFLOAT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R16G16B16A16_SFLOAT, SamplerNumericDomain::UnsignedInteger), + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R16G16B16A16_SFLOAT, + SamplerNumericDomain::UnsignedInteger), VK_FORMAT_R16G16B16A16_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R8G8B8A8_UNORM, SamplerNumericDomain::UnsignedInteger), + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R8G8B8A8_UNORM, + SamplerNumericDomain::UnsignedInteger), VK_FORMAT_R8G8B8A8_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_R32_UINT, SamplerNumericDomain::UnsignedInteger), - VK_FORMAT_R32_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_B10G11R11_UFLOAT_PACK32, SamplerNumericDomain::UnsignedInteger), + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_R32_UINT, SamplerNumericDomain::UnsignedInteger), + VK_FORMAT_R32_UINT); + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_B10G11R11_UFLOAT_PACK32, + SamplerNumericDomain::UnsignedInteger), VK_FORMAT_UNDEFINED); // Depth/stencil formats never resolve through color-class reinterpretation; they pass @@ -2109,26 +2076,23 @@ TEST(DirectVulkanSanity, SampledViewFormatMatchesSamplerNumericDomainWithoutChan // depth-stencil formats are multi-numeric (vkuFormatIsSampledFloat is false for them), // so without the passthrough a plain sampler2D/sampler2DShadow on GL_DEPTH24_STENCIL8 // would resolve to UNDEFINED and the draw would be dropped. - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_D24_UNORM_S8_UINT, SamplerNumericDomain::Float), + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_D24_UNORM_S8_UINT, SamplerNumericDomain::Float), VK_FORMAT_D24_UNORM_S8_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_D32_SFLOAT_S8_UINT, SamplerNumericDomain::Float), - VK_FORMAT_D32_SFLOAT_S8_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_D32_SFLOAT, SamplerNumericDomain::Float), + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_D32_SFLOAT_S8_UINT, SamplerNumericDomain::Float), + VK_FORMAT_D32_SFLOAT_S8_UINT); + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_D32_SFLOAT, SamplerNumericDomain::Float), VK_FORMAT_D32_SFLOAT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_D24_UNORM_S8_UINT, SamplerNumericDomain::UnsignedInteger), + EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_D24_UNORM_S8_UINT, + SamplerNumericDomain::UnsignedInteger), VK_FORMAT_D24_UNORM_S8_UINT); - EXPECT_EQ(VkTextureManager::ResolveSampledImageViewFormat( - VK_FORMAT_D32_SFLOAT, SamplerNumericDomain::UnsignedInteger), - VK_FORMAT_D32_SFLOAT); + EXPECT_EQ( + VkTextureManager::ResolveSampledImageViewFormat(VK_FORMAT_D32_SFLOAT, SamplerNumericDomain::UnsignedInteger), + VK_FORMAT_D32_SFLOAT); - EXPECT_TRUE(VkTextureManager::AreSampledImageViewFormatsCompatible( - VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT)); - EXPECT_FALSE(VkTextureManager::AreSampledImageViewFormatsCompatible( - VK_FORMAT_R32_SFLOAT, VK_FORMAT_R16G16B16A16_UINT)); + EXPECT_TRUE(VkTextureManager::AreSampledImageViewFormatsCompatible(VK_FORMAT_R32_SFLOAT, VK_FORMAT_R32_UINT)); + EXPECT_FALSE( + VkTextureManager::AreSampledImageViewFormatsCompatible(VK_FORMAT_R32_SFLOAT, VK_FORMAT_R16G16B16A16_UINT)); } TEST(RenderStateSanity, ProvokingVertexUpdatesStateAndValidatesEnum) { @@ -2179,8 +2143,8 @@ TEST(LogSanity, UsesEnvOverrideForFilePath) { // process logs on the main thread - the client role - so the line landed in the // client-derived path. In a pull build there is no split and the base IS the file. #if MOBILEGL_BUILD_DISAGGREGATED - const fs::path readPath = MobileGL::MG_Util::Debug::RoleLogPath( - logPath.string().c_str(), MobileGL::MG_Util::Debug::LogRole::Client); + const fs::path readPath = + MobileGL::MG_Util::Debug::RoleLogPath(logPath.string().c_str(), MobileGL::MG_Util::Debug::LogRole::Client); #else const fs::path readPath = logPath; #endif @@ -2193,8 +2157,8 @@ TEST(LogSanity, UsesEnvOverrideForFilePath) { fs::remove(logPath); #if MOBILEGL_BUILD_DISAGGREGATED - fs::remove(MobileGL::MG_Util::Debug::RoleLogPath( - logPath.string().c_str(), MobileGL::MG_Util::Debug::LogRole::Client)); + fs::remove( + MobileGL::MG_Util::Debug::RoleLogPath(logPath.string().c_str(), MobileGL::MG_Util::Debug::LogRole::Client)); #endif } @@ -2346,7 +2310,8 @@ TEST(RenderStateSanity, GetDoublevMatchesGetFloatvWidened) { GetFloatv(GL_COLOR_CLEAR_VALUE, cf); GLdouble cd[4] = {}; GetDoublev(GL_COLOR_CLEAR_VALUE, cd); - for (int i = 0; i < 4; ++i) EXPECT_DOUBLE_EQ(cd[i], static_cast(cf[i])); + for (int i = 0; i < 4; ++i) + EXPECT_DOUBLE_EQ(cd[i], static_cast(cf[i])); // Null params -> GL_INVALID_VALUE. GetDoublev(GL_DEPTH_RANGE, nullptr); @@ -2454,11 +2419,14 @@ TEST(RenderStateSanity, ColorMaskIndexedStoresAndReadsBack) { // Default: every draw buffer's writemask is all-true. GLboolean b0[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE}; GetBooleanv(GL_COLOR_WRITEMASK, b0); - EXPECT_EQ(b0[0], GL_TRUE); EXPECT_EQ(b0[1], GL_TRUE); - EXPECT_EQ(b0[2], GL_TRUE); EXPECT_EQ(b0[3], GL_TRUE); + EXPECT_EQ(b0[0], GL_TRUE); + EXPECT_EQ(b0[1], GL_TRUE); + EXPECT_EQ(b0[2], GL_TRUE); + EXPECT_EQ(b0[3], GL_TRUE); GLboolean bi[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE}; GetBooleani_v(GL_COLOR_WRITEMASK, 3, bi); - EXPECT_EQ(bi[0], GL_TRUE); EXPECT_EQ(bi[3], GL_TRUE); + EXPECT_EQ(bi[0], GL_TRUE); + EXPECT_EQ(bi[3], GL_TRUE); EXPECT_EQ(GetError(), GL_NO_ERROR); // glColorMaski sets ONLY the addressed draw buffer; buffer 0 stays untouched, and the non-indexed @@ -2466,14 +2434,17 @@ TEST(RenderStateSanity, ColorMaskIndexedStoresAndReadsBack) { ColorMaski(2, GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); EXPECT_EQ(GetError(), GL_NO_ERROR); GetBooleani_v(GL_COLOR_WRITEMASK, 2, bi); - EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[1], GL_TRUE); - EXPECT_EQ(bi[2], GL_FALSE); EXPECT_EQ(bi[3], GL_TRUE); + EXPECT_EQ(bi[0], GL_FALSE); + EXPECT_EQ(bi[1], GL_TRUE); + EXPECT_EQ(bi[2], GL_FALSE); + EXPECT_EQ(bi[3], GL_TRUE); GetBooleanv(GL_COLOR_WRITEMASK, b0); - EXPECT_EQ(b0[0], GL_TRUE); EXPECT_EQ(b0[1], GL_TRUE); // buffer 0 unchanged by ColorMaski(2, ...) + EXPECT_EQ(b0[0], GL_TRUE); + EXPECT_EQ(b0[1], GL_TRUE); // buffer 0 unchanged by ColorMaski(2, ...) // GLboolean coercion: any nonzero byte enables the component (NOT == GL_TRUE). - ColorMaski(1, static_cast(2), static_cast(0), - static_cast(2), static_cast(0)); + ColorMaski(1, static_cast(2), static_cast(0), static_cast(2), + static_cast(0)); GetBooleani_v(GL_COLOR_WRITEMASK, 1, bi); EXPECT_EQ(bi[0], GL_TRUE); // 2 -> TRUE EXPECT_EQ(bi[1], GL_FALSE); // 0 -> FALSE @@ -2483,9 +2454,11 @@ TEST(RenderStateSanity, ColorMaskIndexedStoresAndReadsBack) { // glColorMask (non-indexed) broadcasts to EVERY draw buffer, overwriting the per-buffer masks. ColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE); GetBooleani_v(GL_COLOR_WRITEMASK, 2, bi); - EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[2], GL_TRUE); // buffer 2 was overwritten by the broadcast + EXPECT_EQ(bi[0], GL_FALSE); + EXPECT_EQ(bi[2], GL_TRUE); // buffer 2 was overwritten by the broadcast GetBooleani_v(GL_COLOR_WRITEMASK, 1, bi); - EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[3], GL_TRUE); + EXPECT_EQ(bi[0], GL_FALSE); + EXPECT_EQ(bi[3], GL_TRUE); // Out-of-range index -> GL_INVALID_VALUE, no state change. ColorMaski(kMaxDrawBuffers, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); @@ -2522,7 +2495,6 @@ TEST(RenderStateSanity, PrimitiveRestartIndexStoresAndReadsBack) { MG_State::pGLContext.reset(); } - // ---- DirectGLES readback driver-state shadows ---------------------------------------------------- // Regression coverage for the readback-path state-leak overhaul: the pixel-PBO // binding cache, the framebuffer-binding shadow, the PACK pixel-store shadow and @@ -2563,7 +2535,8 @@ namespace { SG_Log("PixelStorei:" + std::to_string(pname) + ":" + std::to_string(param)); } void SG_GenFramebuffers(GLsizei count, GLuint* framebuffers) { - for (GLsizei i = 0; i < count; ++i) framebuffers[i] = g_nextStateGuardFBOId++; + for (GLsizei i = 0; i < count; ++i) + framebuffers[i] = g_nextStateGuardFBOId++; } void SG_FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { SG_Log("FramebufferTexture2D:" + std::to_string(target) + ":" + std::to_string(attachment) + ":" + @@ -2586,7 +2559,7 @@ namespace { // Installs the recording table and resets every readback driver-state shadow on // both ends, so these tests cannot bleed into (or inherit from) other tests. struct ScopedStateGuardMocks { - ScopedStateGuardMocks(): previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs) { + ScopedStateGuardMocks() : previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs) { ResetShadows(); MobileGL::MG_External::GLESFunctionsTable functions{}; functions.glBindBuffer = SG_BindBuffer; @@ -2712,8 +2685,8 @@ TEST(DirectGLESStateGuards, ScratchFBODetachesCrossAspectResidue) { std::to_string(GL_DEPTH_STENCIL_ATTACHMENT) + ":" + std::to_string(GL_TEXTURE_2D) + ":0:0"; const MobileGL::String colorAttach = "FramebufferTexture2D:" + std::to_string(GL_READ_FRAMEBUFFER) + ":" + - std::to_string(GL_COLOR_ATTACHMENT0) + ":" + - std::to_string(GL_TEXTURE_2D) + ":22:0"; + std::to_string(GL_COLOR_ATTACHMENT0) + ":" + std::to_string(GL_TEXTURE_2D) + + ":22:0"; EXPECT_EQ(mocks.log.Count(dsDetach), 1u); EXPECT_EQ(mocks.log.Count(colorAttach), 1u); @@ -2739,8 +2712,8 @@ TEST(DirectGLESStateGuards, ScratchFBOTextureDeletionForcesFullScrub) { ScratchFBOImpl::EnsureColorAttachment2D(fb, GL_READ_FRAMEBUFFER, 22, GL_TEXTURE_2D, 0); EXPECT_GE(mocks.log.Count("FramebufferTexture2D:"), 2u); // scrub (color + depth) ... const MobileGL::String colorAttach = "FramebufferTexture2D:" + std::to_string(GL_READ_FRAMEBUFFER) + ":" + - std::to_string(GL_COLOR_ATTACHMENT0) + ":" + - std::to_string(GL_TEXTURE_2D) + ":22:0"; + std::to_string(GL_COLOR_ATTACHMENT0) + ":" + std::to_string(GL_TEXTURE_2D) + + ":22:0"; EXPECT_EQ(mocks.log.Count(colorAttach), 1u); // ... then the real re-attach } @@ -2768,7 +2741,8 @@ namespace { void SG_DeleteTextures(GLsizei count, const GLuint* textures) { if (!g_deletedTextureIds) return; - for (GLsizei i = 0; i < count; ++i) g_deletedTextureIds->push_back(textures[i]); + for (GLsizei i = 0; i < count; ++i) + g_deletedTextureIds->push_back(textures[i]); } // Clears the recording hook even when a gtest assertion unwinds the test body @@ -2832,25 +2806,31 @@ namespace { GLuint g_nextTwinDriverId = 900; void TW_GenFramebuffers(GLsizei count, GLuint* ids) { - for (GLsizei i = 0; i < count; ++i) ids[i] = g_nextTwinDriverId++; + for (GLsizei i = 0; i < count; ++i) + ids[i] = g_nextTwinDriverId++; } void TW_DeleteFramebuffers(GLsizei count, const GLuint* ids) { if (!g_twinDeletionSinks) return; - for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->framebuffers.push_back(ids[i]); + for (GLsizei i = 0; i < count; ++i) + g_twinDeletionSinks->framebuffers.push_back(ids[i]); } void TW_GenRenderbuffers(GLsizei count, GLuint* ids) { - for (GLsizei i = 0; i < count; ++i) ids[i] = g_nextTwinDriverId++; + for (GLsizei i = 0; i < count; ++i) + ids[i] = g_nextTwinDriverId++; } void TW_DeleteRenderbuffers(GLsizei count, const GLuint* ids) { if (!g_twinDeletionSinks) return; - for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->renderbuffers.push_back(ids[i]); + for (GLsizei i = 0; i < count; ++i) + g_twinDeletionSinks->renderbuffers.push_back(ids[i]); } void TW_GenSamplers(GLsizei count, GLuint* ids) { - for (GLsizei i = 0; i < count; ++i) ids[i] = g_nextTwinDriverId++; + for (GLsizei i = 0; i < count; ++i) + ids[i] = g_nextTwinDriverId++; } void TW_DeleteSamplers(GLsizei count, const GLuint* ids) { if (!g_twinDeletionSinks) return; - for (GLsizei i = 0; i < count; ++i) g_twinDeletionSinks->samplers.push_back(ids[i]); + for (GLsizei i = 0; i < count; ++i) + g_twinDeletionSinks->samplers.push_back(ids[i]); } void TW_BindFramebuffer(GLenum target, GLuint framebuffer) { SG_Log("BindFramebuffer:" + std::to_string(target) + ":" + std::to_string(framebuffer)); @@ -2861,7 +2841,7 @@ namespace { // Installs a table that can create and destroy all three twin kinds, and unwinds it (plus the // recording pointer) even when an assertion aborts the test body. struct ScopedBackendTwinMocks { - ScopedBackendTwinMocks(): previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs) { + ScopedBackendTwinMocks() : previousFunctions(MobileGL::MG_Backend::DirectGLES::g_GLESFuncs) { MobileGL::MG_Backend::DirectGLES::FramebufferImpl::InvalidateFramebufferBindingCache(); MobileGL::MG_External::GLESFunctionsTable functions{}; functions.glGenFramebuffers = TW_GenFramebuffers; @@ -3093,15 +3073,16 @@ namespace { MobileGL::Vector* g_texSpecCalls = nullptr; GLuint g_texSpecBoundTexture = 0; - void TS_BindTexture(GLenum, GLuint texture) { g_texSpecBoundTexture = texture; } + void TS_BindTexture(GLenum, GLuint texture) { + g_texSpecBoundTexture = texture; + } void TS_ActiveTexture(GLenum) {} void TS_TexParameteri(GLenum, GLenum, GLint) {} void TS_TexParameterf(GLenum, GLenum, GLfloat) {} void TS_TexParameterfv(GLenum, GLenum, const GLfloat*) {} void TS_PixelStorei(GLenum, GLint) {} void TS_BindBuffer(GLenum, GLuint) {} - void TS_TexImage2D(GLenum, GLint level, GLint, GLsizei width, GLsizei height, GLint, GLenum, GLenum, - const void*) { + void TS_TexImage2D(GLenum, GLint level, GLint, GLsizei width, GLsizei height, GLint, GLenum, GLenum, const void*) { if (g_texSpecCalls && level == 0) { g_texSpecCalls->push_back({g_texSpecBoundTexture, width, height}); } @@ -3124,9 +3105,8 @@ namespace { MobileGL::Int size) { using namespace MobileGL; MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, name); - auto object = MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(); + auto object = + MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(); object->SetInternalFormat(TextureInternalFormat::RGBA8); MG_State::GLState::AsMipmapTexture(object.get()) ->AllocateStorage(TextureUploadTarget::Texture2D, 0, {{size, size, 1}, 4}); @@ -3134,7 +3114,6 @@ namespace { } } // namespace - // P5e (tx2), CONTRACT-P5E §4.3: THE HANDLE ARM'S SIBLING OF UnitMemoRefusesToDriveATwinFromAnotherTexture, // WITH ABA AS THE HAZARD INSTEAD OF A SILENT SLOT SWAP. (P13 W3b retired that case: it ran only at // the P2 mask, whose legacy unit arm is gone now that subsystem bits 0-13 are fixed on.) @@ -3212,8 +3191,7 @@ TEST(DirectGLESTextureSync, ARecycledTextureSlotReMintsTheTwinOnTheHandleArm) { // ...and the predecessor's handle stops resolving, which is what makes a stale entry left in // a by-handle work list harmless: the lookup answers null and the entry is skipped, where a // borrowed slot would have been replayed against the wrong object. - EXPECT_EQ(registry.FindByHandle(first), nullptr) - << "the predecessor's handle still resolves at a recycled slot"; + EXPECT_EQ(registry.FindByHandle(first), nullptr) << "the predecessor's handle still resolves at a recycled slot"; MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Texture, second); } @@ -3250,8 +3228,9 @@ namespace { return sum; }; while (flavourCapacity() < needed && pools.size() < 100000) { - pools.push_back({VK_NULL_HANDLE, UniformManager::NextDescriptorPoolMaxSets(pools, updateAfterBind, kBaseSets), - 0, updateAfterBind}); + pools.push_back({VK_NULL_HANDLE, + UniformManager::NextDescriptorPoolMaxSets(pools, updateAfterBind, kBaseSets), 0, + updateAfterBind}); } return pools; } @@ -3341,8 +3320,8 @@ TEST(GetterSanity, CombinedUniformComponentsSaturateInsteadOfOverflowing) { // than from a frontend constant, i.e. the only call site where the saturation actually depends // on data a driver supplies. static constexpr GLenum kCombinedPnames[] = { - GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, - GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS, GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, + GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, + GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS, GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS, GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS, }; // The GL 4.6 core table 23.64 floor, which all six combined pnames carry. @@ -3353,7 +3332,7 @@ TEST(GetterSanity, CombinedUniformComponentsSaturateInsteadOfOverflowing) { params.MaxUniformBlockSize = std::numeric_limits::max(); MG_Backend::pActiveBackendObject = MakeUnique(params); - for (const GLenum pname: kCombinedPnames) { + for (const GLenum pname : kCombinedPnames) { GLint reported = 0; MG_Impl::GLImpl::GetIntegerv(pname, &reported); EXPECT_GT(reported, 0) << "pname 0x" << pname << " wrapped to a negative combined component count"; @@ -3380,13 +3359,12 @@ TEST(GetterSanity, CombinedUniformComponentsSaturateInsteadOfOverflowing) { MG_State::pGLContext.reset(); } - namespace { // A stand-in frontend object for the twin table. It carries the one thing the table asks of a // state object - GetLifetimeId() - so these cases can pin the identity contract without a // GLContext, a driver or a backend twin that would want ES entry points. struct FakeStateObject { - explicit FakeStateObject(MobileGL::Uint64 lifetimeId): m_lifetimeId(lifetimeId) {} + explicit FakeStateObject(MobileGL::Uint64 lifetimeId) : m_lifetimeId(lifetimeId) {} MobileGL::Uint64 GetLifetimeId() const { return m_lifetimeId; } private: @@ -3405,16 +3383,18 @@ namespace { // perturb another, so it gets a kind of its own rather than a promise about gtest's // registration order: --gtest_shuffle, --gtest_filter and a future case are all free to // reorder them, and a shared kind would make that a flake. - using FakeSlotTable = MobileGL::MG_Backend::DirectGLES:: - BackendSlotTable; - using FakeSharedKindSlotTable = MobileGL::MG_Backend::DirectGLES:: - BackendSlotTable; + using FakeSlotTable = MobileGL::MG_Backend::DirectGLES::BackendSlotTable; + using FakeSharedKindSlotTable = + MobileGL::MG_Backend::DirectGLES::BackendSlotTable; // P5e (id): ShaderCso is the ONE kind with a composite band, so the band case needs a table // of that kind. It never touches MGPipeSlots() - the whole case runs through the by-handle // overloads - and the holder list is per TABLE TYPE, so this instantiation shares nothing // with the real g_backendProgramObjects. - using FakeShaderCsoSlotTable = MobileGL::MG_Backend::DirectGLES:: - BackendSlotTable; + using FakeShaderCsoSlotTable = + MobileGL::MG_Backend::DirectGLES::BackendSlotTable; #if MOBILEGL_BUILD_DISAGGREGATED // PH-2 (F2, ID-P7-54): on a disaggregated build the handle overload's backward-generation and @@ -3440,8 +3420,8 @@ namespace { #endif const auto ticks = std::chrono::steady_clock::now().time_since_epoch().count(); return std::filesystem::temp_directory_path() / - (std::string(stem) + "-" + std::to_string(pid) + "-" + std::to_string(++counter) + - "-" + std::to_string(ticks) + ".log"); + (std::string(stem) + "-" + std::to_string(pid) + "-" + std::to_string(++counter) + "-" + + std::to_string(ticks) + ".log"); } // Points MobileGL's file log at `path` for the life of the guard and puts back whatever the @@ -3449,7 +3429,7 @@ namespace { // including a failed ASSERT. The log is closed on both sides of the switch, because Log.cpp // reads the variable only when it opens the file. struct ScopedLogFileRedirect { - explicit ScopedLogFileRedirect(const std::filesystem::path& path): m_path(path) { + explicit ScopedLogFileRedirect(const std::filesystem::path& path) : m_path(path) { if (const char* previous = std::getenv("MOBILEGL_LOG_FILE_PATH")) { m_hadPrevious = true; m_previous = previous; @@ -3524,8 +3504,7 @@ TEST(DirectGLESSlotTable, ARecycledSlotIsANewHandleAndTheStaleOneResolvesToNothi EXPECT_TRUE(FakeSlotTable::ReleaseTwinByHandle(firstHandle)); MG_Pipe::MGPipeSlots().Free(kKind, firstHandle); EXPECT_EQ(table.LiveCount(), 0u); - EXPECT_EQ(table.FindByHandle(firstHandle), nullptr) - << "a handle whose object is gone still resolved to a twin"; + EXPECT_EQ(table.FindByHandle(firstHandle), nullptr) << "a handle whose object is gone still resolved to a twin"; const MG_Pipe::MGPipeHandle secondHandle = MintFor(kKind, 0xA2u); EXPECT_EQ(secondHandle.Slot, firstHandle.Slot) << "the free list did not hand the slot back"; @@ -3619,8 +3598,7 @@ TEST(DirectGLESSlotTable, AWholeTableSavesResetsAndRestores) { (*table.FindByHandle(handle))->marker = 7; const FakeSlotTable saved = table; - EXPECT_EQ(FakeSlotTable::HolderCount(), holdersBefore + 2u) - << "the by-value copy did not register as a holder"; + EXPECT_EQ(FakeSlotTable::HolderCount(), holdersBefore + 2u) << "the by-value copy did not register as a holder"; table = {}; EXPECT_EQ(FakeSlotTable::HolderCount(), holdersBefore + 2u) << "the reset changed the holder count - a temporary's registration leaked or the " @@ -3748,9 +3726,8 @@ TEST(DirectGLESSlotTable, AnnouncedDeathKeepsObjectChurnFromAccumulatingWithoutA slots.Free(kKind, handle); } - EXPECT_EQ(peakLive, 1u) - << peakLive << " twins were live at once with " << kChurn - << " objects churned and every death announced - the release stopped dropping the twin"; + EXPECT_EQ(peakLive, 1u) << peakLive << " twins were live at once with " << kChurn + << " objects churned and every death announced - the release stopped dropping the twin"; EXPECT_EQ(table.LiveCount(), 0u); EXPECT_EQ(slots.LiveCount(kKind), liveBefore) << "the churn leaked slots"; // 2 and not 1: on a cold allocator the high-water mark counts the RESERVED slot 0 @@ -3805,12 +3782,10 @@ TEST(DirectGLESSlotTable, EveryReKeyedObjectClassAnnouncesItsOwnDeath) { static Vector> notices; notices.clear(); const MG_State::GLState::StateObjectDeathOps recording = { - .OnDestroyed = [](MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, MG_Pipe::MGPipeHandle) { - notices.emplace_back(kind, lifetimeId); - }, + .OnDestroyed = [](MG_Pipe::MGPipeKind kind, Uint64 lifetimeId, + MG_Pipe::MGPipeHandle) { notices.emplace_back(kind, lifetimeId); }, }; - const MG_State::GLState::StateObjectDeathOps* previous = - MG_State::GLState::GetStateObjectDeathOps(); + const MG_State::GLState::StateObjectDeathOps* previous = MG_State::GLState::GetStateObjectDeathOps(); MG_State::GLState::SetStateObjectDeathOps(&recording); Vector> expected; @@ -3844,8 +3819,7 @@ TEST(DirectGLESSlotTable, EveryReKeyedObjectClassAnnouncesItsOwnDeath) { MG_State::GLState::SetStateObjectDeathOps(previous); - EXPECT_EQ(std::find(notices.begin(), notices.end(), - std::make_pair(MG_Pipe::MGPipeKind::Buffer, bufferLifetimeId)), + EXPECT_EQ(std::find(notices.begin(), notices.end(), std::make_pair(MG_Pipe::MGPipeKind::Buffer, bufferLifetimeId)), notices.end()) << "a BufferObject raised a state-object death notice; P3a routes buffer death through " "resource_destroy instead (D-L), and both firing would free the slot twice"; @@ -3893,8 +3867,7 @@ TEST(DirectGLESSlotTable, TheTwinRegistryCasesInThisBinaryRunOnTheHandleArm) { "binary that builds a twin - the scratch-FBO scrub, the three context-generation " "guards, the sampled-set staleness walk, ScopedDirectGLESTextureBindings - is " "exercising code this package did not change"; - EXPECT_NE(MobileGL::MG_Config::Features.PipePush & MobileGL::MG_Pipe::kMGPipeSubsystemEsprytSlots, - 0ull); + EXPECT_NE(MobileGL::MG_Config::Features.PipePush & MobileGL::MG_Pipe::kMGPipeSubsystemEsprytSlots, 0ull); } namespace { @@ -3916,8 +3889,8 @@ namespace { // and that a successor object landing on the freed slot gets a different Gen while the // predecessor's handle resolves to nothing. template - void ExpectTheHandleArmDrivesThisKind(const char* kindName, MobileGL::MG_Pipe::MGPipeKind kind, - Registry& registry, MakeObject make) { + void ExpectTheHandleArmDrivesThisKind(const char* kindName, MobileGL::MG_Pipe::MGPipeKind kind, Registry& registry, + MakeObject make) { using namespace MobileGL; auto first = make(); @@ -3927,9 +3900,9 @@ namespace { const MG_Pipe::MGPipeHandle firstHandle = MG_Pipe::MGPipeSlots().Acquire(kind, firstLifetimeId); ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(firstHandle)) << kindName; auto* const firstTwin = registry.GetOrCreateByHandle(firstHandle); - ASSERT_NE(firstTwin, nullptr) - << kindName << ": the real registry refused a handle the client minted, so this kind is " - "not running on the {slot, gen} arm at all"; + ASSERT_NE(firstTwin, nullptr) << kindName + << ": the real registry refused a handle the client minted, so this kind is " + "not running on the {slot, gen} arm at all"; ASSERT_NE(MG_State::GLState::GetStateObjectDeathOps(), nullptr) << kindName << ": no death-notice consumer is installed"; EXPECT_EQ(registry.FindByHandle(firstHandle), firstTwin) @@ -3946,19 +3919,19 @@ namespace { auto second = make(); ASSERT_NE(second, nullptr) << kindName; - const MG_Pipe::MGPipeHandle secondHandle = - MG_Pipe::MGPipeSlots().Acquire(kind, second->GetLifetimeId()); + const MG_Pipe::MGPipeHandle secondHandle = MG_Pipe::MGPipeSlots().Acquire(kind, second->GetLifetimeId()); ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(secondHandle)) << kindName; EXPECT_EQ(secondHandle.Slot, firstHandle.Slot) - << kindName << ": the freed slot was not handed back, so this walk did not exercise " - "the recycle it exists to test"; + << kindName + << ": the freed slot was not handed back, so this walk did not exercise " + "the recycle it exists to test"; EXPECT_NE(secondHandle.Gen, firstHandle.Gen) - << kindName << ": Gen did not move on slot reuse - the predecessor's handle would " - "resolve to the successor's twin"; + << kindName + << ": Gen did not move on slot reuse - the predecessor's handle would " + "resolve to the successor's twin"; auto* const secondTwin = registry.GetOrCreateByHandle(secondHandle); ASSERT_NE(secondTwin, nullptr) << kindName; - EXPECT_EQ(registry.FindByHandle(firstHandle), nullptr) - << kindName << ": the STALE handle resolved to a twin"; + EXPECT_EQ(registry.FindByHandle(firstHandle), nullptr) << kindName << ": the STALE handle resolved to a twin"; EXPECT_EQ(registry.FindByHandle(secondHandle), secondTwin) << kindName; second.reset(); @@ -3978,18 +3951,21 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr "null handle, so there is no {slot, gen} walk to drive"; } - ExpectTheHandleArmDrivesThisKind("Texture", MG_Pipe::MGPipeKind::Texture, TextureImpl::g_backendTextureObjects, [] { - return SharedPtr(MakeShared(0u)); - }); - ExpectTheHandleArmDrivesThisKind("Framebuffer", MG_Pipe::MGPipeKind::Framebuffer, FramebufferImpl::g_backendFramebufferObjects, + ExpectTheHandleArmDrivesThisKind("Texture", MG_Pipe::MGPipeKind::Texture, TextureImpl::g_backendTextureObjects, + [] { return SharedPtr(MakeShared(0u)); }); + ExpectTheHandleArmDrivesThisKind("Framebuffer", MG_Pipe::MGPipeKind::Framebuffer, + FramebufferImpl::g_backendFramebufferObjects, [] { return MakeShared(1u); }); - ExpectTheHandleArmDrivesThisKind("Renderbuffer", MG_Pipe::MGPipeKind::Renderbuffer, RenderbufferImpl::g_backendRenderbufferObjects, + ExpectTheHandleArmDrivesThisKind("Renderbuffer", MG_Pipe::MGPipeKind::Renderbuffer, + RenderbufferImpl::g_backendRenderbufferObjects, [] { return MakeShared(0u); }); - ExpectTheHandleArmDrivesThisKind("SamplerCso", MG_Pipe::MGPipeKind::SamplerCso, SamplerImpl::g_backendSamplerObjects, + ExpectTheHandleArmDrivesThisKind("SamplerCso", MG_Pipe::MGPipeKind::SamplerCso, + SamplerImpl::g_backendSamplerObjects, [] { return MakeShared(0u); }); ExpectTheHandleArmDrivesThisKind("ShaderCso", MG_Pipe::MGPipeKind::ShaderCso, PrgramImpl::g_backendProgramObjects, [] { return MakeShared(0u); }); - ExpectTheHandleArmDrivesThisKind("VertexElementsCso", MG_Pipe::MGPipeKind::VertexElementsCso, VertexArrayImpl::g_backendVertexArrayObjects, + ExpectTheHandleArmDrivesThisKind("VertexElementsCso", MG_Pipe::MGPipeKind::VertexElementsCso, + VertexArrayImpl::g_backendVertexArrayObjects, [] { return MakeShared(0u); }); // P3a: the SEVENTH table, and the only one the helper above cannot drive - which is the @@ -4021,8 +3997,7 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr // free the slot - the allocator forgets the lifetime id on Free. const SharedPtr released = table.ReleaseByHandle(first); EXPECT_NE(released, nullptr) << "Buffer: resource_destroy found no twin to retire"; - EXPECT_EQ(table.FindByHandle(first), nullptr) - << "Buffer: the twin outlived its resource_destroy"; + EXPECT_EQ(table.FindByHandle(first), nullptr) << "Buffer: the twin outlived its resource_destroy"; MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::Buffer, first); auto successor = MakeShared(0u); @@ -4062,8 +4037,8 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr // kinds have separate slot spaces, so the same lifetime id names a Texture slot and a // SamplerViewCso slot without either shadowing the other. { - using MobileGL::MG_Backend::DirectGLES::SamplerViewImpl::g_backendSamplerViews; using MobileGL::MG_Backend::DirectGLES::SamplerViewImpl::BackendSamplerViewObject; + using MobileGL::MG_Backend::DirectGLES::SamplerViewImpl::g_backendSamplerViews; auto& table = g_backendSamplerViews; SharedPtr owner = MakeShared(0u); @@ -4088,13 +4063,12 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr const SharedPtr released = table.ReleaseByHandle(first); EXPECT_NE(released, nullptr) << "SamplerViewCso: delete_sampler_view found no twin to retire"; - EXPECT_EQ(table.FindByHandle(first), nullptr) - << "SamplerViewCso: the twin outlived its delete_sampler_view"; + EXPECT_EQ(table.FindByHandle(first), nullptr) << "SamplerViewCso: the twin outlived its delete_sampler_view"; MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::SamplerViewCso, first); auto successorOwner = MakeShared(0u); - const MG_Pipe::MGPipeHandle second = MG_Pipe::MGPipeSlots().Acquire( - MG_Pipe::MGPipeKind::SamplerViewCso, successorOwner->GetLifetimeId()); + const MG_Pipe::MGPipeHandle second = + MG_Pipe::MGPipeSlots().Acquire(MG_Pipe::MGPipeKind::SamplerViewCso, successorOwner->GetLifetimeId()); ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(second)) << "SamplerViewCso"; EXPECT_EQ(second.Slot, first.Slot) << "SamplerViewCso: the freed slot was not handed back, so this walk did not exercise " @@ -4134,9 +4108,8 @@ TEST(DirectGLESSlotTable, EverySwitchedOverKindResolvesItsTwinThroughTheHandleAr "BackendSlotTable.Generation"); #else auto& stale = table.GetOrCreate(first); - EXPECT_EQ(stale, nullptr) - << "SamplerViewCso: a stale handle was answered with the LIVE twin at its slot (this " - "assertion cannot tell a refusal from an adoption - the next one does)"; + EXPECT_EQ(stale, nullptr) << "SamplerViewCso: a stale handle was answered with the LIVE twin at its slot (this " + "assertion cannot tell a refusal from an adoption - the next one does)"; #endif EXPECT_NE(table.FindByHandle(second), nullptr) << "SamplerViewCso: the live twin was destroyed by a handle from its slot's past - " @@ -4202,8 +4175,8 @@ TEST(DirectGLESSlotTable, ADeathNoticeForEveryP4aKindIsIdempotent) { // And the slot really is back: a successor gets it with a moved generation, which is // the property a double free would break by skipping one. auto successor = MakeShared(0u); - const MG_Pipe::MGPipeHandle reused = MG_Pipe::MGPipeSlots().Acquire( - MG_Pipe::MGPipeKind::SamplerViewCso, successor->GetLifetimeId()); + const MG_Pipe::MGPipeHandle reused = + MG_Pipe::MGPipeSlots().Acquire(MG_Pipe::MGPipeKind::SamplerViewCso, successor->GetLifetimeId()); EXPECT_EQ(reused.Slot, view.Slot); EXPECT_NE(reused.Gen, view.Gen); MG_Pipe::MGPipeSlots().Free(MG_Pipe::MGPipeKind::SamplerViewCso, reused); @@ -4257,22 +4230,19 @@ TEST(DirectGLESSlotTable, ADeathNoticeForEveryP4aKindIsIdempotent) { handleFor(MG_Pipe::MGPipeKind::ShaderCso, program->GetLifetimeId())}); for (const auto& one : cases) { - EXPECT_FALSE(MG_Pipe::MGPipeHandleIsNull( - MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) + EXPECT_FALSE(MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) << one.name << ": nothing was twinned, so this walk proves nothing"; } // The real destructors run here and raise the first notice. } for (const auto& one : cases) { - EXPECT_TRUE(MG_Pipe::MGPipeHandleIsNull( - MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) + EXPECT_TRUE(MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) << one.name << ": the object's own death did not return its slot"; // Second and third deliveries of the dead handle: nothing to release, no abort. ops->OnDestroyed(one.kind, one.lifetimeId, one.handle); ops->OnDestroyed(one.kind, one.lifetimeId, one.handle); - EXPECT_TRUE(MG_Pipe::MGPipeHandleIsNull( - MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) + EXPECT_TRUE(MG_Pipe::MGPipeHandleIsNull(MG_Pipe::MGPipeSlots().FindByLifetimeId(one.kind, one.lifetimeId))) << one.name << ": a redundant notice resurrected a mapping"; // m-9, THE ABA LEG: the slot really came back, and its generation MOVED. Two redundant @@ -4281,13 +4251,12 @@ TEST(DirectGLESSlotTable, ADeathNoticeForEveryP4aKindIsIdempotent) { // handed the same {slot, gen} the dead object held, which is precisely the handle a // surviving memo would still be naming. auto successor = MakeShared(0u); - const MG_Pipe::MGPipeHandle reused = - MG_Pipe::MGPipeSlots().Acquire(one.kind, successor->GetLifetimeId()); + const MG_Pipe::MGPipeHandle reused = MG_Pipe::MGPipeSlots().Acquire(one.kind, successor->GetLifetimeId()); ASSERT_FALSE(MG_Pipe::MGPipeHandleIsNull(reused)) << one.name; if (reused.Slot == one.handle.Slot) { - EXPECT_NE(reused.Gen, one.handle.Gen) - << one.name << ": the slot came back at the SAME generation, so a memo holding " - "the dead object's handle would resolve to its successor's twin"; + EXPECT_NE(reused.Gen, one.handle.Gen) << one.name + << ": the slot came back at the SAME generation, so a memo holding " + "the dead object's handle would resolve to its successor's twin"; } MG_Pipe::MGPipeSlots().Free(one.kind, reused); } @@ -4325,7 +4294,6 @@ TEST(DirectGLESSlotTable, ASavedCopyOfARealRegistryDropsTheTwinOnTheSameNotice) << "the saved copy kept the dead texture's twin - the dispatcher told one holder only"; } - // P5 b1's half of the case above: THE PIN IS LIFTED, AND THIS IS WHAT REPLACED IT. // // The case above exists because answering the live-map question from HasLiveHostWrites alone @@ -4374,9 +4342,8 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue { MG_Pipe::MGPCaps caps{}; caps.CallMask = MG_Remote::MGCapsConsumerBits(MG_Pipe::kMGPipeSubsystemResources); - MG_Remote::Client::CapsMirrorInstance().Adopt(caps, MG_Backend::FormatCapabilityCache{}, - RendererInfo{}, String{}, - BackendType::DirectGLES); + MG_Remote::Client::CapsMirrorInstance().Adopt(caps, MG_Backend::FormatCapabilityCache{}, RendererInfo{}, + String{}, BackendType::DirectGLES); } (void)previousCapsGeneration; @@ -4417,8 +4384,8 @@ TEST(DirectGLESBufferDrawProbe, UnderSplitTheRecordAloneAnswersTheLiveHostMapQue "live-map question it exists for"; // ---- map. The RISING EDGE is what has to publish, and nothing else can. ----------- - void* const mapped = owner->AcquireMemoryRange( - Range1D{0, 256}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent); + void* const mapped = owner->AcquireMemoryRange(Range1D{0, 256}, BufferMappingAccessBit::Write | + BufferMappingAccessBit::Persistent); ASSERT_NE(mapped, nullptr); ASSERT_TRUE(owner->IsMapped()); ASSERT_FALSE(owner->IsBackendPersistentMapped()) @@ -4503,12 +4470,10 @@ TEST(DirectGLESBufferDrawProbe, ACapsMaskWithoutTheResourceFamilyEmitsNothingAnd // Invalidate() to call and inventing one would be a second spelling of the same edge. MG_Pipe::MGPCaps caps{}; caps.CallMask = 0; - MG_Remote::Client::CapsMirrorInstance().Adopt(caps, MG_Backend::FormatCapabilityCache{}, - RendererInfo{}, String{}, - BackendType::DirectGLES); + MG_Remote::Client::CapsMirrorInstance().Adopt(caps, MG_Backend::FormatCapabilityCache{}, RendererInfo{}, + String{}, BackendType::DirectGLES); } - ASSERT_FALSE(MG_Remote::Client::CapsMirrorInstance().ServerConsumes( - MG_Pipe::kMGPipeSubsystemResources)) + ASSERT_FALSE(MG_Remote::Client::CapsMirrorInstance().ServerConsumes(MG_Pipe::kMGPipeSubsystemResources)) << "the fixture's own mask consumes the family, so this case cannot observe a refusal"; MG_Remote::Client::ResetConsumerRefusalsForTest(); @@ -4582,9 +4547,8 @@ TEST(DirectGLESSlotTable, AGenerationBehindTheLiveTwinIsRefusedRatherThanAdopted "BackendSlotTable.Generation"); #else auto& answer = table.GetOrCreate(stale); - EXPECT_EQ(answer, nullptr) - << "a backwards generation was handed a twin rather than refused; FindByHandle refuses " - "the same input, so the two entry points disagreed"; + EXPECT_EQ(answer, nullptr) << "a backwards generation was handed a twin rather than refused; FindByHandle refuses " + "the same input, so the two entry points disagreed"; #endif auto* const still = table.FindByHandle(current); @@ -4664,9 +4628,8 @@ TEST(DirectGLESSlotTable, ACompositeHandleDoesNotGrowTheOrdinaryTable) { // ForEachLive reports the composite at THE SLOT THE CLIENT MINTED, never the band index - // the handle it hands over is the one a caller turns straight back into a record lookup. Vector walked; - table.ForEachLive([&](MG_Pipe::MGPipeHandle handle, const SharedPtr&) { - walked.push_back(handle.Slot); - }); + table.ForEachLive( + [&](MG_Pipe::MGPipeHandle handle, const SharedPtr&) { walked.push_back(handle.Slot); }); ASSERT_EQ(walked.size(), 2u); EXPECT_EQ(walked[0], 5u) << "the ordinary space is walked first, at its own slot"; EXPECT_EQ(walked[1], composite.Slot) @@ -4676,7 +4639,7 @@ TEST(DirectGLESSlotTable, ACompositeHandleDoesNotGrowTheOrdinaryTable) { // §4.3's recycle answer holds inside the band exactly as it does outside it. auto& recycled = table.GetOrCreate(MG_Pipe::MGPipeHandle{composite.Slot, 2u}); EXPECT_EQ(recycled, nullptr) << "a successor at a recycled composite slot inherited its " - "predecessor's driver program"; + "predecessor's driver program"; EXPECT_EQ(table.FindByHandle(composite), nullptr) << "the predecessor's handle still resolves"; EXPECT_EQ(table.CompositeCapacityForTest(), 4u) << "the recycle re-grew the band"; } @@ -4734,8 +4697,7 @@ TEST(DirectGLESVertexInputDraw, TheAttributeWalkTakesItsBuffersFromTheRecordNotT // touches a record. const Uint32 savedVertexBufferStart = st.VertexBufferStart; const Uint32 savedVertexBufferCount = st.VertexBufferCount; - const Array savedVertexBuffers = - st.VertexBuffers; + const Array savedVertexBuffers = st.VertexBuffers; st.VertexBufferStart = 0; st.VertexBufferCount = 4; for (Uint32 i = 0; i < 4; ++i) { @@ -4766,12 +4728,11 @@ TEST(DirectGLESVertexInputDraw, TheAttributeWalkTakesItsBuffersFromTheRecordNotT } BufferImpl::DrawVertexBufferRequest requests[VertexArrayObject::MAX_VERTEX_ATTRIBS]; - const Uint count = BufferImpl::ResolveDrawVertexBuffersFromRecord( - st, requests, VertexArrayObject::MAX_VERTEX_ATTRIBS); + const Uint count = + BufferImpl::ResolveDrawVertexBuffersFromRecord(st, requests, VertexArrayObject::MAX_VERTEX_ATTRIBS); - ASSERT_EQ(count, 2u) - << "the walk did not resolve st.VertexBuffers[Start..+Count): two DISTINCT handles are " - "named there (A twice and B), plus one client-memory array with no store to ensure"; + ASSERT_EQ(count, 2u) << "the walk did not resolve st.VertexBuffers[Start..+Count): two DISTINCT handles are " + "named there (A twice and B), plus one client-memory array with no store to ensure"; EXPECT_EQ(requests[0].Res, a) << "the first buffer is not st.VertexBuffers[0].Res. If it is the frontend VAO's C, the " "walk went back to GetAllAttributes() and §5.1's substitution is reverted"; @@ -4854,7 +4815,6 @@ TEST(DirectGLESVertexInputDraw, TheIndexArmTakesItsBufferAndItsSerialFromTheReco #endif } - // P12 (on-screen server window): A SERVER SESSION ENDS IN A PROCESS THAT OUTLIVES IT. // // The in-process display server (the display Activity's process) runs its sessions one after @@ -4880,8 +4840,7 @@ TEST(EsprytServerSession, TheServerBackendsDestructionDropsEveryTwinTheSessionBu const auto populate = [&] { ASSERT_NE(GL::PrgramImpl::g_backendProgramObjects.GetOrCreateByHandle(program), nullptr); ASSERT_NE(GL::TextureImpl::g_backendTextureObjects.GetOrCreateByHandle(texture), nullptr); - GL::BufferImpl::g_backendBufferResources.GetOrCreate(buffer) = - MakeShared(); + GL::BufferImpl::g_backendBufferResources.GetOrCreate(buffer) = MakeShared(); ASSERT_EQ(GL::PrgramImpl::g_backendProgramObjects.LiveGenAt(program.Slot), 3u); ASSERT_EQ(GL::TextureImpl::g_backendTextureObjects.LiveGenAt(texture.Slot), 2u); ASSERT_NE(GL::BufferImpl::g_backendBufferResources.FindByHandle(buffer), nullptr); diff --git a/MobileGL/MG_Test/ScopedPipeVerb.h b/MobileGL/MG_Test/ScopedPipeVerb.h index 6b9d95d2..8bfeaf7d 100644 --- a/MobileGL/MG_Test/ScopedPipeVerb.h +++ b/MobileGL/MG_Test/ScopedPipeVerb.h @@ -40,8 +40,7 @@ namespace MobileGL::MG_Test { // drives a helper of a DIFFERENT verb class opens a nested scope for it. class ScopedPipeVerb { public: - explicit ScopedPipeVerb([[maybe_unused]] MG_Pipe::MGPipeVerb verb) - : m_verb(verb) { + explicit ScopedPipeVerb([[maybe_unused]] MG_Pipe::MGPipeVerb verb) : m_verb(verb) { MG_Pipe::MGPipeValidateForVerb(m_verb); } @@ -51,9 +50,7 @@ namespace MobileGL::MG_Test { // A second verb of the same kind begins: re-fill and re-stamp, so a helper driven // again after the test moved frontend state sees the new values, the way the next GL // entry point's MGP_FILL would. - void Renew() { - MG_Pipe::MGPipeValidateForVerb(m_verb); - } + void Renew() { MG_Pipe::MGPipeValidateForVerb(m_verb); } // Leaving bumps the serial and puts the current verb back to "none": every field // this scope stamped goes stale, and a later test that forgets its own declaration diff --git a/MobileGL/MG_Test/SelfTest/BakedInternalShadersTest.cpp b/MobileGL/MG_Test/SelfTest/BakedInternalShadersTest.cpp index 0baf9dd1..129daa22 100644 --- a/MobileGL/MG_Test/SelfTest/BakedInternalShadersTest.cpp +++ b/MobileGL/MG_Test/SelfTest/BakedInternalShadersTest.cpp @@ -52,11 +52,11 @@ namespace { -// The two Renderer headers are written to be included inside the backend namespace, after -// the project's own type aliases; they name Uint32 and declare nothing else. Including them -// here with a local alias keeps them byte-identical to what VulkanRenderer.cpp compiles - -// the alternative, a copy of the words in this file, is precisely the drift this gate is for. -using Uint32 = std::uint32_t; + // The two Renderer headers are written to be included inside the backend namespace, after + // the project's own type aliases; they name Uint32 and declare nothing else. Including them + // here with a local alias keeps them byte-identical to what VulkanRenderer.cpp compiles - + // the alternative, a copy of the words in this file, is precisely the drift this gate is for. + using Uint32 = std::uint32_t; #include #include #include @@ -64,227 +64,199 @@ using Uint32 = std::uint32_t; #include #include -struct BakedShader { - const char* name; // ctest entry name, and the bake file's stem - const char* source; // repository-relative GLSL source - const char* header; // repository-relative header the words live in - const char* symbol; // the array identifier inside that header - EShLanguage stage; - glslang::EShTargetClientVersion client; - glslang::EShTargetLanguageVersion spv; - const std::uint32_t* words; - std::size_t wordCount; -}; + struct BakedShader { + const char* name; // ctest entry name, and the bake file's stem + const char* source; // repository-relative GLSL source + const char* header; // repository-relative header the words live in + const char* symbol; // the array identifier inside that header + EShLanguage stage; + glslang::EShTargetClientVersion client; + glslang::EShTargetLanguageVersion spv; + const std::uint32_t* words; + std::size_t wordCount; + }; -#define MGL_BAKED_ROW(name, source, header, symbol, stage, client, spv, array) \ - BakedShader{name, source, header, symbol, stage, client, spv, (array), \ - sizeof(array) / sizeof((array)[0])} +#define MGL_BAKED_ROW(name, source, header, symbol, stage, client, spv, array) \ + BakedShader { \ + name, source, header, symbol, stage, client, spv, (array), sizeof(array) / sizeof((array)[0]) \ + } -const BakedShader kBakedShaders[] = { - // The Magma wire arms' own programs: no GL program object, no registry entry, no runtime - // transpilation, so the words in the header are the only copy that exists. - MGL_BAKED_ROW("WireColorBlitVertex", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.vert", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h", - "kWireColorBlitVertexSpirv", EShLangVertex, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireColorBlitVertexSpirv), - MGL_BAKED_ROW("WireColorBlitFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h", - "kWireColorBlitFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireColorBlitFragmentSpirv), - // The raw E5B9G9R9 read route (WireE5RawCopy.h): one compute program per source shape. - MGL_BAKED_ROW("WireE5RawCopy", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.comp", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h", - "kWireE5RawCopySpirv", EShLangCompute, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireE5RawCopySpirv), - MGL_BAKED_ROW("WireE5RawCopy3D", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy3D.comp", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h", - "kWireE5RawCopy3DSpirv", EShLangCompute, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireE5RawCopy3DSpirv), - // The rasterizer-discard state probe (WireDiscardStateProbe.h). - MGL_BAKED_ROW("WireDiscardStateProbeVertex", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.vert", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h", - "kWireDiscardStateProbeVertexSpirv", EShLangVertex, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireDiscardStateProbeVertexSpirv), - MGL_BAKED_ROW("WireDiscardStateProbeFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h", - "kWireDiscardStateProbeFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireDiscardStateProbeFragmentSpirv), - MGL_BAKED_ROW("WireYuvConvertVertex", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvert.vert", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h", - "kWireYuvConvertVertexSpirv", EShLangVertex, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireYuvConvertVertexSpirv), - MGL_BAKED_ROW("WireYuvConvertFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvert.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h", - "kWireYuvConvertFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireYuvConvertFragmentSpirv), - MGL_BAKED_ROW("WireDepthMipmapVertex", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.vert", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h", - "kWireDepthMipmapVertexSpirv", EShLangVertex, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireDepthMipmapVertexSpirv), - MGL_BAKED_ROW("WireDepthMipmapFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h", - "kWireDepthMipmapFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireDepthMipmapFragmentSpirv), - // P7 wave 2-B2: the multisample depth/stencil resolve's own pass. THREE ROWS FOR TWO - // PIPELINES - one vertex stage shared by both, and a fragment stage per aspect, because a - // fragment shader cannot write the stencil aspect without VK_EXT_shader_stencil_export and - // a device without that extension must still be able to compile the depth half. - MGL_BAKED_ROW("WireMultisampleResolveVertex", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.vert", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", - "kWireMultisampleResolveVertexSpirv", EShLangVertex, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireMultisampleResolveVertexSpirv), - MGL_BAKED_ROW("WireMultisampleDepthResolveFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleDepthResolve.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", - "kWireMultisampleDepthResolveFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireMultisampleDepthResolveFragmentSpirv), - MGL_BAKED_ROW("WireMultisampleStencilResolveFragment", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleStencilResolve.frag", - "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", - "kWireMultisampleStencilResolveFragmentSpirv", EShLangFragment, - glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, - kWireMultisampleStencilResolveFragmentSpirv), - // The two driver self-test probes. They are not Magma's, and they are in this table for - // the reason the contract names them: they are baked, so they can be stale, and one of - // them was. - MGL_BAKED_ROW("DriverPostIterationRPWitness", - "MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.comp", - "MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitnessSpv.h", - "kDriverPostIterationRPWitnessSpv", EShLangCompute, - glslang::EShTargetVulkan_1_1, glslang::EShTargetSpv_1_3, - MobileGL::MG_Util::SelfTest::kDriverPostIterationRPWitnessSpv), - MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeVert", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.vert", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", - "kPrimitivesGeneratedNoXfbProbeVertSpv", EShLangVertex, - glslang::EShTargetVulkan_1_1, glslang::EShTargetSpv_1_3, - MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeVertSpv), - MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeTesc", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.tesc", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", - "kPrimitivesGeneratedNoXfbProbeTescSpv", EShLangTessControl, - glslang::EShTargetVulkan_1_1, glslang::EShTargetSpv_1_3, - MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeTescSpv), - MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeTese", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.tese", - "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", - "kPrimitivesGeneratedNoXfbProbeTeseSpv", EShLangTessEvaluation, - glslang::EShTargetVulkan_1_1, glslang::EShTargetSpv_1_3, - MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeTeseSpv), -}; + const BakedShader kBakedShaders[] = { + // The Magma wire arms' own programs: no GL program object, no registry entry, no runtime + // transpilation, so the words in the header are the only copy that exists. + MGL_BAKED_ROW("WireColorBlitVertex", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.vert", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h", "kWireColorBlitVertexSpirv", + EShLangVertex, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, + kWireColorBlitVertexSpirv), + MGL_BAKED_ROW("WireColorBlitFragment", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlitSpirv.h", "kWireColorBlitFragmentSpirv", + EShLangFragment, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, + kWireColorBlitFragmentSpirv), + // The raw E5B9G9R9 read route (WireE5RawCopy.h): one compute program per source shape. + MGL_BAKED_ROW("WireE5RawCopy", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy.comp", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h", "kWireE5RawCopySpirv", + EShLangCompute, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, kWireE5RawCopySpirv), + MGL_BAKED_ROW("WireE5RawCopy3D", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopy3D.comp", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireE5RawCopySpirv.h", "kWireE5RawCopy3DSpirv", + EShLangCompute, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, kWireE5RawCopy3DSpirv), + // The rasterizer-discard state probe (WireDiscardStateProbe.h). + MGL_BAKED_ROW("WireDiscardStateProbeVertex", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.vert", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h", + "kWireDiscardStateProbeVertexSpirv", EShLangVertex, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireDiscardStateProbeVertexSpirv), + MGL_BAKED_ROW("WireDiscardStateProbeFragment", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbe.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDiscardStateProbeSpirv.h", + "kWireDiscardStateProbeFragmentSpirv", EShLangFragment, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireDiscardStateProbeFragmentSpirv), + MGL_BAKED_ROW("WireYuvConvertVertex", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvert.vert", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h", "kWireYuvConvertVertexSpirv", + EShLangVertex, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, + kWireYuvConvertVertexSpirv), + MGL_BAKED_ROW("WireYuvConvertFragment", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvert.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvConvertSpirv.h", "kWireYuvConvertFragmentSpirv", + EShLangFragment, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, + kWireYuvConvertFragmentSpirv), + MGL_BAKED_ROW("WireDepthMipmapVertex", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.vert", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h", "kWireDepthMipmapVertexSpirv", + EShLangVertex, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0, + kWireDepthMipmapVertexSpirv), + MGL_BAKED_ROW("WireDepthMipmapFragment", "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmapSpirv.h", + "kWireDepthMipmapFragmentSpirv", EShLangFragment, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireDepthMipmapFragmentSpirv), + // P7 wave 2-B2: the multisample depth/stencil resolve's own pass. THREE ROWS FOR TWO + // PIPELINES - one vertex stage shared by both, and a fragment stage per aspect, because a + // fragment shader cannot write the stencil aspect without VK_EXT_shader_stencil_export and + // a device without that extension must still be able to compile the depth half. + MGL_BAKED_ROW("WireMultisampleResolveVertex", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolve.vert", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", + "kWireMultisampleResolveVertexSpirv", EShLangVertex, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireMultisampleResolveVertexSpirv), + MGL_BAKED_ROW("WireMultisampleDepthResolveFragment", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleDepthResolve.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", + "kWireMultisampleDepthResolveFragmentSpirv", EShLangFragment, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireMultisampleDepthResolveFragmentSpirv), + MGL_BAKED_ROW("WireMultisampleStencilResolveFragment", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleStencilResolve.frag", + "MobileGL/MG_Backend/DirectVulkan/Renderer/WireMultisampleResolveSpirv.h", + "kWireMultisampleStencilResolveFragmentSpirv", EShLangFragment, glslang::EShTargetVulkan_1_0, + glslang::EShTargetSpv_1_0, kWireMultisampleStencilResolveFragmentSpirv), + // The two driver self-test probes. They are not Magma's, and they are in this table for + // the reason the contract names them: they are baked, so they can be stale, and one of + // them was. + MGL_BAKED_ROW("DriverPostIterationRPWitness", "MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.comp", + "MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitnessSpv.h", "kDriverPostIterationRPWitnessSpv", + EShLangCompute, glslang::EShTargetVulkan_1_1, glslang::EShTargetSpv_1_3, + MobileGL::MG_Util::SelfTest::kDriverPostIterationRPWitnessSpv), + MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeVert", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.vert", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", + "kPrimitivesGeneratedNoXfbProbeVertSpv", EShLangVertex, glslang::EShTargetVulkan_1_1, + glslang::EShTargetSpv_1_3, MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeVertSpv), + MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeTesc", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.tesc", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", + "kPrimitivesGeneratedNoXfbProbeTescSpv", EShLangTessControl, glslang::EShTargetVulkan_1_1, + glslang::EShTargetSpv_1_3, MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeTescSpv), + MGL_BAKED_ROW("PrimitivesGeneratedNoXfbProbeTese", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.tese", + "MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h", + "kPrimitivesGeneratedNoXfbProbeTeseSpv", EShLangTessEvaluation, glslang::EShTargetVulkan_1_1, + glslang::EShTargetSpv_1_3, MobileGL::MG_Util::SelfTest::kPrimitivesGeneratedNoXfbProbeTeseSpv), + }; #undef MGL_BAKED_ROW -std::string RepositoryRoot() { + std::string RepositoryRoot() { #ifdef MOBILEGL_BAKED_SHADER_ROOT - return std::string(MOBILEGL_BAKED_SHADER_ROOT); + return std::string(MOBILEGL_BAKED_SHADER_ROOT); #else - return std::string(); + return std::string(); #endif -} + } -// glslang::InitializeProcess is process-wide and refcounted. One call for the whole binary: -// this test never runs MobileGL::Initialize, which is the other caller. -void EnsureGlslangProcess() { - static const bool ready = glslang::InitializeProcess(); - ASSERT_TRUE(ready) << "glslang::InitializeProcess failed"; -} + // glslang::InitializeProcess is process-wide and refcounted. One call for the whole binary: + // this test never runs MobileGL::Initialize, which is the other caller. + void EnsureGlslangProcess() { + static const bool ready = glslang::InitializeProcess(); + ASSERT_TRUE(ready) << "glslang::InitializeProcess failed"; + } -class BakedInternalShaders : public ::testing::TestWithParam {}; + class BakedInternalShaders : public ::testing::TestWithParam {}; -TEST_P(BakedInternalShaders, HeaderMatchesItsSource) { - const BakedShader& row = GetParam(); - ASSERT_NO_FATAL_FAILURE(EnsureGlslangProcess()); + TEST_P(BakedInternalShaders, HeaderMatchesItsSource) { + const BakedShader& row = GetParam(); + ASSERT_NO_FATAL_FAILURE(EnsureGlslangProcess()); - const std::string root = RepositoryRoot(); - ASSERT_FALSE(root.empty()) - << "MOBILEGL_BAKED_SHADER_ROOT was not defined for this target: the gate cannot find " - "the GLSL sources and must not pass by default"; - const std::string sourcePath = root + "/" + row.source; - std::ifstream in(sourcePath, std::ios::binary); - ASSERT_TRUE(in.good()) << "cannot open " << sourcePath; - std::ostringstream buffer; - buffer << in.rdbuf(); - const std::string source = buffer.str(); - ASSERT_FALSE(source.empty()) << sourcePath << " is empty"; + const std::string root = RepositoryRoot(); + ASSERT_FALSE(root.empty()) + << "MOBILEGL_BAKED_SHADER_ROOT was not defined for this target: the gate cannot find " + "the GLSL sources and must not pass by default"; + const std::string sourcePath = root + "/" + row.source; + std::ifstream in(sourcePath, std::ios::binary); + ASSERT_TRUE(in.good()) << "cannot open " << sourcePath; + std::ostringstream buffer; + buffer << in.rdbuf(); + const std::string source = buffer.str(); + ASSERT_FALSE(source.empty()) << sourcePath << " is empty"; - glslang::TShader shader(row.stage); - const char* strings[] = {source.c_str()}; - shader.setStrings(strings, 1); - shader.setEnvInput(glslang::EShSourceGlsl, row.stage, glslang::EShClientVulkan, 100); - shader.setEnvClient(glslang::EShClientVulkan, row.client); - shader.setEnvTarget(glslang::EShTargetSpv, row.spv); - const EShMessages messages = static_cast(EShMsgSpvRules | EShMsgVulkanRules); - ASSERT_TRUE(shader.parse(GetDefaultResources(), 100, false, messages)) - << row.source << " does not compile:\n" << shader.getInfoLog(); + glslang::TShader shader(row.stage); + const char* strings[] = {source.c_str()}; + shader.setStrings(strings, 1); + shader.setEnvInput(glslang::EShSourceGlsl, row.stage, glslang::EShClientVulkan, 100); + shader.setEnvClient(glslang::EShClientVulkan, row.client); + shader.setEnvTarget(glslang::EShTargetSpv, row.spv); + const EShMessages messages = static_cast(EShMsgSpvRules | EShMsgVulkanRules); + ASSERT_TRUE(shader.parse(GetDefaultResources(), 100, false, messages)) << row.source << " does not compile:\n" + << shader.getInfoLog(); - glslang::TProgram program; - program.addShader(&shader); - ASSERT_TRUE(program.link(messages)) << row.source << " does not link:\n" << program.getInfoLog(); - glslang::TIntermediate* intermediate = program.getIntermediate(row.stage); - ASSERT_NE(intermediate, nullptr); + glslang::TProgram program; + program.addShader(&shader); + ASSERT_TRUE(program.link(messages)) << row.source << " does not link:\n" << program.getInfoLog(); + glslang::TIntermediate* intermediate = program.getIntermediate(row.stage); + ASSERT_NE(intermediate, nullptr); - std::vector spirv; - spv::SpvBuildLogger logger; - glslang::SpvOptions options; - options.disableOptimizer = true; - glslang::GlslangToSpv(*intermediate, spirv, &logger, &options); - ASSERT_FALSE(spirv.empty()) << "GlslangToSpv produced nothing for " << row.source; + std::vector spirv; + spv::SpvBuildLogger logger; + glslang::SpvOptions options; + options.disableOptimizer = true; + glslang::GlslangToSpv(*intermediate, spirv, &logger, &options); + ASSERT_FALSE(spirv.empty()) << "GlslangToSpv produced nothing for " << row.source; - // Bake mode writes what it computed and asserts nothing else: the script that sets this - // variable is about to overwrite the header, so a red here would only stop it doing so. - if (const char* bakeDir = std::getenv("MOBILEGL_BAKE_INTERNAL_SHADERS")) { - const std::string out = std::string(bakeDir) + "/" + row.symbol + ".words"; - std::ofstream sink(out, std::ios::binary); - ASSERT_TRUE(sink.good()) << "cannot write " << out; - sink << row.header << "\n"; - for (unsigned int word : spirv) { - char text[16]; - std::snprintf(text, sizeof(text), "0x%08x\n", word); - sink << text; + // Bake mode writes what it computed and asserts nothing else: the script that sets this + // variable is about to overwrite the header, so a red here would only stop it doing so. + if (const char* bakeDir = std::getenv("MOBILEGL_BAKE_INTERNAL_SHADERS")) { + const std::string out = std::string(bakeDir) + "/" + row.symbol + ".words"; + std::ofstream sink(out, std::ios::binary); + ASSERT_TRUE(sink.good()) << "cannot write " << out; + sink << row.header << "\n"; + for (unsigned int word : spirv) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x\n", word); + sink << text; + } + ASSERT_TRUE(sink.good()) << "failed writing " << out; + return; + } + + ASSERT_EQ(spirv.size(), row.wordCount) + << row.header << ": " << row.symbol << " has " << row.wordCount << " words, recompiling " << row.source + << " with the in-tree glslang produces " << spirv.size() + << ". Regenerate with scripts/bake_internal_shaders.py."; + for (std::size_t at = 0; at < spirv.size(); ++at) { + ASSERT_EQ(spirv[at], row.words[at]) + << row.header << ": " << row.symbol << " word " << at << " is stale against " << row.source + << ". Regenerate with scripts/bake_internal_shaders.py."; } - ASSERT_TRUE(sink.good()) << "failed writing " << out; - return; } - ASSERT_EQ(spirv.size(), row.wordCount) - << row.header << ": " << row.symbol << " has " << row.wordCount << " words, recompiling " - << row.source << " with the in-tree glslang produces " << spirv.size() - << ". Regenerate with scripts/bake_internal_shaders.py."; - for (std::size_t at = 0; at < spirv.size(); ++at) { - ASSERT_EQ(spirv[at], row.words[at]) - << row.header << ": " << row.symbol << " word " << at << " is stale against " - << row.source << ". Regenerate with scripts/bake_internal_shaders.py."; - } -} - -INSTANTIATE_TEST_SUITE_P(BakedShaders, BakedInternalShaders, - ::testing::ValuesIn(kBakedShaders), - [](const ::testing::TestParamInfo& info) { - return std::string(info.param.name); - }); + INSTANTIATE_TEST_SUITE_P(BakedShaders, BakedInternalShaders, ::testing::ValuesIn(kBakedShaders), + [](const ::testing::TestParamInfo& info) { + return std::string(info.param.name); + }); } // namespace diff --git a/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp b/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp index d7fbdacb..c3554710 100644 --- a/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp +++ b/MobileGL/MG_Test/SelfTest/DriverBugProbesTest.cpp @@ -21,13 +21,13 @@ using namespace MobileGL; using MobileGL::MG_Util::SelfTest::CollectGlesKnownDriverBugs; using MobileGL::MG_Util::SelfTest::DriverBugVerdict; +using MobileGL::MG_Util::SelfTest::ProbeBlitIgnoresDestinationArrayLayer; +using MobileGL::MG_Util::SelfTest::ProbeCopyImageMirrorsPacked16FieldOrder; using MobileGL::MG_Util::SelfTest::ProbeCrossStageImageQualifierMergeDropsWrites; +using MobileGL::MG_Util::SelfTest::ProbeExplicitVertexInputLocationCeiling; using MobileGL::MG_Util::SelfTest::ProbeGeometryStageSsboWriteAfterEmitDropped; using MobileGL::MG_Util::SelfTest::ProbeImageLocationPerNameBudget; using MobileGL::MG_Util::SelfTest::ProbeImageWriteReadCoherencyResidual; -using MobileGL::MG_Util::SelfTest::ProbeBlitIgnoresDestinationArrayLayer; -using MobileGL::MG_Util::SelfTest::ProbeCopyImageMirrorsPacked16FieldOrder; -using MobileGL::MG_Util::SelfTest::ProbeExplicitVertexInputLocationCeiling; using MobileGL::MG_Util::SelfTest::ProbeR32FMultisampleSwizzleCorruption; namespace { @@ -55,7 +55,8 @@ namespace { // link log below is: the report shows it to a human, so a probe that stopped capturing it // would stop being useful long before it stopped detecting. const char* const kAttributeRangeCompileLog = - "ERROR: 0:2: '' : the location is not within attribute range [0, MAX_ATTRIBUTES-1] \nERROR: 1 compilation errors. No code generated."; + "ERROR: 0:2: '' : the location is not within attribute range [0, MAX_ATTRIBUTES-1] \nERROR: 1 compilation " + "errors. No code generated."; // The exact text an affected Adreno driver puts in the info log for this refusal. const char* const kImageLocationLinkLog = @@ -202,7 +203,9 @@ namespace { FakeDriver g_fake; - void ResetFakeDriver() { g_fake = FakeDriver{}; } + void ResetFakeDriver() { + g_fake = FakeDriver{}; + } const std::string& SourceOf(GLuint shader) { static const std::string empty; @@ -238,7 +241,8 @@ namespace { const std::size_t end = source.find_first_of(";,)", at); if (end == std::string::npos) break; std::string name = source.substr(at, end - at); - while (!name.empty() && (name.back() == ' ' || name.back() == '\t')) name.pop_back(); + while (!name.empty() && (name.back() == ' ' || name.back() == '\t')) + name.pop_back(); if (std::find(names.begin(), names.end(), name) == names.end()) { names.push_back(name); } @@ -362,8 +366,7 @@ namespace { ++g_fake.aliveShaders; return g_fake.nextShaderId++; }; - funcs.glShaderSource = [](GLuint shader, GLsizei count, const GLchar* const* strings, - const GLint*) { + funcs.glShaderSource = [](GLuint shader, GLsizei count, const GLchar* const* strings, const GLint*) { std::string source; for (GLsizei i = 0; i < count; ++i) { if (strings[i] != nullptr) source += strings[i]; @@ -387,8 +390,8 @@ namespace { if (bufSize <= 0) return; const auto it = g_fake.shaderInfoLogs.find(shader); const std::string& log = it == g_fake.shaderInfoLogs.end() ? std::string() : it->second; - const GLsizei copied = static_cast( - std::min(log.size(), static_cast(bufSize - 1))); + const GLsizei copied = + static_cast(std::min(log.size(), static_cast(bufSize - 1))); std::memcpy(infoLog, log.data(), static_cast(copied)); infoLog[copied] = '\0'; }; @@ -409,9 +412,7 @@ namespace { ++g_fake.alivePrograms; return g_fake.nextProgramId++; }; - funcs.glAttachShader = [](GLuint program, GLuint shader) { - g_fake.programShaders[program].push_back(shader); - }; + funcs.glAttachShader = [](GLuint program, GLuint shader) { g_fake.programShaders[program].push_back(shader); }; funcs.glLinkProgram = [](GLuint program) { const std::vector names = DeclaredImageNames(program); const bool overBudget = static_cast(names.size()) > g_fake.distinctImageNameBudget; @@ -419,8 +420,7 @@ namespace { // refuses the LINK rather than the compile - which is the half of the vertex-input // probe that decides whether the attribute is reachable another way at all. bool attributeOutOfRange = false; - if (const auto it = g_fake.boundAttribLocations.find(program); - it != g_fake.boundAttribLocations.end()) { + if (const auto it = g_fake.boundAttribLocations.find(program); it != g_fake.boundAttribLocations.end()) { for (const auto& [attributeName, location] : it->second) { if (location >= g_fake.bindAttribLocationCeiling) attributeOutOfRange = true; } @@ -440,8 +440,8 @@ namespace { if (bufSize <= 0) return; const auto it = g_fake.programInfoLogs.find(program); const std::string& log = it == g_fake.programInfoLogs.end() ? std::string() : it->second; - const GLsizei copied = static_cast( - std::min(log.size(), static_cast(bufSize - 1))); + const GLsizei copied = + static_cast(std::min(log.size(), static_cast(bufSize - 1))); std::memcpy(infoLog, log.data(), static_cast(copied)); infoLog[copied] = '\0'; }; @@ -469,8 +469,8 @@ namespace { }; // One byte per layer: the layered-blit probe fills every texel of a layer with the same // value and only ever asks which layer a value ended up on. - funcs.glTexSubImage3D = [](GLenum target, GLint, GLint, GLint, GLint zoffset, GLsizei, GLsizei, - GLsizei, GLenum, GLenum, const void* pixels) { + funcs.glTexSubImage3D = [](GLenum target, GLint, GLint, GLint, GLint zoffset, GLsizei, GLsizei, GLsizei, GLenum, + GLenum, const void* pixels) { if (target != GL_TEXTURE_2D_ARRAY || pixels == nullptr) return; auto& fill = g_fake.arrayLayerFill[g_fake.boundArrayTexture]; if (zoffset >= 0 && static_cast(zoffset) < fill.size()) { @@ -492,13 +492,11 @@ namespace { }; funcs.glTexParameteri = [](GLenum target, GLenum pname, GLint param) { if (target == GL_TEXTURE_2D_MULTISAMPLE && pname == GL_TEXTURE_SWIZZLE_A) { - g_fake.multisampleAlphaSwizzle[g_fake.boundMultisampleTexture] = - static_cast(param); + g_fake.multisampleAlphaSwizzle[g_fake.boundMultisampleTexture] = static_cast(param); } // A parameter write on a 2D array that has no uploaded level yet steers the // driver's layout choice to the plain order (see FakeArrayAllocation). - if (target == GL_TEXTURE_2D_ARRAY && - g_fake.packedArrayAllocations.count(g_fake.boundArrayTexture) == 0) { + if (target == GL_TEXTURE_2D_ARRAY && g_fake.packedArrayAllocations.count(g_fake.boundArrayTexture) == 0) { g_fake.packedArrayAllocations[g_fake.boundArrayTexture].paramsTouchedBeforeUpload = true; } }; @@ -506,8 +504,8 @@ namespace { // canonical (non-REV) order on every knob setting - the defect is confined to array // mip levels, and keeping the 2D side clean is what lets the readback below decode // with one order and still reproduce the mirror. - funcs.glTexImage2D = [](GLenum target, GLint level, GLint, GLsizei, GLsizei, GLint, GLenum, - GLenum type, const void* pixels) { + funcs.glTexImage2D = [](GLenum target, GLint level, GLint, GLsizei, GLsizei, GLint, GLenum, GLenum type, + const void* pixels) { if (target != GL_TEXTURE_2D || type != GL_UNSIGNED_SHORT_5_5_5_1 || pixels == nullptr) return; GLushort word = 0; std::memcpy(&word, pixels, sizeof(word)); @@ -516,8 +514,8 @@ namespace { // Records the allocation shape the mirror below is gated on, and the uploaded word. // Under the upload-corruption knob the STORED word is already wrong - the "not this // bug" shape the probe's round-trip control must catch. - funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei width, GLsizei height, - GLsizei depth, GLint, GLenum, GLenum type, const void* pixels) { + funcs.glTexImage3D = [](GLenum target, GLint level, GLint, GLsizei width, GLsizei height, GLsizei depth, GLint, + GLenum, GLenum type, const void* pixels) { if (target != GL_TEXTURE_2D_ARRAY || type != GL_UNSIGNED_SHORT_5_5_5_1 || pixels == nullptr) return; GLushort word = 0; std::memcpy(&word, pixels, sizeof(word)); @@ -534,9 +532,8 @@ namespace { // A raw texel-block move: the PHYSICAL word travels. The defect lives here - a source // in the mirrored ALLOCATION delivers the re-encoded word from EVERY level - and it // only exists for the allocation it was measured on: three levels of a 30x30x12 array. - funcs.glCopyImageSubData = [](GLuint srcName, GLenum, GLint srcLevel, GLint, GLint, GLint, - GLuint dstName, GLenum, GLint dstLevel, GLint, GLint, GLint, - GLsizei, GLsizei, GLsizei) { + funcs.glCopyImageSubData = [](GLuint srcName, GLenum, GLint srcLevel, GLint, GLint, GLint, GLuint dstName, + GLenum, GLint dstLevel, GLint, GLint, GLint, GLsizei, GLsizei, GLsizei) { if (g_fake.packed16CopyDoesNothing) return; const auto source = g_fake.packedTexelWords.find({srcName, srcLevel}); if (source == g_fake.packedTexelWords.end()) return; @@ -544,16 +541,14 @@ namespace { const auto allocation = g_fake.packedArrayAllocations.find(srcName); const bool measuredShape = allocation != g_fake.packedArrayAllocations.end() && allocation->second.width == 30 && allocation->second.height == 30 && - allocation->second.layers == 12 && - allocation->second.levelMask == 0b111u && + allocation->second.layers == 12 && allocation->second.levelMask == 0b111u && !allocation->second.paramsTouchedBeforeUpload; if (measuredShape && g_fake.packed16ArrayAllocationMirrored) { word = MirrorPacked5551(word); } g_fake.packedTexelWords[{dstName, dstLevel}] = word; }; - funcs.glTexSubImage2D = [](GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, - const void*) {}; + funcs.glTexSubImage2D = [](GLenum, GLint, GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, const void*) {}; funcs.glTexStorage2D = [](GLenum, GLsizei, GLenum, GLsizei, GLsizei) {}; funcs.glTexStorage2DMultisample = [](GLenum, GLsizei, GLenum, GLsizei, GLsizei, GLboolean) {}; funcs.glGenFramebuffers = [](GLsizei n, GLuint* framebuffers) { @@ -571,13 +566,13 @@ namespace { } }; funcs.glFramebufferTexture2D = [](GLenum target, GLenum, GLenum, GLuint texture, GLint) { - const GLuint framebuffer = (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer - : g_fake.boundDrawFramebuffer; + const GLuint framebuffer = + (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer : g_fake.boundDrawFramebuffer; g_fake.framebuffer2DAttachment[framebuffer] = texture; }; funcs.glFramebufferTextureLayer = [](GLenum target, GLenum, GLuint texture, GLint level, GLint layer) { - const GLuint framebuffer = (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer - : g_fake.boundDrawFramebuffer; + const GLuint framebuffer = + (target == GL_READ_FRAMEBUFFER) ? g_fake.boundReadFramebuffer : g_fake.boundDrawFramebuffer; g_fake.framebufferLayerAttachment[framebuffer] = {texture, layer}; g_fake.framebufferLayerLevel[framebuffer] = level; }; @@ -585,8 +580,7 @@ namespace { // The defect itself: the source layer is read from where the READ framebuffer says (unless // that knob is on too), and the result is written to the layer the DRAW framebuffer names - // or to layer 0 regardless, which is what an affected driver does. - funcs.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, - GLenum) { + funcs.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) { const auto source = g_fake.framebufferLayerAttachment.find(g_fake.boundReadFramebuffer); const auto destination = g_fake.framebufferLayerAttachment.find(g_fake.boundDrawFramebuffer); if (source == g_fake.framebufferLayerAttachment.end() || @@ -594,13 +588,11 @@ namespace { return; } const GLint sourceLayer = g_fake.blitIgnoresSourceLayer ? 0 : source->second.second; - const GLint destinationLayer = - g_fake.blitIgnoresDestinationLayer ? 0 : destination->second.second; + const GLint destinationLayer = g_fake.blitIgnoresDestinationLayer ? 0 : destination->second.second; auto& sourceFill = g_fake.arrayLayerFill[source->second.first]; auto& destinationFill = g_fake.arrayLayerFill[destination->second.first]; if (sourceLayer < 0 || static_cast(sourceLayer) >= sourceFill.size()) return; - if (destinationLayer < 0 || - static_cast(destinationLayer) >= destinationFill.size()) { + if (destinationLayer < 0 || static_cast(destinationLayer) >= destinationFill.size()) { return; } destinationFill[static_cast(destinationLayer)] = @@ -637,8 +629,7 @@ namespace { int sampleIndex = -1; char component = '?'; ParseSampledFetch(sampling, sampleIndex, component); - const GLenum swizzle = g_fake.multisampleAlphaSwizzle.count( - g_fake.boundMultisampleTexture) != 0 + const GLenum swizzle = g_fake.multisampleAlphaSwizzle.count(g_fake.boundMultisampleTexture) != 0 ? g_fake.multisampleAlphaSwizzle[g_fake.boundMultisampleTexture] : GL_ALPHA; // An R32F texel filled with (1, 0, 0, -) reads 1.0 through both the ALPHA and the @@ -679,18 +670,16 @@ namespace { } if (!writeStage.empty() && !readStage.empty()) { // The qualifier-merge pair: the stores are lost when the two halves share a name. - const bool sharedName = - FirstImageNameIn(writeStage) == FirstImageNameIn(readStage) && - !FirstImageNameIn(writeStage).empty(); - const bool lost = g_fake.everyVertexImageWriteDropped || - (g_fake.sameNameImagePairDropsWrites && sharedName); + const bool sharedName = FirstImageNameIn(writeStage) == FirstImageNameIn(readStage) && + !FirstImageNameIn(writeStage).empty(); + const bool lost = + g_fake.everyVertexImageWriteDropped || (g_fake.sameNameImagePairDropsWrites && sharedName); g_fake.lastFailedTexelCount = lost ? 1 << 20 : 0; return; } g_fake.lastFailedTexelCount = 0; }; - funcs.glReadPixels = [](GLint, GLint, GLsizei width, GLsizei height, GLenum format, GLenum type, - void* pixels) { + funcs.glReadPixels = [](GLint, GLint, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { const std::size_t texels = static_cast(width) * static_cast(height); // A read framebuffer naming a plain 2D texture that holds a 5551 word is the // packed16 probe reading its copy destination. The driver decodes its OWN storage @@ -702,9 +691,7 @@ namespace { // Gated on the texture actually holding a 5551 word, so every OTHER probe that // attaches a plain 2D texture keeps the pass/fail readback paths below. const GLushort w = g_fake.packedTexelWords[{attached->second, 0}]; - const auto expand5 = [](GLushort v) { - return static_cast((v << 3) | (v >> 2)); - }; + const auto expand5 = [](GLushort v) { return static_cast((v << 3) | (v >> 2)); }; GLubyte* out = static_cast(pixels); for (std::size_t i = 0; i < texels; ++i) { out[i * 4 + 0] = expand5((w >> 11) & 0x1F); @@ -725,9 +712,7 @@ namespace { if (const auto word = g_fake.packedTexelWords.find({layered->second.first, attachedLevel}); word != g_fake.packedTexelWords.end()) { const GLushort w = word->second; - const auto expand5 = [](GLushort v) { - return static_cast((v << 3) | (v >> 2)); - }; + const auto expand5 = [](GLushort v) { return static_cast((v << 3) | (v >> 2)); }; GLubyte* out = static_cast(pixels); for (std::size_t i = 0; i < texels; ++i) { out[i * 4 + 0] = expand5((w >> 11) & 0x1F); @@ -742,10 +727,9 @@ namespace { // below share. const auto& fill = g_fake.arrayLayerFill[layered->second.first]; const GLint layer = layered->second.second; - const GLubyte value = - (layer >= 0 && static_cast(layer) < fill.size()) - ? fill[static_cast(layer)] - : 0; + const GLubyte value = (layer >= 0 && static_cast(layer) < fill.size()) + ? fill[static_cast(layer)] + : 0; GLubyte* out = static_cast(pixels); for (std::size_t i = 0; i < texels; ++i) { out[i * 4 + 0] = value; @@ -757,7 +741,8 @@ namespace { } if (format == GL_RED && type == GL_FLOAT) { GLfloat* out = static_cast(pixels); - for (std::size_t i = 0; i < texels; ++i) out[i] = g_fake.lastSampledValue; + for (std::size_t i = 0; i < texels; ++i) + out[i] = g_fake.lastSampledValue; return; } GLubyte* out = static_cast(pixels); @@ -790,8 +775,7 @@ namespace { // section would stop meaning "this device has these bugs". TEST(DriverBugProbes, AProbeThatCannotRunReportsNoBug) { const MG_External::GLESFunctionsTable gl = EmptyFunctionTable(); - EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(gl)) - << "a probe with no entry points has measured nothing"; + EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(gl)) << "a probe with no entry points has measured nothing"; EXPECT_FALSE(ProbeExplicitVertexInputLocationCeiling(gl).detected) << "a probe with no entry points has measured nothing"; EXPECT_FALSE(ProbeGeometryStageSsboWriteAfterEmitDropped(gl)) @@ -800,8 +784,7 @@ TEST(DriverBugProbes, AProbeThatCannotRunReportsNoBug) { EXPECT_FALSE(ProbeImageLocationPerNameBudget(gl).detected); EXPECT_FALSE(ProbeCrossStageImageQualifierMergeDropsWrites(gl)); EXPECT_FALSE(ProbeImageWriteReadCoherencyResidual(gl).detected); - EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl)) - << "a probe with no entry points has measured nothing"; + EXPECT_FALSE(ProbeCopyImageMirrorsPacked16FieldOrder(gl)) << "a probe with no entry points has measured nothing"; } // The section lists only bugs the device HAS, so a driver nothing could be probed on renders @@ -819,8 +802,7 @@ TEST(DriverBugProbes, EveryFindingCarriesANameAndAnExplanation) { for (const auto& finding : CollectGlesKnownDriverBugs(gl)) { EXPECT_FALSE(finding.name.empty()); EXPECT_FALSE(finding.detail.empty()) << finding.name << " must say what MobileGL does about it"; - EXPECT_TRUE(finding.verdict == DriverBugVerdict::Fixed || - finding.verdict == DriverBugVerdict::Unfixable); + EXPECT_TRUE(finding.verdict == DriverBugVerdict::Fixed || finding.verdict == DriverBugVerdict::Unfixable); } } @@ -1108,8 +1090,7 @@ TEST(DriverBugProbes, LayeredBlitDestinationReportsNothingWhenTheBlitMovesNothin ResetFakeDriver(); const MG_External::GLESFunctionsTable gl = MakeFakeGLESFunctions(); MG_External::GLESFunctionsTable inert = gl; - inert.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, - GLenum) {}; + inert.glBlitFramebuffer = [](GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLint, GLbitfield, GLenum) {}; EXPECT_FALSE(ProbeBlitIgnoresDestinationArrayLayer(inert)); ExpectProbeReleasedEverything(); } @@ -1181,7 +1162,7 @@ TEST(DriverBugProbes, Packed16MirrorArithmeticMatchesTheDeviceEvidence) { #if MOBILEGL_BUILD_DISAGGREGATED namespace { MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement DepthResolveMeasurement(Uint32 renderPassMatches, - Uint32 shaderMatches) { + Uint32 shaderMatches) { MG_Backend::DirectVulkan::WireDepthResolveProbeMeasurement measurement; measurement.ran = true; MG_Backend::DirectVulkan::WireDepthResolveFormatReading reading; diff --git a/MobileGL/MG_Test/SelfTest/PersistentBufferOrderingProbeTest.cpp b/MobileGL/MG_Test/SelfTest/PersistentBufferOrderingProbeTest.cpp index de414840..9e949afe 100644 --- a/MobileGL/MG_Test/SelfTest/PersistentBufferOrderingProbeTest.cpp +++ b/MobileGL/MG_Test/SelfTest/PersistentBufferOrderingProbeTest.cpp @@ -27,21 +27,33 @@ namespace { Int channel = 0; Vector staging; }; - struct Draw { GLuint fbo, buffer; Int channel; Bool late; }; + struct Draw { + GLuint fbo, buffer; + Int channel; + Bool late; + }; std::map buffers; std::map vaoBuffers; std::map colors; Vector draws; std::set live; - std::map state = { - {GL_CURRENT_PROGRAM, 1}, {GL_VERTEX_ARRAY_BINDING, 2}, {GL_ARRAY_BUFFER, 3}, - {GL_COPY_READ_BUFFER, 4}, {GL_COPY_WRITE_BUFFER, 5}, - {GL_DRAW_FRAMEBUFFER_BINDING, 6}, {GL_READ_FRAMEBUFFER_BINDING, 7}, - {GL_TEXTURE_BINDING_2D, 8}, {GL_PIXEL_PACK_BUFFER, 9}, - {GL_PACK_ALIGNMENT, 8}, {GL_PACK_ROW_LENGTH, 31}, - {GL_PACK_SKIP_PIXELS, 4}, {GL_PACK_SKIP_ROWS, 5}}; - std::map enabled = {{GL_BLEND, GL_TRUE}, {GL_SCISSOR_TEST, GL_TRUE}, - {GL_SAMPLE_MASK, GL_TRUE}, {GL_RASTERIZER_DISCARD, GL_TRUE}}; + std::map state = {{GL_CURRENT_PROGRAM, 1}, + {GL_VERTEX_ARRAY_BINDING, 2}, + {GL_ARRAY_BUFFER, 3}, + {GL_COPY_READ_BUFFER, 4}, + {GL_COPY_WRITE_BUFFER, 5}, + {GL_DRAW_FRAMEBUFFER_BINDING, 6}, + {GL_READ_FRAMEBUFFER_BINDING, 7}, + {GL_TEXTURE_BINDING_2D, 8}, + {GL_PIXEL_PACK_BUFFER, 9}, + {GL_PACK_ALIGNMENT, 8}, + {GL_PACK_ROW_LENGTH, 31}, + {GL_PACK_SKIP_PIXELS, 4}, + {GL_PACK_SKIP_ROWS, 5}}; + std::map enabled = {{GL_BLEND, GL_TRUE}, + {GL_SCISSOR_TEST, GL_TRUE}, + {GL_SAMPLE_MASK, GL_TRUE}, + {GL_RASTERIZER_DISCARD, GL_TRUE}}; std::array viewport = {3, 4, 5, 6}; std::array clear = {.25f, .5f, .75f, 0}; std::array mask = {GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE}; @@ -54,8 +66,14 @@ namespace { Uint arenaAllocations = 0; Uint8 pointerSentinel = 0; - GLuint Create() { live.insert(next); return next++; } - void Generate(GLsizei count, GLuint* ids) { for (Int i = 0; i < count; ++i) ids[i] = Create(); } + GLuint Create() { + live.insert(next); + return next++; + } + void Generate(GLsizei count, GLuint* ids) { + for (Int i = 0; i < count; ++i) + ids[i] = Create(); + } void Delete(GLsizei count, const GLuint* ids) { for (Int i = 0; i < count; ++i) { live.erase(ids[i]); @@ -84,13 +102,20 @@ namespace { MG_External::GLESFunctionsTable Table() { MG_External::GLESFunctionsTable gl{}; gl.glGetIntegerv = [](GLenum name, GLint* out) { - if (name == GL_MAJOR_VERSION) *out = 3; - else if (name == GL_MINOR_VERSION) *out = 2; - else if (name == GL_NUM_EXTENSIONS) *out = driver.extension ? 1 : 0; - else if (name == GL_VIEWPORT) std::copy(driver.viewport.begin(), driver.viewport.end(), out); - else if (name == GL_ARRAY_BUFFER_BINDING) *out = driver.Get(GL_ARRAY_BUFFER); - else if (name == GL_PIXEL_PACK_BUFFER_BINDING) *out = driver.Get(GL_PIXEL_PACK_BUFFER); - else *out = driver.Get(name); + if (name == GL_MAJOR_VERSION) + *out = 3; + else if (name == GL_MINOR_VERSION) + *out = 2; + else if (name == GL_NUM_EXTENSIONS) + *out = driver.extension ? 1 : 0; + else if (name == GL_VIEWPORT) + std::copy(driver.viewport.begin(), driver.viewport.end(), out); + else if (name == GL_ARRAY_BUFFER_BINDING) + *out = driver.Get(GL_ARRAY_BUFFER); + else if (name == GL_PIXEL_PACK_BUFFER_BINDING) + *out = driver.Get(GL_PIXEL_PACK_BUFFER); + else + *out = driver.Get(name); }; gl.glGetBooleanv = [](GLenum, GLboolean* out) { std::copy(driver.mask.begin(), driver.mask.end(), out); }; gl.glGetFloatv = [](GLenum, GLfloat* out) { std::copy(driver.clear.begin(), driver.clear.end(), out); }; @@ -136,7 +161,8 @@ namespace { if (buffer.arena) { buffer.channel = FakeDriver::Channel(data); buffer.copied = false; - } else std::memcpy(buffer.staging.data() + offset, data, size); + } else + std::memcpy(buffer.staging.data() + offset, data, size); }; gl.glCopyBufferSubData = [](GLenum read, GLenum write, GLintptr offset, GLintptr, GLsizeiptr) { auto& source = driver.buffers[driver.Get(read)]; @@ -173,8 +199,8 @@ namespace { gl.glDrawArrays = [](GLenum, GLint, GLsizei) { const GLuint id = driver.vaoBuffers.at(driver.Get(GL_VERTEX_ARRAY_BINDING)); const auto& buffer = driver.buffers.at(id); - const Bool late = buffer.mapped ? (buffer.copied ? driver.corruptCopy : driver.corruptSubData) - : driver.corruptUnmapped; + const Bool late = + buffer.mapped ? (buffer.copied ? driver.corruptCopy : driver.corruptSubData) : driver.corruptUnmapped; driver.draws.push_back({GLuint(driver.Get(GL_DRAW_FRAMEBUFFER_BINDING)), id, buffer.channel, late}); }; gl.glFinish = []() { driver.Finish(); }; @@ -191,7 +217,8 @@ namespace { const Int channel = driver.colors.at(driver.Get(GL_READ_FRAMEBUFFER_BINDING)); auto* pixels = static_cast(data); for (Int i = 0; i < width * height; ++i) - for (Int c = 0; c < 4; ++c) pixels[4 * i + c] = c == channel || c == 3 ? 255 : 0; + for (Int c = 0; c < 4; ++c) + pixels[4 * i + c] = c == channel || c == 3 ? 255 : 0; }; return gl; } @@ -199,9 +226,12 @@ namespace { class PersistentBufferOrderingProbeTest : public ::testing::Test { protected: void SetUp() override { driver = FakeDriver{}; } - void TearDown() override { EXPECT_TRUE(driver.live.empty()); EXPECT_TRUE(driver.draws.empty()); } + void TearDown() override { + EXPECT_TRUE(driver.live.empty()); + EXPECT_TRUE(driver.draws.empty()); + } }; -} +} // namespace TEST_F(PersistentBufferOrderingProbeTest, RequiresExtensionAndCompleteDispatchBeforeAllocating) { auto gl = Table(); @@ -282,7 +312,8 @@ TEST_F(PersistentBufferOrderingProbeTest, CorruptNeverMappedControlCannotAccuseP TEST_F(PersistentBufferOrderingProbeTest, CorruptSerializedControlCannotConfirmOrderingDefect) { driver.corruptSubData = driver.corruptCopy = driver.corruptSerialized = true; const auto measurement = ProbePersistentBufferUpdateOrdering(Table()); - for (const auto& row : measurement.uploads) EXPECT_GT(row.finishBoth.badFrames, 0u); + for (const auto& row : measurement.uploads) + EXPECT_GT(row.finishBoth.badFrames, 0u); EXPECT_FALSE(DescribePersistentBufferOrderingBug(measurement)); } diff --git a/MobileGL/MG_Test/SelfTest/PrimitivesGeneratedNoXfbProbeTest.cpp b/MobileGL/MG_Test/SelfTest/PrimitivesGeneratedNoXfbProbeTest.cpp index 69d635fa..53e97c3b 100644 --- a/MobileGL/MG_Test/SelfTest/PrimitivesGeneratedNoXfbProbeTest.cpp +++ b/MobileGL/MG_Test/SelfTest/PrimitivesGeneratedNoXfbProbeTest.cpp @@ -40,8 +40,8 @@ using MobileGL::Bool; using MobileGL::Uint32; using MobileGL::Uint64; using MobileGL::MG_Config::QuirkOverride; -using MobileGL::MG_Util::SelfTest::EvaluatePrimitivesGeneratedNoXfbVerdict; using MobileGL::MG_Util::SelfTest::ChoosePrimitivesGeneratedReroute; +using MobileGL::MG_Util::SelfTest::EvaluatePrimitivesGeneratedNoXfbVerdict; using MobileGL::MG_Util::SelfTest::PrimGenRerouteKind; using MobileGL::MG_Util::SelfTest::PrimitivesGeneratedNoXfbMeasurement; using MobileGL::MG_Util::SelfTest::PrimitivesGeneratedNoXfbProbeContext; @@ -97,19 +97,16 @@ namespace { // Controls agreeing changes nothing - health is decided by the subject. TEST(PrimitivesGeneratedNoXfbVerdictTest, AConformingDriverReadsStreamCounts) { const auto measurement = Measurement(Shape(kHealthy), Shape(kHealthy), Shape(kHealthy)); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), - PrimitivesGeneratedNoXfbVerdict::StreamCounts); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::StreamCounts); } // ...and stays healthy with no tessellation stage to draw the patches shape with, // and with no control at all - a control is only required to QUALIFY a // substitute, never to certify health. TEST(PrimitivesGeneratedNoXfbVerdictTest, HealthNeedsNeitherTessellationNorAControl) { - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape(kHealthy), Shape(kHealthy), NotDrawn())), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement(Shape(kHealthy), Shape(kHealthy), NotDrawn())), PrimitivesGeneratedNoXfbVerdict::StreamCounts); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({1}), Shape({1}), Shape({1}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement(Shape({1}), Shape({1}), Shape({1}))), PrimitivesGeneratedNoXfbVerdict::StreamCounts); } @@ -118,8 +115,7 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, HealthNeedsNeitherTessellationNorACont // included), the statistics control dead under discard. The dedicated query must // win - it is the only substitute that covers the CTS shape there. TEST(PrimitivesGeneratedNoXfbVerdictTest, LavapipeShapedMeasurementTakesTheDedicatedQuery) { - const auto measurement = - Measurement(Shape(kLavapipePlain), Shape(kLavapipeDiscard), Shape(kLavapipeDiscard)); + const auto measurement = Measurement(Shape(kLavapipePlain), Shape(kLavapipeDiscard), Shape(kLavapipeDiscard)); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute); } @@ -127,8 +123,8 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, LavapipeShapedMeasurementTakesTheDedic // The affected-device hypothesis with no dedicated query: statistics exact on // every shape, the CTS's discarded shapes included. TEST(PrimitivesGeneratedNoXfbVerdictTest, StatisticsExactEverywhereIsTheFullStatisticsSubstitute) { - const auto measurement = Measurement(Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 1}), - Shape({0, false, 0, true, 1})); + const auto measurement = + Measurement(Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 1})); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute); } @@ -136,8 +132,8 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, StatisticsExactEverywhereIsTheFullStat // A dedicated query that is silent in the same way the stream query is must not // be armed - the statistics tier decides instead. TEST(PrimitivesGeneratedNoXfbVerdictTest, ASilentDedicatedQueryFallsThroughToStatistics) { - const auto measurement = Measurement(Shape({0, true, 0, true, 1}), Shape({0, true, 0, true, 1}), - Shape({0, true, 0, true, 1})); + const auto measurement = + Measurement(Shape({0, true, 0, true, 1}), Shape({0, true, 0, true, 1}), Shape({0, true, 0, true, 1})); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute); } @@ -146,8 +142,8 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, ASilentDedicatedQueryFallsThroughToSta // the plain shape, dead under discard. Repairs undiscarded queries only, and the // verdict must say so. TEST(PrimitivesGeneratedNoXfbVerdictTest, StatisticsDeadUnderDiscardIsThePlainOnlySubstitute) { - const auto measurement = Measurement(Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 0}), - Shape({0, false, 0, true, 0})); + const auto measurement = + Measurement(Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 0}), Shape({0, false, 0, true, 0})); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly); } @@ -159,20 +155,18 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, StatisticsDeadUnderDiscardIsThePlainOn // whose silence is selective) and whose statistics read 0 - rerouting would turn // that correct 1 into a 0, so the honest verdict is that nothing may be armed. TEST(PrimitivesGeneratedNoXfbVerdictTest, ASubstituteWorseThanTheStreamOnAnyShapeIsRefused) { - const auto measurement = Measurement(Shape({1, false, 0, true, 1}), Shape({1, false, 0, true, 0}), - Shape({0, false, 0, true, 0})); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), - PrimitivesGeneratedNoXfbVerdict::Unfixable); + const auto measurement = + Measurement(Shape({1, false, 0, true, 1}), Shape({1, false, 0, true, 0}), Shape({0, false, 0, true, 0})); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::Unfixable); // The same shape with the statistics slot MISSING on the stream-exact shape is // the same trade: an unmeasured control cannot be assumed to answer. - const auto unmeasured = Measurement(Shape({1, false, 0, true, 1}), Shape({1, false, 0, false, 0}), - Shape({0, false, 0, true, 0})); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(unmeasured), - PrimitivesGeneratedNoXfbVerdict::Unfixable); + const auto unmeasured = + Measurement(Shape({1, false, 0, true, 1}), Shape({1, false, 0, false, 0}), Shape({0, false, 0, true, 0})); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(unmeasured), PrimitivesGeneratedNoXfbVerdict::Unfixable); // ...while the same selective silence WITH a substitute that covers the shapes // it must still qualifies: every shape the statistics miss read 0 anyway. - const auto dominating = Measurement(Shape({1, false, 0, true, 1}), Shape({0, false, 0, true, 1}), - Shape({0, false, 0, true, 0})); + const auto dominating = + Measurement(Shape({1, false, 0, true, 1}), Shape({0, false, 0, true, 1}), Shape({0, false, 0, true, 0})); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(dominating), PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly); } @@ -181,16 +175,13 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, ASubstituteWorseThanTheStreamOnAnyShap // OVERCOUNTS the plain shape (as disqualifying as one that reads 0 - an exact // match is what qualifies a substitute). TEST(PrimitivesGeneratedNoXfbVerdictTest, StreamSilentWithoutAWorkingPlainControlIsUnfixable) { - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({0}), Shape({0}), Shape({0}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement(Shape({0}), Shape({0}), Shape({0}))), PrimitivesGeneratedNoXfbVerdict::Unfixable); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({0, true, 0, true, 0}), Shape({0, true, 0, true, 0}), - Shape({0, true, 0, true, 0}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement( + Shape({0, true, 0, true, 0}), Shape({0, true, 0, true, 0}), Shape({0, true, 0, true, 0}))), PrimitivesGeneratedNoXfbVerdict::Unfixable); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({0, true, 2, true, 2}), Shape({0, true, 1, true, 1}), - Shape({0, true, 1, true, 1}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement( + Shape({0, true, 2, true, 2}), Shape({0, true, 1, true, 1}), Shape({0, true, 1, true, 1}))), PrimitivesGeneratedNoXfbVerdict::Unfixable); } @@ -198,19 +189,16 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, StreamSilentWithoutAWorkingPlainContro // defect (exact silence) nor health (the exact count), whichever shape carries // it; and a probe that never ran, or lost its mandatory shapes, says nothing. TEST(PrimitivesGeneratedNoXfbVerdictTest, AnswersFittingNeitherHealthNorTheDefectAreInconclusive) { - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({2, true, 1, true, 1}), Shape({0, true, 1, true, 1}), - Shape({0, true, 1, true, 1}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement( + Shape({2, true, 1, true, 1}), Shape({0, true, 1, true, 1}), Shape({0, true, 1, true, 1}))), PrimitivesGeneratedNoXfbVerdict::Inconclusive); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict( - Measurement(Shape({0, true, 1, true, 1}), Shape({3, true, 1, true, 1}), - Shape({0, true, 1, true, 1}))), + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(Measurement( + Shape({0, true, 1, true, 1}), Shape({3, true, 1, true, 1}), Shape({0, true, 1, true, 1}))), PrimitivesGeneratedNoXfbVerdict::Inconclusive); PrimitivesGeneratedNoXfbMeasurement neverRan; neverRan.ran = false; - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(neverRan), - PrimitivesGeneratedNoXfbVerdict::Inconclusive); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(neverRan), PrimitivesGeneratedNoXfbVerdict::Inconclusive); const auto missingMandatoryShape = Measurement(Shape({0, true, 1, true, 1}), NotDrawn(), NotDrawn()); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(missingMandatoryShape), @@ -221,8 +209,8 @@ TEST(PrimitivesGeneratedNoXfbVerdictTest, AnswersFittingNeitherHealthNorTheDefec // ones read 0 (a driver that gates the stream counter on rasterization rather // than on the capture). With a whole control the substitute is whole. TEST(PrimitivesGeneratedNoXfbVerdictTest, SilenceOnOnlyTheDiscardShapesIsStillTheDefect) { - const auto measurement = Measurement(Shape({1, true, 1, true, 1}), Shape({0, true, 1, true, 1}), - Shape({0, true, 1, true, 1})); + const auto measurement = + Measurement(Shape({1, true, 1, true, 1}), Shape({0, true, 1, true, 1}), Shape({0, true, 1, true, 1})); EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute); } @@ -242,14 +230,13 @@ namespace { PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly, PrimitivesGeneratedNoXfbVerdict::Unfixable, }; -} +} // namespace TEST(PrimitivesGeneratedNoXfbArmingTest, ForceOffNeverReroutes) { for (const auto verdict : kAllVerdicts) { for (const Bool pgqUsable : {false, true}) { for (const Bool statsUsable : {false, true}) { - EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::ForceOff, verdict, pgqUsable, - statsUsable), + EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::ForceOff, verdict, pgqUsable, statsUsable), PrimGenRerouteKind::None); } } @@ -274,34 +261,30 @@ TEST(PrimitivesGeneratedNoXfbArmingTest, ForceOnBypassesTheVerdictButNeverTheStr TEST(PrimitivesGeneratedNoXfbArmingTest, AutoFollowsExactlyTheSubstituteVerdicts) { EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute, - true, true), + QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute, true, true), PrimGenRerouteKind::PrimitivesGeneratedExt); - EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, false, true), + EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::Auto, + PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, false, true), PrimGenRerouteKind::ClippingStatistics); EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly, - false, true), + QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly, false, true), PrimGenRerouteKind::ClippingStatistics); // The statistics verdicts never take the dedicated pool: that verdict only // exists when the dedicated query did NOT qualify. - EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, true, true), + EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::Auto, + PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, true, true), PrimGenRerouteKind::ClippingStatistics); for (const auto verdict : {PrimitivesGeneratedNoXfbVerdict::Inconclusive, PrimitivesGeneratedNoXfbVerdict::StreamCounts, PrimitivesGeneratedNoXfbVerdict::Unfixable}) { - EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::Auto, verdict, true, true), - PrimGenRerouteKind::None); + EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::Auto, verdict, true, true), PrimGenRerouteKind::None); } // The structural checks bind Auto too. EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute, - false, true), + QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute, false, true), PrimGenRerouteKind::None); - EXPECT_EQ(ChoosePrimitivesGeneratedReroute( - QuirkOverride::Auto, PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, false, false), + EXPECT_EQ(ChoosePrimitivesGeneratedReroute(QuirkOverride::Auto, + PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute, false, false), PrimGenRerouteKind::None); } @@ -358,7 +341,9 @@ namespace { VKAPI_ATTR VkResult VKAPI_CALL FakeBeginCommandBuffer(VkCommandBuffer, const VkCommandBufferBeginInfo*) { return VK_SUCCESS; } - VKAPI_ATTR VkResult VKAPI_CALL FakeEndCommandBuffer(VkCommandBuffer) { return VK_SUCCESS; } + VKAPI_ATTR VkResult VKAPI_CALL FakeEndCommandBuffer(VkCommandBuffer) { + return VK_SUCCESS; + } VKAPI_ATTR VkResult VKAPI_CALL FakeCreateQueryPool(VkDevice, const VkQueryPoolCreateInfo*, const VkAllocationCallbacks*, VkQueryPool* out) { *out = FakeHandle(); @@ -370,8 +355,8 @@ namespace { VKAPI_ATTR void VKAPI_CALL FakeCmdResetQueryPool(VkCommandBuffer, VkQueryPool, uint32_t, uint32_t) {} VKAPI_ATTR void VKAPI_CALL FakeCmdBeginQuery(VkCommandBuffer, VkQueryPool, uint32_t, VkQueryControlFlags) {} VKAPI_ATTR void VKAPI_CALL FakeCmdEndQuery(VkCommandBuffer, VkQueryPool, uint32_t) {} - VKAPI_ATTR void VKAPI_CALL FakeCmdBeginQueryIndexedEXT(VkCommandBuffer, VkQueryPool, uint32_t, - VkQueryControlFlags, uint32_t) {} + VKAPI_ATTR void VKAPI_CALL FakeCmdBeginQueryIndexedEXT(VkCommandBuffer, VkQueryPool, uint32_t, VkQueryControlFlags, + uint32_t) {} VKAPI_ATTR void VKAPI_CALL FakeCmdEndQueryIndexedEXT(VkCommandBuffer, VkQueryPool, uint32_t, uint32_t) {} VKAPI_ATTR VkResult VKAPI_CALL FakeCreateRenderPass(VkDevice, const VkRenderPassCreateInfo*, const VkAllocationCallbacks*, VkRenderPass* out) { @@ -401,13 +386,11 @@ namespace { ++g_fake.destroyCalls; } VKAPI_ATTR VkResult VKAPI_CALL FakeCreatePipelineLayout(VkDevice, const VkPipelineLayoutCreateInfo*, - const VkAllocationCallbacks*, - VkPipelineLayout* out) { + const VkAllocationCallbacks*, VkPipelineLayout* out) { *out = FakeHandle(); return VK_SUCCESS; } - VKAPI_ATTR void VKAPI_CALL FakeDestroyPipelineLayout(VkDevice, VkPipelineLayout, - const VkAllocationCallbacks*) { + VKAPI_ATTR void VKAPI_CALL FakeDestroyPipelineLayout(VkDevice, VkPipelineLayout, const VkAllocationCallbacks*) { ++g_fake.destroyCalls; } VKAPI_ATTR VkResult VKAPI_CALL FakeCreateGraphicsPipelines(VkDevice, VkPipelineCache, uint32_t count, @@ -423,8 +406,8 @@ namespace { } VKAPI_ATTR void VKAPI_CALL FakeCmdBindPipeline(VkCommandBuffer, VkPipelineBindPoint, VkPipeline) {} VKAPI_ATTR void VKAPI_CALL FakeCmdDraw(VkCommandBuffer, uint32_t, uint32_t, uint32_t, uint32_t) {} - VKAPI_ATTR VkResult VKAPI_CALL FakeCreateFence(VkDevice, const VkFenceCreateInfo*, - const VkAllocationCallbacks*, VkFence* out) { + VKAPI_ATTR VkResult VKAPI_CALL FakeCreateFence(VkDevice, const VkFenceCreateInfo*, const VkAllocationCallbacks*, + VkFence* out) { *out = FakeHandle(); return VK_SUCCESS; } @@ -438,9 +421,8 @@ namespace { VKAPI_ATTR VkResult VKAPI_CALL FakeWaitForFences(VkDevice, uint32_t, const VkFence*, VkBool32, uint64_t) { return g_fake.fenceWaitResult; } - VKAPI_ATTR VkResult VKAPI_CALL FakeGetQueryPoolResults(VkDevice, VkQueryPool, uint32_t, uint32_t, - size_t dataSize, void* data, VkDeviceSize, - VkQueryResultFlags) { + VKAPI_ATTR VkResult VKAPI_CALL FakeGetQueryPoolResults(VkDevice, VkQueryPool, uint32_t, uint32_t, size_t dataSize, + void* data, VkDeviceSize, VkQueryResultFlags) { // The stream pool's {primitivesWritten, primitivesNeeded} pair; the probe // reads primitivesNeeded, and this fake device counts capture-less draws. if (data == nullptr || dataSize < 2 * sizeof(Uint64)) { @@ -523,8 +505,7 @@ TEST(PrimitivesGeneratedNoXfbProbeTeardownTest, AFenceTimeoutLeaksEverythingAndR << "vkDeviceWaitIdle on a queue that missed a 5 s deadline is the hang the bound exists to " "prevent"; // The verdict must not read a timed-out probe as anything but "no verdict". - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), - PrimitivesGeneratedNoXfbVerdict::Inconclusive); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::Inconclusive); } // The control: a driver that signals normally gets the ordinary teardown - idle @@ -541,6 +522,5 @@ TEST(PrimitivesGeneratedNoXfbProbeTeardownTest, ASignalledFenceTearsDownNormally EXPECT_FALSE(measurement.fenceWaitTimedOut); EXPECT_EQ(g_fake.deviceWaitIdleCalls, 1u); EXPECT_GT(g_fake.destroyCalls, 0u); - EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), - PrimitivesGeneratedNoXfbVerdict::StreamCounts); + EXPECT_EQ(EvaluatePrimitivesGeneratedNoXfbVerdict(measurement), PrimitivesGeneratedNoXfbVerdict::StreamCounts); } diff --git a/MobileGL/MG_Test/ShaderTranspiler/ClampMultisampleFetchTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/ClampMultisampleFetchTest.cpp index 544066a8..cc5dc77a 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/ClampMultisampleFetchTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/ClampMultisampleFetchTest.cpp @@ -74,8 +74,7 @@ namespace { EXPECT_TRUE(programResult) << (programResult ? String{} : programResult.error().log); if (!programResult) return {}; - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, - .program = *programResult.value()}; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, .program = *programResult.value()}; auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); EXPECT_TRUE(binaryResult) << (binaryResult ? String{} : binaryResult.error().log); if (!binaryResult || binaryResult->empty()) return {}; @@ -275,9 +274,9 @@ TEST_F(ClampMultisampleFetchTest, LeavesAModuleWithoutAMultisampledImageUntouche ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true)); + ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(input, output, /*maxColorSamples=*/4, + /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, + kAdvertisedMaxSamples, true)); EXPECT_EQ(output, input) << Disassemble(output); } @@ -299,9 +298,9 @@ TEST_F(ClampMultisampleFetchTest, ReplacesAnOutOfRangeIntegerSampleWithZero) { EXPECT_EQ(constantsBefore.at(sampleBefore->maskOperandIds.front()), 3u); Vector output; - ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true)); + ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(input, output, /*maxColorSamples=*/4, + /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, + kAdvertisedMaxSamples, true)); ASSERT_FALSE(output.empty()); const String dis = Disassemble(output); ASSERT_TRUE(Validates(output)) << dis; @@ -335,9 +334,9 @@ TEST_F(ClampMultisampleFetchTest, LeavesTheFetchAloneWhenTheCategoryReachesTheAd ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/4, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true)); + ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(input, output, /*maxColorSamples=*/4, + /*maxIntegerSamples=*/4, /*maxDepthSamples=*/4, + kAdvertisedMaxSamples, true)); EXPECT_EQ(output, input) << Disassemble(output); } @@ -353,9 +352,9 @@ TEST_F(ClampMultisampleFetchTest, ClampsAColorSampleWithAMinimum) { const Uint32 originalSampleId = before.front().maskOperandIds.front(); Vector output; - ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/2, /*maxIntegerSamples=*/4, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true)); + ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(input, output, /*maxColorSamples=*/2, + /*maxIntegerSamples=*/4, /*maxDepthSamples=*/4, + kAdvertisedMaxSamples, true)); ASSERT_FALSE(output.empty()); const String dis = Disassemble(output); ASSERT_TRUE(Validates(output)) << dis; @@ -422,8 +421,8 @@ TEST_F(ClampMultisampleFetchTest, DefersADirectUnsignedFetchToTheRuntimeBound) { Vector output; ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true, /*deferDirectUnsignedFetches=*/true)); + input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, kAdvertisedMaxSamples, + true, /*deferDirectUnsignedFetches=*/true)); ASSERT_FALSE(output.empty()); const String dis = Disassemble(output); ASSERT_TRUE(Validates(output)) << dis; @@ -437,9 +436,9 @@ TEST_F(ClampMultisampleFetchTest, WithoutTheRuntimeBoundEveryIntegerFetchKeepsTh ASSERT_FALSE(input.empty()); Vector output; - ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl( - input, output, /*maxColorSamples=*/4, /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, - kAdvertisedMaxSamples, true)); + ASSERT_TRUE(ShaderCompiler::ClampMultisampleFetchesForEssl(input, output, /*maxColorSamples=*/4, + /*maxIntegerSamples=*/1, /*maxDepthSamples=*/4, + kAdvertisedMaxSamples, true)); ASSERT_FALSE(output.empty()); const String dis = Disassemble(output); EXPECT_EQ(CountSampleOperandsEqualTo(output, 3u), 0u) << dis; diff --git a/MobileGL/MG_Test/ShaderTranspiler/DeferGeometryEmitsTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/DeferGeometryEmitsTest.cpp index 72c98db7..b55518bf 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/DeferGeometryEmitsTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/DeferGeometryEmitsTest.cpp @@ -64,7 +64,8 @@ namespace { SizeT Count(const String& haystack, const String& needle) { SizeT count = 0; - for (SizeT at = haystack.find(needle); at != String::npos; at = haystack.find(needle, at + 1)) ++count; + for (SizeT at = haystack.find(needle); at != String::npos; at = haystack.find(needle, at + 1)) + ++count; return count; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp b/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp index 4b99224b..6a9e0cf0 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/DemoteFloat64Test.cpp @@ -91,7 +91,8 @@ namespace { }); Vector offsets; - for (const auto& [member, offset] : offsetByMember) offsets.push_back(offset); + for (const auto& [member, offset] : offsetByMember) + offsets.push_back(offset); return offsets; } @@ -206,8 +207,7 @@ void main() { ASSERT_FALSE(input.empty()); // What glslang laid out for the 64-bit members, which is what an application computing // std140 by hand would also get. - EXPECT_EQ(CollectOffsetsOf(input, "Blk"), (Vector{0, 8, 16, 32, 64, 96, 224, 272})) - << Disassemble(input); + EXPECT_EQ(CollectOffsetsOf(input, "Blk"), (Vector{0, 8, 16, 32, 64, 96, 224, 272})) << Disassemble(input); Vector output; ASSERT_TRUE(ShaderCompiler::DemoteFloat64ToFloat32(input, output, true)); @@ -216,8 +216,7 @@ void main() { // vec4 at 32, mat4 at 48 with a 16-byte column stride, the array at 112 with the std140 // 16-byte element stride, and the trailing float at 160. This is what SPIRV-Cross has to be // able to re-derive for GLSL ES, which has no member layout(offset=) to fall back on. - EXPECT_EQ(CollectOffsetsOf(output, "Blk"), (Vector{0, 4, 8, 16, 32, 48, 112, 160})) - << Disassemble(output); + EXPECT_EQ(CollectOffsetsOf(output, "Blk"), (Vector{0, 4, 8, 16, 32, 48, 112, 160})) << Disassemble(output); const String text = Disassemble(output); EXPECT_NE(text.find("MatrixStride 16"), String::npos) << text; EXPECT_NE(text.find("ArrayStride 16"), String::npos) << text; @@ -560,11 +559,10 @@ void main() { // one of these before demotion, so the program simply could not be transpiled at all. class DemoteFloat64EsslTest : public DemoteFloat64Test, public ::testing::WithParamInterface {}; -INSTANTIATE_TEST_SUITE_P( - Shapes, DemoteFloat64EsslTest, - ::testing::Values( - // A double that never reaches an interface: locals and literals only. - R"(#version 460 core +INSTANTIATE_TEST_SUITE_P(Shapes, DemoteFloat64EsslTest, + ::testing::Values( + // A double that never reaches an interface: locals and literals only. + R"(#version 460 core layout(location = 0) out float vOut; void main() { double s = 0.5lf; @@ -573,9 +571,9 @@ void main() { gl_Position = vec4(float(s)); } )", - // A default-block double uniform: the glUniform*d path, and the block MobileGL lays out - // itself. - R"(#version 460 core + // A default-block double uniform: the glUniform*d path, and the block MobileGL lays out + // itself. + R"(#version 460 core layout(location = 0) uniform double uScale; layout(location = 1) uniform dvec3 uOffset; layout(location = 2) uniform dmat4 uTransform; @@ -586,8 +584,8 @@ void main() { gl_Position = vec4(float(p.x)); } )", - // An application-declared std140 block whose members are 64-bit. - R"(#version 460 core + // An application-declared std140 block whose members are 64-bit. + R"(#version 460 core layout(std140, binding = 0) uniform Blk { float a; double d; @@ -605,8 +603,8 @@ void main() { gl_Position = vec4(float(s)); } )", - // A 64-bit vertex input, which is what glVertexAttribLFormat feeds. - R"(#version 460 core + // A 64-bit vertex input, which is what glVertexAttribLFormat feeds. + R"(#version 460 core layout(location = 0) in dvec3 inPos; layout(location = 2) in double inWeight; layout(location = 0) out float vOut; @@ -615,8 +613,8 @@ void main() { gl_Position = vec4(vOut); } )", - // An std430 storage block, whose double members pack differently again. - R"(#version 460 core + // An std430 storage block, whose double members pack differently again. + R"(#version 460 core layout(std430, binding = 0) buffer Ssbo { double head; dvec4 wide; @@ -697,10 +695,14 @@ namespace { return "#version 430 core\n" "layout(local_size_x = 1) in;\n" "buffer Result { int g_result; };\n" - "uniform " + type + " g_0;\n" + "uniform " + + type + + " g_0;\n" "void main() {\n" " g_result = 0;\n" - " if (g_0 " + op + " " + literal + ") g_result = 1;\n" + " if (g_0 " + + op + " " + literal + + ") g_result = 1;\n" "}\n"; } @@ -720,7 +722,8 @@ namespace { Vector tokens; StringStream words(line); String word; - while (words >> word) tokens.push_back(word); + while (words >> word) + tokens.push_back(word); instructions.push_back(tokens); } return instructions; @@ -769,15 +772,13 @@ namespace { const String& unorderedOpcode) { const String disassembly = OptimizedDisassembly(source); const Vector compare = FindRewrittenCompare(disassembly); - ASSERT_FALSE(compare.empty()) << "no rewritten compare in the optimized module\n" - << disassembly; + ASSERT_FALSE(compare.empty()) << "no rewritten compare in the optimized module\n" << disassembly; EXPECT_TRUE(compare[2] == orderedOpcode || compare[2] == unorderedOpcode) << "expected " << orderedOpcode << " (or its unordered twin), got " << compare[2] << "\n" << disassembly; const String zeroId = FindZeroConstantId(disassembly); - ASSERT_FALSE(zeroId.empty()) << "the module has no 0.0 constant of the abs() type\n" - << disassembly; + ASSERT_FALSE(zeroId.empty()) << "the module has no 0.0 constant of the abs() type\n" << disassembly; EXPECT_EQ(compare.back(), zeroId) << "the rewrite compares against " << compare.back() << " instead of the module's own zero; a synthesized threshold is the epsilon bug\n" @@ -818,8 +819,8 @@ TEST_F(DemoteFloat64Test, AComparisonAgainstASmallNonZeroLiteralIsLeftAlone) { TEST_F(DemoteFloat64Test, TheRewriteComparesAbsAgainstTheModulesOwnZero) { // `x == 0.0` -> `abs(x) <= 0.0`. The equality has to be INSIDE the replacement: with a strict // `<` and no epsilon left to hide behind, +/-0 would stop comparing equal to zero. - ExpectComparedAgainstModuleZero(CompareAgainstUsing("float", "==", "0.0"), - "OpFOrdLessThanEqual", "OpFUnordLessThanEqual"); + ExpectComparedAgainstModuleZero(CompareAgainstUsing("float", "==", "0.0"), "OpFOrdLessThanEqual", + "OpFUnordLessThanEqual"); // `x != 0.0` -> `abs(x) > 0.0`, the strict complement of the above. ExpectComparedAgainstModuleZero(CompareAgainstUsing("float", "!=", "0.0"), "OpFOrdGreaterThan", "OpFUnordGreaterThan"); diff --git a/MobileGL/MG_Test/ShaderTranspiler/DemotePointSizeTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/DemotePointSizeTest.cpp index 318dcdfe..24f33d7e 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/DemotePointSizeTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/DemotePointSizeTest.cpp @@ -245,8 +245,8 @@ TEST_F(DemotePointSizeTest, DemotesAFiveStageProgramWholesale) { {GL_GEOMETRY_SHADER, kGeometrySource}, {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 5u); - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER, + GL_FRAGMENT_SHADER}; // The defect, pinned first: every tessellation/geometry stage really does declare the // capability the device lacks - the same probe production's declines use. @@ -321,8 +321,8 @@ TEST_F(DemotePointSizeTest, DemotesAFiveStageProgramWholesale) { // the same modules finds nothing to arm on and must not touch a byte. Vector> again = modules; ShaderCompiler::PointSizeDemotionOutcome secondOutcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - again, types, true, true, true, secondOutcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(again, types, true, true, true, + secondOutcome, true, true)); EXPECT_FALSE(secondOutcome.demoted); EXPECT_TRUE(secondOutcome.declineDetail.empty()) << secondOutcome.declineDetail; EXPECT_EQ(again, modules); @@ -334,19 +334,16 @@ TEST_F(DemotePointSizeTest, WithoutAGeometryStageTheEvaluationStageOwnsTheCaptur {GL_TESS_EVALUATION_SHADER, kTessEvalSource}, {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 4u); - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, true, true, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, true, true, true, outcome, + true, true)); EXPECT_TRUE(outcome.demoted) << outcome.declineDetail; const String tes = Disassemble(modules[2]); EXPECT_NE(tes.find("OpName %mg_PointSizeCapture"), String::npos) << tes; EXPECT_NE(tes.find("OpStore %mg_PointSizeCapture"), String::npos) << tes; - EXPECT_EQ(tes.find("OpName %mg_PointSizeIo2"), String::npos) - << "no geometry stage, no Io2 boundary:\n" - << tes; + EXPECT_EQ(tes.find("OpName %mg_PointSizeIo2"), String::npos) << "no geometry stage, no Io2 boundary:\n" << tes; } TEST_F(DemotePointSizeTest, AGeometryOnlyProgramReadsTheVertexBoundary) { @@ -367,8 +364,7 @@ void main() { const Vector types{GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, /*demoteTessellation=*/false, /*demoteGeometry=*/true, false, outcome, true, - true)); + modules, types, /*demoteTessellation=*/false, /*demoteGeometry=*/true, false, outcome, true, true)); EXPECT_TRUE(outcome.demoted) << outcome.declineDetail; const String gs = Disassemble(modules[1]); @@ -406,14 +402,13 @@ in vec4 g_color; layout(location = 0) out vec4 fragColor; void main() { fragColor = g_color; } )"; - Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, vsWithVarying}, - {GL_GEOMETRY_SHADER, gsWithVarying}, - {GL_FRAGMENT_SHADER, fsWithVarying}}); + Vector> modules = CompileProgramToSpirv( + {{GL_VERTEX_SHADER, vsWithVarying}, {GL_GEOMETRY_SHADER, gsWithVarying}, {GL_FRAGMENT_SHADER, fsWithVarying}}); ASSERT_EQ(modules.size(), 3u); const Vector types{GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, false, true, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, false, true, true, + outcome, true, true)); EXPECT_TRUE(outcome.demoted) << outcome.declineDetail; // v_color / g_color occupy location 0, so every carrier must sit at 1 - in every stage, @@ -426,18 +421,16 @@ void main() { fragColor = g_color; } } TEST_F(DemotePointSizeTest, APointSizeFreeProgramStaysByteIdentical) { - Vector> modules = - CompileProgramToSpirv({{GL_VERTEX_SHADER, kPlainVertexSource}, - {GL_TESS_CONTROL_SHADER, kPlainTessControlSource}, - {GL_TESS_EVALUATION_SHADER, kPlainTessEvalSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}); + Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, kPlainVertexSource}, + {GL_TESS_CONTROL_SHADER, kPlainTessControlSource}, + {GL_TESS_EVALUATION_SHADER, kPlainTessEvalSource}, + {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 4u); const Vector> before = modules; - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, true, true, false, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, true, true, false, + outcome, true, true)); EXPECT_FALSE(outcome.demoted); EXPECT_TRUE(outcome.declineDetail.empty()) << outcome.declineDetail; EXPECT_EQ(modules, before); @@ -450,12 +443,11 @@ TEST_F(DemotePointSizeTest, AHostingDeviceStaysByteIdentical) { {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 4u); const Vector> before = modules; - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; // Both verdicts say the device hosts the built-in: the un-forced lane's contract. - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, false, false, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, false, false, true, + outcome, true, true)); EXPECT_FALSE(outcome.demoted); EXPECT_EQ(modules, before); EXPECT_TRUE(ShaderCompiler::ModuleDeclaresTessellationOrGeometryPointSize(modules[1])) @@ -466,14 +458,12 @@ TEST_F(DemotePointSizeTest, ACaptureRequestForcesTheCarrierOnANonWritingCaptureS // The control stage writes point size (arming the demotion); the evaluation stage never // does - but a by-name capture must still find the carrier declared there, holding // whatever an unwritten varying holds, exactly as the unwritten built-in would have. - Vector> modules = - CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, - {GL_TESS_CONTROL_SHADER, kTessControlSource}, - {GL_TESS_EVALUATION_SHADER, kPlainTessEvalSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}); + Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, + {GL_TESS_CONTROL_SHADER, kTessControlSource}, + {GL_TESS_EVALUATION_SHADER, kPlainTessEvalSource}, + {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 4u); - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( modules, types, true, true, /*captureRequestsPointSize=*/true, outcome, true, true)); @@ -552,8 +542,7 @@ void main() { fragColor = vec4(g_echo); } ASSERT_EQ(unrequested.size(), 3u); ShaderCompiler::PointSizeDemotionOutcome unrequestedOutcome; ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - unrequested, types, false, true, /*captureRequestsPointSize=*/false, unrequestedOutcome, true, - true)); + unrequested, types, false, true, /*captureRequestsPointSize=*/false, unrequestedOutcome, true, true)); EXPECT_TRUE(unrequestedOutcome.demoted) << unrequestedOutcome.declineDetail; EXPECT_EQ(Disassemble(unrequested[1]).find("OpName %mg_PointSizeCapture"), String::npos) << "with no capture asking for it, the carrier must not be declared"; @@ -592,18 +581,16 @@ TEST_F(DemotePointSizeTest, ARedeclaredControlBlockPrintsAnUnaccessedPointSizeMe // driver still rejects is strictly worse than the honest refusal, because it also flips the // program-wide verdict and the L1 key. TEST_F(DemotePointSizeTest, AControlStageCarryingClipDistanceDeclinesTheProgram) { - Vector> modules = - CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, - {GL_TESS_CONTROL_SHADER, kClipDistanceTessControlSource}, - {GL_TESS_EVALUATION_SHADER, kTessEvalSource}, - {GL_FRAGMENT_SHADER, kFragmentSource}}); + Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, + {GL_TESS_CONTROL_SHADER, kClipDistanceTessControlSource}, + {GL_TESS_EVALUATION_SHADER, kTessEvalSource}, + {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 4u); const Vector> before = modules; - const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, - GL_FRAGMENT_SHADER}; + const Vector types{GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, true, true, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, true, true, true, outcome, + true, true)); EXPECT_FALSE(outcome.demoted); EXPECT_NE(outcome.declineDetail.find("clip/cull"), String::npos) << outcome.declineDetail; EXPECT_EQ(modules, before) << "a decline must leave every module byte-identical"; @@ -625,16 +612,15 @@ void main() { gl_PointSize = gl_in[0].gl_PointSize + 1.0; } )"; - Vector> modules = - CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, - {GL_TESS_EVALUATION_SHADER, readingTessEval}, - {GL_FRAGMENT_SHADER, kFragmentSource}}); + Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, kVertexSource}, + {GL_TESS_EVALUATION_SHADER, readingTessEval}, + {GL_FRAGMENT_SHADER, kFragmentSource}}); ASSERT_EQ(modules.size(), 3u); const Vector> before = modules; const Vector types{GL_VERTEX_SHADER, GL_TESS_EVALUATION_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, true, true, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, true, true, true, outcome, + true, true)); EXPECT_FALSE(outcome.demoted); EXPECT_NE(outcome.declineDetail.find("control stage"), String::npos) << outcome.declineDetail; EXPECT_EQ(modules, before) << "a decline must leave every module byte-identical"; @@ -680,14 +666,13 @@ layout(location = 0) flat in i64vec4 g_wide; layout(location = 0) out vec4 fragColor; void main() { fragColor = vec4(float(g_wide.x)); } )"; - Vector> modules = CompileProgramToSpirv({{GL_VERTEX_SHADER, wideVertex}, - {GL_GEOMETRY_SHADER, wideGeometry}, - {GL_FRAGMENT_SHADER, wideFragment}}); + Vector> modules = CompileProgramToSpirv( + {{GL_VERTEX_SHADER, wideVertex}, {GL_GEOMETRY_SHADER, wideGeometry}, {GL_FRAGMENT_SHADER, wideFragment}}); ASSERT_EQ(modules.size(), 3u); const Vector types{GL_VERTEX_SHADER, GL_GEOMETRY_SHADER, GL_FRAGMENT_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, false, true, true, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, false, true, true, + outcome, true, true)); EXPECT_TRUE(outcome.demoted) << outcome.declineDetail; // v_wide / g_wide sit at location 0 and occupy 0 AND 1, so every carrier must clear 2. @@ -709,11 +694,10 @@ TEST_F(DemotePointSizeTest, AWholeStructCopyDeclinesTheProgramByteIdentically) { Vector> modules{module}; const Vector types{GL_TESS_EVALUATION_SHADER}; ShaderCompiler::PointSizeDemotionOutcome outcome; - ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - modules, types, true, true, false, outcome, true, true)); + ASSERT_TRUE(ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram(modules, types, true, true, false, + outcome, true, true)); EXPECT_FALSE(outcome.demoted); - EXPECT_FALSE(outcome.declineDetail.empty()) - << "a shape the pass cannot express must say so, not silently no-op"; + EXPECT_FALSE(outcome.declineDetail.empty()) << "a shape the pass cannot express must say so, not silently no-op"; EXPECT_EQ(modules[0], module) << "a decline must not leave a half-demoted module behind"; EXPECT_TRUE(ShaderCompiler::ModuleDeclaresTessellationOrGeometryPointSize(modules[0])) << "the declined module must still arm the existing honest refusals"; diff --git a/MobileGL/MG_Test/ShaderTranspiler/EmulateSubgroupsTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/EmulateSubgroupsTest.cpp index f008b50f..6cefb8a1 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/EmulateSubgroupsTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/EmulateSubgroupsTest.cpp @@ -68,8 +68,7 @@ namespace { ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) { if (opcode != spv::Op::OpCapability || wordCount < 2u) return; const auto capability = static_cast(words[1]); - if (capability >= spv::Capability::GroupNonUniform && - capability <= spv::Capability::GroupNonUniformQuad) { + if (capability >= spv::Capability::GroupNonUniform && capability <= spv::Capability::GroupNonUniformQuad) { ++count; } }); @@ -97,10 +96,10 @@ namespace { bool Validates(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); - tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& position, - const char* message) { - ADD_FAILURE() << "spirv-val at word " << position.index << ": " << message; - }); + tools.SetMessageConsumer( + [](spv_message_level_t, const char*, const spv_position_t& position, const char* message) { + ADD_FAILURE() << "spirv-val at word " << position.index << ": " << message; + }); return tools.Validate(spirv); } diff --git a/MobileGL/MG_Test/ShaderTranspiler/FixIterationRPSubgroupScratchTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FixIterationRPSubgroupScratchTest.cpp index d090d4b9..7a59bf88 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FixIterationRPSubgroupScratchTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FixIterationRPSubgroupScratchTest.cpp @@ -59,10 +59,10 @@ namespace { // The declared lengths of every Workgroup-storage array variable, sorted. std::vector WorkgroupArrayLengths(const Vector& spirv) { - std::map constantValues; // constant id -> value - std::map arrayLengthIds; // array type id -> length constant id - std::map pointerPointees; // pointer type id -> pointee type id - std::vector workgroupPointerTypes; // type ids of Workgroup variables + std::map constantValues; // constant id -> value + std::map arrayLengthIds; // array type id -> length constant id + std::map pointerPointees; // pointer type id -> pointee type id + std::vector workgroupPointerTypes; // type ids of Workgroup variables ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) { switch (opcode) { case spv::Op::OpConstant: @@ -72,14 +72,12 @@ namespace { if (wordCount >= 4u) arrayLengthIds[words[1]] = words[3]; break; case spv::Op::OpTypePointer: - if (wordCount >= 4u && - static_cast(words[2]) == spv::StorageClass::Workgroup) { + if (wordCount >= 4u && static_cast(words[2]) == spv::StorageClass::Workgroup) { pointerPointees[words[1]] = words[3]; } break; case spv::Op::OpVariable: - if (wordCount >= 4u && - static_cast(words[3]) == spv::StorageClass::Workgroup) { + if (wordCount >= 4u && static_cast(words[3]) == spv::StorageClass::Workgroup) { workgroupPointerTypes.push_back(words[1]); } break; @@ -102,10 +100,10 @@ namespace { bool Validates(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); - tools.SetMessageConsumer([](spv_message_level_t, const char*, const spv_position_t& position, - const char* message) { - ADD_FAILURE() << "spirv-val at word " << position.index << ": " << message; - }); + tools.SetMessageConsumer( + [](spv_message_level_t, const char*, const spv_position_t& position, const char* message) { + ADD_FAILURE() << "spirv-val at word " << position.index << ": " << message; + }); return tools.Validate(spirv); } @@ -282,16 +280,16 @@ TEST(FixIterationRPSubgroupScratchPass, LeavesPackWidthAssumptionsAloneOnWideDev ASSERT_FALSE(input.empty()); for (const Uint32 nativeSize : {16u, 32u, 64u, 128u}) { Vector output; - ASSERT_TRUE(ShaderCompiler::FixIterationRPSubgroupScratchForVulkan( - input, output, nativeSize, 32768u, true)); + ASSERT_TRUE( + ShaderCompiler::FixIterationRPSubgroupScratchForVulkan(input, output, nativeSize, 32768u, true)); EXPECT_EQ(output, input) << "native width " << nativeSize; } } } TEST(FixIterationRPSubgroupScratchPass, RefusesAModuleOutsideTheIdiom) { - for (const char* source : {kInvocationIndexedSource, kNonFloatScratchSource, - kSubgroupFreeSource, kMaskedSubgroupIndexSource}) { + for (const char* source : + {kInvocationIndexedSource, kNonFloatScratchSource, kSubgroupFreeSource, kMaskedSubgroupIndexSource}) { const Vector input = CompileCompute(source); ASSERT_FALSE(input.empty()); Vector output; @@ -309,8 +307,7 @@ TEST(FixIterationRPSubgroupScratchPass, LeavesEveryModuleAloneAtThePacksAssumedW ASSERT_FALSE(input.empty()); for (const Uint32 nativeSize : {16u, 32u, 64u}) { Vector output; - ASSERT_TRUE(ShaderCompiler::FixIterationRPSubgroupScratchForVulkan( - input, output, nativeSize, 32768u, true)); + ASSERT_TRUE(ShaderCompiler::FixIterationRPSubgroupScratchForVulkan(input, output, nativeSize, 32768u, true)); EXPECT_EQ(output, input) << "native width " << nativeSize; } } diff --git a/MobileGL/MG_Test/ShaderTranspiler/FlattenAtomicCounterBlockTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FlattenAtomicCounterBlockTest.cpp index 0ea30401..8a9a944d 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FlattenAtomicCounterBlockTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FlattenAtomicCounterBlockTest.cpp @@ -158,21 +158,21 @@ void main() { } )"; -// glslang emits constants in FIRST-USE order, so a shader that does not use the flattened -// array's length until after it has declared the counter block leaves that constant BELOW the -// block. The pass needs the length to build `uint[length]` immediately before the block (SPIR-V -// forbids forward type references), and it used to decline the whole block in that case - which -// left the offsets in place and made SPIRV-Cross refuse the stage outright: -// -// Push constant block cannot be expressed as neither std430 nor std140. -// -// That is KHR-GL43.compute_shader.pipeline-compute-chain: its first kernel declares two counters -// at offset 8 (so the flattened array is 4 elements) and first uses the value 4 after the block, -// so the kernel never reached the driver and every buffer, image and counter it writes kept its -// initial value. Here `i < 4u` is what puts `uint 4` below the block; the ordering assertion -// below is the fixture's own latch, so a future glslang that emits constants differently reports -// a stale fixture rather than silently testing nothing. -constexpr const char* kLateLengthConstantCounters = R"(#version 430 core + // glslang emits constants in FIRST-USE order, so a shader that does not use the flattened + // array's length until after it has declared the counter block leaves that constant BELOW the + // block. The pass needs the length to build `uint[length]` immediately before the block (SPIR-V + // forbids forward type references), and it used to decline the whole block in that case - which + // left the offsets in place and made SPIRV-Cross refuse the stage outright: + // + // Push constant block cannot be expressed as neither std430 nor std140. + // + // That is KHR-GL43.compute_shader.pipeline-compute-chain: its first kernel declares two counters + // at offset 8 (so the flattened array is 4 elements) and first uses the value 4 after the block, + // so the kernel never reached the driver and every buffer, image and counter it writes kept its + // initial value. Here `i < 4u` is what puts `uint 4` below the block; the ordering assertion + // below is the fixture's own latch, so a future glslang that emits constants differently reports + // a stale fixture rather than silently testing nothing. + constexpr const char* kLateLengthConstantCounters = R"(#version 430 core layout(local_size_x = 1) in; layout(binding = 1, offset = 8) uniform atomic_uint g_counter[2]; layout(std430, binding = 0) buffer Output { uint value[]; } g_out; @@ -182,27 +182,26 @@ void main() { } )"; -// Index of the first OpConstant of type uint with value |value|, and of struct |structId|, in -// the module's instruction order. -1 when absent. -std::pair UintConstantAndStructOrder(const Vector& spirv, Uint32 structId, - Uint32 value) { - Int64 index = 0, constantIndex = -1, structIndex = -1; - Uint32 uintTypeId = 0; - ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) { - if (opcode == spv::Op::OpTypeInt && wordCount >= 4u && words[2] == 32u && words[3] == 0u) { - uintTypeId = words[1]; - } - if (opcode == spv::Op::OpConstant && wordCount >= 4u && words[1] == uintTypeId && - words[3] == value && constantIndex < 0) { - constantIndex = index; - } - if (opcode == spv::Op::OpTypeStruct && wordCount >= 2u && words[1] == structId) { - structIndex = index; - } - ++index; - }); - return {constantIndex, structIndex}; -} + // Index of the first OpConstant of type uint with value |value|, and of struct |structId|, in + // the module's instruction order. -1 when absent. + std::pair UintConstantAndStructOrder(const Vector& spirv, Uint32 structId, Uint32 value) { + Int64 index = 0, constantIndex = -1, structIndex = -1; + Uint32 uintTypeId = 0; + ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) { + if (opcode == spv::Op::OpTypeInt && wordCount >= 4u && words[2] == 32u && words[3] == 0u) { + uintTypeId = words[1]; + } + if (opcode == spv::Op::OpConstant && wordCount >= 4u && words[1] == uintTypeId && words[3] == value && + constantIndex < 0) { + constantIndex = index; + } + if (opcode == spv::Op::OpTypeStruct && wordCount >= 2u && words[1] == structId) { + structIndex = index; + } + ++index; + }); + return {constantIndex, structIndex}; + } } // namespace @@ -296,8 +295,7 @@ TEST(FlattenAtomicCounterBlockPass, FlattensWhenTheLengthConstantIsDeclaredAfter EXPECT_TRUE(Validates(output)); // The symptom the CTS case actually failed on: with the block declined this throws. - MG_Util::ShaderTranspiler::SpvcSession session( - output, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); + MG_Util::ShaderTranspiler::SpvcSession session(output, MG_Util::ShaderTranspiler::SessionUsageBit::Transpile); auto essl = ShaderCompiler::DecompileShader(session); EXPECT_TRUE(essl) << "ESSL transpile failed: " << (essl ? String{} : essl.error().log); } diff --git a/MobileGL/MG_Test/ShaderTranspiler/FlattenFloat64StorageBlockTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FlattenFloat64StorageBlockTest.cpp index 8696c237..db05831e 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FlattenFloat64StorageBlockTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FlattenFloat64StorageBlockTest.cpp @@ -75,7 +75,8 @@ namespace { Vector members; ForEachInstruction(spirv, [&](Uint32 opcode, const Uint32* words, Uint32 wordCount) { if (opcode != kOpTypeStruct || wordCount < 2 || words[1] != structId) return; - for (Uint32 i = 2; i < wordCount; ++i) members.push_back(words[i]); + for (Uint32 i = 2; i < wordCount; ++i) + members.push_back(words[i]); }); return members; } @@ -88,7 +89,8 @@ namespace { byMember[words[2]] = words[4]; }); Vector offsets; - for (const auto& [member, offset] : byMember) offsets.push_back(offset); + for (const auto& [member, offset] : byMember) + offsets.push_back(offset); return offsets; } @@ -284,8 +286,7 @@ TEST_F(FlattenFloat64StorageBlockTest, AStorageBlockWithDoublesBecomesOneWordArr // Before: seven members, at the std140 offsets the standard requires WITH the doubles. const Uint32 inputStructId = StructIdNamed(input, "Wide"); ASSERT_NE(inputStructId, 0u) << Disassemble(input); - EXPECT_EQ(MemberOffsetsOf(input, inputStructId), - (Vector{0, 16, 64, 112, 128, 160, 192})) + EXPECT_EQ(MemberOffsetsOf(input, inputStructId), (Vector{0, 16, 64, 112, 128, 160, 192})) << Disassemble(input); const Vector output = Sanitize(input); @@ -294,8 +295,7 @@ TEST_F(FlattenFloat64StorageBlockTest, AStorageBlockWithDoublesBecomesOneWordArr const Uint32 structId = StructIdNamed(output, "Wide"); ASSERT_NE(structId, 0u) << Disassemble(output); const Vector members = MemberTypesOf(output, structId); - ASSERT_EQ(members.size(), 1u) << "the block should have collapsed to one member\n" - << Disassemble(output); + ASSERT_EQ(members.size(), 1u) << "the block should have collapsed to one member\n" << Disassemble(output); EXPECT_EQ(MemberOffsetsOf(output, structId), (Vector{0})); const auto [elementTypeId, length] = ArrayShapeOf(output, members[0]); @@ -336,8 +336,7 @@ void main() { const Uint32 structId = StructIdNamed(output, "Plain"); ASSERT_NE(structId, 0u) << Disassemble(output); EXPECT_EQ(MemberTypesOf(output, structId).size(), 4u) << Disassemble(output); - EXPECT_EQ(MemberOffsetsOf(output, structId), (Vector{0, 16, 64, 112})) - << Disassemble(output); + EXPECT_EQ(MemberOffsetsOf(output, structId), (Vector{0, 16, 64, 112})) << Disassemble(output); } // A plain UNIFORM block is deliberately NOT flattened, however many doubles it holds: the @@ -366,9 +365,8 @@ void main() { const Uint32 structId = StructIdNamed(output, "Params"); ASSERT_NE(structId, 0u) << Disassemble(output); - EXPECT_EQ(MemberTypesOf(output, structId).size(), 3u) - << "a uniform block must not be flattened\n" - << Disassemble(output); + EXPECT_EQ(MemberTypesOf(output, structId).size(), 3u) << "a uniform block must not be flattened\n" + << Disassemble(output); // The demotion's re-derived std140 layout for `int, float, int`, which is what the frontend // reflects and what glUniform*d then writes into. EXPECT_EQ(MemberOffsetsOf(output, structId), (Vector{0, 4, 8})) << Disassemble(output); @@ -689,8 +687,7 @@ void main() { const Vector input = CompileToSpirv(GL_COMPUTE_SHADER, source); ASSERT_FALSE(input.empty()); // The premise: glslang really did mark the member row-major. - EXPECT_EQ(CountLinesWith(Disassemble(input), "OpMemberDecorate %Data 0 RowMajor", ""), 1u) - << Disassemble(input); + EXPECT_EQ(CountLinesWith(Disassemble(input), "OpMemberDecorate %Data 0 RowMajor", ""), 1u) << Disassemble(input); const Vector output = Sanitize(input); ASSERT_FALSE(output.empty()); @@ -703,9 +700,7 @@ void main() { EXPECT_NE(DecorationValueOf(output, members[0], kDecorationArrayStride), 4u) << "a row-major matrix element must not have been flattened into words\n" << text; - EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) - << "nothing should have been re-addressed\n" - << text; + EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) << "nothing should have been re-addressed\n" << text; } TEST_F(FlattenFloat64StorageBlockTest, AColumnMajorMatrixRuntimeArrayStepsByItsColumnStride) { @@ -805,15 +800,11 @@ TEST_F(FlattenFloat64StorageBlockTest, TheNativePathLeavesAnOpenEndedBlockAndIts ASSERT_NE(structId, 0u) << text; const Vector members = MemberTypesOf(output, structId); ASSERT_EQ(members.size(), 1u) << text; - EXPECT_NE(RuntimeArrayElementOf(output, members[0]), 0u) - << "the member should still be a runtime array\n" - << text; + EXPECT_NE(RuntimeArrayElementOf(output, members[0]), 0u) << "the member should still be a runtime array\n" << text; EXPECT_EQ(DecorationValueOf(output, members[0], kDecorationArrayStride), 8u) << "the array must keep the 8-byte stride the application bound\n" << text; - EXPECT_GT(CountFloatTypesOfWidth(output, 64), 0u) - << "nothing narrows here, so the doubles must survive\n" - << text; + EXPECT_GT(CountFloatTypesOfWidth(output, 64), 0u) << "nothing narrows here, so the doubles must survive\n" << text; } // The other backend prints the flattened module through SPIRV-Cross: an open-ended `uint[]` @@ -980,9 +971,7 @@ TEST_F(FlattenFloat64StorageBlockTest, ARuntimeArrayThatIsNotTheBlocksLastMember ASSERT_NE(structId, 0u) << text; EXPECT_EQ(MemberTypesOf(output, structId).size(), 2u) << text; EXPECT_EQ(CountFloatTypesOfWidth(output, 64), 0u) << text; - EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) - << "nothing should have been re-addressed\n" - << text; + EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) << "nothing should have been re-addressed\n" << text; } } @@ -1036,8 +1025,7 @@ void main() { ASSERT_NE(inputStructId, 0u); const Uint32 inputUintId = Uint32TypeIdOf(input); // The premise: the module's uint really is declared after the block (or not at all). - ASSERT_TRUE(inputUintId == 0 || - DeclarationIndexOf(input, inputUintId) > DeclarationIndexOf(input, inputStructId)) + ASSERT_TRUE(inputUintId == 0 || DeclarationIndexOf(input, inputUintId) > DeclarationIndexOf(input, inputStructId)) << "this shader was meant to declare the block before any uint\n" << Disassemble(input); @@ -1074,8 +1062,7 @@ void main() { const Uint32 inputStructId = StructIdNamed(input, "Wide"); ASSERT_NE(inputStructId, 0u); const Uint32 inputUintId = Uint32TypeIdOf(input); - ASSERT_TRUE(inputUintId == 0 || - DeclarationIndexOf(input, inputUintId) > DeclarationIndexOf(input, inputStructId)) + ASSERT_TRUE(inputUintId == 0 || DeclarationIndexOf(input, inputUintId) > DeclarationIndexOf(input, inputStructId)) << "this shader was meant to declare the block before any uint\n" << Disassemble(input); @@ -1087,7 +1074,5 @@ void main() { EXPECT_EQ(MemberTypesOf(output, structId).size(), 2u) << "a bounded block in this position must keep the decline it shipped with\n" << text; - EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) - << "nothing should have been re-addressed\n" - << text; + EXPECT_EQ(CountLinesWith(text, "OpIMul %uint", ""), 0u) << "nothing should have been re-addressed\n" << text; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp index 1688d3f1..c18fb193 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FlattenXfbInterfaceBlocksTest.cpp @@ -148,9 +148,8 @@ TEST_F(FlattenXfbInterfaceBlocksTest, TheEmittedDeclarationIsAPlainArrayNotABloc const String after = Transpile(output); EXPECT_NE(after.find("StageData_attrib[16]"), String::npos) << after; - EXPECT_EQ(after.find("out StageData"), String::npos) - << "the block must not still be declared as an output block:\n" - << after; + EXPECT_EQ(after.find("out StageData"), String::npos) << "the block must not still be declared as an output block:\n" + << after; } // GL 4.6 core 11.1.2.1: consecutive members take consecutive locations, and a member takes as @@ -182,8 +181,7 @@ TEST_F(FlattenXfbInterfaceBlocksTest, LeavesABlockNoCaptureNamesAlone) { std::set flattened; Vector output; - ASSERT_TRUE( - ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output, true)); + ASSERT_TRUE(ShaderCompiler::FlattenXfbInterfaceBlocksForEssl(input, {"SomeOtherBlock"}, flattened, output, true)); EXPECT_TRUE(flattened.empty()); const String after = Transpile(output); @@ -209,16 +207,14 @@ TEST_F(FlattenXfbInterfaceBlocksTest, DeclinesAnEmptyRequestWithoutRewriting) { // does a name with no block prefix at all (gl_Position, a plain varying). TEST_F(FlattenXfbInterfaceBlocksTest, RewritesOnlyTheCaptureNamesOfFlattenedBlocks) { String rewritten; - EXPECT_TRUE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("StageData.attrib[0]", {"StageData"}, - rewritten)); + EXPECT_TRUE( + ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("StageData.attrib[0]", {"StageData"}, rewritten)); EXPECT_EQ(rewritten, "StageData_attrib[0]"); - EXPECT_TRUE( - ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("StageData.attrib", {"StageData"}, rewritten)); + EXPECT_TRUE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("StageData.attrib", {"StageData"}, rewritten)); EXPECT_EQ(rewritten, "StageData_attrib"); - EXPECT_FALSE( - ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("Other.member", {"StageData"}, rewritten)); + EXPECT_FALSE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("Other.member", {"StageData"}, rewritten)); EXPECT_FALSE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("gl_Position", {"StageData"}, rewritten)); EXPECT_FALSE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock("vColor", {"StageData"}, rewritten)); EXPECT_FALSE(ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock(".leading", {"StageData"}, rewritten)); diff --git a/MobileGL/MG_Test/ShaderTranspiler/FragCoordYFlipTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FragCoordYFlipTest.cpp index 9fc7b291..8fb98cab 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FragCoordYFlipTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FragCoordYFlipTest.cpp @@ -29,8 +29,8 @@ namespace { using namespace MG_Util::ShaderTranspiler; Vector> shaders; Vector types; - for (const auto& [stage, source] : Vector>{{GL_VERTEX_SHADER, vertex}, - {GL_FRAGMENT_SHADER, fragment}}) { + for (const auto& [stage, source] : + Vector>{{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}) { ShaderAttrib attrib{.shaderType = stage, .sourceStr = source}; auto shader = ShaderCompiler::CompileShader(attrib); EXPECT_TRUE(shader) << (shader ? String{} : shader.error().log); @@ -63,8 +63,9 @@ namespace { String ValidationError(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String error; - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { error += message != nullptr ? message : ""; }); + tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + error += message != nullptr ? message : ""; + }); return tools.Validate(spirv) ? String{} : (error.empty() ? String{"invalid"} : error); } @@ -125,8 +126,7 @@ TEST(ClipControlTransformFlags, OriginTogglesTheFlipAndDepthModeDropsTheRemap) { EXPECT_EQ(apply(fbo, GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE), fbo); EXPECT_EQ(apply(window, GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE), window); EXPECT_EQ(apply(fbo, GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE), fbo | static_cast(Bit::PositionYFlip)); - EXPECT_EQ(apply(window, GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE), - window & ~static_cast(Bit::PositionYFlip)); + EXPECT_EQ(apply(window, GL_UPPER_LEFT, GL_NEGATIVE_ONE_TO_ONE), window & ~static_cast(Bit::PositionYFlip)); EXPECT_EQ(apply(fbo, GL_LOWER_LEFT, GL_ZERO_TO_ONE), 0u); EXPECT_EQ(apply(window, GL_UPPER_LEFT, GL_ZERO_TO_ONE), static_cast(Bit::FragCoordYFlip) | static_cast(Bit::SurfaceRotate90)); diff --git a/MobileGL/MG_Test/ShaderTranspiler/FullCoverageSampleMaskTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/FullCoverageSampleMaskTest.cpp index b4a16d97..27c668d9 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/FullCoverageSampleMaskTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/FullCoverageSampleMaskTest.cpp @@ -64,14 +64,16 @@ void main() { gl_Position = vec4(position, 0.0, 1.0); } String ValidationError(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String error; - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { error += message != nullptr ? message : ""; }); + tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + error += message != nullptr ? message : ""; + }); return tools.Validate(spirv) ? String{} : (error.empty() ? String{"invalid"} : error); } SizeT CountOccurrences(const String& text, const String& needle) { SizeT count = 0; - for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) ++count; + for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) + ++count; return count; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/GlslangCaptureTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/GlslangCaptureTest.cpp index 6b2b1935..0b7a4261 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/GlslangCaptureTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/GlslangCaptureTest.cpp @@ -204,8 +204,7 @@ void main() { EXPECT_EQ(probed->at("probeSampler").binding, 3u); ASSERT_TRUE(probed->contains("probeImage")); - EXPECT_TRUE(probed->at("probeImage").hasBinding) - << "images take the same path as samplers (both are EbtSampler)"; + EXPECT_TRUE(probed->at("probeImage").hasBinding) << "images take the same path as samplers (both are EbtSampler)"; EXPECT_EQ(probed->at("probeImage").binding, 5u); // The default-block uniform is gone from the entity list entirely - it was swept into @@ -566,8 +565,7 @@ void main() { // A UNIFORM block is a different binding space with its own glUniformBlockBinding path, so it // must not reach the storage-block seeder - it has a capture set of its own (see // UnqualifiedUniformBlocksAreCapturedSeparatelyFromStorageBlocks below). - EXPECT_EQ(capture.storageBlocksWithoutBinding.count("InputBuffer"), 0u) - << "uniform blocks belong to the other set"; + EXPECT_EQ(capture.storageBlocksWithoutBinding.count("InputBuffer"), 0u) << "uniform blocks belong to the other set"; } // The uniform-block half of the same capture, and the reason it exists: glslang packs uniform @@ -598,8 +596,7 @@ void main() { << "a declared binding must never be defaulted away"; EXPECT_EQ(capture.uniformBlocksWithoutBinding.count("OutputBuffer"), 0u) << "storage blocks belong to the other set"; - EXPECT_EQ(capture.storageBlocksWithoutBinding.count("GOKU"), 0u) - << "the two sets must not cross-contaminate"; + EXPECT_EQ(capture.storageBlocksWithoutBinding.count("GOKU"), 0u) << "the two sets must not cross-contaminate"; // The negative control every capture case here carries: with the OUT pointer left null the // resolver must write nothing at all. @@ -630,8 +627,7 @@ void main() { noBinding.c = afterLayout.a + beforeLayout.b + uint(texelFetch(tex << "a qualifier may precede the layout list too"; EXPECT_EQ(capture.storageBlocksWithoutBinding.count("NoBindingAtAll"), 1u) << "a memory-qualified block with no binding is still an unqualified block"; - EXPECT_EQ(capture.storageBlocksWithoutBinding.count("texelSampler"), 0u) - << "a samplerBuffer is not a buffer block"; + EXPECT_EQ(capture.storageBlocksWithoutBinding.count("texelSampler"), 0u) << "a samplerBuffer is not a buffer block"; } // THE REGRESSION THIS MIGRATION EXISTS FOR (7de7cfc6, the original capture now replayed as @@ -722,8 +718,7 @@ out vec4 colour; void main() { colour = vec4(float(sharedInstance.u + fragmentBound.f)); } )"; - const LinkCapture capture = - CaptureFromLink({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); + const LinkCapture capture = CaptureFromLink({{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}); ASSERT_TRUE(capture.linked) << capture.log; EXPECT_EQ(capture.storageBlocksWithoutBinding.count("SharedBlock"), 1u); @@ -770,8 +765,7 @@ TEST_F(GlslangCaptureProbeTest, AtomicCounterOffsetRulesAreRaisedByTheParse) { // offset spelled as a macro, are now both judged. The scanner declined both - it read // unexpanded text and only understood integer literals. EXPECT_FALSE(compiles("const int kCount = 4;\nlayout(binding = 0, offset = " + - std::to_string(MAX_ATOMIC_COUNTER_BUFFER_SIZE - 8) + - ") uniform atomic_uint c[kCount];\n")); + std::to_string(MAX_ATOMIC_COUNTER_BUFFER_SIZE - 8) + ") uniform atomic_uint c[kCount];\n")); EXPECT_FALSE(compiles("#define BAD_OFFSET 2\nlayout(binding = 0, offset = BAD_OFFSET) uniform atomic_uint c;\n")); // The counterweight: `offset` as an ordinary identifier is not a layout qualifier, and an diff --git a/MobileGL/MG_Test/ShaderTranspiler/LegalizeResourceArrayIndexTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/LegalizeResourceArrayIndexTest.cpp index b60dc689..b08693ac 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/LegalizeResourceArrayIndexTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/LegalizeResourceArrayIndexTest.cpp @@ -79,19 +79,18 @@ namespace { // at an array-of-storage-blocks variable carries a non-constant FIRST index, which is // exactly what the Qualcomm ES compiler refuses. bool HasDynamicBlockArrayIndex(const Vector& spirv) { - std::set blockStructs; // OpTypeStruct ids decorated Block / BufferBlock - std::set constants; // OpConstant / OpConstantNull result ids - std::set blockArrayTypes; // OpTypeArray ids whose element is such a struct - std::set blockArrayPointers;// OpTypePointer ids pointing at one of those arrays - std::set blockArrayVars; // OpVariable ids of one of those pointer types + std::set blockStructs; // OpTypeStruct ids decorated Block / BufferBlock + std::set constants; // OpConstant / OpConstantNull result ids + std::set blockArrayTypes; // OpTypeArray ids whose element is such a struct + std::set blockArrayPointers; // OpTypePointer ids pointing at one of those arrays + std::set blockArrayVars; // OpVariable ids of one of those pointer types ForEachInstruction(spirv, [&](spv::Op opcode, const Uint32* words, Uint32 wordCount) { switch (opcode) { case spv::Op::OpDecorate: if (wordCount >= 3u) { const auto decoration = static_cast(words[2]); - if (decoration == spv::Decoration::Block || - decoration == spv::Decoration::BufferBlock) { + if (decoration == spv::Decoration::Block || decoration == spv::Decoration::BufferBlock) { blockStructs.insert(words[1]); } } diff --git a/MobileGL/MG_Test/ShaderTranspiler/PixelCenterIntegerTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/PixelCenterIntegerTest.cpp index c778895a..feb375d9 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/PixelCenterIntegerTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/PixelCenterIntegerTest.cpp @@ -31,8 +31,8 @@ layout(location = 0) in vec2 position; void main() { gl_Position = vec4(position, 0.0, 1.0); } )"; Vector> shaders; - for (const auto& [stage, source] : Vector>{{GL_VERTEX_SHADER, kVertex}, - {GL_FRAGMENT_SHADER, fragment}}) { + for (const auto& [stage, source] : + Vector>{{GL_VERTEX_SHADER, kVertex}, {GL_FRAGMENT_SHADER, fragment}}) { ShaderAttrib attrib{.shaderType = stage, .sourceStr = source}; auto shader = ShaderCompiler::CompileShader(attrib); EXPECT_TRUE(shader) << (shader ? String{} : shader.error().log); @@ -63,14 +63,16 @@ void main() { gl_Position = vec4(position, 0.0, 1.0); } String ValidationError(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String error; - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { error += message != nullptr ? message : ""; }); + tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + error += message != nullptr ? message : ""; + }); return tools.Validate(spirv) ? String{} : (error.empty() ? String{"invalid"} : error); } SizeT CountOccurrences(const String& text, const String& needle) { SizeT count = 0; - for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) ++count; + for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) + ++count; return count; } } // namespace @@ -94,8 +96,7 @@ void main() { color = texelFetch(data, ivec2(gl_FragCoord.xy), 0); } const String text = Disassemble(output); EXPECT_EQ(text.find("PixelCenterInteger"), String::npos) << text; EXPECT_EQ(CountOccurrences(text, "OpFSub"), 1u) << text; - EXPECT_NE(text.find("OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0 %float_0"), String::npos) - << text; + EXPECT_NE(text.find("OpConstantComposite %v4float %float_0_5 %float_0_5 %float_0 %float_0"), String::npos) << text; } // Single components arrive through an access chain: x and y move, z and w keep their values. diff --git a/MobileGL/MG_Test/ShaderTranspiler/RelaxedOpaqueStructTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/RelaxedOpaqueStructTest.cpp index 7bfae2b5..f431ffce 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/RelaxedOpaqueStructTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/RelaxedOpaqueStructTest.cpp @@ -32,8 +32,8 @@ namespace { using namespace MG_Util::ShaderTranspiler; Vector> shaders; Vector types; - for (const auto& [stage, source] : Vector>{{GL_VERTEX_SHADER, vertex}, - {GL_FRAGMENT_SHADER, fragment}}) { + for (const auto& [stage, source] : + Vector>{{GL_VERTEX_SHADER, vertex}, {GL_FRAGMENT_SHADER, fragment}}) { ShaderAttrib attrib{.shaderType = stage, .sourceStr = source}; auto shader = ShaderCompiler::CompileShader(attrib); EXPECT_TRUE(shader) << (shader ? String{} : shader.error().log); @@ -60,8 +60,9 @@ namespace { String VulkanValidationError(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String error; - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { error += message != nullptr ? message : ""; }); + tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + error += message != nullptr ? message : ""; + }); if (tools.Validate(spirv)) return {}; String text; tools.Disassemble(spirv, &text); @@ -74,7 +75,8 @@ namespace { TEST(RelaxedOpaqueStructTest, AnAnonymousUniformStructLosesItsSamplerInTheDefaultBlock) { MobileGL::Initialize(); - const auto modules = CompileProgram(kVertex, + const auto modules = CompileProgram( + kVertex, "#version 330 core\n" "uniform struct { vec3 size; float time; int loaded; sampler2D smp; } c0, c1, c2, c3;\n" "uniform vec3 res; out vec4 o;\n" @@ -85,11 +87,11 @@ TEST(RelaxedOpaqueStructTest, AnAnonymousUniformStructLosesItsSamplerInTheDefaul TEST(RelaxedOpaqueStructTest, ANamedUniformStructLosesItsSamplerInTheDefaultBlock) { MobileGL::Initialize(); - const auto modules = CompileProgram(kVertex, - "#version 330 core\n" - "struct Channel { vec3 size; float time; int loaded; sampler2D smp; };\n" - "uniform Channel c0; uniform Channel c1; uniform vec3 res; out vec4 o;\n" - "void main() { o = texture(c1.smp, gl_FragCoord.xy / res.xy) + vec4(c0.size, c0.time); }"); + const auto modules = CompileProgram( + kVertex, "#version 330 core\n" + "struct Channel { vec3 size; float time; int loaded; sampler2D smp; };\n" + "uniform Channel c0; uniform Channel c1; uniform vec3 res; out vec4 o;\n" + "void main() { o = texture(c1.smp, gl_FragCoord.xy / res.xy) + vec4(c0.size, c0.time); }"); ASSERT_EQ(modules.size(), 2u); EXPECT_EQ(VulkanValidationError(modules[1]), String{}); } diff --git a/MobileGL/MG_Test/ShaderTranspiler/ShadingRateEsslTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/ShadingRateEsslTest.cpp index a2489502..f84e6961 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/ShadingRateEsslTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/ShadingRateEsslTest.cpp @@ -94,7 +94,7 @@ namespace { "}\n"; } // namespace -class ShadingRateEsslTest: public ::testing::Test { +class ShadingRateEsslTest : public ::testing::Test { protected: void SetUp() override { MobileGL::Initialize(); } }; diff --git a/MobileGL/MG_Test/ShaderTranspiler/SpirvPassTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/SpirvPassTest.cpp index 87c16ed2..75246315 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/SpirvPassTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/SpirvPassTest.cpp @@ -24,71 +24,56 @@ namespace { // layout(location = 0) in vec4 inPos; // void main() { gl_Position = inPos; } constexpr Uint32 kPlainVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u, - 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, - 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, - 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, - 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, - 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, - 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u, - 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu, - 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, - 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, - 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu, - 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, - 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, - 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, - 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, - 0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, - 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, - 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, - 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, - 0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, - 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, - 0x00000001u, 0x00040020u, 0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u, - 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, - 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u, - 0x00000014u, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x00000014u, 0x00000012u, - 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0007000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u, + 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x0000000bu, + 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, + 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, + 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, + 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, + 0x0000000du, 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu, + 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00050048u, 0x0000000bu, + 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, + 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, + 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, + 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, + 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, + 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, + 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, + 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu, 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, + 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, 0x00040020u, + 0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, + 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, 0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u, + 0x00000014u, 0x0000000du, 0x0000000fu, 0x0003003eu, 0x00000014u, 0x00000012u, 0x000100fdu, 0x00010038u, }; // ... plus `invariant gl_Position;` - already carries OpMemberDecorate %gl_PerVertex 0 // Invariant, so the pass must not add a duplicate. constexpr Uint32 kAlreadyInvariantVertexSpirv[] = { - 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u, - 0x00000001u, 0x0006000bu, 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, - 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, 0x0007000fu, 0x00000000u, - 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u, - 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, - 0x00060005u, 0x0000000bu, 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, - 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, 0x7469736fu, 0x006e6f69u, - 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, - 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, - 0x61747369u, 0x0065636eu, 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, - 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, 0x0000000du, 0x00000000u, - 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu, - 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, - 0x00040048u, 0x0000000bu, 0x00000000u, 0x00000012u, 0x00050048u, 0x0000000bu, - 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, 0x0000000bu, 0x00000002u, - 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, - 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, 0x00000000u, 0x00020013u, - 0x00000002u, 0x00030021u, 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, - 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, 0x00000004u, 0x00040015u, - 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, - 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, - 0x0000000bu, 0x00000007u, 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, - 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, 0x0000000cu, 0x0000000du, - 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu, - 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, 0x00000001u, - 0x00000007u, 0x0004003bu, 0x00000010u, 0x00000011u, 0x00000001u, 0x00040020u, - 0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u, 0x00000002u, 0x00000004u, - 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, - 0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u, 0x00000014u, 0x0000000du, - 0x0000000fu, 0x0003003eu, 0x00000014u, 0x00000012u, 0x000100fdu, 0x00010038u, + 0x07230203u, 0x00010000u, 0x0008000bu, 0x00000015u, 0x00000000u, 0x00020011u, 0x00000001u, 0x0006000bu, + 0x00000001u, 0x4c534c47u, 0x6474732eu, 0x3035342eu, 0x00000000u, 0x0003000eu, 0x00000000u, 0x00000001u, + 0x0007000fu, 0x00000000u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x0000000du, 0x00000011u, 0x00030003u, + 0x00000002u, 0x000001c2u, 0x00040005u, 0x00000004u, 0x6e69616du, 0x00000000u, 0x00060005u, 0x0000000bu, + 0x505f6c67u, 0x65567265u, 0x78657472u, 0x00000000u, 0x00060006u, 0x0000000bu, 0x00000000u, 0x505f6c67u, + 0x7469736fu, 0x006e6f69u, 0x00070006u, 0x0000000bu, 0x00000001u, 0x505f6c67u, 0x746e696fu, 0x657a6953u, + 0x00000000u, 0x00070006u, 0x0000000bu, 0x00000002u, 0x435f6c67u, 0x4470696cu, 0x61747369u, 0x0065636eu, + 0x00070006u, 0x0000000bu, 0x00000003u, 0x435f6c67u, 0x446c6c75u, 0x61747369u, 0x0065636eu, 0x00030005u, + 0x0000000du, 0x00000000u, 0x00040005u, 0x00000011u, 0x6f506e69u, 0x00000073u, 0x00030047u, 0x0000000bu, + 0x00000002u, 0x00050048u, 0x0000000bu, 0x00000000u, 0x0000000bu, 0x00000000u, 0x00040048u, 0x0000000bu, + 0x00000000u, 0x00000012u, 0x00050048u, 0x0000000bu, 0x00000001u, 0x0000000bu, 0x00000001u, 0x00050048u, + 0x0000000bu, 0x00000002u, 0x0000000bu, 0x00000003u, 0x00050048u, 0x0000000bu, 0x00000003u, 0x0000000bu, + 0x00000004u, 0x00040047u, 0x00000011u, 0x0000001eu, 0x00000000u, 0x00020013u, 0x00000002u, 0x00030021u, + 0x00000003u, 0x00000002u, 0x00030016u, 0x00000006u, 0x00000020u, 0x00040017u, 0x00000007u, 0x00000006u, + 0x00000004u, 0x00040015u, 0x00000008u, 0x00000020u, 0x00000000u, 0x0004002bu, 0x00000008u, 0x00000009u, + 0x00000001u, 0x0004001cu, 0x0000000au, 0x00000006u, 0x00000009u, 0x0006001eu, 0x0000000bu, 0x00000007u, + 0x00000006u, 0x0000000au, 0x0000000au, 0x00040020u, 0x0000000cu, 0x00000003u, 0x0000000bu, 0x0004003bu, + 0x0000000cu, 0x0000000du, 0x00000003u, 0x00040015u, 0x0000000eu, 0x00000020u, 0x00000001u, 0x0004002bu, + 0x0000000eu, 0x0000000fu, 0x00000000u, 0x00040020u, 0x00000010u, 0x00000001u, 0x00000007u, 0x0004003bu, + 0x00000010u, 0x00000011u, 0x00000001u, 0x00040020u, 0x00000013u, 0x00000003u, 0x00000007u, 0x00050036u, + 0x00000002u, 0x00000004u, 0x00000000u, 0x00000003u, 0x000200f8u, 0x00000005u, 0x0004003du, 0x00000007u, + 0x00000012u, 0x00000011u, 0x00050041u, 0x00000013u, 0x00000014u, 0x0000000du, 0x0000000fu, 0x0003003eu, + 0x00000014u, 0x00000012u, 0x000100fdu, 0x00010038u, }; // OpMemberDecorate diff --git a/MobileGL/MG_Test/ShaderTranspiler/StripIoBlockLocationsTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/StripIoBlockLocationsTest.cpp index 6b45b452..078064af 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/StripIoBlockLocationsTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/StripIoBlockLocationsTest.cpp @@ -172,9 +172,8 @@ TEST_F(StripIoBlockLocationsTest, DropsTheQualifierFromBothBlocksAndLeavesVaryin // fragment stage's inputs and a vertex stage's attributes are matched by them. EXPECT_NE(after.find("in vec4 tcs_tes_result"), String::npos) << after; EXPECT_NE(after.find("out vec4 tes_gs_result"), String::npos) << after; - EXPECT_EQ(CountOf(after, "layout(location"), 2u) - << "exactly the two plain varyings should still be located:\n" - << after; + EXPECT_EQ(CountOf(after, "layout(location"), 2u) << "exactly the two plain varyings should still be located:\n" + << after; } TEST_F(StripIoBlockLocationsTest, StripsOnlyTheDirectionTheCallerArmed) { @@ -199,8 +198,7 @@ TEST_F(StripIoBlockLocationsTest, StripsOnlyTheDirectionTheCallerArmed) { // And the mirror image, for a program that BEGINS at this stage. bool strippedOutputOnly = false; Vector outputOnly; - ASSERT_TRUE( - ShaderCompiler::StripIoBlockLocationsForEssl(input, false, true, strippedOutputOnly, outputOnly, true)); + ASSERT_TRUE(ShaderCompiler::StripIoBlockLocationsForEssl(input, false, true, strippedOutputOnly, outputOnly, true)); ASSERT_FALSE(outputOnly.empty()); EXPECT_TRUE(strippedOutputOnly); const String afterOutputOnly = Transpile(outputOnly); diff --git a/MobileGL/MG_Test/ShaderTranspiler/TranslationCacheTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/TranslationCacheTest.cpp index fa298676..f06e9bb2 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/TranslationCacheTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/TranslationCacheTest.cpp @@ -48,8 +48,7 @@ namespace { // AsyncSpirvPhaseTest's AsyncModeScope uses for its own toggle. class CacheModeScope { public: - explicit CacheModeScope(const Bool enabled) - : m_saved(MG_Config::Features.ShaderTranslationCache) { + explicit CacheModeScope(const Bool enabled) : m_saved(MG_Config::Features.ShaderTranslationCache) { MG_Config::Features.ShaderTranslationCache = enabled ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff; } @@ -86,7 +85,9 @@ namespace { return MakeTranslationCacheKey(builder); } - SizeT PayloadBytes(const TestPayload& payload) { return payload.text.size(); } + SizeT PayloadBytes(const TestPayload& payload) { + return payload.text.size(); + } // ---- L1 fixtures ---- const char* kVertexSource = R"(#version 460 @@ -112,11 +113,15 @@ void main() { String SwizzleLikeFragment(const String& basicType) { return "#version 460\n" "in vec3 vPos;\n" - "layout(location = 0) out " + basicType + "vec4 fragColor;\n" + "layout(location = 0) out " + + basicType + + "vec4 fragColor;\n" "uniform sampler2D uTex;\n" "void main() {\n" " vec4 s = texture(uTex, vPos.xy);\n" - " fragColor = " + basicType + "vec4(s);\n" + " fragColor = " + + basicType + + "vec4(s);\n" "}\n"; } @@ -148,7 +153,8 @@ void main() { Uint64 DigestOf(const Vector& words) { Uint64 hash = 1469598103934665603ull; - for (const Uint32 word : words) hash = (hash ^ static_cast(word)) * 1099511628211ull; + for (const Uint32 word : words) + hash = (hash ^ static_cast(word)) * 1099511628211ull; return hash; } @@ -156,7 +162,8 @@ void main() { Vector digest; const auto& object = MG_State::pGLContext->GetProgramObject(program); if (!object) return digest; - for (const auto& module : object->GetGeneratedSpirv()) digest.push_back(DigestOf(module)); + for (const auto& module : object->GetGeneratedSpirv()) + digest.push_back(DigestOf(module)); return digest; } @@ -403,8 +410,7 @@ TEST_F(TranslationCacheTest, TextAppendsCannotRunIntoEachOther) { TEST_F(TranslationCacheTest, L1AndL2KeysNeverAlias) { const Vector spirv{1u, 2u, 3u}; const TranslationCacheKey l2 = BuildEsslTranslationKey(BaselineEsslInputs(spirv)); - const TranslationCacheKey l1 = - BuildSpirvTranslationKey(BaselineSpirvInputs({{GL_FRAGMENT_SHADER, "source"}})); + const TranslationCacheKey l1 = BuildSpirvTranslationKey(BaselineSpirvInputs({{GL_FRAGMENT_SHADER, "source"}})); EXPECT_FALSE(l1 == l2); } @@ -415,8 +421,7 @@ TEST_F(TranslationCacheTest, L1AndL2KeysNeverAlias) { TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) { const String vs = kVertexSource; const String fs = kFragmentSource; - const Vector baseStages{ - {GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, fs}}; + const Vector baseStages{{GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, fs}}; const UnorderedMap attribs{{"aPos", 3u}}; const UnorderedMap fragOut{{"fragColor", 1u}}; @@ -433,44 +438,41 @@ TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) { Vector> variants; - { // the environment fingerprint (glslang resource limits, backend identity, + { // the environment fingerprint (glslang resource limits, backend identity, // advertised extension set, compute limits) SpirvTranslationKeyInputs v = base; v.frontendFingerprint ^= 1ull; variants.emplace_back("frontendFingerprint", BuildSpirvTranslationKey(v)); } - { // a stage's source text + { // a stage's source text const String otherFs = SwizzleLikeFragment("i"); - SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_VERTEX_SHADER, vs}, - {GL_FRAGMENT_SHADER, otherFs}}); + SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, otherFs}}); variants.emplace_back("stage source", BuildSpirvTranslationKey(v)); } - { // a stage's TYPE, with the text unchanged - SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_VERTEX_SHADER, vs}, - {GL_COMPUTE_SHADER, fs}}); + { // a stage's TYPE, with the text unchanged + SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_VERTEX_SHADER, vs}, {GL_COMPUTE_SHADER, fs}}); variants.emplace_back("stage type", BuildSpirvTranslationKey(v)); } - { // the SET of stages - mapIO resolves a fragment stage's Locations against the + { // the SET of stages - mapIO resolves a fragment stage's Locations against the // vertex stage's outputs, which is why this key is per PROGRAM and not per stage SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_FRAGMENT_SHADER, fs}}); variants.emplace_back("stage set", BuildSpirvTranslationKey(v)); } - { // stage ORDER - SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_FRAGMENT_SHADER, fs}, - {GL_VERTEX_SHADER, vs}}); + { // stage ORDER + SpirvTranslationKeyInputs v = BaselineSpirvInputs({{GL_FRAGMENT_SHADER, fs}, {GL_VERTEX_SHADER, vs}}); variants.emplace_back("stage order", BuildSpirvTranslationKey(v)); } - { // glBindAttribLocation + { // glBindAttribLocation SpirvTranslationKeyInputs v = base; v.explicitVertexInLocations = &attribs; variants.emplace_back("explicitVertexInLocations", BuildSpirvTranslationKey(v)); } - { // glBindFragDataLocation + { // glBindFragDataLocation SpirvTranslationKeyInputs v = base; v.explicitFragmentOutLocations = &fragOut; variants.emplace_back("explicitFragmentOutLocations", BuildSpirvTranslationKey(v)); } - { // glBindFragDataLocationIndexed + { // glBindFragDataLocationIndexed SpirvTranslationKeyInputs v = base; v.explicitFragmentOutIndices = &fragIndex; variants.emplace_back("explicitFragmentOutIndices", BuildSpirvTranslationKey(v)); @@ -480,18 +482,18 @@ TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) { // pure function of the stage sources this key already carries in full. It was dropped from // SpirvTranslationKeyInputs with the glslang-capture migration; kKeyLayoutVersion moved to // 4 so no blob written under the old shape can be honoured. - { // ShaderCompileBits (0 on both production parse paths; keyed so a future value + { // ShaderCompileBits (0 on both production parse paths; keyed so a future value // cannot alias a module parsed without it) SpirvTranslationKeyInputs v = base; v.shaderCompileFlags = 1u; variants.emplace_back("shaderCompileFlags", BuildSpirvTranslationKey(v)); } - { // MOBILEGL_ENABLE_SPIRV_VALIDATION + { // MOBILEGL_ENABLE_SPIRV_VALIDATION SpirvTranslationKeyInputs v = base; v.enableSpirvValidation = true; variants.emplace_back("enableSpirvValidation", BuildSpirvTranslationKey(v)); } - { // CompileEnv::ConsumesFloat64Natively(): the fp64 tail of SanitizeAndOptimizeBinary is + { // CompileEnv::ConsumesFloat64Natively(): the fp64 tail of SanitizeAndOptimizeBinary is // skipped under it, so the SAME GLSL yields modules with real doubles under one answer // and demoted, storage-block-flattened ones under the other. The one backend capability // bit in this key, and the only one allowed in without changing what glslang produces. @@ -499,14 +501,14 @@ TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) { v.nativeFloat64 = true; variants.emplace_back("nativeFloat64", BuildSpirvTranslationKey(v)); } - { // CompileEnv::DemotesTessellationPointSize(): phase B rewrites the cached modules + { // CompileEnv::DemotesTessellationPointSize(): phase B rewrites the cached modules // under it (the point-size demotion), so one key shape would describe two module // sets - built-in kept vs carried as a varying with the capability stripped. SpirvTranslationKeyInputs v = base; v.demoteTessellationPointSize = true; variants.emplace_back("demoteTessellationPointSize", BuildSpirvTranslationKey(v)); } - { // ... and its geometry twin, keyed separately because the ES loader really does + { // ... and its geometry twin, keyed separately because the ES loader really does // probe the two extension families independently. SpirvTranslationKeyInputs v = base; v.demoteGeometryPointSize = true; @@ -516,22 +518,22 @@ TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) { // They cannot move a word of the generated SPIR-V, but they do shape the reflection the // payload now carries, so they have to split the key. This is the group that would go // stale first if the payload ever grew again without the key following it. - { // glTransformFeedbackVaryings: shapes xfbVaryings / xfbStrides / gsStripTriangles + { // glTransformFeedbackVaryings: shapes xfbVaryings / xfbStrides / gsStripTriangles SpirvTranslationKeyInputs v = base; v.requestedXfbVaryings = &xfbVaryings; variants.emplace_back("requestedXfbVaryings", BuildSpirvTranslationKey(v)); } - { // ... and its ORDER, which gl_NextBuffer / gl_SkipComponentsN make load-bearing + { // ... and its ORDER, which gl_NextBuffer / gl_SkipComponentsN make load-bearing SpirvTranslationKeyInputs v = base; v.requestedXfbVaryings = &xfbVaryingsReordered; variants.emplace_back("requestedXfbVaryings order", BuildSpirvTranslationKey(v)); } - { // GL_INTERLEAVED_ATTRIBS vs GL_SEPARATE_ATTRIBS + { // GL_INTERLEAVED_ATTRIBS vs GL_SEPARATE_ATTRIBS SpirvTranslationKeyInputs v = base; v.xfbBufferMode = GL_SEPARATE_ATTRIBS; variants.emplace_back("xfbBufferMode", BuildSpirvTranslationKey(v)); } - { // GL_MAX_DRAW_BUFFERS: decides whether the link is REJECTED at all + { // GL_MAX_DRAW_BUFFERS: decides whether the link is REJECTED at all SpirvTranslationKeyInputs v = base; v.maxFragmentOutputColorNumber = 4; variants.emplace_back("maxFragmentOutputColorNumber", BuildSpirvTranslationKey(v)); @@ -614,8 +616,7 @@ TEST_F(TranslationCacheTest, TwoBackendsCompilingTheSameGlslShareOneL1Entry) { const auto [a, b] = BackendOnlyDifferentEnvs(); const String vs = kVertexSource; const String fs = kFragmentSource; - const Vector stages{{GL_VERTEX_SHADER, vs}, - {GL_FRAGMENT_SHADER, fs}}; + const Vector stages{{GL_VERTEX_SHADER, vs}, {GL_FRAGMENT_SHADER, fs}}; SpirvTranslationKeyInputs onA = BaselineSpirvInputs(stages); onA.frontendFingerprint = ComputeFrontendCompileEnvFingerprint(a); @@ -632,9 +633,9 @@ TEST_F(TranslationCacheTest, TwoBackendsCompilingTheSameGlslShareOneL1Entry) { // payload is the module AFTER SanitizeAndOptimizeBinary and the fp64 tail of that chain is // exactly what this bit gates. TEST_F(TranslationCacheTest, NativeFloat64IsOutOfTheFrontendFingerprintAndInsideTheL1Key) { - CompileEnv none; // no backend at all - CompileEnv emulated; // a backend without the feature - CompileEnv nativeEnv; // a backend with it + CompileEnv none; // no backend at all + CompileEnv emulated; // a backend without the feature + CompileEnv nativeEnv; // a backend with it emulated.backend = BackendType::DirectVulkan; nativeEnv.backend = BackendType::DirectVulkan; nativeEnv.params.SupportsShaderFloat64 = true; @@ -670,9 +671,9 @@ TEST_F(TranslationCacheTest, NativeFloat64IsOutOfTheFrontendFingerprintAndInside // accessor direction is pinned too, because it is INVERTED relative to the params field // and a swap of the arms would disable the device repair with every rendering test green. TEST_F(TranslationCacheTest, PointSizeDemotionBitsAreOutOfTheFrontendFingerprintAndInsideTheL1Key) { - CompileEnv none; // no backend at all: never demote, standalone compiles stay standard - CompileEnv hosting; // a backend that hosts the built-in - CompileEnv demoting; // a backend that cannot + CompileEnv none; // no backend at all: never demote, standalone compiles stay standard + CompileEnv hosting; // a backend that hosts the built-in + CompileEnv demoting; // a backend that cannot hosting.backend = BackendType::DirectVulkan; demoting.backend = BackendType::DirectVulkan; demoting.params.SupportsTessellationPointSize = false; @@ -721,8 +722,7 @@ TEST_F(TranslationCacheTest, TheFrontendFingerprintMovesWithEveryFrontendLimit) {"params.MaxDrawBuffers", [](CompileEnv& e) { e.params.MaxDrawBuffers += 1; }}, {"params.MaxVertexImageUniforms", [](CompileEnv& e) { e.params.MaxVertexImageUniforms += 1; }}, {"params.MaxTessControlImageUniforms", [](CompileEnv& e) { e.params.MaxTessControlImageUniforms += 1; }}, - {"params.MaxTessEvaluationImageUniforms", - [](CompileEnv& e) { e.params.MaxTessEvaluationImageUniforms += 1; }}, + {"params.MaxTessEvaluationImageUniforms", [](CompileEnv& e) { e.params.MaxTessEvaluationImageUniforms += 1; }}, {"params.MaxGeometryImageUniforms", [](CompileEnv& e) { e.params.MaxGeometryImageUniforms += 1; }}, {"params.MaxFragmentImageUniforms", [](CompileEnv& e) { e.params.MaxFragmentImageUniforms += 1; }}, {"params.MaxComputeImageUniforms", [](CompileEnv& e) { e.params.MaxComputeImageUniforms += 1; }}, @@ -735,8 +735,7 @@ TEST_F(TranslationCacheTest, TheFrontendFingerprintMovesWithEveryFrontendLimit) // compiles to different SPIR-V under two different values, so each must split the // key - one case per COMPONENT, because a per-axis difference is exactly the shape // real drivers produce (z = 64 on ES against 1024 elsewhere). - {"params.MaxComputeTextureImageUnits", - [](CompileEnv& e) { e.params.MaxComputeTextureImageUnits += 1; }}, + {"params.MaxComputeTextureImageUnits", [](CompileEnv& e) { e.params.MaxComputeTextureImageUnits += 1; }}, // wave4's 4fc3531d: glslang rejects gl_ClipDistance[i] past this at parse AND expands // gl_MaxClipDistances from it, so it is both a compile gate and a baked constant. {"params.MaxClipDistances", [](CompileEnv& e) { e.params.MaxClipDistances += 1; }}, @@ -751,8 +750,7 @@ TEST_F(TranslationCacheTest, TheFrontendFingerprintMovesWithEveryFrontendLimit) [](CompileEnv& e) { e.params.MaxCombinedClipAndCullDistances += 1; }}, {"params.MaxTextureImageUnits", [](CompileEnv& e) { e.params.MaxTextureImageUnits += 1; }}, {"params.MaxVertexTextureImageUnits", [](CompileEnv& e) { e.params.MaxVertexTextureImageUnits += 1; }}, - {"params.MaxCombinedTextureImageUnits", - [](CompileEnv& e) { e.params.MaxCombinedTextureImageUnits += 1; }}, + {"params.MaxCombinedTextureImageUnits", [](CompileEnv& e) { e.params.MaxCombinedTextureImageUnits += 1; }}, {"params.MaxSamples", [](CompileEnv& e) { e.params.MaxSamples += 1; }}, {"maxComputeWorkGroupSize[0]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[0] += 1; }}, {"maxComputeWorkGroupSize[1]", [](CompileEnv& e) { e.maxComputeWorkGroupSize[1] += 1; }}, @@ -881,8 +879,7 @@ TEST_F(TranslationCacheTest, AProgramServedFromTheMemoStillAnswersGetFragDataLoc const Int memoLocation = MG_Impl::GLImpl::GetFragDataLocation(fromMemo, "fragColor"); EXPECT_EQ(parsedLocation, 0) << "the parsed program's own answer moved; this case is testing " "the wrong thing"; - EXPECT_EQ(memoLocation, parsedLocation) - << "a program served from the L1 memo lost its fragment output location"; + EXPECT_EQ(memoLocation, parsedLocation) << "a program served from the L1 memo lost its fragment output location"; // A name that is not an output must still be -1 from both, so the case cannot pass by // making the accessor answer everything. @@ -929,7 +926,7 @@ TEST_F(TranslationCacheTest, TheEscapeHatchDisablesL1Entirely) { const SyncCompileScope sync; const String fs = SwizzleLikeFragment(""); - { // prime the cache with the switch ON, so a later hit would be available + { // prime the cache with the switch ON, so a later hit would be available const CacheModeScope cacheOn(true); (void)LinkProgramFromSources(kVertexSource, fs); } @@ -966,60 +963,60 @@ TEST_F(TranslationCacheTest, L2KeyMovesWithEveryGateThatSteersTheEsslChain) { Vector> variants; - { // the module itself + { // the module itself EsslTranslationKeyInputs v = base; v.spirv = &otherSpirv; variants.emplace_back("spirv", BuildEsslTranslationKey(v)); } - { // stage: gates LowerDrawParametersForEssl and SplitArrayVertexInputsForEssl (vertex) + { // stage: gates LowerDrawParametersForEssl and SplitArrayVertexInputsForEssl (vertex) // and LegalizeFragmentOutputIndexingForEssl (fragment) EsslTranslationKeyInputs v = base; v.shaderType = GL_VERTEX_SHADER; variants.emplace_back("shaderType", BuildEsslTranslationKey(v)); } - { // arms LowerViewportIndexForEssl + { // arms LowerViewportIndexForEssl EsslTranslationKeyInputs v = base; v.viewportIndexLoweringArmed = true; variants.emplace_back("viewportIndexLoweringArmed", BuildEsslTranslationKey(v)); } - { // arms EmulateSubgroupsPass on the ESSL path + { // arms EmulateSubgroupsPass on the ESSL path EsslTranslationKeyInputs v = base; v.subgroupEmulationArmed = true; variants.emplace_back("subgroupEmulationArmed", BuildEsslTranslationKey(v)); } - { // the emulation's scratch budget decides whether it declines + { // the emulation's scratch budget decides whether it declines EsslTranslationKeyInputs v = base; v.subgroupEmulationScratchBytes = 32768; variants.emplace_back("subgroupEmulationScratchBytes", BuildEsslTranslationKey(v)); } - { // arms FixIterationRPBarrierPass on the ESSL path + { // arms FixIterationRPBarrierPass on the ESSL path EsslTranslationKeyInputs v = base; v.iterationRPBarrierFixArmed = true; variants.emplace_back("iterationRPBarrierFixArmed", BuildEsslTranslationKey(v)); } - { // the subgroup emulation reads its scratch through atomic loads + { // the subgroup emulation reads its scratch through atomic loads EsslTranslationKeyInputs v = base; v.subgroupScratchAtomicReads = true; variants.emplace_back("subgroupScratchAtomicReads", BuildEsslTranslationKey(v)); } - { // arms DeferGeometryEmitsForEssl + { // arms DeferGeometryEmitsForEssl EsslTranslationKeyInputs v = base; v.deferGeometryEmitsArmed = true; variants.emplace_back("deferGeometryEmitsArmed", BuildEsslTranslationKey(v)); } - { // arms EmulateNoPerspectiveForEssl + { // arms EmulateNoPerspectiveForEssl EsslTranslationKeyInputs v = base; v.supportsNoperspectiveInterpolation = true; variants.emplace_back("supportsNoperspectiveInterpolation", BuildEsslTranslationKey(v)); } - { // arms WidenImageFormatsForEssl - a driver WITH GL_NV_image_formats keeps the declared + { // arms WidenImageFormatsForEssl - a driver WITH GL_NV_image_formats keeps the declared // rg32f/r8ui/... image formats, one without has them re-declared in a core carrier and // every access to them masked, so the two get materially different ESSL from one module. EsslTranslationKeyInputs v = base; v.supportsExtendedImageFormats = true; variants.emplace_back("supportsExtendedImageFormats", BuildEsslTranslationKey(v)); } - { // arms AND parameterizes ClampMultisampleFetchesForEssl + { // arms AND parameterizes ClampMultisampleFetchesForEssl EsslTranslationKeyInputs v = base; v.maxColorTextureSamples = 1; variants.emplace_back("maxColorTextureSamples", BuildEsslTranslationKey(v)); @@ -1034,38 +1031,38 @@ TEST_F(TranslationCacheTest, L2KeyMovesWithEveryGateThatSteersTheEsslChain) { v.maxDepthTextureSamples = 1; variants.emplace_back("maxDepthTextureSamples", BuildEsslTranslationKey(v)); } - { // the ceiling the three above are compared against + { // the ceiling the three above are compared against EsslTranslationKeyInputs v = base; v.advertisedMaxSamples = 8; variants.emplace_back("advertisedMaxSamples", BuildEsslTranslationKey(v)); } - { // the argument to FlattenXfbInterfaceBlocksForEssl + { // the argument to FlattenXfbInterfaceBlocksForEssl EsslTranslationKeyInputs v = base; v.xfbCaptureBlockNames = &xfbBlocks; variants.emplace_back("xfbCaptureBlockNames", BuildEsslTranslationKey(v)); } - { // the argument to BakeImageFormatsForEssl - live glBindImageTexture state + { // the argument to BakeImageFormatsForEssl - live glBindImageTexture state EsslTranslationKeyInputs v = base; v.glFormatByUniformName = &imageFormats; variants.emplace_back("glFormatByUniformName", BuildEsslTranslationKey(v)); } - { // SpvcSession::SetShaderStorageBlockBinding + { // SpvcSession::SetShaderStorageBlockBinding EsslTranslationKeyInputs v = base; v.storageBlockBindingOverrides = &storageBindings; variants.emplace_back("storageBlockBindingOverrides", BuildEsslTranslationKey(v)); } - { // SPVC_COMPILER_OPTION_GLSL_VERSION (ResolveBackendEsslVersion) + { // SPVC_COMPILER_OPTION_GLSL_VERSION (ResolveBackendEsslVersion) EsslTranslationKeyInputs v = base; v.esslVersion = 300; variants.emplace_back("esslVersion", BuildEsslTranslationKey(v)); } - { // SpvcSession::SetAtomicCounterBlockBindings - printed into the layout(binding=) + { // SpvcSession::SetAtomicCounterBlockBindings - printed into the layout(binding=) // qualifier of every synthesized counter block, so it changes the emitted text. EsslTranslationKeyInputs v = base; v.atomicCounterEsslBindingTop = 6; variants.emplace_back("atomicCounterEsslBindingTop", BuildEsslTranslationKey(v)); } - { // the two arguments to UniquifyIoBlockNamesForEssl. Separate cases, because a stage + { // the two arguments to UniquifyIoBlockNamesForEssl. Separate cases, because a stage // that CONSUMES a block renames it after the previous stage while one that PRODUCES // it renames after itself - so the same block name legitimately maps to different // spellings in the two maps, and a key that folded them together would let a @@ -1079,12 +1076,12 @@ TEST_F(TranslationCacheTest, L2KeyMovesWithEveryGateThatSteersTheEsslChain) { v.outputBlockRenames = &ioBlockRenames; variants.emplace_back("outputBlockRenames", BuildEsslTranslationKey(v)); } - { // ...and a DIFFERENT target spelling for the same block name must not share either. + { // ...and a DIFFERENT target spelling for the same block name must not share either. EsslTranslationKeyInputs v = base; v.outputBlockRenames = &otherIoBlockRenames; variants.emplace_back("outputBlockRenames(other target)", BuildEsslTranslationKey(v)); } - { // the two arguments to StripIoBlockLocationsForEssl, and separate cases for the same + { // the two arguments to StripIoBlockLocationsForEssl, and separate cases for the same // reason the rename maps are: a stage strips the blocks it CONSUMES only when the // producer is in this program and the ones it PRODUCES only when the consumer is, so // the two directions are independently armed and a key that folded them together @@ -1218,7 +1215,8 @@ TEST_F(TranslationCacheTest, ConcurrentLookupsOverOverlappingKeysAgreeWithTheSer } }); } - for (std::thread& thread : threads) thread.join(); + for (std::thread& thread : threads) + thread.join(); EXPECT_EQ(mismatches.load(), 0) << "a worker was handed a payload belonging to another key"; EXPECT_EQ(nulls.load(), 0); @@ -1259,7 +1257,8 @@ TEST_F(TranslationCacheTest, ConcurrentLookupsStaySafeWhileEvictionChurns) { } }); } - for (std::thread& thread : threads) thread.join(); + for (std::thread& thread : threads) + thread.join(); EXPECT_EQ(mismatches.load(), 0); EXPECT_LE(cache.EntryCount(), 4u); @@ -1274,7 +1273,8 @@ TEST_F(TranslationCacheTest, ConcurrentLinksOfSharedSourcesProduceIdenticalSpirv constexpr Int kProgramsPerVariant = 8; Vector sources; - for (Int v = 0; v < kVariants; ++v) sources.push_back(SwizzleLikeFragment(v == 0 ? "" : (v == 1 ? "i" : "u"))); + for (Int v = 0; v < kVariants; ++v) + sources.push_back(SwizzleLikeFragment(v == 0 ? "" : (v == 1 ? "i" : "u"))); Vector programs; Vector variantOf; @@ -1328,9 +1328,13 @@ namespace { return "#version 460\n" "in vec3 vPos;\n" "layout(location = 0) out vec4 fragColor;\n" - "uniform vec3 uTint_" + tag + ";\n" + "uniform vec3 uTint_" + + tag + + ";\n" "void main() {\n" - " fragColor = vec4(uTint_" + tag + " * vPos, 1.0);\n" + " fragColor = vec4(uTint_" + + tag + + " * vPos, 1.0);\n" "}\n"; } @@ -1360,14 +1364,11 @@ TEST_F(TranslationCacheTest, TheParseVerdictKeyMovesWithEveryInput) { const TranslationCacheKey baseline = BuildShaderParseVerdictKey(BaselineParseVerdictInputs(source)); const Vector>> mutations{ - {"frontendFingerprint", - [](ShaderParseVerdictKeyInputs& i, const String&) { i.frontendFingerprint ^= 1ull; }}, - {"shaderType", - [](ShaderParseVerdictKeyInputs& i, const String&) { i.shaderType = GL_VERTEX_SHADER; }}, + {"frontendFingerprint", [](ShaderParseVerdictKeyInputs& i, const String&) { i.frontendFingerprint ^= 1ull; }}, + {"shaderType", [](ShaderParseVerdictKeyInputs& i, const String&) { i.shaderType = GL_VERTEX_SHADER; }}, {"preprocessedSource", [](ShaderParseVerdictKeyInputs& i, const String& other) { i.preprocessedSource = other; }}, - {"shaderCompileFlags", - [](ShaderParseVerdictKeyInputs& i, const String&) { i.shaderCompileFlags = 1u; }}, + {"shaderCompileFlags", [](ShaderParseVerdictKeyInputs& i, const String&) { i.shaderCompileFlags = 1u; }}, }; Vector seen{baseline}; diff --git a/MobileGL/MG_Test/ShaderTranspiler/UniquifyIoBlockNamesTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/UniquifyIoBlockNamesTest.cpp index 67cb654e..60993a4f 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/UniquifyIoBlockNamesTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/UniquifyIoBlockNamesTest.cpp @@ -165,8 +165,7 @@ TEST_F(UniquifyIoBlockNamesTest, ProbeIgnoresAStageWhoseBlocksAlreadyHaveDistinc } TEST_F(UniquifyIoBlockNamesTest, ProbeNeverReportsTheBuiltinBlock) { - const Vector input = - CompileToSpirv(GL_TESS_EVALUATION_SHADER, kBuiltinBlockOnlyTessEvalSource); + const Vector input = CompileToSpirv(GL_TESS_EVALUATION_SHADER, kBuiltinBlockOnlyTessEvalSource); ASSERT_FALSE(input.empty()); std::set colliding; @@ -196,17 +195,15 @@ TEST_F(UniquifyIoBlockNamesTest, RenamesTheTwoBlocksApartInTheEmittedEssl) { std::set renamed; Vector output; - ASSERT_TRUE(ShaderCompiler::UniquifyIoBlockNamesForEssl(input, inputRenames, outputRenames, renamed, - output, true)); + ASSERT_TRUE(ShaderCompiler::UniquifyIoBlockNamesForEssl(input, inputRenames, outputRenames, renamed, output, true)); ASSERT_FALSE(output.empty()); EXPECT_EQ(renamed, (std::set{"TCSOutputBlock"})); const String dis = Disassemble(output); spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); ASSERT_TRUE(tools.Validate(output)) << dis; - EXPECT_EQ(dis.find("\"TCSOutputBlock\""), String::npos) - << "the colliding name is still on a block struct:\n" - << dis; + EXPECT_EQ(dis.find("\"TCSOutputBlock\""), String::npos) << "the colliding name is still on a block struct:\n" + << dis; EXPECT_NE(dis.find("\"TCSOutputBlock_mgio1\""), String::npos) << dis; EXPECT_NE(dis.find("\"TCSOutputBlock_mgio2\""), String::npos) << dis; @@ -231,8 +228,7 @@ TEST_F(UniquifyIoBlockNamesTest, RenamesOnlyTheDirectionTheCallerPlanned) { std::set renamed; Vector output; - ASSERT_TRUE( - ShaderCompiler::UniquifyIoBlockNamesForEssl(input, inputRenames, {}, renamed, output, true)); + ASSERT_TRUE(ShaderCompiler::UniquifyIoBlockNamesForEssl(input, inputRenames, {}, renamed, output, true)); ASSERT_FALSE(output.empty()); EXPECT_EQ(renamed, (std::set{"TCSOutputBlock"})); diff --git a/MobileGL/MG_Test/ShaderTranspiler/WidenImageFormatsTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/WidenImageFormatsTest.cpp index 468be941..c83ee059 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/WidenImageFormatsTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/WidenImageFormatsTest.cpp @@ -67,8 +67,7 @@ namespace { EXPECT_TRUE(programResult) << (programResult ? String{} : programResult.error().log); if (!programResult) return {}; - ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, - .program = *programResult.value()}; + ProgramBinaryAttrib binaryAttrib{.shaderTypes = {GL_FRAGMENT_SHADER}, .program = *programResult.value()}; auto binaryResult = ShaderCompiler::GetSpirvBinaryFromProgram(binaryAttrib); EXPECT_TRUE(binaryResult) << (binaryResult ? String{} : binaryResult.error().log); if (!binaryResult || binaryResult->empty()) return {}; @@ -396,26 +395,22 @@ TEST(WidenImageFormats, TwentySixNonCoreFormatsHaveALosslessCoreCarrier) { {0x8F98, 0x8D76, 1, "GL_R16_SNORM -> GL_RGBA16UI"}, }; for (const Case& testCase : cases) { - EXPECT_EQ(ShaderCompiler::WidenedCoreEsslImageFormat(testCase.requested), testCase.carrier) - << testCase.name; - EXPECT_EQ(ShaderCompiler::ImageFormatChannelCount(testCase.requested), testCase.channels) - << testCase.name; + EXPECT_EQ(ShaderCompiler::WidenedCoreEsslImageFormat(testCase.requested), testCase.carrier) << testCase.name; + EXPECT_EQ(ShaderCompiler::ImageFormatChannelCount(testCase.requested), testCase.channels) << testCase.name; // Every carrier is one of the thirteen ES has in core, or the widening would have moved // the problem rather than solved it - and every carrier has four channels, or the mask // selectors would address components that are not there. - EXPECT_TRUE(ShaderCompiler::GLInternalFormatIsCoreEsslImageFormat(testCase.carrier)) - << testCase.name; + EXPECT_TRUE(ShaderCompiler::GLInternalFormatIsCoreEsslImageFormat(testCase.carrier)) << testCase.name; EXPECT_EQ(ShaderCompiler::ImageFormatChannelCount(testCase.carrier), 4u) << testCase.name; } } TEST(WidenImageFormats, CoreFormatsAreRefused) { // The thirteen GLSL ES already has: nothing to carry. - for (const Uint coreFormat : {0x8814u /*RGBA32F*/, 0x881Au /*RGBA16F*/, 0x822Eu /*R32F*/, - 0x8058u /*RGBA8*/, 0x8F97u /*RGBA8_SNORM*/, 0x8D82u /*RGBA32I*/, - 0x8D88u /*RGBA16I*/, 0x8D8Eu /*RGBA8I*/, 0x8235u /*R32I*/, - 0x8D70u /*RGBA32UI*/, 0x8D76u /*RGBA16UI*/, 0x8D7Cu /*RGBA8UI*/, - 0x8236u /*R32UI*/}) { + for (const Uint coreFormat : + {0x8814u /*RGBA32F*/, 0x881Au /*RGBA16F*/, 0x822Eu /*R32F*/, 0x8058u /*RGBA8*/, 0x8F97u /*RGBA8_SNORM*/, + 0x8D82u /*RGBA32I*/, 0x8D88u /*RGBA16I*/, 0x8D8Eu /*RGBA8I*/, 0x8235u /*R32I*/, 0x8D70u /*RGBA32UI*/, + 0x8D76u /*RGBA16UI*/, 0x8D7Cu /*RGBA8UI*/, 0x8236u /*R32UI*/}) { EXPECT_EQ(ShaderCompiler::WidenedCoreEsslImageFormat(coreFormat), 0u) << "core format 0x" << std::hex << coreFormat; } @@ -454,8 +449,7 @@ TEST(WidenImageFormats, OnlyTheNormalizedFormatsCarryCodesAndTheirDenominatorsAr EXPECT_TRUE(ShaderCompiler::NormalizedImageCarrierCodes(testCase.format, channelMax, isSigned)) << testCase.name; for (Uint channel = 0; channel < 4; ++channel) { - EXPECT_EQ(channelMax[channel], testCase.channelMax[channel]) - << testCase.name << " channel " << channel; + EXPECT_EQ(channelMax[channel], testCase.channelMax[channel]) << testCase.name << " channel " << channel; } EXPECT_EQ(isSigned, testCase.isSigned) << testCase.name; } @@ -463,9 +457,9 @@ TEST(WidenImageFormats, OnlyTheNormalizedFormatsCarryCodesAndTheirDenominatorsAr // Everything else keeps its own component type in the carrier, so nothing is converted: an // rg8's carrier channel really is an 8-bit unsigned normalized one, and an rgb10_a2ui's // channel really does hold the integer the shader stored. - for (const Uint direct : {0x8230u /*RG32F*/, 0x8229u /*R8*/, 0x8F94u /*R8_SNORM*/, - 0x8232u /*R8UI*/, 0x8C3Au /*R11F_G11F_B10F*/, 0x906Fu /*RGB10_A2UI*/, - 0x8814u /*RGBA32F*/, 0x8051u /*RGB8, not an image format*/}) { + for (const Uint direct : + {0x8230u /*RG32F*/, 0x8229u /*R8*/, 0x8F94u /*R8_SNORM*/, 0x8232u /*R8UI*/, 0x8C3Au /*R11F_G11F_B10F*/, + 0x906Fu /*RGB10_A2UI*/, 0x8814u /*RGBA32F*/, 0x8051u /*RGB8, not an image format*/}) { Uint32 channelMax[4] = {7u, 7u, 7u, 7u}; bool isSigned = true; EXPECT_FALSE(ShaderCompiler::NormalizedImageCarrierCodes(direct, channelMax, isSigned)) @@ -516,8 +510,7 @@ TEST(WidenImageFormats, TwoChannelFloatImageBecomesRgba32fWithBothAccessesMasked const VectorShuffle* loadMask = FindShuffleOver(shuffles, readIds.front()); ASSERT_NE(loadMask, nullptr) << "the imageLoad result is consumed unmasked"; EXPECT_TRUE(HasComponents(*loadMask, {0u, 1u, 6u, 7u})); - EXPECT_NE(loadMask->resultId, readIds.front()) - << "the mask must be a separate value, or it would feed itself"; + EXPECT_NE(loadMask->resultId, readIds.front()) << "the mask must be a separate value, or it would feed itself"; } // The three-channel case, which no format exercised before r11f_g11f_b10f was carried: only ALPHA @@ -600,10 +593,9 @@ TEST(WidenImageFormats, PackedFloatImageOnlyReachesEsslThroughTheCarrier) { ASSERT_FALSE(spirv.empty()); const EsslAttempt before = EmitEssl(spirv); - EXPECT_FALSE(before.succeeded) - << "SPIRV-Cross printed r11f_g11f_b10f for an ES target; the widening's premise has " - "changed:\n" - << before.text; + EXPECT_FALSE(before.succeeded) << "SPIRV-Cross printed r11f_g11f_b10f for an ES target; the widening's premise has " + "changed:\n" + << before.text; Vector widened; ASSERT_TRUE(ShaderCompiler::WidenImageFormatsForEssl(spirv, widened, /*onlyFormatsSpirvCrossRefusesToPrint=*/false, @@ -752,8 +744,7 @@ TEST(WidenImageFormats, OnlyTheThirtyTwoBitTwoChannelFormatsSplitAsBufferImages) {0x823C, 0x8236, "GL_RG32UI -> GL_R32UI"}, }; for (const Case& testCase : cases) { - EXPECT_EQ(ShaderCompiler::SplitCoreEsslBufferImageFormat(testCase.format), testCase.base) - << testCase.name; + EXPECT_EQ(ShaderCompiler::SplitCoreEsslBufferImageFormat(testCase.format), testCase.base) << testCase.name; EXPECT_TRUE(ShaderCompiler::GLInternalFormatIsCoreEsslImageFormat(testCase.base)) << testCase.name; EXPECT_EQ(ShaderCompiler::ImageFormatChannelCount(testCase.base), 1u) << testCase.name; } @@ -762,8 +753,7 @@ TEST(WidenImageFormats, OnlyTheThirtyTwoBitTwoChannelFormatsSplitAsBufferImages) 0x8F95u /*RG8_SNORM*/, 0x822Cu /*RG16*/, 0x8237u /*RG8I*/, 0x8238u /*RG8UI*/, // Already core, or four-channel, or not an image format at all. 0x8814u /*RGBA32F*/, 0x822Eu /*R32F*/, 0x8051u /*RGB8*/, 0u}) { - EXPECT_EQ(ShaderCompiler::SplitCoreEsslBufferImageFormat(refused), 0u) - << "format 0x" << std::hex << refused; + EXPECT_EQ(ShaderCompiler::SplitCoreEsslBufferImageFormat(refused), 0u) << "format 0x" << std::hex << refused; } } @@ -856,7 +846,7 @@ TEST(WidenImageFormats, TheExtensionNarrowsTheWideningToWhatSpirvCrossWillNotPri ASSERT_TRUE(ShaderCompiler::SpirvCrossCanPrintEsslImageFormat(0x8230 /*GL_RG32F*/)); ASSERT_FALSE(ShaderCompiler::SpirvCrossCanPrintEsslImageFormat(0x8232 /*GL_R8UI*/)); - { // rg32f: printable, so the narrow mode leaves it exactly as declared. + { // rg32f: printable, so the narrow mode leaves it exactly as declared. const Vector spirv = CompileFragment(kRg32fLoadStore); ASSERT_FALSE(spirv.empty()); EXPECT_TRUE(ShaderCompiler::DeclaresWidenableImageFormat(spirv, false)); @@ -872,15 +862,15 @@ TEST(WidenImageFormats, TheExtensionNarrowsTheWideningToWhatSpirvCrossWillNotPri EXPECT_EQ(CollectVectorShuffles(widened).size(), CollectVectorShuffles(spirv).size()); } } - { // r8ui: unprintable, so the narrow mode still carries it - and must mask it exactly as + { // r8ui: unprintable, so the narrow mode still carries it - and must mask it exactly as // the wide mode does, because the storage and the bind widen with it either way. const Vector spirv = CompileFragment(kR8uiLoadStore); ASSERT_FALSE(spirv.empty()); EXPECT_TRUE(ShaderCompiler::DeclaresWidenableImageFormat(spirv, true)); Vector widened; - ASSERT_TRUE(ShaderCompiler::WidenImageFormatsForEssl( - spirv, widened, /*onlyFormatsSpirvCrossRefusesToPrint=*/true, true)); + ASSERT_TRUE(ShaderCompiler::WidenImageFormatsForEssl(spirv, widened, + /*onlyFormatsSpirvCrossRefusesToPrint=*/true, true)); ASSERT_FALSE(widened.empty()); EXPECT_TRUE(Validates(widened)); const auto types = CollectStorageImageTypes(widened); @@ -904,7 +894,7 @@ TEST(WidenImageFormats, CoreFormatModuleIsHandedBackUntouched) { Vector widened; ShaderCompiler::WidenImageFormatsForEssl(spirv, widened, /*onlyFormatsSpirvCrossRefusesToPrint=*/false, - /*enableSpirvValidation=*/true); + /*enableSpirvValidation=*/true); if (!widened.empty()) { EXPECT_EQ(CollectVectorShuffles(widened).size(), CollectVectorShuffles(spirv).size()) << "a core-format module must gain no masks"; @@ -967,12 +957,10 @@ TEST(WidenImageFormats, NormalizedImageBecomesAUimageWhoseAccessesConvertItsCode EXPECT_EQ(after.text.find("rg16"), String::npos) << "the token no ES driver accepts is still in the emitted source:\n" << after.text; - EXPECT_NE(after.text.find("65535.0"), String::npos) - << "the unsigned-normalized denominator is 2^16 - 1:\n" - << after.text; - EXPECT_EQ(after.text.find("32767.0"), String::npos) - << "an unsigned format must not take the SIGNED denominator:\n" - << after.text; + EXPECT_NE(after.text.find("65535.0"), String::npos) << "the unsigned-normalized denominator is 2^16 - 1:\n" + << after.text; + EXPECT_EQ(after.text.find("32767.0"), String::npos) << "an unsigned format must not take the SIGNED denominator:\n" + << after.text; } // The signed half, which needs two things the unsigned one does not: the code is sign-extended @@ -1010,12 +998,9 @@ TEST(WidenImageFormats, SignedNormalizedImageSignExtendsItsCodeAndClampsAtMinusO EXPECT_NE(after.text.find("uimage2D"), String::npos) << after.text; EXPECT_NE(after.text.find("rgba16ui"), String::npos) << after.text; EXPECT_EQ(after.text.find("rgba16_snorm"), String::npos) << after.text; - EXPECT_NE(after.text.find("32767.0"), String::npos) - << "the signed-normalized denominator is 2^15 - 1:\n" - << after.text; - EXPECT_NE(after.text.find("-1.0"), String::npos) - << "GL clamps the signed decode at -1:\n" - << after.text; + EXPECT_NE(after.text.find("32767.0"), String::npos) << "the signed-normalized denominator is 2^15 - 1:\n" + << after.text; + EXPECT_NE(after.text.find("-1.0"), String::npos) << "GL clamps the signed decode at -1:\n" << after.text; } // rgb10_a2, whose four channels are 10, 10, 10 and 2 bits: the only entry where one denominator @@ -1038,8 +1023,6 @@ TEST(WidenImageFormats, TenTenTenTwoImageTakesAPerChannelDenominator) { const EsslAttempt after = EmitEssl(widened); ASSERT_TRUE(after.succeeded) << after.error; EXPECT_NE(after.text.find("1023.0"), String::npos) << after.text; - EXPECT_NE(after.text.find("3.0"), String::npos) - << "the two-bit alpha saturates at 3, not at 1023:\n" - << after.text; + EXPECT_NE(after.text.find("3.0"), String::npos) << "the two-bit alpha saturates at 3, not at 1023:\n" << after.text; EXPECT_EQ(after.text.find("rgb10_a2)"), String::npos) << after.text; } diff --git a/MobileGL/MG_Test/ShaderTranspiler/ZeroLayerOutputTest.cpp b/MobileGL/MG_Test/ShaderTranspiler/ZeroLayerOutputTest.cpp index 4b680c62..f7afce85 100644 --- a/MobileGL/MG_Test/ShaderTranspiler/ZeroLayerOutputTest.cpp +++ b/MobileGL/MG_Test/ShaderTranspiler/ZeroLayerOutputTest.cpp @@ -68,14 +68,16 @@ void main() { color = vec4(1.0); } String ValidationError(const Vector& spirv) { spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); String error; - tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { error += message != nullptr ? message : ""; }); + tools.SetMessageConsumer([&](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + error += message != nullptr ? message : ""; + }); return tools.Validate(spirv) ? String{} : (error.empty() ? String{"invalid"} : error); } SizeT CountOccurrences(const String& text, const String& needle) { SizeT count = 0; - for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) ++count; + for (SizeT at = text.find(needle); at != String::npos; at = text.find(needle, at + needle.size())) + ++count; return count; } diff --git a/MobileGL/MG_Test/State/ContextStateOwnershipTest.cpp b/MobileGL/MG_Test/State/ContextStateOwnershipTest.cpp index 8d54e6f1..ff5be90a 100644 --- a/MobileGL/MG_Test/State/ContextStateOwnershipTest.cpp +++ b/MobileGL/MG_Test/State/ContextStateOwnershipTest.cpp @@ -69,17 +69,14 @@ namespace { return context; } - void MakeCurrent(EGLContext context) { - ASSERT_TRUE(State->MakeCurrent(Display, Surface, Surface, context)); - } + void MakeCurrent(EGLContext context) { ASSERT_TRUE(State->MakeCurrent(Display, Surface, Surface, context)); } void Release() { if (State) State->MakeCurrent(EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } static void DrainErrors() { - for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {} } EGLStateContext* State = nullptr; diff --git a/MobileGL/MG_Test/State/DebugTest.cpp b/MobileGL/MG_Test/State/DebugTest.cpp index 5144d393..694bc0aa 100644 --- a/MobileGL/MG_Test/State/DebugTest.cpp +++ b/MobileGL/MG_Test/State/DebugTest.cpp @@ -34,8 +34,7 @@ namespace { class DebugTest : public ::testing::Test { protected: static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } static void ExpectSingleGlError(GLenum expected) { diff --git a/MobileGL/MG_Test/State/MipmapFootprintTest.cpp b/MobileGL/MG_Test/State/MipmapFootprintTest.cpp index 46a5adc3..2938a7fb 100644 --- a/MobileGL/MG_Test/State/MipmapFootprintTest.cpp +++ b/MobileGL/MG_Test/State/MipmapFootprintTest.cpp @@ -56,7 +56,8 @@ namespace { storage.MarkDirtyRegion(0, offset, extent); for (Int z = offset.z(); z < offset.z() + extent.z(); ++z) for (Int y = offset.y(); y < offset.y() + extent.y(); ++y) - for (Int x = offset.x(); x < offset.x() + extent.x(); ++x) written[Index(x, y, z)] = true; + for (Int x = offset.x(); x < offset.x() + extent.x(); ++x) + written[Index(x, y, z)] = true; } void Expect(const char* what) const { @@ -75,7 +76,8 @@ namespace { ASSERT_LE(box.hi.z(), size.z()); for (Int z = box.lo.z(); z < box.hi.z(); ++z) for (Int y = box.lo.y(); y < box.hi.y(); ++y) - for (Int x = box.lo.x(); x < box.hi.x(); ++x) ++covered[Index(x, y, z)]; + for (Int x = box.lo.x(); x < box.hi.x(); ++x) + ++covered[Index(x, y, z)]; } SizeT missing = 0, extra = 0, twice = 0; MipmapDirtyRegion bounds{size, IntVec3{0, 0, 0}}; @@ -149,7 +151,8 @@ namespace { TEST(MipmapFootprintTest, MoreBoxesThanTheListKeeps) { Level level({40, 40, 1}); for (int j = 0; j < 10; ++j) - for (int i = 0; i < 10; ++i) level.Write({4 * i + 1, 4 * j + 1, 0}, {2, 2, 1}); + for (int i = 0; i < 10; ++i) + level.Write({4 * i + 1, 4 * j + 1, 0}, {2, 2, 1}); level.Expect("a hundred 2x2"); std::vector footprint; level.storage.GetDirtyFootprint(0, footprint); @@ -159,7 +162,8 @@ namespace { // Scanlines of one width merge exactly and stay one box without ever starting the log. TEST(MipmapFootprintTest, AbuttingScanlinesStayOneBox) { Level level({32, 32, 1}); - for (int y = 4; y < 20; ++y) level.Write({3, y, 0}, {10, 1, 1}); + for (int y = 4; y < 20; ++y) + level.Write({3, y, 0}, {10, 1, 1}); level.Expect("scanlines"); std::vector footprint; level.storage.GetDirtyFootprint(0, footprint); diff --git a/MobileGL/MG_Test/State/NegativeApiErrorsTest.cpp b/MobileGL/MG_Test/State/NegativeApiErrorsTest.cpp index efc59894..bafa2724 100644 --- a/MobileGL/MG_Test/State/NegativeApiErrorsTest.cpp +++ b/MobileGL/MG_Test/State/NegativeApiErrorsTest.cpp @@ -59,13 +59,10 @@ namespace { MG_State::pGLContext = MakeUnique(); } - void TearDown() override { - EXPECT_EQ(GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind"; - } + void TearDown() override { EXPECT_EQ(GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind"; } static void DrainErrors() { - for (int i = 0; i < 16 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && GetError() != GL_NO_ERROR; ++i) {} } static GLuint MakeBuffer(GLenum target, GLsizeiptr size) { @@ -119,10 +116,8 @@ namespace { {"glBindBuffersBase with a reserved-but-uncreated name", [&] { BindBuffersBase(GL_UNIFORM_BUFFER, 0, 2, mixedBuffers); }, GL_INVALID_OPERATION}, {"glBindBuffersRange with a reserved-but-uncreated name", - [&] { BindBuffersRange(GL_UNIFORM_BUFFER, 0, 2, mixedBuffers, offsets, sizes); }, - GL_INVALID_OPERATION}, - {"glBindSamplers with a deleted sampler name", [&] { BindSamplers(0, 1, samplers); }, - GL_INVALID_OPERATION}, + [&] { BindBuffersRange(GL_UNIFORM_BUFFER, 0, 2, mixedBuffers, offsets, sizes); }, GL_INVALID_OPERATION}, + {"glBindSamplers with a deleted sampler name", [&] { BindSamplers(0, 1, samplers); }, GL_INVALID_OPERATION}, }); // ARB_multi_bind defines these as a LOOP of single binds, so the bad entry costs its own @@ -294,8 +289,8 @@ namespace { RunRows({ {"glDispatchComputeIndirect(-2)", [] { DispatchComputeIndirect(-2); }, GL_INVALID_VALUE}, {"glDispatchComputeIndirect(3)", [] { DispatchComputeIndirect(3); }, GL_INVALID_VALUE}, - {"glDispatchComputeIndirect(16) past the end of a 24-byte buffer", - [] { DispatchComputeIndirect(16); }, GL_INVALID_OPERATION}, + {"glDispatchComputeIndirect(16) past the end of a 24-byte buffer", [] { DispatchComputeIndirect(16); }, + GL_INVALID_OPERATION}, {"glDispatchComputeIndirect(0) with nothing bound", [&] { BindBuffer(GL_DISPATCH_INDIRECT_BUFFER, 0); @@ -526,10 +521,10 @@ void main() { g_color = vec4(1); } {"glDrawElementsInstanced with an unaccepted mode", [] { DrawElementsInstanced(kBadMode, 3, GL_UNSIGNED_INT, nullptr, 4); }, GL_INVALID_ENUM}, {"glDrawArrays with an unaccepted mode", [] { DrawArrays(kBadMode, 0, 3); }, GL_INVALID_ENUM}, - {"glDrawElements with an unaccepted mode", - [] { DrawElements(kBadMode, 3, GL_UNSIGNED_INT, nullptr); }, GL_INVALID_ENUM}, - {"glMultiDrawArrays with an unaccepted mode", - [] { MultiDrawArrays(kBadMode, nullptr, nullptr, 0); }, GL_INVALID_ENUM}, + {"glDrawElements with an unaccepted mode", [] { DrawElements(kBadMode, 3, GL_UNSIGNED_INT, nullptr); }, + GL_INVALID_ENUM}, + {"glMultiDrawArrays with an unaccepted mode", [] { MultiDrawArrays(kBadMode, nullptr, nullptr, 0); }, + GL_INVALID_ENUM}, {"glDrawRangeElements with an unaccepted mode", [] { DrawRangeElements(kBadMode, 0, 2, 3, GL_UNSIGNED_INT, nullptr); }, GL_INVALID_ENUM}, {"glDrawElementsIndirect with an unaccepted mode", @@ -560,9 +555,7 @@ void main() { g_color = vec4(1); } RunRows({ {"glBlitFramebuffer with a mask bit outside COLOR|DEPTH|STENCIL", - [] { - BlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | kForeignBit, GL_NEAREST); - }, + [] { BlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | kForeignBit, GL_NEAREST); }, GL_INVALID_VALUE}, {"glBlitFramebuffer with a filter that is neither GL_NEAREST nor GL_LINEAR", [] { BlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT, GL_NONE); }, GL_INVALID_ENUM}, @@ -572,13 +565,11 @@ void main() { g_color = vec4(1); } }, GL_INVALID_OPERATION}, {"glBlitFramebuffer of depth with GL_LINEAR", - [] { BlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT, GL_LINEAR); }, - GL_INVALID_OPERATION}, + [] { BlitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT, GL_LINEAR); }, GL_INVALID_OPERATION}, // The DSA form has to answer identically. {"glBlitNamedFramebuffer with a mask bit outside COLOR|DEPTH|STENCIL", [] { - BlitNamedFramebuffer(0, 0, 0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | kForeignBit, - GL_NEAREST); + BlitNamedFramebuffer(0, 0, 0, 0, 16, 16, 0, 0, 16, 16, GL_COLOR_BUFFER_BIT | kForeignBit, GL_NEAREST); }, GL_INVALID_VALUE}, {"glBlitNamedFramebuffer with a bad filter", @@ -586,8 +577,8 @@ void main() { g_color = vec4(1); } GL_INVALID_ENUM}, {"glBlitNamedFramebuffer of depth+stencil with GL_LINEAR", [] { - BlitNamedFramebuffer(0, 0, 0, 0, 16, 16, 0, 0, 16, 16, - GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_LINEAR); + BlitNamedFramebuffer(0, 0, 0, 0, 16, 16, 0, 0, 16, 16, GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, + GL_LINEAR); }, GL_INVALID_OPERATION}, }); diff --git a/MobileGL/MG_Test/State/RenderStateTest.cpp b/MobileGL/MG_Test/State/RenderStateTest.cpp index 66e718ef..2b8bdc17 100644 --- a/MobileGL/MG_Test/State/RenderStateTest.cpp +++ b/MobileGL/MG_Test/State/RenderStateTest.cpp @@ -39,13 +39,12 @@ using namespace MobileGL; namespace { - class RenderStateTest: public ::testing::Test { + class RenderStateTest : public ::testing::Test { protected: // GL error flags are sticky per code and the context outlives an individual test in this // binary, so a pending error from an earlier case would be handed to the next GetError(). static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } static void ExpectSingleGlError(GLenum expected) { @@ -675,19 +674,22 @@ TEST_F(RenderStateTest, ClearColorStartsAtTransparentBlack) { const GLenum error = MG_Impl::GLImpl::GetError(); MG_State::pGLContext = previous; EXPECT_EQ(error, GL_NO_ERROR); - for (const GLfloat channel : clear) EXPECT_FLOAT_EQ(channel, 0.0f); + for (const GLfloat channel : clear) + EXPECT_FLOAT_EQ(channel, 0.0f); } TEST_F(RenderStateTest, PatchDefaultLevelsStartAtTheGLDefault) { GLfloat outer[4] = {-1.0f, -1.0f, -1.0f, -1.0f}; MG_Impl::GLImpl::GetFloatv(GL_PATCH_DEFAULT_OUTER_LEVEL, outer); ExpectSingleGlError(GL_NO_ERROR); - for (const GLfloat level : outer) EXPECT_FLOAT_EQ(level, 1.0f); + for (const GLfloat level : outer) + EXPECT_FLOAT_EQ(level, 1.0f); GLfloat inner[2] = {-1.0f, -1.0f}; MG_Impl::GLImpl::GetFloatv(GL_PATCH_DEFAULT_INNER_LEVEL, inner); ExpectSingleGlError(GL_NO_ERROR); - for (const GLfloat level : inner) EXPECT_FLOAT_EQ(level, 1.0f); + for (const GLfloat level : inner) + EXPECT_FLOAT_EQ(level, 1.0f); } TEST_F(RenderStateTest, PatchDefaultLevelsRoundTripThroughEveryGetter) { @@ -820,8 +822,7 @@ TEST_F(RenderStateTest, ARedundantNaNPatchLevelWriteSettlesInsteadOfBumpingForev const Uint afterFirst = state.GetPipelineStateVersion(); state.SetPatchDefaultOuterLevel(FloatVec4(notANumber, 1.0f, 1.0f, 1.0f)); - EXPECT_EQ(state.GetPipelineStateVersion(), afterFirst) - << "the identical NaN tuple is not a state change"; + EXPECT_EQ(state.GetPipelineStateVersion(), afterFirst) << "the identical NaN tuple is not a state change"; state.SetPatchDefaultInnerLevel(FloatVec2(notANumber, 1.0f)); const Uint afterInner = state.GetPipelineStateVersion(); @@ -1100,9 +1101,10 @@ namespace { class ScopedFragmentShadingRateBackend { public: - ScopedFragmentShadingRateBackend(Bool primitive, Bool attachment, Bool nonTrivial): - m_previous(Move(MG_Backend::pActiveBackendObject)) { - MG_Backend::pActiveBackendObject = MakeUnique(primitive, attachment, nonTrivial); + ScopedFragmentShadingRateBackend(Bool primitive, Bool attachment, Bool nonTrivial) + : m_previous(Move(MG_Backend::pActiveBackendObject)) { + MG_Backend::pActiveBackendObject = + MakeUnique(primitive, attachment, nonTrivial); } ~ScopedFragmentShadingRateBackend() { // Leave the context at the extension's initial state for whichever test runs next. diff --git a/MobileGL/MG_Test/Swapchain/SwapchainPresentModeTest.cpp b/MobileGL/MG_Test/Swapchain/SwapchainPresentModeTest.cpp index 92cca6fc..9380f669 100644 --- a/MobileGL/MG_Test/Swapchain/SwapchainPresentModeTest.cpp +++ b/MobileGL/MG_Test/Swapchain/SwapchainPresentModeTest.cpp @@ -17,9 +17,9 @@ namespace { // What each surface reports. Android's loader exposes MAILBOX and FIFO (plus the shared // modes with VK_KHR_shared_presentable_image) but never IMMEDIATE; a desktop X11 WSI has // all four classic modes. - const Vector kAndroidModes{ - VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, - VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR}; + const Vector kAndroidModes{VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, + VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR, + VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR}; const Vector kAllClassicModes{VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR}; const Vector kFifoOnly{VK_PRESENT_MODE_FIFO_KHR}; diff --git a/MobileGL/MG_Test/Texture/TextureTest.cpp b/MobileGL/MG_Test/Texture/TextureTest.cpp index 884396c3..20b5b735 100644 --- a/MobileGL/MG_Test/Texture/TextureTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureTest.cpp @@ -49,8 +49,7 @@ protected: // which silently turns error-code assertions into reads of someone else's error. Bounded because // there is one flag per code; a runaway would otherwise hang the suite. static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } // The call under test must raise exactly the expected error and nothing more: a second pending @@ -77,12 +76,9 @@ namespace { public: FormatCapabilityBackend() { auto& cache = MutableFormatCapabilities(); - const auto texture2DIndex = - MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture2D); - const auto texture3DIndex = - MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture3D); - const auto renderbufferIndex = - MobileGL::MG_Backend::GetRenderbufferFormatCapabilityTargetIndex(); + const auto texture2DIndex = MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture2D); + const auto texture3DIndex = MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(TextureTarget::Texture3D); + const auto renderbufferIndex = MobileGL::MG_Backend::GetRenderbufferFormatCapabilityTargetIndex(); const auto rgba8Index = static_cast(TextureInternalFormat::RGBA8); const auto rg8Index = static_cast(TextureInternalFormat::RG8); const auto depthStencilIndex = static_cast(TextureInternalFormat::Depth24Stencil8); @@ -91,8 +87,7 @@ namespace { cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::Sampled; cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::LinearFilter; cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::GenerateMipmap; - cache.FullCaps[texture2DIndex][rgba8Index] |= - MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; + cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; cache.FullCaps[texture2DIndex][rgba8Index] |= MobileGL::MG_Backend::FormatCapability::ColorAttachment; cache.SampleCounts[texture2DIndex][rgba8Index] = {1}; @@ -100,8 +95,7 @@ namespace { cache.CaveatCaps[texture2DIndex][rg8Index] |= MobileGL::MG_Backend::FormatCapability::Sampled; cache.CaveatCaps[texture2DIndex][rg8Index] |= MobileGL::MG_Backend::FormatCapability::LinearFilter; - cache.FullCaps[texture3DIndex][depthStencilIndex] |= - MobileGL::MG_Backend::FormatCapability::Creatable; + cache.FullCaps[texture3DIndex][depthStencilIndex] |= MobileGL::MG_Backend::FormatCapability::Creatable; cache.FullCaps[texture3DIndex][depthStencilIndex] |= MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; cache.FullCaps[texture3DIndex][depthStencilIndex] |= @@ -111,8 +105,7 @@ namespace { cache.FullCaps[texture3DIndex][depthStencilIndex] |= MobileGL::MG_Backend::FormatCapability::StencilAttachment; - cache.FullCaps[renderbufferIndex][depthStencilIndex] |= - MobileGL::MG_Backend::FormatCapability::Creatable; + cache.FullCaps[renderbufferIndex][depthStencilIndex] |= MobileGL::MG_Backend::FormatCapability::Creatable; cache.FullCaps[renderbufferIndex][depthStencilIndex] |= MobileGL::MG_Backend::FormatCapability::FramebufferRenderable; cache.FullCaps[renderbufferIndex][depthStencilIndex] |= @@ -149,14 +142,12 @@ namespace { class ScopedBackendOverride { public: - explicit ScopedBackendOverride(UniquePtr backend): - m_previous(Move(MobileGL::MG_Backend::pActiveBackendObject)) { + explicit ScopedBackendOverride(UniquePtr backend) + : m_previous(Move(MobileGL::MG_Backend::pActiveBackendObject)) { MobileGL::MG_Backend::pActiveBackendObject = Move(backend); } - ~ScopedBackendOverride() { - MobileGL::MG_Backend::pActiveBackendObject = Move(m_previous); - } + ~ScopedBackendOverride() { MobileGL::MG_Backend::pActiveBackendObject = Move(m_previous); } private: UniquePtr m_previous; @@ -170,7 +161,7 @@ namespace { class ScopedTextureBackendFunctionsOverride { public: - ScopedTextureBackendFunctionsOverride(): m_snapshot(MG_Backend::gBackendFunctionsTable) {} + ScopedTextureBackendFunctionsOverride() : m_snapshot(MG_Backend::gBackendFunctionsTable) {} ~ScopedTextureBackendFunctionsOverride() { MG_Backend::gBackendFunctionsTable = m_snapshot; } private: @@ -193,7 +184,15 @@ namespace { void RecordCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { g_copyTexSubImage2DCall = { - true, target, level, xoffset, yoffset, x, y, width, height, + true, + target, + level, + xoffset, + yoffset, + x, + y, + width, + height, MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()) .GetBindingSlot(TextureTarget::Texture2D) .GetBoundObject() @@ -226,13 +225,9 @@ TEST_F(TextureTest, ClearTexImageNullClearsWholeNamedTextureAndMarksStorageDirty MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 initialPixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, - GL_RGBA, GL_UNSIGNED_BYTE, initialPixels); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, initialPixels); const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); auto* mipmapObject = static_cast(textureObject.get()); @@ -252,8 +247,7 @@ TEST_F(TextureTest, ClearTexImageRepeatsConvertedClearPixel) { GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); const Uint8 clearPixel[] = {17, 34, 51, 68}; MG_Impl::GLImpl::ClearTexImage(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, clearPixel); @@ -261,10 +255,7 @@ TEST_F(TextureTest, ClearTexImageRepeatsConvertedClearPixel) { const Uint8* stored = GetBoundTexture2DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 17, 34, 51, 68, - 17, 34, 51, 68, - 17, 34, 51, 68, - 17, 34, 51, 68, + 17, 34, 51, 68, 17, 34, 51, 68, 17, 34, 51, 68, 17, 34, 51, 68, }; EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -276,11 +267,9 @@ TEST_F(TextureTest, ClearTexSubImageClearsOnlyRequestedRectangle) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); Uint8 initialPixels[3 * 2 * 4]; std::memset(initialPixels, 0x7f, sizeof(initialPixels)); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 3, 2, 0, - GL_RGBA, GL_UNSIGNED_BYTE, initialPixels); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 3, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, initialPixels); - MG_Impl::GLImpl::ClearTexSubImage(texture, 0, 1, 0, 0, 1, 2, 1, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::ClearTexSubImage(texture, 0, 1, 0, 0, 1, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); const Uint8* stored = GetBoundTexture2DLevelBytes(texture); ASSERT_NE(stored, nullptr); @@ -317,9 +306,8 @@ TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) { MG_Impl::GLImpl::NamedFramebufferTexture(readFramebuffer, GL_COLOR_ATTACHMENT0, readTexture, 0); MG_Impl::GLImpl::BindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer); - const auto boundBefore = MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(); + const auto boundBefore = + MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(); MG_Impl::GLImpl::CopyTextureSubImage2D(namedTexture, 2, 3, 4, 5, 6, 7, 8); EXPECT_TRUE(g_copyTexSubImage2DCall.Called); @@ -332,9 +320,7 @@ TEST_F(TextureTest, CopyTextureSubImage2DUsesNamedObjectAndRestoresBinding) { EXPECT_EQ(g_copyTexSubImage2DCall.Width, 7); EXPECT_EQ(g_copyTexSubImage2DCall.Height, 8); EXPECT_EQ(g_copyTexSubImage2DCall.BoundTexture, namedTexture); - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), boundBefore); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -357,8 +343,7 @@ TEST_F(TextureTest, ClearTexImageErrorContracts) { GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); // Zero texture name is INVALID_OPERATION (ARB_clear_texture). MG_Impl::GLImpl::ClearTexImage(0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); @@ -373,8 +358,7 @@ TEST_F(TextureTest, ClearTexImageErrorContracts) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), static_cast(GL_INVALID_OPERATION)); // A clear region outside the level is INVALID_VALUE. - MG_Impl::GLImpl::ClearTexSubImage(texture, 0, 1, 1, 0, 4, 4, 1, - GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::ClearTexSubImage(texture, 0, 1, 1, 0, 4, 4, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), static_cast(GL_INVALID_VALUE)); // An invalid pixel-transfer format is INVALID_ENUM from the shared validators. @@ -402,8 +386,7 @@ TEST_F(TextureTest, ClearTexImageRejectsCompressedTextures) { GLuint specificTexture = 0; MG_Impl::GLImpl::GenTextures(1, &specificTexture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, specificTexture); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); MG_Impl::GLImpl::ClearTexImage(specificTexture, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); ExpectSingleGlError(GL_INVALID_OPERATION); @@ -1084,8 +1067,8 @@ TEST_F(TextureTest, EverySamplerScalarPnameConvertsToTheQueriedType) { GLuint asUnsignedForm = 0; MG_Impl::GLImpl::GetSamplerParameterIuiv(sampler, entry.pname, &asUnsignedForm); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << entry.name; - EXPECT_EQ(asUnsignedForm, static_cast(entry.asInteger)) << entry.name - << " through glGetSamplerParameterIuiv"; + EXPECT_EQ(asUnsignedForm, static_cast(entry.asInteger)) + << entry.name << " through glGetSamplerParameterIuiv"; } // And the other half: float-valued pnames queried through the integer spellings. The values are @@ -1106,8 +1089,9 @@ TEST_F(TextureTest, EverySamplerScalarPnameConvertsToTheQueriedType) { GLint asInt = 0; MG_Impl::GLImpl::GetSamplerParameteriv(sampler, entry.pname, &asInt); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << entry.name; - EXPECT_EQ(asInt, entry.asInteger) << entry.name << " through glGetSamplerParameteriv returned the " - "float's bit pattern"; + EXPECT_EQ(asInt, entry.asInteger) << entry.name + << " through glGetSamplerParameteriv returned the " + "float's bit pattern"; GLint asIntegerForm = 0; MG_Impl::GLImpl::GetSamplerParameterIiv(sampler, entry.pname, &asIntegerForm); @@ -1263,20 +1247,16 @@ TEST_F(TextureTest, GenThenBindCreatesObjectForUnsizedPackedBgraSubImageUpload) EXPECT_TRUE(MG_State::pGLContext->ValidateTextureObject(texture)); EXPECT_EQ(MG_Impl::GLImpl::IsTexture(texture), GL_TRUE); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, - GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; - MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, - GL_UNSIGNED_INT_8_8_8_8_REV, pixels); + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); const auto* stored = GetBoundTexture2DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 30, 20, 10, 40, - 70, 60, 50, 80, + 30, 20, 10, 40, 70, 60, 50, 80, }; EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -1327,12 +1307,10 @@ namespace { // MOBILEGL_RELAXED_SEMANTICS loosens strict core rules even on explicit core-profile // contexts. RAII so a failed ASSERT cannot leak the flag into the rest of the suite. struct ScopedRelaxedSemantics { - ScopedRelaxedSemantics(): m_previous(MG_Config::Features.RelaxedSemantics) { + ScopedRelaxedSemantics() : m_previous(MG_Config::Features.RelaxedSemantics) { MG_Config::Features.RelaxedSemantics = true; } - ~ScopedRelaxedSemantics() { - MG_Config::Features.RelaxedSemantics = m_previous; - } + ~ScopedRelaxedSemantics() { MG_Config::Features.RelaxedSemantics = m_previous; } ScopedRelaxedSemantics(const ScopedRelaxedSemantics&) = delete; ScopedRelaxedSemantics& operator=(const ScopedRelaxedSemantics&) = delete; @@ -1376,7 +1354,8 @@ TEST_F(TextureTest, ExternalImageExtensionNamesStayOffStrictDesktopContexts) { EXPECT_TRUE(contains(es, "GL_OES_EGL_image_external")); EXPECT_TRUE(contains(es, "GL_OES_EGL_image_external_essl3")); EXPECT_TRUE(contains(es, "GL_KHR_robustness")); - for (const char* name : es) EXPECT_EQ(std::strstr(name, "ARB"), nullptr) << name; + for (const char* name : es) + EXPECT_EQ(std::strstr(name, "ARB"), nullptr) << name; } // GL 3.3 core 3.8.1: on an explicit core-profile context, DeleteTextures makes the name unused @@ -1441,20 +1420,16 @@ TEST_F(TextureTest, RelaxedDefaultDeleteGeneratedReservationThenBindCreatesObjec EXPECT_TRUE(MG_State::pGLContext->ValidateTextureObject(texture)); EXPECT_EQ(MG_Impl::GLImpl::IsTexture(texture), GL_TRUE); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, - GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; - MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, - GL_UNSIGNED_INT_8_8_8_8_REV, pixels); + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); const auto* stored = GetBoundTexture2DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 30, 20, 10, 40, - 70, 60, 50, 80, + 30, 20, 10, 40, 70, 60, 50, 80, }; EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -1502,9 +1477,7 @@ TEST_F(TextureTest, DeleteInstantiatedTextureInvalidatesNameUntilRegenerated) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textures[0]); ExpectSingleGlError(GL_INVALID_OPERATION); - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), fallbackObject); } @@ -1521,9 +1494,7 @@ TEST_F(TextureTest, DeleteUnknownNamesIsSilentButBindUnknownNameIsInvalid) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, unknownNames[1]); ExpectSingleGlError(GL_INVALID_OPERATION); - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), boundObject); EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(unknownNames[1])); EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(unknownNames[1])); @@ -1542,9 +1513,7 @@ TEST_F(TextureTest, BindTextureUnitEnumAsNameIsSilentNoOp) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textureUnitEnum); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), boundObject); EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(textureUnitEnum)); EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(textureUnitEnum)); @@ -1575,10 +1544,7 @@ TEST_F(TextureTest, TextureStorageAndSubImageModifyNamedObjectOnly) { MG_Impl::GLImpl::TextureStorage2D(namedTexture, 2, GL_RGBA8, 2, 2); const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; MG_Impl::GLImpl::TextureSubImage2D(namedTexture, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -1603,22 +1569,17 @@ TEST_F(TextureTest, BoundTexSubImage2DUsesCompactRowsAfterUnpackProcessing) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 5, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, initialPixels); const Uint8 subImageWithGuard[] = { - 1, 2, 3, 4, 5, 6, 7, 8, 9, - 101, 102, 103, - 10, 11, 12, 13, 14, 15, 16, 17, 18, - 201, 202, 203, 204, 205, 206, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 101, 102, 103, 10, 11, 12, 13, 14, 15, 16, 17, 18, 201, 202, 203, 204, 205, 206, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4); MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 1, 0, 3, 2, GL_RGB, GL_UNSIGNED_BYTE, subImageWithGuard); const auto textureObject = MG_State::pGLContext->GetTextureObject(texture); auto* mipmapObject = static_cast(textureObject.get()); - const auto* stored = static_cast( - mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, 0)); + const auto* stored = static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, 0)); const Uint8 expected[] = { - 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, - 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 0, 0, 0, + 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 0, 0, 0, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -1633,15 +1594,13 @@ TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedBgra8888ToRgba8) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8, pixels); const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 20, 30, 40, 10, - 60, 70, 80, 50, + 20, 30, 40, 10, 60, 70, 80, 50, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -1655,18 +1614,8 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksPackedBgra8888ToRgba8WithPixelStoreSki MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, - 17, 18, 19, 20, - 10, 20, 30, 40, - 50, 60, 70, 80, - 21, 22, 23, 24, - 25, 26, 27, 28, - 90, 100, 110, 120, - 130, 140, 150, 160, - 29, 30, 31, 32, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 10, 20, 30, 40, + 50, 60, 70, 80, 21, 22, 23, 24, 25, 26, 27, 28, 90, 100, 110, 120, 130, 140, 150, 160, 29, 30, 31, 32, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 4); @@ -1679,10 +1628,7 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksPackedBgra8888ToRgba8WithPixelStoreSki const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 20, 30, 40, 10, - 60, 70, 80, 50, - 100, 110, 120, 90, - 140, 150, 160, 130, + 20, 30, 40, 10, 60, 70, 80, 50, 100, 110, 120, 90, 140, 150, 160, 130, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -1740,21 +1686,20 @@ TEST_F(TextureTest, TexImage2DAcceptsSpecCompliantFormatCombinations) { MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 2, 2, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 2, 2, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // Depth-component internal format accepts DEPTH_STENCIL input (stencil bits are dropped). - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 2, 2, 0, GL_DEPTH_STENCIL, - GL_UNSIGNED_INT_24_8, nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 2, 2, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, + nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // Packed RGB types allow the integer variant of the RGB format. - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB8UI, 2, 2, 0, GL_RGB_INTEGER, GL_UNSIGNED_BYTE_3_3_2, - nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB8UI, 2, 2, 0, GL_RGB_INTEGER, GL_UNSIGNED_BYTE_3_3_2, nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGB9_E5, 2, 2, 0, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV, nullptr); @@ -1796,8 +1741,10 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksGreenAndBlueIntoRgba8Channels) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, greenTexture); const Uint8 pixels[] = { - 10, 20, - 30, 40, + 10, + 20, + 30, + 40, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_GREEN, GL_UNSIGNED_BYTE, pixels); @@ -1805,10 +1752,7 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksGreenAndBlueIntoRgba8Channels) { const auto* storedGreen = GetBoundTexture2DLevelBytes(greenTexture); const Uint8 expectedGreen[] = { - 0, 10, 0, 255, - 0, 20, 0, 255, - 0, 30, 0, 255, - 0, 40, 0, 255, + 0, 10, 0, 255, 0, 20, 0, 255, 0, 30, 0, 255, 0, 40, 0, 255, }; for (SizeT i = 0; i < sizeof(expectedGreen); ++i) { EXPECT_EQ(storedGreen[i], expectedGreen[i]) << "byte " << i; @@ -1822,10 +1766,7 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksGreenAndBlueIntoRgba8Channels) { const auto* storedBlue = GetBoundTexture2DLevelBytes(blueTexture); const Uint8 expectedBlue[] = { - 0, 0, 10, 255, - 0, 0, 20, 255, - 0, 0, 30, 255, - 0, 0, 40, 255, + 0, 0, 10, 255, 0, 0, 20, 255, 0, 0, 30, 255, 0, 0, 40, 255, }; for (SizeT i = 0; i < sizeof(expectedBlue); ++i) { EXPECT_EQ(storedBlue[i], expectedBlue[i]) << "byte " << i; @@ -1840,8 +1781,10 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksGreenIntegerIntoRgba8UiChannels) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 10, 20, - 30, 40, + 10, + 20, + 30, + 40, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 2, 0, GL_GREEN_INTEGER, GL_UNSIGNED_BYTE, pixels); @@ -1851,10 +1794,7 @@ TEST_F(TextureTest, BoundTexImage2DUnpacksGreenIntegerIntoRgba8UiChannels) { const auto* stored = GetBoundTexture2DLevelBytes(texture); // Missing integer channels default to R=0, B=0, A=1. const Uint8 expected[] = { - 0, 10, 0, 1, - 0, 20, 0, 1, - 0, 30, 0, 1, - 0, 40, 0, 1, + 0, 10, 0, 1, 0, 20, 0, 1, 0, 30, 0, 1, 0, 40, 0, 1, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -1905,15 +1845,13 @@ TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedBgra8888RevToRgba8) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 30, 20, 10, 40, - 70, 60, 50, 80, + 30, 20, 10, 40, 70, 60, 50, 80, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -1973,8 +1911,7 @@ TEST_F(TextureTest, BoundTexImageAndSubImage2DUseInferredRgba8ForPackedBgra8888R MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 initialPixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, initialPixels); @@ -1983,24 +1920,20 @@ TEST_F(TextureTest, BoundTexImageAndSubImage2DUseInferredRgba8ForPackedBgra8888R EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::RGBA8); const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expectedInitial[] = { - 30, 20, 10, 40, - 70, 60, 50, 80, + 30, 20, 10, 40, 70, 60, 50, 80, }; for (SizeT i = 0; i < sizeof(expectedInitial); ++i) { EXPECT_EQ(stored[i], expectedInitial[i]) << "initial byte " << i; } const Uint8 updatedPixels[] = { - 90, 100, 110, 120, - 130, 140, 150, 160, + 90, 100, 110, 120, 130, 140, 150, 160, }; - MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, - updatedPixels); + MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, updatedPixels); stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expectedUpdated[] = { - 110, 100, 90, 120, - 150, 140, 130, 160, + 110, 100, 90, 120, 150, 140, 130, 160, }; for (SizeT i = 0; i < sizeof(expectedUpdated); ++i) { EXPECT_EQ(stored[i], expectedUpdated[i]) << "updated byte " << i; @@ -2015,15 +1948,13 @@ TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedRgba8888ToRgba8) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, pixels); const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 40, 30, 20, 10, - 80, 70, 60, 50, + 40, 30, 20, 10, 80, 70, 60, 50, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -2038,8 +1969,7 @@ TEST_F(TextureTest, BoundTexSubImage2DKeepsPackedRgba8888RevAsRgba8) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, + 10, 20, 30, 40, 50, 60, 70, 80, }; MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); @@ -2062,10 +1992,7 @@ TEST_F(TextureTest, BoundTexStorage2DAllocatesRedTextureForSubImageUpdates) { EXPECT_TRUE(textureObject->IsComplete()); const Uint8 pixels[4 * 4] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 20, 28, 4, 4, GL_RED, GL_UNSIGNED_BYTE, pixels); @@ -2106,8 +2033,7 @@ TEST_F(TextureTest, GetTextureImageReadsNamedObjectWithoutBinding) { MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8, 2, 1); const Uint8 pixels[] = { - 21, 22, 23, 24, - 31, 32, 33, 34, + 21, 22, 23, 24, 31, 32, 33, 34, }; MG_Impl::GLImpl::TextureSubImage2D(texture, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -2128,8 +2054,7 @@ TEST_F(TextureTest, GetTexImageReadsALevelWhoseLowerLevelsWereNeverDefined) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 61, 62, 63, 64, - 71, 72, 73, 74, + 61, 62, 63, 64, 71, 72, 73, 74, }; MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2180,14 +2105,13 @@ TEST_F(TextureTest, GetTextureSubImageReadsFullNamedLevelWithoutBinding) { MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8, 2, 1); const Uint8 pixels[] = { - 41, 42, 43, 44, - 51, 52, 53, 54, + 41, 42, 43, 44, 51, 52, 53, 54, }; MG_Impl::GLImpl::TextureSubImage2D(texture, 0, 0, 0, 2, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixels); Uint8 output[sizeof(pixels)] = {}; - MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, 0, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(output), output); + MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, 0, 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(output), + output); EXPECT_EQ(std::memcmp(output, pixels, sizeof(pixels)), 0); EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), @@ -2201,8 +2125,8 @@ TEST_F(TextureTest, GetTextureSubImageRejectsPartialReadbackForNow) { MG_Impl::GLImpl::TextureStorage2D(texture, 1, GL_RGBA8, 2, 2); Uint8 output[4] = {}; - MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(output), output); + MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, 0, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(output), + output); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION); } @@ -2228,8 +2152,8 @@ TEST_F(TextureTest, GetTextureSubImageSelectsTheCubeFaceZOffsetNames) { for (int face = 0; face < 6; ++face) { Uint8 output[4] = {}; - MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, face, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(output), output); + MG_Impl::GLImpl::GetTextureSubImage(texture, 0, 0, 0, face, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(output), + output); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "reading face " << face << " errored"; EXPECT_EQ(static_cast(output[0]), 10 + face) << "zoffset " << face << " answered with face " << (static_cast(output[0]) - 10); @@ -2259,8 +2183,8 @@ TEST_F(TextureTest, GetTextureSubImageReportsRegionErrorsAsInvalidValue) { Uint8 output[64] = {}; const auto read = [&](GLuint texture, GLint x, GLint y, GLint z, GLsizei w, GLsizei h, GLsizei d) { - MG_Impl::GLImpl::GetTextureSubImage(texture, 0, x, y, z, w, h, d, GL_RGBA, GL_UNSIGNED_BYTE, - sizeof(output), output); + MG_Impl::GLImpl::GetTextureSubImage(texture, 0, x, y, z, w, h, d, GL_RGBA, GL_UNSIGNED_BYTE, sizeof(output), + output); }; read(textures[0], -1, 0, 0, 2, 2, 1); ExpectSingleGlError(GL_INVALID_VALUE); @@ -2345,10 +2269,7 @@ TEST_F(TextureTest, TextureStorage1DAndSubImageModifyNamedObjectOnly) { MG_Impl::GLImpl::TextureStorage1D(namedTexture, 2, GL_RGBA8, 4); const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, - 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, }; MG_Impl::GLImpl::TextureSubImage1D(namedTexture, 0, 0, 4, GL_RGBA, GL_UNSIGNED_BYTE, pixels); @@ -2616,8 +2537,8 @@ TEST_F(TextureTest, TexImage3DAndTexStorage3DTagASpecificCompressedInternalForma GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, texture); - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 2, 0, GL_RG, - GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 2, 0, GL_RG, GL_UNSIGNED_BYTE, + nullptr); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); GLint compressed = GL_FALSE; @@ -2709,17 +2630,17 @@ TEST_F(TextureTest, CopyImageSubDataAcceptsALevelTheTextureDoesHave) { const GLuint twoLevelDst = MakeCopyImageTexture(2); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(oneLevelSrc, GL_TEXTURE_2D, 1, 0, 0, 0, oneLevelDst, GL_TEXTURE_2D, 1, 0, 0, 0, - 0, 0, 0); + MG_Impl::GLImpl::CopyImageSubData(oneLevelSrc, GL_TEXTURE_2D, 1, 0, 0, 0, oneLevelDst, GL_TEXTURE_2D, 1, 0, 0, 0, 0, + 0, 0); ExpectSingleGlError(GL_INVALID_VALUE); - MG_Impl::GLImpl::CopyImageSubData(twoLevelSrc, GL_TEXTURE_2D, 1, 0, 0, 0, twoLevelDst, GL_TEXTURE_2D, 1, 0, 0, 0, - 0, 0, 0); + MG_Impl::GLImpl::CopyImageSubData(twoLevelSrc, GL_TEXTURE_2D, 1, 0, 0, 0, twoLevelDst, GL_TEXTURE_2D, 1, 0, 0, 0, 0, + 0, 0); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "level 1 of a two-level texture is a level"; // And the boundary from the other side: two levels means 0 and 1, not 2. - MG_Impl::GLImpl::CopyImageSubData(twoLevelSrc, GL_TEXTURE_2D, 2, 0, 0, 0, twoLevelDst, GL_TEXTURE_2D, 0, 0, 0, 0, - 0, 0, 0); + MG_Impl::GLImpl::CopyImageSubData(twoLevelSrc, GL_TEXTURE_2D, 2, 0, 0, 0, twoLevelDst, GL_TEXTURE_2D, 0, 0, 0, 0, 0, + 0, 0); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -2747,8 +2668,8 @@ TEST_F(TextureTest, CopyImageSubDataRejectsTwoTexturesWithNoImageAtAll) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, secondEmpty); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(firstEmpty, GL_TEXTURE_2D, 0, 0, 0, 0, secondEmpty, GL_TEXTURE_2D, 0, 0, 0, 0, - 1, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(firstEmpty, GL_TEXTURE_2D, 0, 0, 0, 0, secondEmpty, GL_TEXTURE_2D, 0, 0, 0, 0, 1, + 1, 1); ExpectSingleGlError(GL_INVALID_OPERATION); } @@ -2809,7 +2730,8 @@ namespace { TEST_F(TextureTest, GetCompressedTextureSubImageCropsWholeBlocks) { const GLuint texture = MakeCompressedRgtc1Texture8x8(); Uint8 whole[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) whole[i] = static_cast(i + 1); + for (Int i = 0; i < kRgtc1Size8x8; ++i) + whole[i] = static_cast(i + 1); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, whole); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2854,7 +2776,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DReplacesTheStoredBlocks) { ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); Uint8 whole[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) whole[i] = static_cast(i + 1); + for (Int i = 0; i < kRgtc1Size8x8; ++i) + whole[i] = static_cast(i + 1); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, whole); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2889,7 +2812,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DReplacesTheStoredBlocks) { TEST_F(TextureTest, CompressedTexSubImage2DPlacesTheFirstBlockRow) { const GLuint texture = MakeCompressedRgtc1Texture8x8(); Uint8 whole[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) whole[i] = static_cast(i + 1); + for (Int i = 0; i < kRgtc1Size8x8; ++i) + whole[i] = static_cast(i + 1); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, whole); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2942,7 +2866,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DHandlesPartialBlocksAndAMipLevel) { EXPECT_EQ(imageSize, kBptcSize6x10); Uint8 whole[kBptcSize6x10]; - for (Int i = 0; i < kBptcSize6x10; ++i) whole[i] = static_cast(i + 1); + for (Int i = 0; i < kBptcSize6x10; ++i) + whole[i] = static_cast(i + 1); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, 6, 10, GL_COMPRESSED_RGBA_BPTC_UNORM, kBptcSize6x10, whole); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2950,7 +2875,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DHandlesPartialBlocksAndAMipLevel) { // The right-hand column (2 texels wide - a partial block that runs to the edge) of the middle // block row: one block, at byte 32 + 16. Uint8 patch[16]; - for (Int i = 0; i < 16; ++i) patch[i] = static_cast(0xF0 + i); + for (Int i = 0; i < 16; ++i) + patch[i] = static_cast(0xF0 + i); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 1, 4, 4, 2, 4, GL_COMPRESSED_RGBA_BPTC_UNORM, static_cast(sizeof(patch)), patch); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -2975,7 +2901,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DHandlesPartialBlocksAndAMipLevel) { // KHR-GL44.buffer_storage.map_persistent_texture uses for every one of its operations. TEST_F(TextureTest, CompressedTexSubImage2DUnpacksFromAPixelUnpackBuffer) { Uint8 source[256]; - for (Int i = 0; i < 256; ++i) source[i] = static_cast(i); + for (Int i = 0; i < 256; ++i) + source[i] = static_cast(i); GLuint buffer = 0; MG_Impl::GLImpl::GenBuffers(1, &buffer); MG_Impl::GLImpl::BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer); @@ -3006,7 +2933,8 @@ TEST_F(TextureTest, CompressedTexSubImage2DUnpacksFromAPixelUnpackBuffer) { // Both compressed entry points share one validator, so both are checked here. TEST_F(TextureTest, CompressedUploadsAcceptAPersistentlyMappedUnpackBuffer) { Uint8 source[256]; - for (Int i = 0; i < 256; ++i) source[i] = static_cast(255 - i); + for (Int i = 0; i < 256; ++i) + source[i] = static_cast(255 - i); GLuint buffer = 0; MG_Impl::GLImpl::GenBuffers(1, &buffer); MG_Impl::GLImpl::BindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer); @@ -3058,7 +2986,8 @@ TEST_F(TextureTest, CompressedUploadsAcceptAPersistentlyMappedUnpackBuffer) { TEST_F(TextureTest, CompressedTextureSubImage2DModifiesTheNamedTextureOnly) { const GLuint bound = MakeCompressedRgtc1Texture8x8(); Uint8 boundImage[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) boundImage[i] = 0x11; + for (Int i = 0; i < kRgtc1Size8x8; ++i) + boundImage[i] = 0x11; MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, boundImage); @@ -3067,7 +2996,8 @@ TEST_F(TextureTest, CompressedTextureSubImage2DModifiesTheNamedTextureOnly) { ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); Uint8 namedImage[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) namedImage[i] = 0x22; + for (Int i = 0; i < kRgtc1Size8x8; ++i) + namedImage[i] = 0x22; MG_Impl::GLImpl::CompressedTextureSubImage2D(named, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, namedImage); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -3105,7 +3035,8 @@ namespace { // stack has to come back byte for byte. TEST_F(TextureTest, CompressedTexImage3DShadowsTheWholeStackForReadback) { Uint8 whole[kRgtc1Size8x8x8]; - for (Int i = 0; i < kRgtc1Size8x8x8; ++i) whole[i] = static_cast(i); + for (Int i = 0; i < kRgtc1Size8x8x8; ++i) + whole[i] = static_cast(i); GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); @@ -3121,8 +3052,7 @@ TEST_F(TextureTest, CompressedTexImage3DShadowsTheWholeStackForReadback) { // An imageSize that is not the one the format and the three dimensions imply - the depth axis // is the term a 2D-shaped size calculation would drop. - MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 8, 0, kRgtc1Slice8x8, - whole); + MG_Impl::GLImpl::CompressedTexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RED_RGTC1, 8, 8, 8, 0, kRgtc1Slice8x8, whole); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -3171,8 +3101,8 @@ TEST_F(TextureTest, CompressedTextureSubImage3DModifiesTheNamedTextureOnly) { Uint8 namedImage[kRgtc1Size8x8x8]; std::memset(namedImage, 0x22, sizeof(namedImage)); - MG_Impl::GLImpl::CompressedTextureSubImage3D(named, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1, - kRgtc1Size8x8x8, namedImage); + MG_Impl::GLImpl::CompressedTextureSubImage3D(named, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8x8, + namedImage); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); Uint8 stored[kRgtc1Size8x8x8] = {}; @@ -3226,8 +3156,8 @@ TEST_F(TextureTest, CompressedTextureSubImage3DRejectsAGeneratedButNeverBoundNam GLuint created = 0; MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &created); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CompressedTextureSubImage3D(created, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1, - kRgtc1Size8x8x8, blocks); + MG_Impl::GLImpl::CompressedTextureSubImage3D(created, 0, 0, 0, 0, 8, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8x8, + blocks); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION) << "a created 3D texture with no compressed image is an image error, not a name error"; DrainPendingGlErrors(); @@ -3322,8 +3252,7 @@ TEST_F(TextureTest, TextureStorage3DAndSubImageModifyNamedObjectOnly) { MG_Impl::GLImpl::TextureStorage3D(texture, 2, GL_R8, 2, 2, 2); const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TextureSubImage3D(texture, 0, 0, 0, 0, 2, 2, 2, GL_RED, GL_UNSIGNED_BYTE, pixels); @@ -3355,10 +3284,7 @@ TEST_F(TextureTest, BoundTexImage3DUnsizedRgbaInfersRgba8AndUnpacksBgra8888Rev) MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture); const Uint8 pixels[] = { - 10, 20, 30, 40, - 50, 60, 70, 80, - 90, 100, 110, 120, - 130, 140, 150, 160, + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, }; MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 2, 1, 2, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, pixels); @@ -3368,10 +3294,7 @@ TEST_F(TextureTest, BoundTexImage3DUnsizedRgbaInfersRgba8AndUnpacksBgra8888Rev) const auto* stored = GetBoundTexture3DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 30, 20, 10, 40, - 70, 60, 50, 80, - 110, 100, 90, 120, - 150, 140, 130, 160, + 30, 20, 10, 40, 70, 60, 50, 80, 110, 100, 90, 120, 150, 140, 130, 160, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3388,17 +3311,80 @@ TEST_F(TextureTest, BoundTexImage3DHonorsImageHeightAndSkipImages) { // the upload reads a 2x2x2 sub-cuboid. const Uint8 pixels[] = { // image 0 (skipped) - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, - 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, + 0xAA, // image 1: rows 0-1 are slice 0, row 2 is padding - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, // image 2: rows 0-1 are slice 1, row 2 is padding - 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, - 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, 0xBB, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, + 0xBB, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_IMAGE_HEIGHT, 3); @@ -3410,10 +3396,8 @@ TEST_F(TextureTest, BoundTexImage3DHonorsImageHeightAndSkipImages) { const auto* stored = GetBoundTexture3DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 24, - 25, 26, 27, 28, 29, 30, 31, 32, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3430,14 +3414,26 @@ TEST_F(TextureTest, BoundTexImage3DConvertsRedToRgba8WithImageHeightAndSkips) { // skip 1 image, 0 rows, 1 pixel; upload a 2x1x2 sub-cuboid of GL_RED texels. const Uint8 pixels[] = { // image 0 (skipped) - 90, 91, 92, - 93, 94, 95, + 90, + 91, + 92, + 93, + 94, + 95, // image 1: row 0 holds slice 0 at x offset 1, row 1 is padding - 80, 11, 12, - 81, 82, 83, + 80, + 11, + 12, + 81, + 82, + 83, // image 2: row 0 holds slice 1 at x offset 1, row 1 is padding - 84, 21, 22, - 85, 86, 87, + 84, + 21, + 22, + 85, + 86, + 87, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); @@ -3455,8 +3451,7 @@ TEST_F(TextureTest, BoundTexImage3DConvertsRedToRgba8WithImageHeightAndSkips) { const auto* stored = GetBoundTexture3DLevelBytes(texture); ASSERT_NE(stored, nullptr); const Uint8 expected[] = { - 11, 0, 0, 255, 12, 0, 0, 255, - 21, 0, 0, 255, 22, 0, 0, 255, + 11, 0, 0, 255, 12, 0, 0, 255, 21, 0, 0, 255, 22, 0, 0, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3536,12 +3531,11 @@ TEST_F(TextureTest, BoundTexImage3DOn2DArrayHonorsUnpackSubcuboidSelection) { auto* mipmapObject = static_cast(textureObject.get()); EXPECT_EQ(mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture2DArray, 0), IntVec3(2, 2, 2)); - const auto* stored = - static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2DArray, 0)); + const auto* stored = static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2DArray, 0)); ASSERT_NE(stored, nullptr); SizeT storedIndex = 0; - for (SizeT image = 1; image <= 2; ++image) { // SKIP_IMAGES = 1 - for (SizeT row = 1; row <= 2; ++row) { // SKIP_ROWS = 1 + for (SizeT image = 1; image <= 2; ++image) { // SKIP_IMAGES = 1 + for (SizeT row = 1; row <= 2; ++row) { // SKIP_ROWS = 1 for (SizeT column = 1; column <= 2; ++column) { // SKIP_PIXELS = 1 const SizeT srcOffset = image * 36 + row * 12 + column * 4; for (SizeT b = 0; b < 4; ++b, ++storedIndex) { @@ -3576,8 +3570,7 @@ TEST_F(TextureTest, NormalizePixelFormatKeepsPackedTransferTypesForPackedSizedFo }; for (const auto& c : cases) { GLenum outInternal = 0, outFormat = 0, outType = 0; - NormalizePixelFormat(c.internalFormat, PixelFormatNormalizeOptionBit::None, &outInternal, &outFormat, - &outType); + NormalizePixelFormat(c.internalFormat, PixelFormatNormalizeOptionBit::None, &outInternal, &outFormat, &outType); EXPECT_EQ(outInternal, c.internalFormat) << "internalformat 0x" << std::hex << c.internalFormat; EXPECT_EQ(outFormat, c.expectedFormat) << "internalformat 0x" << std::hex << c.internalFormat; EXPECT_EQ(outType, c.expectedType) << "internalformat 0x" << std::hex << c.internalFormat; @@ -3603,8 +3596,7 @@ TEST_F(TextureTest, NormalizePixelFormatMapsStencilOnlyStorageToStencilIndex) { // ConvertGLEnumToTextureInternalFormat), so both must answer together. for (const GLenum internalFormat : {GL_STENCIL_INDEX8, GL_STENCIL_INDEX}) { GLenum outInternal = 0, outFormat = 0, outType = 0; - NormalizePixelFormat(internalFormat, PixelFormatNormalizeOptionBit::None, &outInternal, &outFormat, - &outType); + NormalizePixelFormat(internalFormat, PixelFormatNormalizeOptionBit::None, &outInternal, &outFormat, &outType); EXPECT_EQ(outInternal, internalFormat) << "internalformat 0x" << std::hex << internalFormat; EXPECT_EQ(outFormat, GL_STENCIL_INDEX) << "internalformat 0x" << std::hex << internalFormat; EXPECT_EQ(outType, GL_UNSIGNED_BYTE) << "internalformat 0x" << std::hex << internalFormat; @@ -3787,8 +3779,10 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedUnsignedByteToRgba8) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 10, 20, - 30, 40, + 10, + 20, + 30, + 40, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RED, GL_UNSIGNED_BYTE, pixels); @@ -3796,10 +3790,7 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedUnsignedByteToRgba8) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 10, 0, 0, 255, - 20, 0, 0, 255, - 30, 0, 0, 255, - 40, 0, 0, 255, + 10, 0, 0, 255, 20, 0, 0, 255, 30, 0, 0, 255, 40, 0, 0, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3814,8 +3805,10 @@ TEST_F(TextureTest, BoundTexSubImage2DExpandsRgUnsignedByteToRgba8) { MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); const Uint8 pixels[] = { - 10, 20, - 30, 40, + 10, + 20, + 30, + 40, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RG, GL_UNSIGNED_BYTE, pixels); @@ -3823,8 +3816,7 @@ TEST_F(TextureTest, BoundTexSubImage2DExpandsRgUnsignedByteToRgba8) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 10, 20, 0, 255, - 30, 40, 0, 255, + 10, 20, 0, 255, 30, 40, 0, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3838,8 +3830,7 @@ TEST_F(TextureTest, BoundTexImage2DReordersBgrUnsignedByteToRgba8) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 1, 2, 3, - 4, 5, 6, + 1, 2, 3, 4, 5, 6, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGR, GL_UNSIGNED_BYTE, pixels); @@ -3847,8 +3838,7 @@ TEST_F(TextureTest, BoundTexImage2DReordersBgrUnsignedByteToRgba8) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 3, 2, 1, 255, - 6, 5, 4, 255, + 3, 2, 1, 255, 6, 5, 4, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3862,8 +3852,10 @@ TEST_F(TextureTest, BoundTexImage2DConvertsRedFloatToRgba8) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const GLfloat pixels[] = { - 0.0f, 0.5f, - 1.0f, 2.0f, // out-of-range values clamp to [0, 1] + 0.0f, + 0.5f, + 1.0f, + 2.0f, // out-of-range values clamp to [0, 1] }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RED, GL_FLOAT, pixels); @@ -3871,10 +3863,7 @@ TEST_F(TextureTest, BoundTexImage2DConvertsRedFloatToRgba8) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 0, 0, 0, 255, - 128, 0, 0, 255, - 255, 0, 0, 255, - 255, 0, 0, 255, + 0, 0, 0, 255, 128, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3888,7 +3877,8 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedIntegerUnsignedShortToRgba8ui) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint16 pixels[] = { - 10, 300, // 300 exceeds the 8-bit destination and clamps to 255 + 10, + 300, // 300 exceeds the 8-bit destination and clamps to 255 }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 1, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, pixels); @@ -3896,7 +3886,7 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedIntegerUnsignedShortToRgba8ui) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 10, 0, 0, 1, // integer formats default missing alpha to 1, not the type maximum + 10, 0, 0, 1, // integer formats default missing alpha to 1, not the type maximum 255, 0, 0, 1, }; for (SizeT i = 0; i < sizeof(expected); ++i) { @@ -3911,9 +3901,7 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedToRgba8WithRowLengthAndSkips) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture); const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, }; MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1); MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 4); @@ -3927,10 +3915,7 @@ TEST_F(TextureTest, BoundTexImage2DExpandsRedToRgba8WithRowLengthAndSkips) { const auto* stored = GetBoundTexture2DLevelBytes(texture); const Uint8 expected[] = { - 6, 0, 0, 255, - 7, 0, 0, 255, - 10, 0, 0, 255, - 11, 0, 0, 255, + 6, 0, 0, 255, 7, 0, 0, 255, 10, 0, 0, 255, 11, 0, 0, 255, }; for (SizeT i = 0; i < sizeof(expected); ++i) { EXPECT_EQ(stored[i], expected[i]) << "byte " << i; @@ -3942,8 +3927,7 @@ TEST_F(TextureTest, NormalizeDepth24Stencil8UsesPackedDepthStencilType) { GLenum internalFormat = 0; GLenum format = 0; GLenum type = 0; - MG_Util::TextureFormatProcessor::NormalizePixelFormat(GL_DEPTH24_STENCIL8, - PixelFormatNormalizeOptionBit::None, + MG_Util::TextureFormatProcessor::NormalizePixelFormat(GL_DEPTH24_STENCIL8, PixelFormatNormalizeOptionBit::None, &internalFormat, &format, &type); EXPECT_EQ(internalFormat, GL_DEPTH24_STENCIL8); @@ -3966,9 +3950,7 @@ TEST_F(TextureTest, DefaultTextureIsBoundInitiallyAndIsPerTarget) { // Binding 0 restores the default object, and the binding query reports name 0. MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0); - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), default2D); GLint binding = -1; MG_Impl::GLImpl::GetIntegerv(GL_TEXTURE_BINDING_2D, &binding); @@ -3976,9 +3958,7 @@ TEST_F(TextureTest, DefaultTextureIsBoundInitiallyAndIsPerTarget) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // One default per target per context, shared across all texture units. - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(5) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(5).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), default2D); } @@ -4041,16 +4021,14 @@ TEST_F(TextureTest, DefaultTextureAcceptsImageAndParameterCalls) { // A real upload works like on any texture: data lands in the default object's shadow store. const Uint8 pixels[] = { - 1, 2, 3, 4, - 5, 6, 7, 8, + 1, 2, 3, 4, 5, 6, 7, 8, }; MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); const auto& default2D = MG_State::pGLContext->GetDefaultTextureObject(TextureTarget::Texture2D); auto* mipmapObject = static_cast(default2D.get()); EXPECT_EQ(mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0), IntVec3(2, 1, 1)); - const auto* stored = - static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, 0)); + const auto* stored = static_cast(mipmapObject->MapMipmapData(TextureUploadTarget::Texture2D, 0)); ASSERT_NE(stored, nullptr); EXPECT_EQ(std::memcmp(stored, pixels, sizeof(pixels)), 0); @@ -4113,9 +4091,7 @@ TEST_F(TextureTest, DeletingBoundTextureRebindsDefaultTexture) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // GL 3.3 core 3.8.1: deleting the bound texture is as if BindTexture(target, 0) had run. - EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0) - .GetBindingSlot(TextureTarget::Texture2D) - .GetBoundObject(), + EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).GetBoundObject(), MG_State::pGLContext->GetDefaultTextureObject(TextureTarget::Texture2D)); GLint binding = -1; MG_Impl::GLImpl::GetIntegerv(GL_TEXTURE_BINDING_2D, &binding); @@ -4211,8 +4187,7 @@ TEST_F(TextureTest, CtsStyleStateResetOnDefaultTexturesLeavesNoError) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "GL_TEXTURE_CUBE_MAP reset failed"; MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0); - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 0, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); resetCommonTexParams(GL_TEXTURE_2D_ARRAY); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "GL_TEXTURE_2D_ARRAY reset failed"; @@ -4223,14 +4198,12 @@ TEST_F(TextureTest, CtsStyleStateResetOnDefaultTexturesLeavesNoError) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "GL_TEXTURE_3D reset failed"; MG_Impl::GLImpl::BindTexture(GL_TEXTURE_1D_ARRAY, 0); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_1D_ARRAY, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); resetCommonTexParams(GL_TEXTURE_1D_ARRAY); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "GL_TEXTURE_1D_ARRAY reset failed"; MG_Impl::GLImpl::BindTexture(GL_TEXTURE_RECTANGLE, 0); - MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, 0, 0, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "GL_TEXTURE_RECTANGLE reset failed"; MG_Impl::GLImpl::BindTexture(GL_TEXTURE_BUFFER, 0); @@ -4348,8 +4321,7 @@ TEST_F(TextureTest, NormalizeLegacySizedFormatsMapToCanonicalShadowLayouts) { GLenum internalFormat = 0; GLenum format = 0; GLenum type = 0; - MG_Util::TextureFormatProcessor::NormalizePixelFormat(testCase.requested, - PixelFormatNormalizeOptionBit::None, + MG_Util::TextureFormatProcessor::NormalizePixelFormat(testCase.requested, PixelFormatNormalizeOptionBit::None, &internalFormat, &format, &type); EXPECT_EQ(internalFormat, testCase.internalFormat) << "requested 0x" << std::hex << testCase.requested; EXPECT_EQ(format, testCase.format) << "requested 0x" << std::hex << testCase.requested; @@ -4597,8 +4569,8 @@ TEST(SharedExponentRGB9E5Test, RawPackedPixelTransferCoversOnlyIdenticalLayouts) EXPECT_FALSE(IsRawPackedPixelTransfer(TextureInternalFormat::R11FG11FB10F, TextureInputFormat::RGB, TexturePixelDataType::UnsignedInt5999Rev)); // So is a component client type, or the same word against a component internal format. - EXPECT_FALSE(IsRawPackedPixelTransfer(TextureInternalFormat::RGB9E5, TextureInputFormat::RGB, - TexturePixelDataType::Float)); + EXPECT_FALSE( + IsRawPackedPixelTransfer(TextureInternalFormat::RGB9E5, TextureInputFormat::RGB, TexturePixelDataType::Float)); EXPECT_FALSE(IsRawPackedPixelTransfer(TextureInternalFormat::RGB8, TextureInputFormat::RGB, TexturePixelDataType::UnsignedInt5999Rev)); EXPECT_FALSE(IsRawPackedPixelTransfer(TextureInternalFormat::RGBA32F, TextureInputFormat::RGBA, @@ -4884,8 +4856,8 @@ TEST_F(TextureTest, BindSamplerRejectsUnitsBeyondMaxCombinedTextureImageUnits) { EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(kCombinedUnits).GetSamplerObject(), nullptr); // Past the unit array as well is the same error, not an out-of-bounds index. - MG_Impl::GLImpl::BindSampler( - static_cast(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) + 4u, sampler); + MG_Impl::GLImpl::BindSampler(static_cast(MG_State::GLState::TextureState::MAX_TEXTURE_IMAGE_UNITS) + 4u, + sampler); ExpectSingleGlError(GL_INVALID_VALUE); // Both gates now read the same limit: a multi-bind that ends exactly at it binds, and one that @@ -4971,11 +4943,11 @@ TEST_F(TextureTest, ColorAttachableTargetsRequestTheThreeChannelWidening) { // Every image that can be a colour attachment, not just the multisample pair: an ordinary 2D // texture is what Iris attaches, and it used to be excluded. - for (const TextureTarget target : {TextureTarget::Texture2D, TextureTarget::Texture3D, - TextureTarget::TextureCubeMap, TextureTarget::Texture2DArray, - TextureTarget::TextureCubeMapArray, TextureTarget::Texture2DMultisample, - TextureTarget::Texture2DMultisampleArray, TextureTarget::Texture1D, - TextureTarget::Texture1DArray, TextureTarget::TextureRectangle}) { + for (const TextureTarget target : + {TextureTarget::Texture2D, TextureTarget::Texture3D, TextureTarget::TextureCubeMap, + TextureTarget::Texture2DArray, TextureTarget::TextureCubeMapArray, TextureTarget::Texture2DMultisample, + TextureTarget::Texture2DMultisampleArray, TextureTarget::Texture1D, TextureTarget::Texture1DArray, + TextureTarget::TextureRectangle}) { const SizeT targetIndex = MobileGL::MG_Backend::GetFormatCapabilityTargetIndex(target); EXPECT_TRUE(TargetRequiresRenderableFormat(targetIndex)) << "target " << MG_Util::ConvertTextureTargetToString(target); @@ -5054,9 +5026,9 @@ TEST_F(TextureTest, SnormRenderTargetOptionsApplyToEverySignedNormalizedFormat) // GL_RGB16_SNORM used to be granted the 16-bit bit only when the three-channel widening was // requested alongside it, which made the answer depend on the order the caller assembled its // option set in. The capability probe and the runtime storage choice assemble different sets. - EXPECT_TRUE(GetApplicablePixelFormatNormalizeOptions(GL_RGB16_SNORM, - PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) & - PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget); + EXPECT_TRUE( + GetApplicablePixelFormatNormalizeOptions(GL_RGB16_SNORM, PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget) & + PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget); // Nothing else responds to either bit; an unsigned-normalized or float format keeps its storage. for (const GLenum internalFormat : {GL_R8, GL_R16, GL_RGBA8, GL_RGBA16, GL_RGB16F, GL_RGBA32F, GL_RGB9_E5}) { @@ -5095,8 +5067,8 @@ TEST_F(TextureTest, SnormRenderTargetSubstitutesKeepEveryChannelValueExactly) { {GL_R16_SNORM, snorm16RT | PixelFormatNormalizeOptionBit::NoNorm16, GL_R32F, GL_RED, GL_FLOAT}, {GL_RG16_SNORM, snorm16RT | PixelFormatNormalizeOptionBit::NoSnorm16, GL_RG32F, GL_RG, GL_FLOAT}, {GL_RGBA16_SNORM, - snorm16RT | PixelFormatNormalizeOptionBit::NoNorm16 | PixelFormatNormalizeOptionBit::NoSnorm16, - GL_RGBA32F, GL_RGBA, GL_FLOAT}, + snorm16RT | PixelFormatNormalizeOptionBit::NoNorm16 | PixelFormatNormalizeOptionBit::NoSnorm16, GL_RGBA32F, + GL_RGBA, GL_FLOAT}, // The three-channel formats go on through the widening, which outranks everything. {GL_RGB8_SNORM, snorm8RT | PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget, GL_RGBA16F, GL_RGBA, GL_FLOAT}, @@ -5128,14 +5100,13 @@ TEST_F(TextureTest, SnormRenderTargetSubstitutesKeepEveryChannelValueExactly) { TEST_F(TextureTest, ThreeChannelRenderTargetOptionAppliesToEveryDeniedThreeChannelFormat) { using MG_Util::TextureFormatProcessor::GetApplicablePixelFormatNormalizeOptions; - const Flags requested = - PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; + const Flags requested = PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget; // GL_RGB16F in particular matched no case at all, so no option could ever apply to it and it // fell through NormalizePixelFormat's default passthrough unchanged. - for (const GLenum internalFormat : {GL_RGB8_SNORM, GL_RGB16_SNORM, GL_RGB16, GL_RGB10, GL_RGB12, GL_RGB16F, - GL_RGB32F, GL_SRGB8, GL_RGB8I, GL_RGB8UI, GL_RGB16I, GL_RGB16UI, GL_RGB32I, - GL_RGB32UI}) { + for (const GLenum internalFormat : + {GL_RGB8_SNORM, GL_RGB16_SNORM, GL_RGB16, GL_RGB10, GL_RGB12, GL_RGB16F, GL_RGB32F, GL_SRGB8, GL_RGB8I, + GL_RGB8UI, GL_RGB16I, GL_RGB16UI, GL_RGB32I, GL_RGB32UI}) { EXPECT_TRUE(GetApplicablePixelFormatNormalizeOptions(internalFormat, requested) & PixelFormatNormalizeOptionBit::NoThreeChannelRenderTarget) << "internalformat 0x" << std::hex << internalFormat; @@ -5253,8 +5224,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Float source[] = {0.25f, -0.5f, 0.75f, -1.0f, 0.0f, 1.0f}; Vector widened; - const auto* result = static_cast(PrepareChannelWidenedUpload( - 3, texelSize, source, sizeof(source), GL_FLOAT, widened)); + const auto* result = static_cast( + PrepareChannelWidenedUpload(3, texelSize, source, sizeof(source), GL_FLOAT, widened)); ASSERT_NE(result, static_cast(source)); ASSERT_EQ(widened.size(), 8 * sizeof(Float)); const Float expected[] = {0.25f, -0.5f, 0.75f, 1.0f, -1.0f, 0.0f, 1.0f, 1.0f}; @@ -5268,8 +5239,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Uint16 source[] = {0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006}; Vector widened; - const auto* result = static_cast(PrepareChannelWidenedUpload( - 3, texelSize, source, sizeof(source), GL_HALF_FLOAT, widened)); + const auto* result = static_cast( + PrepareChannelWidenedUpload(3, texelSize, source, sizeof(source), GL_HALF_FLOAT, widened)); ASSERT_NE(result, static_cast(source)); const Uint16 expected[] = {0x0001, 0x0002, 0x0003, 0x3C00, 0x0004, 0x0005, 0x0006, 0x3C00}; for (SizeT i = 0; i < 8; ++i) { @@ -5281,8 +5252,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Uint8 source[] = {1, 2, 3, 4, 5, 6}; Vector widened; - const auto* result = static_cast(PrepareChannelWidenedUpload( - 3, texelSize, source, sizeof(source), GL_UNSIGNED_BYTE, widened)); + const auto* result = static_cast( + PrepareChannelWidenedUpload(3, texelSize, source, sizeof(source), GL_UNSIGNED_BYTE, widened)); const Uint8 expected[] = {1, 2, 3, 0xFF, 4, 5, 6, 0xFF}; ASSERT_NE(result, static_cast(source)); EXPECT_EQ(std::memcmp(result, expected, sizeof(expected)), 0); @@ -5292,8 +5263,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Int16 source[] = {-1, 2, -3, 4, -5, 6}; Vector widened; - const auto* result = static_cast(PrepareChannelWidenedUpload( - 3, texelSize, source, sizeof(source), GL_SHORT, widened)); + const auto* result = static_cast( + PrepareChannelWidenedUpload(3, texelSize, source, sizeof(source), GL_SHORT, widened)); const Int16 expected[] = {-1, 2, -3, 0x7FFF, 4, -5, 6, 0x7FFF}; ASSERT_NE(result, static_cast(source)); EXPECT_EQ(std::memcmp(result, expected, sizeof(expected)), 0); @@ -5318,8 +5289,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Int8 source[] = {-1, 2, -3, 4, -5, 6}; Vector widened; - const auto* asInteger = static_cast(PrepareChannelWidenedUpload( - 3, texelSize, source, sizeof(source), GL_BYTE, widened, /*integerData=*/true)); + const auto* asInteger = static_cast( + PrepareChannelWidenedUpload(3, texelSize, source, sizeof(source), GL_BYTE, widened, /*integerData=*/true)); const Int8 expectedInteger[] = {-1, 2, -3, 1, 4, -5, 6, 1}; ASSERT_NE(asInteger, static_cast(source)); EXPECT_EQ(std::memcmp(asInteger, expectedInteger, sizeof(expectedInteger)), 0); @@ -5352,8 +5323,8 @@ TEST_F(TextureTest, WidenedRenderTargetUploadExpandsThreeChannelDataWithOpaqueAl { const Float shortSource[] = {0.5f, 0.25f, 0.125f}; Vector widened; - const auto* result = static_cast(PrepareChannelWidenedUpload( - 3, IntVec3(2, 2, 1), shortSource, sizeof(shortSource), GL_FLOAT, widened)); + const auto* result = static_cast( + PrepareChannelWidenedUpload(3, IntVec3(2, 2, 1), shortSource, sizeof(shortSource), GL_FLOAT, widened)); ASSERT_NE(result, static_cast(shortSource)); ASSERT_EQ(widened.size(), 4 * 4 * sizeof(Float)); const Float expected[] = {0.5f, 0.25f, 0.125f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, @@ -5511,8 +5482,7 @@ TEST_F(TextureTest, GetTexLevelParameterReportsBufferDataStoreBinding) { EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(binding, static_cast(buffer)); GLfloat bindingFloat = -1.0f; - MG_Impl::GLImpl::GetTexLevelParameterfv(GL_TEXTURE_BUFFER, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, - &bindingFloat); + MG_Impl::GLImpl::GetTexLevelParameterfv(GL_TEXTURE_BUFFER, 0, GL_TEXTURE_BUFFER_DATA_STORE_BINDING, &bindingFloat); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(bindingFloat, static_cast(buffer)); @@ -5566,7 +5536,8 @@ TEST_F(TextureTest, TexStorage2DTagsEveryLevelForASpecificCompressedFormat) { // ...and the sub-image call the whole arrangement exists for now reaches both levels. Uint8 blocks[kRgtc1Size8x8]; - for (Int i = 0; i < kRgtc1Size8x8; ++i) blocks[i] = static_cast(0x40 + i); + for (Int i = 0; i < kRgtc1Size8x8; ++i) + blocks[i] = static_cast(0x40 + i); MG_Impl::GLImpl::CompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_COMPRESSED_RED_RGTC1, kRgtc1Size8x8, blocks); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -5611,11 +5582,10 @@ namespace { Bool DstIsRenderbuffer = false; } g_copyImageSubDataCall; - void RecordCopyImageSubData(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, - GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, - const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, - GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, - GLsizei srcHeight, GLsizei srcDepth) { + void RecordCopyImageSubData(const MG_Backend::CopyImageEndpoint& src, GLenum srcTarget, GLint srcLevel, GLint srcX, + GLint srcY, GLint srcZ, const MG_Backend::CopyImageEndpoint& dst, GLenum dstTarget, + GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, + GLsizei srcDepth) { (void)srcLevel; (void)srcX; (void)srcY; @@ -5624,8 +5594,8 @@ namespace { (void)dstY; (void)srcWidth; (void)srcHeight; - g_copyImageSubDataCall = {true, srcTarget, dstTarget, srcZ, - dstZ, srcDepth, src.IsRenderbuffer(), dst.IsRenderbuffer()}; + g_copyImageSubDataCall = { + true, srcTarget, dstTarget, srcZ, dstZ, srcDepth, src.IsRenderbuffer(), dst.IsRenderbuffer()}; } // Two storage-backed 2D textures of the requested formats, so a copy between them is a legal @@ -5653,8 +5623,8 @@ TEST_F(TextureTest, CopyImageSubDataAcceptsEqualTexelSizeAcrossDifferentBaseForm MakeCopyImagePair(GL_RGB10_A2, GL_R11F_G11F_B10F, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -5671,8 +5641,8 @@ TEST_F(TextureTest, CopyImageSubDataRejectsDifferentTexelSizesWithTheSameBaseFor MakeCopyImagePair(GL_RGBA8, GL_RGBA32F, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_OPERATION); } @@ -5688,8 +5658,8 @@ TEST_F(TextureTest, CopyImageSubDataAcceptsIntegerAndFloatOfTheSameTexelSize) { MakeCopyImagePair(GL_RGBA32UI, GL_RGBA32F, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -5719,8 +5689,8 @@ TEST_F(TextureTest, CopyImageSubDataTargetNotMatchingTheObjectIsInvalidEnum) { MakeCopyImagePair(GL_RGBA8, GL_RGBA8, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D_ARRAY, 0, 0, - 0, 0, 1, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, + 0, 1, 1, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_ENUM); } @@ -5737,8 +5707,8 @@ TEST_F(TextureTest, CopyImageSubDataRejectsTargetsOutsideTheSpecList) { MakeCopyImagePair(GL_RGBA8, GL_RGBA8, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, dstTexture, - GL_TEXTURE_2D, 0, 0, 0, 0, 1, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, + 0, 0, 0, 0, 1, 1, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_ENUM); } @@ -5755,8 +5725,8 @@ TEST_F(TextureTest, CopyImageSubDataRejectsLevelTheImageDoesNotHave) { MakeCopyImagePair(GL_RGBA8, GL_RGBA8, srcTexture, dstTexture); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 1, 0, 0, 0, - 1, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 1, 0, 0, 0, 1, + 1, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -5812,8 +5782,8 @@ TEST_F(TextureTest, CopyImageSubDataForwardsTheWholeLayerRangeToTheBackend) { MG_Impl::GLImpl::TextureStorage3D(dstTexture, 1, GL_RGBA8, 8, 8, 12); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, dstTexture, GL_TEXTURE_2D_ARRAY, - 0, 0, 0, 5, 4, 4, 7); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, dstTexture, GL_TEXTURE_2D_ARRAY, 0, + 0, 0, 5, 4, 4, 7); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(g_copyImageSubDataCall.SrcZ, 2); EXPECT_EQ(g_copyImageSubDataCall.DstZ, 5); @@ -5844,8 +5814,8 @@ TEST_F(TextureTest, CopyImageSubDataAcceptsAPlainMutableTexImage2DPair) { MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 1, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 1, + 1, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); @@ -5871,8 +5841,8 @@ TEST_F(TextureTest, CopyImageSubDataAcceptsAPlainMutableTexImage2DPair) { MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(reusedSrc, GL_TEXTURE_2D, 0, 0, 0, 0, reusedDst, GL_TEXTURE_2D, 0, 0, 0, 0, 1, - 1, 1); + MG_Impl::GLImpl::CopyImageSubData(reusedSrc, GL_TEXTURE_2D, 0, 0, 0, 0, reusedDst, GL_TEXTURE_2D, 0, 0, 0, 0, 1, 1, + 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -5893,8 +5863,8 @@ TEST_F(TextureTest, CopyImageSubDataPassesTheRectangleTargetThroughUntranslated) MG_Impl::GLImpl::TextureStorage2D(dstTexture, 1, GL_RGBA8, 8, 8); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_RECTANGLE, 0, 0, 0, 0, dstTexture, GL_TEXTURE_RECTANGLE, - 0, 0, 0, 0, 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_RECTANGLE, 0, 0, 0, 0, dstTexture, GL_TEXTURE_RECTANGLE, 0, + 0, 0, 0, 4, 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(g_copyImageSubDataCall.SrcTarget, static_cast(GL_TEXTURE_RECTANGLE)); EXPECT_EQ(g_copyImageSubDataCall.DstTarget, static_cast(GL_TEXTURE_RECTANGLE)); @@ -5919,8 +5889,8 @@ TEST_F(TextureTest, CopyImageSubDataResolvesARenderbufferEndpointInTheRenderbuff MG_Impl::GLImpl::NamedRenderbufferStorage(renderbuffer, GL_RGBA8, 8, 8); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_FALSE(g_copyImageSubDataCall.SrcIsRenderbuffer); EXPECT_TRUE(g_copyImageSubDataCall.DstIsRenderbuffer); @@ -5930,8 +5900,8 @@ TEST_F(TextureTest, CopyImageSubDataResolvesARenderbufferEndpointInTheRenderbuff // ...and back the other way, which is the second half of the conformance case's two-copy // shape (texture -> renderbuffer -> texture). g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_TRUE(g_copyImageSubDataCall.SrcIsRenderbuffer); EXPECT_FALSE(g_copyImageSubDataCall.DstIsRenderbuffer); @@ -5954,16 +5924,16 @@ TEST_F(TextureTest, CopyImageSubDataKeepsTheTwoNameNamespacesApart) { MG_Impl::GLImpl::NamedRenderbufferStorage(dstRenderbuffer, GL_RGBA8, 8, 8); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(srcRenderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, dstRenderbuffer, - GL_RENDERBUFFER, 0, 0, 0, 0, 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcRenderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, dstRenderbuffer, GL_RENDERBUFFER, 0, + 0, 0, 0, 4, 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_TRUE(g_copyImageSubDataCall.SrcIsRenderbuffer); EXPECT_TRUE(g_copyImageSubDataCall.DstIsRenderbuffer); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcRenderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, 4243, GL_RENDERBUFFER, 0, 0, 0, - 0, 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcRenderbuffer, GL_RENDERBUFFER, 0, 0, 0, 0, 4243, GL_RENDERBUFFER, 0, 0, 0, 0, + 4, 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -5984,8 +5954,8 @@ TEST_F(TextureTest, CopyImageSubDataChecksARenderbufferLevelAndStorage) { MG_Impl::GLImpl::NamedRenderbufferStorage(renderbuffer, GL_RGBA8, 8, 8); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 1, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 1, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); @@ -5994,8 +5964,8 @@ TEST_F(TextureTest, CopyImageSubDataChecksARenderbufferLevelAndStorage) { MG_Impl::GLImpl::CreateRenderbuffers(1, &emptyRenderbuffer); DrainPendingGlErrors(); - MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, emptyRenderbuffer, GL_RENDERBUFFER, 0, 0, - 0, 0, 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, emptyRenderbuffer, GL_RENDERBUFFER, 0, 0, 0, + 0, 4, 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_OPERATION); } @@ -6019,34 +5989,34 @@ TEST_F(TextureTest, CopyImageSubDataRejectsARegionThatLeavesTheImage) { // The region that exactly reaches the far edge is the boundary this must NOT reject - a // validator that answered INVALID_VALUE to every non-origin region would satisfy the negatives // below and break every legal partial copy. - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 4, 4, 0, dstTexture, GL_TEXTURE_2D, 0, 4, 4, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 4, 4, 0, dstTexture, GL_TEXTURE_2D, 0, 4, 4, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // One texel past it on x, on y, and on the destination side. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 5, 4, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 5, 4, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 4, 5, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 4, 5, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 5, 5, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 5, 5, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); // A negative origin is out of bounds on the other side of the same rule. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, -1, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D, 0, -1, 0, 0, dstTexture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -6068,28 +6038,28 @@ TEST_F(TextureTest, CopyImageSubDataBoundsARenderbufferRegion) { MG_Impl::GLImpl::NamedRenderbufferStorage(renderbuffer, GL_RGBA8, 16, 16); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 12, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 12, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 14, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 14, 0, texture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); // ...and as the destination, where the same renderbuffer has the same one image. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 0, 14, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(texture, GL_TEXTURE_2D, 0, 0, 0, 0, renderbuffer, GL_RENDERBUFFER, 0, 14, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); // A renderbuffer has exactly one slice, so any z at all is out of range. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 1, texture, GL_TEXTURE_2D, 0, 0, 0, 0, - 4, 4, 1); + MG_Impl::GLImpl::CopyImageSubData(renderbuffer, GL_RENDERBUFFER, 0, 0, 0, 1, texture, GL_TEXTURE_2D, 0, 0, 0, 0, 4, + 4, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -6110,20 +6080,20 @@ TEST_F(TextureTest, CopyImageSubDataBoundsTheLayerRangeOfAnArray) { ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // Layers 5..11 of a 12-layer array: the last one the range may reach. - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 5, dstTexture, GL_TEXTURE_2D_ARRAY, - 0, 0, 0, 5, 4, 4, 7); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 5, dstTexture, GL_TEXTURE_2D_ARRAY, 0, + 0, 0, 5, 4, 4, 7); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 6, dstTexture, GL_TEXTURE_2D_ARRAY, - 0, 0, 0, 0, 4, 4, 7); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 6, dstTexture, GL_TEXTURE_2D_ARRAY, 0, + 0, 0, 0, 4, 4, 7); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D_ARRAY, - 0, 0, 0, 6, 4, 4, 7); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, dstTexture, GL_TEXTURE_2D_ARRAY, 0, + 0, 0, 6, 4, 4, 7); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -6153,15 +6123,15 @@ TEST_F(TextureTest, CopyImageSubDataCountsCubeMapFacesOnTheZAxis) { GTEST_SKIP() << "this context could not give the cube maps storage"; } - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP, 0, 0, 0, 0, dstTexture, GL_TEXTURE_CUBE_MAP, - 0, 0, 0, 0, 8, 8, 6); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP, 0, 0, 0, 0, dstTexture, GL_TEXTURE_CUBE_MAP, 0, + 0, 0, 0, 8, 8, 6); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // A seventh face does not exist. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP, 0, 0, 0, 1, dstTexture, GL_TEXTURE_CUBE_MAP, - 0, 0, 0, 0, 8, 8, 6); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_CUBE_MAP, 0, 0, 0, 1, dstTexture, GL_TEXTURE_CUBE_MAP, 0, + 0, 0, 0, 8, 8, 6); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -6196,15 +6166,15 @@ TEST_F(TextureTest, CopyImageSubDataBoundsA1DArraysLayersOnTheZAxis) { // 16 wide, 8 layers. Five layers from layer 3 is legal, and it is spelled on z with a // height of 1 - the layer count rides on srcDepth. - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 3, dstTexture, GL_TEXTURE_1D_ARRAY, - 0, 0, 0, 3, 4, 1, 5); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 3, dstTexture, GL_TEXTURE_1D_ARRAY, 0, + 0, 0, 3, 4, 1, 5); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); // One layer past the last one is out of bounds on z. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 4, dstTexture, GL_TEXTURE_1D_ARRAY, - 0, 0, 0, 0, 4, 1, 5); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 4, dstTexture, GL_TEXTURE_1D_ARRAY, 0, + 0, 0, 0, 4, 1, 5); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); @@ -6212,15 +6182,15 @@ TEST_F(TextureTest, CopyImageSubDataBoundsA1DArraysLayersOnTheZAxis) { // bounds - this is the KHR-GL43.copy_image case that used to be measured against the 8 // layers and pass. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 6, 0, dstTexture, GL_TEXTURE_1D_ARRAY, - 0, 0, 6, 0, 4, 1, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 6, 0, dstTexture, GL_TEXTURE_1D_ARRAY, 0, + 0, 6, 0, 4, 1, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); // ... and a height of 1 at y = 0 is the only legal y extent. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 0, dstTexture, GL_TEXTURE_1D_ARRAY, - 0, 0, 0, 0, 4, 2, 1); + MG_Impl::GLImpl::CopyImageSubData(srcTexture, GL_TEXTURE_1D_ARRAY, 0, 0, 0, 0, dstTexture, GL_TEXTURE_1D_ARRAY, 0, + 0, 0, 0, 4, 2, 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_VALUE); } @@ -6237,8 +6207,8 @@ TEST_F(TextureTest, CopyImageSubDataSizesACompressedArrayLevelByItsBlock) { GLuint compressedSource = 0; MG_Impl::GLImpl::GenTextures(1, &compressedSource); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, compressedSource); - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 1, 0, GL_RG, - GL_UNSIGNED_BYTE, nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RG_RGTC2, 8, 8, 1, 0, GL_RG, GL_UNSIGNED_BYTE, + nullptr); MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BASE_LEVEL, 0); MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, 0); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, 0); @@ -6274,15 +6244,15 @@ TEST_F(TextureTest, CopyImageSubDataRejectsAMipmapIncompleteTexture) { MG_Impl::GLImpl::TextureStorage2D(complete, 1, GL_RGBA8, 16, 16); ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); - MG_Impl::GLImpl::CopyImageSubData(incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, complete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, - 4, 1); + MG_Impl::GLImpl::CopyImageSubData(incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, complete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, + 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_OPERATION); // The destination side is checked the same way. g_copyImageSubDataCall = {}; - MG_Impl::GLImpl::CopyImageSubData(complete, GL_TEXTURE_2D, 0, 0, 0, 0, incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, - 4, 1); + MG_Impl::GLImpl::CopyImageSubData(complete, GL_TEXTURE_2D, 0, 0, 0, 0, incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, + 1); EXPECT_FALSE(g_copyImageSubDataCall.Called); ExpectSingleGlError(GL_INVALID_OPERATION); @@ -6295,8 +6265,8 @@ TEST_F(TextureTest, CopyImageSubDataRejectsAMipmapIncompleteTexture) { MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0); DrainPendingGlErrors(); - MG_Impl::GLImpl::CopyImageSubData(incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, complete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, - 4, 1); + MG_Impl::GLImpl::CopyImageSubData(incomplete, GL_TEXTURE_2D, 0, 0, 0, 0, complete, GL_TEXTURE_2D, 0, 0, 0, 0, 4, 4, + 1); EXPECT_TRUE(g_copyImageSubDataCall.Called); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } @@ -6464,8 +6434,7 @@ TEST_F(TextureTest, ImageWidenedUploadExpandsOneAndTwoChannelDataWithGLsMissingC { const Uint8 source[] = {1, 2, 3, 4, 5, 6, 7, 8}; Vector widened; - EXPECT_EQ(PrepareChannelWidenedUpload(4, texelSize, source, sizeof(source), GL_UNSIGNED_BYTE, widened, - false), + EXPECT_EQ(PrepareChannelWidenedUpload(4, texelSize, source, sizeof(source), GL_UNSIGNED_BYTE, widened, false), static_cast(source)); EXPECT_TRUE(widened.empty()); } @@ -6492,8 +6461,8 @@ TEST_F(TextureTest, ImageWidenedUploadSplitsAPacked2101010RevShadowIntoFourChann 0u | (1023u << 10) | (0u << 20) | (0u << 30), }; Vector widened; - const auto* result = static_cast( - PreparePackedIntWidenedUpload(texelSize, source, sizeof(source), widened)); + const auto* result = + static_cast(PreparePackedIntWidenedUpload(texelSize, source, sizeof(source), widened)); ASSERT_NE(result, static_cast(source)); ASSERT_EQ(widened.size(), 12 * sizeof(Uint16)); const Uint16 expected[] = {1, 2, 3, 1, 1023, 0, 1023, 3, 0, 1023, 0, 0}; @@ -6505,8 +6474,8 @@ TEST_F(TextureTest, ImageWidenedUploadSplitsAPacked2101010RevShadowIntoFourChann // for the transfer it was handed, so a short source still has to leave a full destination. { Vector shortWidened; - const auto* shortResult = static_cast( - PreparePackedIntWidenedUpload(texelSize, source, sizeof(Uint32), shortWidened)); + const auto* shortResult = + static_cast(PreparePackedIntWidenedUpload(texelSize, source, sizeof(Uint32), shortWidened)); ASSERT_EQ(shortWidened.size(), 12 * sizeof(Uint16)); for (SizeT i = 4; i < 12; ++i) { EXPECT_EQ(shortResult[i], 0u) << "component " << i << " past the source must be zero"; @@ -6538,13 +6507,11 @@ TEST_F(TextureTest, TexImage3DAppliesTheCubeMapArrayShapeRules) { DrainPendingGlErrors(); // Non-square faces are GL_INVALID_VALUE. - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 8, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 8, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); ExpectSingleGlError(GL_INVALID_VALUE); // A depth that is not a whole number of cubes is GL_INVALID_VALUE. - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 5, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 5, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); ExpectSingleGlError(GL_INVALID_VALUE); // The legal shape still goes through untouched. @@ -6644,8 +6611,7 @@ TEST_F(TextureTest, CopyTexSubImage3DValidatesTheDestinationRegionOnACubeMapArra GLuint texture = 0; MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture); - MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, - nullptr); + MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA8, 4, 4, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); DrainPendingGlErrors(); // A negative level is GL_INVALID_VALUE, and reaching it at all proves the entry point now @@ -6871,7 +6837,7 @@ namespace { // GL context, so the hook is stubbed for the duration of the case. What is under test is the // frontend allocation the stub cannot influence. struct ScopedNoOpGenerateMipmap { - ScopedNoOpGenerateMipmap(): m_snapshot(MobileGL::MG_Backend::gBackendFunctionsTable) { + ScopedNoOpGenerateMipmap() : m_snapshot(MobileGL::MG_Backend::gBackendFunctionsTable) { MobileGL::MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap = [](GLenum) {}; } ~ScopedNoOpGenerateMipmap() { MobileGL::MG_Backend::gBackendFunctionsTable = m_snapshot; } @@ -7041,20 +7007,24 @@ namespace { // GL_COMPRESSED_RGB8_ETC2 image (2x2 blocks) and the RGBA8 image it was compressed from. // Texel row y has G = 32 * y and column x has B = 32 * x (the suite draws the +Z face, whose // t runs against window y, and compares bottom-up, so its table reads G = 224 first). - constexpr Uint8 kEtc2Rgb8x8[32] = {0x0, 0x0, 0x4, 0x2, 0x1, 0x0, 0x6f, 0xc0, 0x0, 0x1, 0x4, - 0x2, 0x1, 0xf8, 0x6f, 0xe0, 0x1, 0x0, 0x4, 0x2, 0x80, 0xf8, - 0x5f, 0xc0, 0x1, 0x0, 0xfb, 0x82, 0x81, 0xf8, 0x5f, 0xe0}; + constexpr Uint8 kEtc2Rgb8x8[32] = {0x0, 0x0, 0x4, 0x2, 0x1, 0x0, 0x6f, 0xc0, 0x0, 0x1, 0x4, + 0x2, 0x1, 0xf8, 0x6f, 0xe0, 0x1, 0x0, 0x4, 0x2, 0x80, 0xf8, + 0x5f, 0xc0, 0x1, 0x0, 0xfb, 0x82, 0x81, 0xf8, 0x5f, 0xe0}; - Int ReferenceGreen(Int y) { return 32 * y; } - Int ReferenceBlue(Int x) { return 32 * x; } + Int ReferenceGreen(Int y) { + return 32 * y; + } + Int ReferenceBlue(Int x) { + return 32 * x; + } } // namespace // The decoder against an image a reference encoder produced; the suite's own tolerance for it is // 5% (13 of 255), which ETC2's quantisation needs. TEST(EtcDecoder, DecodesTheConformanceSuitesEtc2RgbImage) { Uint8 texels[8 * 8 * 3] = {}; - ASSERT_TRUE(MG_Util::EtcDecoder::DecodeRegion(GL_COMPRESSED_RGB8_ETC2, kEtc2Rgb8x8, 8, 8, 1, texels, 8, 8, 1, 0, - 0, 0)); + ASSERT_TRUE( + MG_Util::EtcDecoder::DecodeRegion(GL_COMPRESSED_RGB8_ETC2, kEtc2Rgb8x8, 8, 8, 1, texels, 8, 8, 1, 0, 0, 0)); for (Int y = 0; y < 8; ++y) { for (Int x = 0; x < 8; ++x) { const Uint8* t = texels + (y * 8 + x) * 3; @@ -7074,7 +7044,8 @@ TEST(EtcDecoder, DecodesAnEacAlphaBlockAndAnIndividualModeColourBlock) { block[1] = 0x10; // multiplier 1, table 0 // Every 3-bit index = 4 (0b100): the pattern 100 repeated over 48 bits. const Uint64 indices = 0x924924924924ull; - for (Int i = 0; i < 6; ++i) block[2 + i] = static_cast(indices >> (40 - 8 * i)); + for (Int i = 0; i < 6; ++i) + block[2 + i] = static_cast(indices >> (40 - 8 * i)); // Colour: individual mode, both subblocks 0x8 per channel, codewords 0, all indices 0. block[8] = 0x88; block[9] = 0x88; diff --git a/MobileGL/MG_Test/Texture/TextureViewTest.cpp b/MobileGL/MG_Test/Texture/TextureViewTest.cpp index 3600934f..7fb7d23a 100644 --- a/MobileGL/MG_Test/Texture/TextureViewTest.cpp +++ b/MobileGL/MG_Test/Texture/TextureViewTest.cpp @@ -66,8 +66,7 @@ namespace { class ScopedBackendOverride { public: - explicit ScopedBackendOverride(Bool advertiseTextureView) - : m_previous(Move(MG_Backend::pActiveBackendObject)) { + explicit ScopedBackendOverride(Bool advertiseTextureView) : m_previous(Move(MG_Backend::pActiveBackendObject)) { MG_Backend::pActiveBackendObject = MakeUnique(advertiseTextureView); } ~ScopedBackendOverride() { MG_Backend::pActiveBackendObject = Move(m_previous); } @@ -83,8 +82,7 @@ namespace { // GetError() call - which silently turns error-code assertions into reads of someone // else's error. Bounded because there is one flag per code. static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } // The call under test must raise exactly the expected error and nothing more: a second @@ -294,21 +292,21 @@ namespace { // ============================ the error surface ============================ // KHR-GL43.texture_view.errors walks these in this order; the letters are its own. - TEST_F(TextureViewTest, ZeroTextureIsInvalidValue) { // (a) + TEST_F(TextureViewTest, ZeroTextureIsInvalidValue) { // (a) const GLuint storage = MakeImmutable2D(); DrainPendingGlErrors(); MG_Impl::GLImpl::TextureView(0, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, TextureThatGenTexturesNeverReturnedIsInvalidOperation) { // (b) + TEST_F(TextureViewTest, TextureThatGenTexturesNeverReturnedIsInvalidOperation) { // (b) const GLuint storage = MakeImmutable2D(); DrainPendingGlErrors(); MG_Impl::GLImpl::TextureView(0xFFFFFFFFu, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, AlreadyBoundTextureIsInvalidOperation) { // (c) + TEST_F(TextureViewTest, AlreadyBoundTextureIsInvalidOperation) { // (c) const GLuint storage = MakeImmutable2D(); const GLuint alreadyBound = GenTexture(); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, alreadyBound); @@ -318,7 +316,7 @@ namespace { ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, OrigTextureThatIsNotATextureObjectIsInvalidValue) { // (d) + TEST_F(TextureViewTest, OrigTextureThatIsNotATextureObjectIsInvalidValue) { // (d) const GLuint view = GenTexture(); DrainPendingGlErrors(); // Note the code differs from (b): INVALID_VALUE here, INVALID_OPERATION there. @@ -326,7 +324,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, MutableOrigTextureIsInvalidOperation) { // (e) + TEST_F(TextureViewTest, MutableOrigTextureIsInvalidOperation) { // (e) const GLuint mutableTexture = GenTexture(); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, mutableTexture); MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); @@ -337,7 +335,7 @@ namespace { ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, IncompatibleTargetPairIsInvalidOperation) { // (f) + TEST_F(TextureViewTest, IncompatibleTargetPairIsInvalidOperation) { // (f) const GLuint storage = MakeImmutable2D(); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -346,7 +344,7 @@ namespace { ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, LegalTargetPairsAreAccepted) { // (f), the other way round + TEST_F(TextureViewTest, LegalTargetPairsAreAccepted) { // (f), the other way round const GLuint storage = MakeImmutable2D(1, 16, 16); const GLuint sameTarget = GenTexture(); MG_Impl::GLImpl::TextureView(sameTarget, GL_TEXTURE_2D, storage, GL_RGBA8, 0, 1, 0, 1); @@ -356,7 +354,7 @@ namespace { ExpectSingleGlError(GL_NO_ERROR); } - TEST_F(TextureViewTest, FormatFromAnotherViewClassIsInvalidOperation) { // (g) + TEST_F(TextureViewTest, FormatFromAnotherViewClassIsInvalidOperation) { // (g) const GLuint storage = MakeImmutable2D(1, 16, 16, GL_RGBA8); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -365,7 +363,7 @@ namespace { ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, FormatFromTheSameViewClassIsAccepted) { // (g), the other way round + TEST_F(TextureViewTest, FormatFromTheSameViewClassIsAccepted) { // (g), the other way round const GLuint storage = MakeImmutable2D(1, 16, 16, GL_RGBA8); const GLuint view = GenTexture(); // Both VIEW_CLASS_32_BITS. @@ -379,7 +377,7 @@ namespace { EXPECT_EQ(internalFormat, GL_R32UI) << "the view must take the format it was asked for, not its parent's"; } - TEST_F(TextureViewTest, ClasslessFormatMayOnlyBeViewedAsItself) { // (h) + TEST_F(TextureViewTest, ClasslessFormatMayOnlyBeViewedAsItself) { // (h) // Depth, stencil and depth/stencil formats have NO entry in table 8.21, which the spec // turns into a stricter rule than "same class": the view's format must be IDENTICAL. // This is the rule the Better Clouds D24S8 view depends on being permissive enough. @@ -393,7 +391,7 @@ namespace { ExpectSingleGlError(GL_INVALID_OPERATION); } - TEST_F(TextureViewTest, MinLevelPastTheLastLevelIsInvalidValue) { // (i) + TEST_F(TextureViewTest, MinLevelPastTheLastLevelIsInvalidValue) { // (i) const GLuint storage = MakeImmutable2D(2, 16, 16); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -401,7 +399,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, MinLayerPastTheLastLayerIsInvalidValue) { // (j) + TEST_F(TextureViewTest, MinLayerPastTheLastLayerIsInvalidValue) { // (j) const GLuint storage = MakeImmutable2DArray(1, 16, 4); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -409,7 +407,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, CubeMapViewDemandsExactlySixLayers) { // (k) + TEST_F(TextureViewTest, CubeMapViewDemandsExactlySixLayers) { // (k) const GLuint storage = MakeImmutable2DArray(1, 16, 6); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -417,7 +415,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, CubeMapArrayViewDemandsAMultipleOfSixLayers) { // (l) + TEST_F(TextureViewTest, CubeMapArrayViewDemandsAMultipleOfSixLayers) { // (l) const GLuint storage = MakeImmutable2DArray(1, 16, 12); const GLuint view = GenTexture(); DrainPendingGlErrors(); @@ -425,7 +423,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, SingleLayerTargetsRejectMoreThanOneLayer) { // (m).. (q) + TEST_F(TextureViewTest, SingleLayerTargetsRejectMoreThanOneLayer) { // (m).. (q) const GLuint storage = MakeImmutable2DArray(1, 16, 4); DrainPendingGlErrors(); for (const GLenum target : {GL_TEXTURE_2D}) { @@ -444,7 +442,7 @@ namespace { ExpectSingleGlError(GL_INVALID_VALUE); } - TEST_F(TextureViewTest, CubeMapViewDemandsSquareLevels) { // (r) + TEST_F(TextureViewTest, CubeMapViewDemandsSquareLevels) { // (r) const GLuint storage = GenTexture(); MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D_ARRAY, storage); MG_Impl::GLImpl::TexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 32, 33, 6); diff --git a/MobileGL/MG_Test/Util/ConfigFileTest.cpp b/MobileGL/MG_Test/Util/ConfigFileTest.cpp index 6ed76f71..2cc286fb 100644 --- a/MobileGL/MG_Test/Util/ConfigFileTest.cpp +++ b/MobileGL/MG_Test/Util/ConfigFileTest.cpp @@ -33,10 +33,12 @@ namespace { // Every variable a case touches, restored after it. constexpr const char* kTouched[] = { - "MOBILEGL_CONFIG_FILE", "MOBILEGL_BACKEND_FILE", "MOBILEGL_BACKEND_TYPE", - "MOBILEGL_MAGMA_FRAMESINFLIGHT", "MOBILEGL_TRANSPORT", "MOBILEGL_IPC_CONTROL", - "MOBILEGL_IPC_DATA", "MOBILEGL_IPC_ROLE", "MOBILEGL_TEST_ONLY_KEY", - "MOBILEGL_FRAMES_IN_FLIGHT", "MOBILEGL_IPC_PRESENT_CREDIT", + "MOBILEGL_CONFIG_FILE", "MOBILEGL_BACKEND_FILE", + "MOBILEGL_BACKEND_TYPE", "MOBILEGL_MAGMA_FRAMESINFLIGHT", + "MOBILEGL_TRANSPORT", "MOBILEGL_IPC_CONTROL", + "MOBILEGL_IPC_DATA", "MOBILEGL_IPC_ROLE", + "MOBILEGL_TEST_ONLY_KEY", "MOBILEGL_FRAMES_IN_FLIGHT", + "MOBILEGL_IPC_PRESENT_CREDIT", }; class ConfigFileTest : public ::testing::Test { @@ -60,8 +62,10 @@ namespace { } void TearDown() override { for (const auto& [name, had, value] : saved_) { - if (had) ::setenv(name.c_str(), value.c_str(), 1); - else ::unsetenv(name.c_str()); + if (had) + ::setenv(name.c_str(), value.c_str(), 1); + else + ::unsetenv(name.c_str()); } std::error_code ec; std::filesystem::remove_all(dir_, ec); diff --git a/MobileGL/MG_Test/Util/DamageTest.cpp b/MobileGL/MG_Test/Util/DamageTest.cpp index de74f221..ca6b770a 100644 --- a/MobileGL/MG_Test/Util/DamageTest.cpp +++ b/MobileGL/MG_Test/Util/DamageTest.cpp @@ -37,7 +37,9 @@ namespace { return true; } - Region Of(std::initializer_list rects) { return Region::FromRects(rects.begin(), rects.size()); } + Region Of(std::initializer_list rects) { + return Region::FromRects(rects.begin(), rects.size()); + } } // namespace // EGL damage (origin bottom-left) -> wl_surface.damage_buffer / shared-image rows (origin top-left). @@ -87,12 +89,14 @@ TEST(Damage, NormalizeClipsDropsAndPromotesToFull) { TEST(Damage, NormalizeMergesDownToTheLimitAndStillCoversEverything) { Vector input; - for (Int32 i = 0; i < 40; ++i) input.push_back({(i * 37) % 900, (i * 53) % 600, 9 + i % 5, 7 + i % 3}); + for (Int32 i = 0; i < 40; ++i) + input.push_back({(i * 37) % 900, (i * 53) % 600, 9 + i % 5, 7 + i % 3}); Region region = Region::FromRects(input.data(), input.size()); region.Normalize(1000, 700, kMaxRects); ASSERT_FALSE(region.IsFull()); EXPECT_LE(region.Rects().size(), kMaxRects); - for (const Rect& r : input) EXPECT_TRUE(Covers(region, r, 1000, 700)) << r.X << "," << r.Y; + for (const Rect& r : input) + EXPECT_TRUE(Covers(region, r, 1000, 700)) << r.X << "," << r.Y; // Two neighbours and one far away, limit 2: the neighbours merge, the far one stays apart. Region three = Of({{0, 0, 10, 10}, {10, 0, 10, 10}, {500, 500, 10, 10}}); @@ -107,8 +111,10 @@ TEST(Damage, DisjointCoversTheSamePixelsOnce) { for (Int32 y = 0; y < 40; ++y) { for (Int32 x = 0; x < 40; ++x) { Int32 inInput = 0, inOut = 0; - for (const Rect& r : input) inInput |= (x >= r.X && x < r.X + r.Width && y >= r.Y && y < r.Y + r.Height); - for (const Rect& r : out) inOut += (x >= r.X && x < r.X + r.Width && y >= r.Y && y < r.Y + r.Height); + for (const Rect& r : input) + inInput |= (x >= r.X && x < r.X + r.Width && y >= r.Y && y < r.Y + r.Height); + for (const Rect& r : out) + inOut += (x >= r.X && x < r.X + r.Width && y >= r.Y && y < r.Y + r.Height); EXPECT_EQ(inOut, inInput) << x << "," << y; } } @@ -180,11 +186,13 @@ TEST(Damage, EachBufferCopiesTheDamageItMissedSinceItsLastWrite) { // Buffer 0 last held frame A; frames B and C went elsewhere, D comes now. Region copy0 = tracker.TakeForWrite(0, d, W, H); ASSERT_FALSE(copy0.IsFull()); - for (const Region* r : {&b, &c, &d}) EXPECT_TRUE(Covers(copy0, r->Rects()[0], W, H)); + for (const Region* r : {&b, &c, &d}) + EXPECT_TRUE(Covers(copy0, r->Rects()[0], W, H)); EXPECT_FALSE(Covers(copy0, a.Rects()[0], W, H)) << "A is already in buffer 0"; Region copy1 = tracker.TakeForWrite(1, e, W, H); - for (const Region* r : {&c, &d, &e}) EXPECT_TRUE(Covers(copy1, r->Rects()[0], W, H)); + for (const Region* r : {&c, &d, &e}) + EXPECT_TRUE(Covers(copy1, r->Rects()[0], W, H)); EXPECT_FALSE(Covers(copy1, b.Rects()[0], W, H)); // A reallocated buffer starts over. @@ -223,8 +231,10 @@ TEST(Damage, MissedDamageStaysBoundedAndClipped) { const Region copy = tracker.TakeForWrite(0, Region(), 100, 100); ASSERT_FALSE(copy.IsFull()); EXPECT_LE(copy.Rects().size(), kMaxRects); - for (const Rect& r : copy.Rects()) EXPECT_FALSE(Clip(r, 100, 100) != r) << "clipped to the surface"; - for (const Rect& r : written) EXPECT_TRUE(Covers(copy, r, 100, 100)); + for (const Rect& r : copy.Rects()) + EXPECT_FALSE(Clip(r, 100, 100) != r) << "clipped to the surface"; + for (const Rect& r : written) + EXPECT_TRUE(Covers(copy, r, 100, 100)); } // EGL_EXT_buffer_age per swapchain image. @@ -241,7 +251,8 @@ TEST(Damage, SwapchainAgeCountsPresentsSinceTheImageWasShown) { // Triple buffering, in order. ages.Reset(3); - for (SizeT i = 0; i < 3; ++i) ages.OnPresented(i, true); + for (SizeT i = 0; i < 3; ++i) + ages.OnPresented(i, true); EXPECT_EQ(ages.AgeOf(0), 3); ages.OnPresented(0, true); EXPECT_EQ(ages.AgeOf(1), 3); diff --git a/MobileGL/MG_Test/Util/JobNodeTest.cpp b/MobileGL/MG_Test/Util/JobNodeTest.cpp index b27be492..5321a662 100644 --- a/MobileGL/MG_Test/Util/JobNodeTest.cpp +++ b/MobileGL/MG_Test/Util/JobNodeTest.cpp @@ -209,7 +209,8 @@ TEST(JobNodeSubmit, ManyJobsAllComplete) { pool.Post(jobs.back()); } - for (const auto& job : jobs) job->Wait(); + for (const auto& job : jobs) + job->Wait(); EXPECT_EQ(completed.load(), kJobs); for (const auto& job : jobs) { EXPECT_TRUE(job->IsComplete()); @@ -232,15 +233,15 @@ TEST(JobNodeSubmit, ConcurrencyBudgetIsNeverExceeded) { jobs.push_back(MakeShared([&](TestJob&) { const Uint current = inFlight.fetch_add(1, std::memory_order_acq_rel) + 1; Uint observed = peak.load(std::memory_order_acquire); - while (current > observed && !peak.compare_exchange_weak(observed, current)) { - } + while (current > observed && !peak.compare_exchange_weak(observed, current)) {} std::this_thread::sleep_for(std::chrono::milliseconds(1)); inFlight.fetch_sub(1, std::memory_order_acq_rel); })); pool.Post(jobs.back()); } - for (const auto& job : jobs) job->Wait(); + for (const auto& job : jobs) + job->Wait(); // This is also the memory bound: it is what stops a 300-program pack load from putting // 300 glslang arenas in flight at once. EXPECT_LE(peak.load(), kBudget); @@ -302,7 +303,8 @@ TEST(JobNodeContinuation, DependencyCounterReachesZeroExactlyOnceAndOnlyAfterEve Vector> deps; deps.reserve(kDeps); - for (Uint i = 0; i < kDeps; ++i) deps.push_back(MakeShared()); + for (Uint i = 0; i < kDeps; ++i) + deps.push_back(MakeShared()); std::atomic remaining{static_cast(kDeps) + 1}; // +1 guard: nothing fires mid-registration std::atomic released{0}; @@ -311,7 +313,8 @@ TEST(JobNodeContinuation, DependencyCounterReachesZeroExactlyOnceAndOnlyAfterEve const auto settle = [&] { if (remaining.fetch_sub(1, std::memory_order_acq_rel) == 1) { Bool allTerminal = true; - for (const auto& dep : deps) allTerminal = allTerminal && dep->IsTerminal(); + for (const auto& dep : deps) + allTerminal = allTerminal && dep->IsTerminal(); allDepsTerminalAtRelease.store(allTerminal, std::memory_order_release); released.fetch_add(1, std::memory_order_acq_rel); } @@ -326,7 +329,8 @@ TEST(JobNodeContinuation, DependencyCounterReachesZeroExactlyOnceAndOnlyAfterEve EXPECT_TRUE(WaitUntil([&] { return released.load() == 1u; })); EXPECT_EQ(released.load(), 1u); EXPECT_TRUE(allDepsTerminalAtRelease.load()); - for (const auto& dep : deps) EXPECT_TRUE(dep->IsComplete()); + for (const auto& dep : deps) + EXPECT_TRUE(dep->IsComplete()); } TEST(JobNodeContinuation, AlreadyTerminalDependenciesStillSettleTheCounterExactlyOnce) { @@ -344,7 +348,8 @@ TEST(JobNodeContinuation, AlreadyTerminalDependenciesStillSettleTheCounterExactl const auto settle = [&] { if (remaining.fetch_sub(1, std::memory_order_acq_rel) == 1) ++released; }; - for (const auto& dep : deps) dep->OnTerminal(settle); + for (const auto& dep : deps) + dep->OnTerminal(settle); settle(); EXPECT_EQ(released, 1u); diff --git a/MobileGL/MG_Test/Util/LogLevelTest.cpp b/MobileGL/MG_Test/Util/LogLevelTest.cpp index 53356220..1dd33fc7 100644 --- a/MobileGL/MG_Test/Util/LogLevelTest.cpp +++ b/MobileGL/MG_Test/Util/LogLevelTest.cpp @@ -169,9 +169,9 @@ namespace { // live" - which is checkable in whichever configuration this was built in, // and is what makes the renumbering safe. const Emitted e = EmitAndRead(); - EXPECT_EQ(kAssertsLive, e.debug) - << "MOBILEGL_ASSERT liveness (" << kAssertsLive << ") disagrees with MGLOG_D liveness (" - << e.debug << "). The Defines.h assert gate and the Log.h MGLOG_D gate have drifted."; + EXPECT_EQ(kAssertsLive, e.debug) << "MOBILEGL_ASSERT liveness (" << kAssertsLive + << ") disagrees with MGLOG_D liveness (" << e.debug + << "). The Defines.h assert gate and the Log.h MGLOG_D gate have drifted."; } TEST(LogLevel, AssertIsCompiledOutOfProductionBuilds) { @@ -218,8 +218,7 @@ int main(int argc, char** argv) { // main thread (client role), so the read path is the client-derived one; a pull build has // no split and keeps the base. The env keeps the base either way. #if MOBILEGL_BUILD_DISAGGREGATED - g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), - MobileGL::MG_Util::Debug::LogRole::Client); + g_logPath = MobileGL::MG_Util::Debug::RoleLogPath(g_logPath.c_str(), MobileGL::MG_Util::Debug::LogRole::Client); #endif ::testing::InitGoogleTest(&argc, argv); diff --git a/MobileGL/MG_Test/Util/PipeStatsTest.cpp b/MobileGL/MG_Test/Util/PipeStatsTest.cpp index 002cfa79..b4c63ed9 100644 --- a/MobileGL/MG_Test/Util/PipeStatsTest.cpp +++ b/MobileGL/MG_Test/Util/PipeStatsTest.cpp @@ -224,11 +224,11 @@ namespace { // when a generator first calls it. TEST_F(PipeStatsTest, PayloadHistogramBucketsByPowerOfTwo) { PS::RecordDrawPayloadBytes(0); - PS::RecordDrawPayloadBytes(1); // [1, 2) -> bucket 1 - PS::RecordDrawPayloadBytes(2); // [2, 4) -> bucket 2 - PS::RecordDrawPayloadBytes(3); // [2, 4) -> bucket 2 - PS::RecordDrawPayloadBytes(48); // [32, 64) -> bucket 6 - PS::RecordDrawPayloadBytes(64); // [64, 128)-> bucket 7 + PS::RecordDrawPayloadBytes(1); // [1, 2) -> bucket 1 + PS::RecordDrawPayloadBytes(2); // [2, 4) -> bucket 2 + PS::RecordDrawPayloadBytes(3); // [2, 4) -> bucket 2 + PS::RecordDrawPayloadBytes(48); // [32, 64) -> bucket 6 + PS::RecordDrawPayloadBytes(64); // [64, 128)-> bucket 7 EXPECT_EQ(PS::TotalPayloadBucket(0), 1u); EXPECT_EQ(PS::TotalPayloadBucket(1), 1u); @@ -340,8 +340,7 @@ namespace { EXPECT_EQ(PS::TotalBytes(PS::ByteClass::StageSegmentBytes), 6144u); EXPECT_EQ(PS::FrameBytes(PS::ByteClass::StageSegmentBytes), 0u) << "a Present clears the frame"; // 6144 over one frame, two decimals, in the ordinary bytes[] bracket. - EXPECT_NE(PS::FormatWindowLine().find("seg=6144.00"), String::npos) - << PS::FormatWindowLine(); + EXPECT_NE(PS::FormatWindowLine().find("seg=6144.00"), String::npos) << PS::FormatWindowLine(); PS::AdvanceSummaryWindow(); // And it obeys the file's LABEL rule, not a rule of its own: the new window has no @@ -392,10 +391,10 @@ namespace { // bucket rather than a byte sum: the shape is the deliverable and the bytes have their own // counter in `seg`. TEST_F(PipeStatsTest, StagedBlobHistogramSharesThePayloadBucketEdges) { - PS::RecordStagedBlobBytes(1); // [1, 2) -> bucket 1 - PS::RecordStagedBlobBytes(4096); // [4096,8192)-> bucket 13 - PS::RecordStagedBlobBytes(4096); // same bucket - PS::RecordStagedBlobBytes(8192); // [8192,...) -> bucket 14: the edge is exclusive + PS::RecordStagedBlobBytes(1); // [1, 2) -> bucket 1 + PS::RecordStagedBlobBytes(4096); // [4096,8192)-> bucket 13 + PS::RecordStagedBlobBytes(4096); // same bucket + PS::RecordStagedBlobBytes(8192); // [8192,...) -> bucket 14: the edge is exclusive EXPECT_EQ(PS::TotalStagedBlobBucket(1), 1u); EXPECT_EQ(PS::TotalStagedBlobBucket(13), 2u); @@ -425,9 +424,7 @@ namespace { // The ordering is the JSON document's shape, not decoration: the new array is a SIBLING // of the draw-payload one, so a reader that walks the object finds both where the two // comments say they are. - EXPECT_LT(json.find("\"cmd-bytes-per-draw-histogram\""), - json.find("\"staged-blob-bytes-histogram\"")) - << json; + EXPECT_LT(json.find("\"cmd-bytes-per-draw-histogram\""), json.find("\"staged-blob-bytes-histogram\"")) << json; } // Successive summaries report WINDOWS, not run totals: a run total over a workload that @@ -541,8 +538,7 @@ namespace { EXPECT_STREQ(PS::NameOf(PS::CallClass::SamplerViewEmissions), "sampler-view-emissions"); EXPECT_STREQ(PS::NameOf(PS::CallClass::SamplerStateEmissions), "sampler-state-emissions"); EXPECT_STREQ(PS::NameOf(PS::CallClass::ShaderImageEmissions), "shader-image-emissions"); - EXPECT_STREQ(PS::NameOf(PS::CallClass::ClientTextureUploadEmissions), - "client-tex-upload-emissions"); + EXPECT_STREQ(PS::NameOf(PS::CallClass::ClientTextureUploadEmissions), "client-tex-upload-emissions"); EXPECT_STREQ(PS::NameOf(PS::ByteClass::CsoBlobBytes), "cso-blob-bytes"); // P6 gate 8's two, under their long names - the JSON dump keys and the names the // MEASUREMENTS.md entry will quote. diff --git a/MobileGL/MG_Test/Util/X11DisplayGuardTest.cpp b/MobileGL/MG_Test/Util/X11DisplayGuardTest.cpp index 31d35362..75861092 100644 --- a/MobileGL/MG_Test/Util/X11DisplayGuardTest.cpp +++ b/MobileGL/MG_Test/Util/X11DisplayGuardTest.cpp @@ -35,14 +35,13 @@ namespace X = MobileGL::MG_Util::X11; namespace { // /proc/net/unix as Xwayland's process sees it under KWin: the two listening sockets KWin bound // and handed over with -listenfd, plus connected ones. - const char* kProcNetUnix = - "Num RefCount Protocol Flags Type St Inode Path\n" - "0000000000000000: 00000002 00000000 00010000 0001 01 41001 @/tmp/.X11-unix/X0\n" - "0000000000000000: 00000002 00000000 00010000 0001 01 41002 /tmp/.X11-unix/X0\n" - "0000000000000000: 00000003 00000000 00000000 0001 03 41003 /tmp/.X11-unix/X0\n" - "0000000000000000: 00000002 00000000 00010000 0001 01 41004 /run/user/1000/wayland-0\n" - "0000000000000000: 00000003 00000000 00000000 0001 03 41005\n" - "0000000000000000: 00000002 00000000 00010000 0001 01 41006 @/tmp/.X11-unix/X1\n"; + const char* kProcNetUnix = "Num RefCount Protocol Flags Type St Inode Path\n" + "0000000000000000: 00000002 00000000 00010000 0001 01 41001 @/tmp/.X11-unix/X0\n" + "0000000000000000: 00000002 00000000 00010000 0001 01 41002 /tmp/.X11-unix/X0\n" + "0000000000000000: 00000003 00000000 00000000 0001 03 41003 /tmp/.X11-unix/X0\n" + "0000000000000000: 00000002 00000000 00010000 0001 01 41004 /run/user/1000/wayland-0\n" + "0000000000000000: 00000003 00000000 00000000 0001 03 41005\n" + "0000000000000000: 00000002 00000000 00010000 0001 01 41006 @/tmp/.X11-unix/X1\n"; } // namespace TEST(X11DisplayGuard, DisplayNamesYieldTheirNumber) { @@ -113,7 +112,9 @@ namespace { return fd; } - int UnusedDisplayNumber() { return 3000 + static_cast(::getpid() % 2000); } + int UnusedDisplayNumber() { + return 3000 + static_cast(::getpid() % 2000); + } } // namespace TEST(X11DisplayGuard, ThisProcessServesADisplayWhileItListensOnItsSocket) { diff --git a/MobileGL/MG_Test/Util/X11PresentTest.cpp b/MobileGL/MG_Test/Util/X11PresentTest.cpp index 9c943239..d82c42c7 100644 --- a/MobileGL/MG_Test/Util/X11PresentTest.cpp +++ b/MobileGL/MG_Test/Util/X11PresentTest.cpp @@ -94,7 +94,8 @@ namespace { freed.push_back(pixmap); pixmaps.erase(pixmap); } - Bool PresentPixmap(Uint32 pixmap, Uint32 serial, Uint32 options, Uint64 targetMsc, const Region& update) override { + Bool PresentPixmap(Uint32 pixmap, Uint32 serial, Uint32 options, Uint64 targetMsc, + const Region& update) override { presents.push_back({pixmap, serial, options, targetMsc, update}); return true; } @@ -227,7 +228,8 @@ TEST(X11PresentChain, AnImageComesBackWithIdleNotifyAndIsReused) { TEST(X11PresentChain, AllImagesHeldWaitsThenGrowsToFourThenDropsTheFrame) { Rig rig; - for (int i = 0; i < 3; ++i) ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)); + for (int i = 0; i < 3; ++i) + ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)); const Int64 before = rig.clock.now; ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)) << "a fourth image after the idle wait"; EXPECT_GE(rig.clock.now - before, XP::Chain::kIdleWaitMs); @@ -310,7 +312,8 @@ TEST(X11PresentChain, AResizeRetiresOldImagesAsTheyComeBack) { rig.x.Idle(c); ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)); EXPECT_EQ(rig.x.pixmaps.count(b), 0u); - for (const auto& [id, size] : rig.images.live) EXPECT_EQ(size, newSize); + for (const auto& [id, size] : rig.images.live) + EXPECT_EQ(size, newSize); } TEST(X11PresentChain, ConfigureNotifyReportsTheNewSizeOnce) { @@ -378,7 +381,8 @@ TEST(X11PresentChain, FailuresSendTheWindowBackToReadback) { TEST(X11PresentChain, TheChainFreesEveryPixmapAndReleasesEveryImage) { Rig rig; - for (int i = 0; i < 3; ++i) ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)); + for (int i = 0; i < 3; ++i) + ASSERT_TRUE(rig.chain->Present(Region::Full(), 0)); EXPECT_EQ(rig.images.live.size(), 3u); rig.chain.reset(); EXPECT_TRUE(rig.images.live.empty()); diff --git a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp index 050e7e3b..a1fd2c37 100644 --- a/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexArrayTest.cpp @@ -41,8 +41,7 @@ protected: // binary, so drain whatever an earlier test left pending - otherwise an error-code assertion // here reads someone else's error. Bounded: one flag per code, so this cannot hang the suite. static void DrainPendingGlErrors() { - for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) { - } + for (Int drained = 0; drained < 16 && MG_Impl::GLImpl::GetError() != GL_NO_ERROR; ++drained) {} } // The call under test must raise exactly the expected error and nothing more: a second pending @@ -408,8 +407,7 @@ class GeneralVertexArrayTest : public ::testing::Test { protected: void SetUp() override { MG_State::pGLContext = MakeUnique(); } - void TearDown() override { - } + void TearDown() override {} GLuint CreateVAO() { GLuint vao; @@ -1007,9 +1005,9 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_SignedNormalizedUsesGl33Formula) { const GLbyte extremes[4] = {-128, 127, 0, 127}; VertexAttrib4Nbv(1, extremes); GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); - EXPECT_FLOAT_EQ(out[0], -1.0f); // exact, no clamp - EXPECT_FLOAT_EQ(out[1], 1.0f); // exact - EXPECT_FLOAT_EQ(out[2], 1.0f / 255.0f); // 0 -> 1/255, NOT 0.0 + EXPECT_FLOAT_EQ(out[0], -1.0f); // exact, no clamp + EXPECT_FLOAT_EQ(out[1], 1.0f); // exact + EXPECT_FLOAT_EQ(out[2], 1.0f / 255.0f); // 0 -> 1/255, NOT 0.0 EXPECT_FLOAT_EQ(out[3], 1.0f); const GLshort sExtremes[4] = {-32768, 32767, 0, 0}; @@ -1131,10 +1129,10 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedUnsigned) { VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, packed); GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); EXPECT_EQ(GetError(), GL_NO_ERROR); - EXPECT_FLOAT_EQ(out[0], 1.0f); // 1023/1023 - EXPECT_FLOAT_EQ(out[1], 0.0f); // 0/1023 - EXPECT_FLOAT_EQ(out[2], 512.0f / 1023.0f); // 10-bit divisor, NOT 1023 for w - EXPECT_FLOAT_EQ(out[3], 1.0f); // 3/3 (2-bit divisor) + EXPECT_FLOAT_EQ(out[0], 1.0f); // 1023/1023 + EXPECT_FLOAT_EQ(out[1], 0.0f); // 0/1023 + EXPECT_FLOAT_EQ(out[2], 512.0f / 1023.0f); // 10-bit divisor, NOT 1023 for w + EXPECT_FLOAT_EQ(out[3], 1.0f); // 3/3 (2-bit divisor) // Unnormalized: the field values are cast straight to float, no scaling. VertexAttribP4ui(1, GL_UNSIGNED_INT_2_10_10_10_REV, GL_FALSE, packed); @@ -1156,10 +1154,10 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedSignedUsesGl33Formula) { VertexAttribP4ui(1, GL_INT_2_10_10_10_REV, GL_TRUE, packed); GetVertexAttribfv(1, GL_CURRENT_VERTEX_ATTRIB, out); EXPECT_EQ(GetError(), GL_NO_ERROR); - EXPECT_FLOAT_EQ(out[0], 1.0f); // (2*511+1)/1023 = 1023/1023 - EXPECT_FLOAT_EQ(out[1], -1.0f); // (2*-512+1)/1023 = -1023/1023 - EXPECT_FLOAT_EQ(out[2], 1.0f / 1023.0f); // (2*0+1)/1023 -- 4.2 rule would give 0.0 - EXPECT_FLOAT_EQ(out[3], 1.0f); // (2*1+1)/3 = 3/3 + EXPECT_FLOAT_EQ(out[0], 1.0f); // (2*511+1)/1023 = 1023/1023 + EXPECT_FLOAT_EQ(out[1], -1.0f); // (2*-512+1)/1023 = -1023/1023 + EXPECT_FLOAT_EQ(out[2], 1.0f / 1023.0f); // (2*0+1)/1023 -- 4.2 rule would give 0.0 + EXPECT_FLOAT_EQ(out[3], 1.0f); // (2*1+1)/3 = 3/3 // 2-bit signed minimum: w field = 0b10 = -2 -> (2*-2+1)/3 = -1.0. VertexAttribP4ui(1, GL_INT_2_10_10_10_REV, GL_TRUE, 2u << 30); @@ -1222,7 +1220,7 @@ TEST_F(GeneralVertexArrayTest, CurrentAttrib_PackedValidation) { EXPECT_EQ(GetError(), GL_NO_ERROR); GetVertexAttribfv(0, GL_CURRENT_VERTEX_ATTRIB, out); EXPECT_EQ(GetError(), GL_NO_ERROR); - EXPECT_FLOAT_EQ(out[0], 1.0f); // x field of the packed word + EXPECT_FLOAT_EQ(out[0], 1.0f); // x field of the packed word VertexAttrib4f(0, 0.0f, 0.0f, 0.0f, 1.0f); // restore the initial current value EXPECT_EQ(GetError(), GL_NO_ERROR); @@ -1331,7 +1329,6 @@ TEST_F(GeneralVertexArrayTest, ArrayFormat_IntegerPathRejectsPackedAndBgra) { EXPECT_FALSE(a0.IsBgra); } - // The integer path takes exactly the eight signed/unsigned integer types (GL 4.6 core 10.3.2). // The old check was a blacklist of Unknown + the packed types, so GL_FLOAT / GL_HALF_FLOAT / // GL_DOUBLE / GL_FIXED all converted to a valid DataType and were silently recorded as integer diff --git a/MobileGL/MG_Test/VertexArray/VertexAttribBindingStateTest.cpp b/MobileGL/MG_Test/VertexArray/VertexAttribBindingStateTest.cpp index b03202a7..61b45075 100644 --- a/MobileGL/MG_Test/VertexArray/VertexAttribBindingStateTest.cpp +++ b/MobileGL/MG_Test/VertexArray/VertexAttribBindingStateTest.cpp @@ -171,9 +171,7 @@ namespace { BindVertexArray(m_vao); } - void TearDown() override { - EXPECT_EQ(GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind"; - } + void TearDown() override { EXPECT_EQ(GetError(), GL_NO_ERROR) << "test left an unconsumed GL error behind"; } GLuint CreateVbo(GLsizeiptr size) { GLuint vbo = 0; @@ -185,8 +183,7 @@ namespace { } static void DrainErrors() { - for (int i = 0; i < 16 && GetError() != GL_NO_ERROR; ++i) { - } + for (int i = 0; i < 16 && GetError() != GL_NO_ERROR; ++i) {} } GLuint m_vao = 0; diff --git a/MobileGL/MG_Test/Wire/AdoptTierStreamTest.cpp b/MobileGL/MG_Test/Wire/AdoptTierStreamTest.cpp index afb2aaee..53bfa900 100644 --- a/MobileGL/MG_Test/Wire/AdoptTierStreamTest.cpp +++ b/MobileGL/MG_Test/Wire/AdoptTierStreamTest.cpp @@ -47,9 +47,9 @@ namespace { ::setenv("MOBILEGL_IPC_ROLE", "server", 1); ::setenv("MOBILEGL_IPC_DIAL", "no", 1); ::setenv("MOBILEGL_IPC_ADOPT_TIER", adoptTier, 1); - for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", - "MOBILEGL_IPC_STAGE_MB", "MOBILEGL_IPC_CONTROL", "MOBILEGL_IPC_SURFACE", - "MOBILEGL_BACKEND_TYPE"}) + for (const char* name : + {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB", + "MOBILEGL_IPC_CONTROL", "MOBILEGL_IPC_SURFACE", "MOBILEGL_BACKEND_TYPE"}) ::unsetenv(name); sigset_t stop; sigemptyset(&stop); @@ -77,8 +77,8 @@ namespace { // this process exports for the forks): a peer writes the lines the server forwards into // `.server.log`, opened "w", so sharing one base truncates the server's own file under // it (measured: NUL holes and the handshake's lines gone). - bool LaunchInProcessServerWithAdoptTier(const std::string& label, const char* adoptTier, const std::string& peerBase, - ServerProcess* server) { + bool LaunchInProcessServerWithAdoptTier(const std::string& label, const char* adoptTier, + const std::string& peerBase, ServerProcess* server) { server->logBase = "/tmp/mgl-p11-" + label + "-server-" + std::to_string(::getpid()) + ".log"; ::setenv("MOBILEGL_LOG_FILE_PATH", peerBase.c_str(), 1); Debug::TruncateRoleLogs(peerBase.c_str()); @@ -136,18 +136,21 @@ TEST(AdoptTierStream, AClientNamingT0OrT1IsRefusedOnceAndItsMapPersistentIsDecli ASSERT_TRUE(LaunchSupervisor("adopt-client", &server)) << server.Log(); for (Uint32 tier : {0u, 1u}) { SCOPED_TRACE(tier); - const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0, - nullptr, [tier] { MG_Config::Ipc.AdoptTier = tier; }); + const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0, nullptr, + [tier] { MG_Config::Ipc.AdoptTier = tier; }); const std::string client = ClientLog(server); ASSERT_EQ(r.started, 1) << "the session did not come up: " << r.note << "\n" << client; EXPECT_EQ(r.status, Remote::Wire::ReplySink::kStatusDeclined) - << "map_persistent was not DECLINED on a stream session\n" << client; + << "map_persistent was not DECLINED on a stream session\n" + << client; EXPECT_EQ(r.applied, 1) << "the session stopped applying after the map_persistent\n" << client; EXPECT_EQ(Count(client, RefusalLine(tier)), 1u) - << "a stream session names T" << tier << " exactly once, at the handshake\n" << client; + << "a stream session names T" << tier << " exactly once, at the handshake\n" + << client; EXPECT_EQ(Count(client, "Fatal{"), 0u) << client; EXPECT_EQ(Count(server.Log(), "AdoptTierOnStream"), 0u) - << "the server's own knob is 2; the line is the client's\n" << server.Log(); + << "the server's own knob is 2; the line is the client's\n" + << server.Log(); } } @@ -187,15 +190,16 @@ TEST(AdoptTierStream, AServerNamingT0RefusesOncePerSessionAndServesT2) { const auto serverLog = [&server] { return Debug::ReadRoleLogs(server.logBase.c_str()); }; for (Uint32 session = 1; session <= 2; ++session) { SCOPED_TRACE(session); - const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0, - nullptr, [] { MG_Config::Ipc.AdoptTier = 2u; }); + const PeerReport r = RunPeer(server.endpoint, MapPersistentThenARecord, /*stopCleanly=*/true, 30000, 0, nullptr, + [] { MG_Config::Ipc.AdoptTier = 2u; }); const std::string client = ReadFile(Debug::RoleLogPath(peerBase.c_str(), Debug::LogRole::Client)); ASSERT_EQ(r.started, 1) << r.note << "\n" << serverLog(); EXPECT_EQ(r.status, Remote::Wire::ReplySink::kStatusDeclined) << serverLog(); EXPECT_EQ(r.applied, 1) << serverLog(); EXPECT_EQ(Count(client, "AdoptTierOnStream"), 0u) << "the client's knob is 2\n" << client; EXPECT_EQ(Count(serverLog(), RefusalLine(0)), session) - << "one refusal line per server session, at its handshake\n" << serverLog(); + << "one refusal line per server session, at its handshake\n" + << serverLog(); } EXPECT_EQ(Count(serverLog(), "Fatal{UnimplementedAdoptTier"), 0u) << serverLog(); if (!::testing::Test::HasFailure()) { diff --git a/MobileGL/MG_Test/Wire/ApplierOwnershipTest.cpp b/MobileGL/MG_Test/Wire/ApplierOwnershipTest.cpp index 6a0c03c0..42383402 100644 --- a/MobileGL/MG_Test/Wire/ApplierOwnershipTest.cpp +++ b/MobileGL/MG_Test/Wire/ApplierOwnershipTest.cpp @@ -196,8 +196,8 @@ namespace { BindSession(kSessionA, 4); P::MGPipeApplierState& fourth = P::MGPipeApplier(); EXPECT_NE(&third, &fourth) << "two contexts of one share group share one applier - the " - "bindings, the current program and the CSO store would all " - "overwrite each other"; + "bindings, the current program and the CSO store would all " + "overwrite each other"; EXPECT_EQ(&third.Resources, &fourth.Resources) << "two contexts of one share group were given different object tables"; EXPECT_NE(ResourceRecord(shared), nullptr) @@ -237,8 +237,7 @@ namespace { P::MGPipeApplierState& inB = P::MGPipeApplier(); EXPECT_NE(&inA, &inB) << "two sessions with identical tokens resolved one applier"; EXPECT_NE(&inA.Resources, &inB.Resources); - EXPECT_EQ(ResourceRecord(handle), nullptr) - << "session B resolved an object record session A created"; + EXPECT_EQ(ResourceRecord(handle), nullptr) << "session B resolved an object record session A created"; } // THE GROUP'S RECORDS GO WITH ITS LAST CONTEXT, NOT ITS FIRST. A context of a live group is @@ -261,8 +260,7 @@ namespace { EXPECT_EQ(P::MGPipeApplierShareGroupCountForTesting(kSessionA), 1u); P::MGPipeApplierUnregisterContext(kSessionA, 6); - EXPECT_EQ(P::MGPipeApplierShareGroupCountForTesting(kSessionA), 0u) - << "the group outlived its last context"; + EXPECT_EQ(P::MGPipeApplierShareGroupCountForTesting(kSessionA), 0u) << "the group outlived its last context"; // A RECREATED CONTEXT MUST NOT INHERIT THE DEAD GROUP'S RECORDS. The frontend never // re-uses a token, so this is the belt: a token that comes back resolves to a fresh @@ -285,8 +283,7 @@ namespace { t_sessionKey = 0; t_contextToken = 0; P::MGPipeApplierState& processWide = P::MGPipeApplier(); - EXPECT_EQ(ResourceRecord(handle), nullptr) - << "the process-wide applier resolved a record a session registered"; + EXPECT_EQ(ResourceRecord(handle), nullptr) << "the process-wide applier resolved a record a session registered"; BindSession(kSessionA, 1); EXPECT_NE(&P::MGPipeApplier(), &processWide) << "a session resolved the process-wide applier"; diff --git a/MobileGL/MG_Test/Wire/ApplyThreadPolicyTest.cpp b/MobileGL/MG_Test/Wire/ApplyThreadPolicyTest.cpp index a8a5d36b..220a631f 100644 --- a/MobileGL/MG_Test/Wire/ApplyThreadPolicyTest.cpp +++ b/MobileGL/MG_Test/Wire/ApplyThreadPolicyTest.cpp @@ -38,8 +38,8 @@ namespace { TEST(ApplyThreadPolicy, AutoKeepsEveryBigCoreAndTheConfiguredSpinForAnInProcessOrForkedPeer) { // inproc and the same-app fork spawn: exactly the pre-B1 `auto` (A4's measured shapes). for (const ApplyPeer peer : {ApplyPeer::InProcess, ApplyPeer::Forked}) { - const auto policy = SelectApplyThreadPolicy("auto", false, kConfigured, peer, kShm, kRedmiBig, kRedmiPrime, - kRedmiOnline); + const auto policy = + SelectApplyThreadPolicy("auto", false, kConfigured, peer, kShm, kRedmiBig, kRedmiPrime, kRedmiOnline); EXPECT_TRUE(policy.recognised); EXPECT_EQ(policy.requestedMask, kRedmiBig) << ApplyPeerName(peer); EXPECT_EQ(policy.spinUs, kConfigured) << ApplyPeerName(peer); @@ -100,9 +100,9 @@ TEST(ApplyThreadPolicy, NoAsymmetryLeavesTheApplyThreadUnpinned) { std::string::npos); } // ...and the in-process / forked rows keep the whole (symmetric) big set as before. - EXPECT_EQ(SelectApplyThreadPolicy("auto", false, kConfigured, ApplyPeer::Forked, kShm, 0xff, 0xff, 0xff) - .requestedMask, - 0xffu); + EXPECT_EQ( + SelectApplyThreadPolicy("auto", false, kConfigured, ApplyPeer::Forked, kShm, 0xff, 0xff, 0xff).requestedMask, + 0xffu); } TEST(ApplyThreadPolicy, AnEmptyOrMissingAffinityIsAuto) { @@ -117,8 +117,8 @@ TEST(ApplyThreadPolicy, AnEmptyOrMissingAffinityIsAuto) { } TEST(ApplyThreadPolicy, AnExplicitSpinWinsForADialledInPeer) { - const auto policy = SelectApplyThreadPolicy("auto", true, 7, ApplyPeer::Connected, kShm, kRedmiBig, kRedmiPrime, - kRedmiOnline); + const auto policy = + SelectApplyThreadPolicy("auto", true, 7, ApplyPeer::Connected, kShm, kRedmiBig, kRedmiPrime, kRedmiOnline); EXPECT_EQ(policy.spinUs, 7u); EXPECT_EQ(policy.requestedMask, 0x40u); } @@ -126,19 +126,19 @@ TEST(ApplyThreadPolicy, AnExplicitSpinWinsForADialledInPeer) { TEST(ApplyThreadPolicy, AnExplicitMaskOrOffWinsForEveryPeerAndPlaneAndReservesNothing) { for (const ApplyPeer peer : {ApplyPeer::InProcess, ApplyPeer::Forked, ApplyPeer::Connected}) { for (const Bool plane : {kShm, kStream}) { - const auto off = SelectApplyThreadPolicy("off", false, kConfigured, peer, plane, kRedmiBig, kRedmiPrime, - kRedmiOnline); + const auto off = + SelectApplyThreadPolicy("off", false, kConfigured, peer, plane, kRedmiBig, kRedmiPrime, kRedmiOnline); EXPECT_TRUE(off.recognised); EXPECT_EQ(off.requestedMask, 0u) << ApplyPeerName(peer); EXPECT_EQ(off.spinUs, kConfigured) << "an operator's affinity keeps the configured spin"; - const auto mask = SelectApplyThreadPolicy("0x80", false, kConfigured, peer, plane, kRedmiBig, - kRedmiPrime, kRedmiOnline); + const auto mask = + SelectApplyThreadPolicy("0x80", false, kConfigured, peer, plane, kRedmiBig, kRedmiPrime, kRedmiOnline); EXPECT_TRUE(mask.recognised); EXPECT_EQ(mask.requestedMask, 0x80u) << ApplyPeerName(peer); - EXPECT_EQ(SelectApplyThreadPolicy("12", false, kConfigured, peer, plane, kRedmiBig, kRedmiPrime, - kRedmiOnline) - .requestedMask, - 12u); + EXPECT_EQ( + SelectApplyThreadPolicy("12", false, kConfigured, peer, plane, kRedmiBig, kRedmiPrime, kRedmiOnline) + .requestedMask, + 12u); } } // What the supervisor announces (and the helper avoids) under an operator's affinity: nothing. diff --git a/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp b/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp index df5b49fe..b31353b8 100644 --- a/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp +++ b/MobileGL/MG_Test/Wire/ClientRecoveryTest.cpp @@ -43,46 +43,47 @@ #include #include -extern "C" { -void glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); -const GLubyte* glGetString(GLenum name); -const GLubyte* glGetStringi(GLenum name, GLuint index); -void glGetIntegerv(GLenum pname, GLint* data); -void glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); -void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); -void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -void glClear(GLbitfield mask); -void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); -GLenum glGetError(void); -GLenum glGetGraphicsResetStatus(void); -void glGenTextures(GLsizei n, GLuint* textures); -void glBindTexture(GLenum target, GLuint texture); -void glActiveTexture(GLenum texture); -void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, - GLenum format, GLenum type, const void* pixels); -void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, - GLenum format, GLenum type, const void* pixels); -void glTexParameteri(GLenum target, GLenum pname, GLint param); -GLboolean glIsTexture(GLuint texture); -void glGenFramebuffers(GLsizei n, GLuint* framebuffers); -void glBindFramebuffer(GLenum target, GLuint framebuffer); -void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -GLenum glCheckFramebufferStatus(GLenum target); -GLuint glCreateShader(GLenum type); -void glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); -void glCompileShader(GLuint shader); -void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); -GLuint glCreateProgram(void); -void glAttachShader(GLuint program, GLuint shader); -void glLinkProgram(GLuint program); -void glGetProgramiv(GLuint program, GLenum pname, GLint* params); -void glUseProgram(GLuint program); -GLint glGetUniformLocation(GLuint program, const GLchar* name); -void glUniform1i(GLint location, GLint v0); -void glGenVertexArrays(GLsizei n, GLuint* arrays); -void glBindVertexArray(GLuint array); -void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); -void glDrawArrays(GLenum mode, GLint first, GLsizei count); +extern "C" +{ + void glTexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height); + const GLubyte* glGetString(GLenum name); + const GLubyte* glGetStringi(GLenum name, GLuint index); + void glGetIntegerv(GLenum pname, GLint* data); + void glGetnUniformfv(GLuint program, GLint location, GLsizei bufSize, GLfloat* params); + void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); + void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void glClear(GLbitfield mask); + void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); + GLenum glGetError(void); + GLenum glGetGraphicsResetStatus(void); + void glGenTextures(GLsizei n, GLuint* textures); + void glBindTexture(GLenum target, GLuint texture); + void glActiveTexture(GLenum texture); + void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, + GLenum format, GLenum type, const void* pixels); + void glTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, + GLenum format, GLenum type, const void* pixels); + void glTexParameteri(GLenum target, GLenum pname, GLint param); + GLboolean glIsTexture(GLuint texture); + void glGenFramebuffers(GLsizei n, GLuint* framebuffers); + void glBindFramebuffer(GLenum target, GLuint framebuffer); + void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); + GLenum glCheckFramebufferStatus(GLenum target); + GLuint glCreateShader(GLenum type); + void glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); + void glCompileShader(GLuint shader); + void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); + GLuint glCreateProgram(void); + void glAttachShader(GLuint program, GLuint shader); + void glLinkProgram(GLuint program); + void glGetProgramiv(GLuint program, GLenum pname, GLint* params); + void glUseProgram(GLuint program); + GLint glGetUniformLocation(GLuint program, const GLchar* name); + void glUniform1i(GLint location, GLint v0); + void glGenVertexArrays(GLsizei n, GLuint* arrays); + void glBindVertexArray(GLuint array); + void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); + void glDrawArrays(GLenum mode, GLint first, GLsizei count); } using namespace MobileGL; @@ -182,9 +183,10 @@ namespace { GLuint texture = 0; Bool Build(RecoveryReport& r, Uint32 rgba) { - static const char* kVertex = "#version 330 core\n" - "const vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n" - "void main() { gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0); }\n"; + static const char* kVertex = + "#version 330 core\n" + "const vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n" + "void main() { gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0); }\n"; static const char* kFragment = "#version 330 core\n" "uniform sampler2D image;\n" "out vec4 color;\n" @@ -469,8 +471,8 @@ namespace { RecoveryReport built{}; if (!old.Build(built, kGreen)) return Note(r, built.note); old.Draw(); - (void)ReadPixel(); // device check #1 - (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost + (void)ReadPixel(); // device check #1 + (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost (void)WaitFor([] { return glGetGraphicsResetStatus() != GL_NO_ERROR; }, 5000); (void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); (void)EGL::DestroyContext(egl.dpy, working); @@ -496,7 +498,7 @@ namespace { if (EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, second) != EGL_TRUE) return Note(r, "the second replacement did not go current"); r.textureSeenAcross = glIsTexture(shared.texture) == GL_TRUE ? 1 : 0; - GLuint vertexArray = 0; // a vertex array is per context + GLuint vertexArray = 0; // a vertex array is per context glGenVertexArrays(1, &vertexArray); shared.vertexArray = vertexArray; shared.Draw(); @@ -593,8 +595,8 @@ namespace { if (!old.Build(built, kGreen)) return Note(r, built.note); r.initialized = 1; old.Draw(); - r.firstPixel = ReadPixel(); // device check #1 - (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost + r.firstPixel = ReadPixel(); // device check #1 + (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost (void)WaitFor([] { return glGetGraphicsResetStatus() != GL_NO_ERROR; }, 5000); }); if (!r.initialized) return; @@ -697,8 +699,8 @@ namespace { return Note(r, "the contexts did not go current"); r.initialized = 1; glGetIntegerv(GL_RESET_NOTIFICATION_STRATEGY, &r.coreStrategy); - r.firstPixel = ClearAndRead(0.0f, 1.0f, 0.0f); // device check #1 - (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost + r.firstPixel = ClearAndRead(0.0f, 1.0f, 0.0f); // device check #1 + (void)ClearAndRead(0.0f, 0.0f, 1.0f); // device check #2: lost r.lossSeen = WaitFor([] { return Remote::Client::ClientSession::SessionLatchedLost(); }, 5000) ? 1 : 0; r.coreStatus = glGetGraphicsResetStatus(); @@ -716,7 +718,8 @@ namespace { if (r.coreAgain == EGL_TRUE) (void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); const EGLContext fresh = egl.Context(); - r.freshCurrent = fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE; + r.freshCurrent = + fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE; if (!r.freshCurrent) return Note(r, "no fresh context went current"); r.freshStatus = glGetGraphicsResetStatus(); r.freshPixel = ClearAndRead(0.0f, 0.0f, 1.0f); @@ -816,7 +819,8 @@ namespace { (void)EGL::MakeCurrent(egl.dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); const EGLContext fresh = egl.Context(); (void)EGL::DestroyContext(egl.dpy, lost); - r.freshCurrent = fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE; + r.freshCurrent = + fresh != EGL_NO_CONTEXT && EGL::MakeCurrent(egl.dpy, egl.surface, egl.surface, fresh) == EGL_TRUE; if (!r.freshCurrent) return Note(r, "no fresh context went current"); const auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(500); while (std::chrono::steady_clock::now() < until) { @@ -979,10 +983,9 @@ namespace { reinterpret_cast(glGetString(GL_SHADING_LANGUAGE_VERSION))); glGetIntegerv(GL_MAJOR_VERSION, &r.esMajor); glGetIntegerv(GL_MINOR_VERSION, &r.esMinor); - r.bgraAdvertised = HasToken(reinterpret_cast(glGetString(GL_EXTENSIONS)), - "GL_EXT_texture_format_BGRA8888") - ? 1 - : 0; + r.bgraAdvertised = + HasToken(reinterpret_cast(glGetString(GL_EXTENSIONS)), "GL_EXT_texture_format_BGRA8888") ? 1 + : 0; GLint count = 0; glGetIntegerv(GL_NUM_EXTENSIONS, &count); r.bgraIndexed = 0; @@ -990,8 +993,7 @@ namespace { const auto* name = reinterpret_cast(glGetStringi(GL_EXTENSIONS, static_cast(i))); if (name != nullptr && std::string(name) == "GL_EXT_texture_format_BGRA8888") r.bgraIndexed = 1; } - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} // BGRA as EXT_texture_format_BGRA8888 spells it: GL_BGRA_EXT as the internal format, and // GL_BGRA8_EXT through glTexStorage2D; a BGRA texel reads back in RGBA order. const Uint8 bgra[4] = {0x10, 0x20, 0x30, 0xFF}; // B, G, R, A @@ -1061,8 +1063,10 @@ namespace { // What every route has to show from the application's side, before and after the loss. static void ExpectLossReportedAndRecovered(const RecoveryReport& r, const std::string& diagnostics) { ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics; - ASSERT_EQ(r.firstPixel, kGreen) << "the first draw (device check #1) did not read back: " << std::hex - << r.firstPixel << "\n" << r.note << "\n" << diagnostics; + ASSERT_EQ(r.firstPixel, kGreen) + << "the first draw (device check #1) did not read back: " << std::hex << r.firstPixel << "\n" + << r.note << "\n" + << diagnostics; EXPECT_NE(r.lostPixel, kBlue) << "a read-back on the lost device returned a frame"; // What the application checked before it asked to be told (ANGLE mirrors the EGL // extension onto its own display; Chrome checks that and GL_*_robustness), and what @@ -1070,8 +1074,8 @@ namespace { EXPECT_EQ(r.eglRobustnessAdvertised, 1) << "EGL_EXT_create_context_robustness is not advertised"; EXPECT_EQ(r.glRobustnessAdvertised, 1) << "GL_KHR_robustness is not in glGetString(GL_EXTENSIONS)"; EXPECT_EQ(r.glRobustnessIndexed, 1) << "GL_KHR_robustness is not in glGetStringi(GL_EXTENSIONS, i)"; - EXPECT_EQ(r.strategyBefore, GL_LOSE_CONTEXT_ON_RESET) << "GL_RESET_NOTIFICATION_STRATEGY: 0x" << std::hex - << r.strategyBefore; + EXPECT_EQ(r.strategyBefore, GL_LOSE_CONTEXT_ON_RESET) + << "GL_RESET_NOTIFICATION_STRATEGY: 0x" << std::hex << r.strategyBefore; EXPECT_EQ(r.robustAccess, GL_FALSE); // The loss is reported. EXPECT_TRUE(r.resetStatusLost == GL_UNKNOWN_CONTEXT_RESET || r.resetStatusLost == GL_GUILTY_CONTEXT_RESET) @@ -1083,23 +1087,27 @@ namespace { // LOSE_CONTEXT_ON_RESET: the context stays lost, so the status does not go back to // GL_NO_ERROR (that would read as a reset that completed in place), and the KHR name // an ES application looks up answers the same. - EXPECT_EQ(r.resetStatusRepeat, r.resetStatusLost) << "the reset status of a lost context went back to 0x" - << std::hex << r.resetStatusRepeat; - EXPECT_EQ(r.resetStatusKhr, r.resetStatusLost) << "glGetGraphicsResetStatusKHR: 0x" << std::hex - << r.resetStatusKhr; + EXPECT_EQ(r.resetStatusRepeat, r.resetStatusLost) + << "the reset status of a lost context went back to 0x" << std::hex << r.resetStatusRepeat; + EXPECT_EQ(r.resetStatusKhr, r.resetStatusLost) + << "glGetGraphicsResetStatusKHR: 0x" << std::hex << r.resetStatusKhr; EXPECT_EQ(r.swapLost, EGL_FALSE) << "eglSwapBuffers on the lost context succeeded"; - EXPECT_EQ(r.swapLostError, EGL_CONTEXT_LOST) << "eglSwapBuffers on the lost context: 0x" << std::hex - << r.swapLostError; + EXPECT_EQ(r.swapLostError, EGL_CONTEXT_LOST) + << "eglSwapBuffers on the lost context: 0x" << std::hex << r.swapLostError; EXPECT_EQ(r.releaseOk, 1) << "releasing the lost context failed"; EXPECT_EQ(r.oldCurrent, EGL_FALSE) << "the lost context was made current again"; - EXPECT_EQ(r.oldCurrentError, EGL_CONTEXT_LOST) << "eglMakeCurrent of the lost context: 0x" << std::hex - << r.oldCurrentError; + EXPECT_EQ(r.oldCurrentError, EGL_CONTEXT_LOST) + << "eglMakeCurrent of the lost context: 0x" << std::hex << r.oldCurrentError; EXPECT_EQ(r.destroyOldOk, 1) << "the lost context could not be destroyed"; // A fresh session, and the new context renders. ASSERT_EQ(r.newContextOk, 1) << "no context could be created after the loss (EGL error 0x" << std::hex - << r.newContextError << ")\n" << r.note << "\n" << diagnostics; + << r.newContextError << ")\n" + << r.note << "\n" + << diagnostics; ASSERT_EQ(r.newCurrentOk, 1) << "the new context did not go current (EGL error 0x" << std::hex - << r.newCurrentError << ")\n" << r.note << "\n" << diagnostics; + << r.newCurrentError << ")\n" + << r.note << "\n" + << diagnostics; EXPECT_EQ(r.serverPidAfter, r.serverPidBefore) << "the new session is not on the same server process"; EXPECT_EQ(r.resetStatusNew, static_cast(GL_NO_ERROR)) << "the new context reports a reset"; EXPECT_EQ(r.strategyNew, GL_LOSE_CONTEXT_ON_RESET) << "the replacement is not told of resets"; @@ -1114,13 +1122,15 @@ namespace { EXPECT_EQ(r.oldTexturePublishedAfter, 0) << "the fresh session's client still believes it holds the " "lost texture's record"; EXPECT_EQ(r.oldTextureOwedAfter, 0) << "an upload owed to the lost session is queued for the fresh one"; - EXPECT_EQ(r.drawnPixel, kRed) << "the new context's textured draw did not read back: " << std::hex - << r.drawnPixel << "\n" << r.note << "\n" << diagnostics; - EXPECT_EQ(r.secondPixel, kBlue) << "the new context's clear did not read back: " << std::hex - << r.secondPixel; + EXPECT_EQ(r.drawnPixel, kRed) + << "the new context's textured draw did not read back: " << std::hex << r.drawnPixel << "\n" + << r.note << "\n" + << diagnostics; + EXPECT_EQ(r.secondPixel, kBlue) + << "the new context's clear did not read back: " << std::hex << r.secondPixel; EXPECT_EQ(r.fboStatus, static_cast(GL_FRAMEBUFFER_COMPLETE)) << std::hex << r.fboStatus; - EXPECT_EQ(r.fboPixel, kYellow) << "the new context's texture framebuffer did not read back: " << std::hex - << r.fboPixel; + EXPECT_EQ(r.fboPixel, kYellow) + << "the new context's texture framebuffer did not read back: " << std::hex << r.fboPixel; EXPECT_EQ(r.swapNew, EGL_TRUE) << "eglSwapBuffers on the new context failed"; EXPECT_EQ(r.resetStatusEnd, static_cast(GL_NO_ERROR)) << "the new session was lost too"; } @@ -1129,10 +1139,12 @@ namespace { // second session started and ended cleanly; nothing from the first reached it. static void ExpectOneLossAndACleanSecondSession(ServerProcess& server) { ASSERT_TRUE(WaitFor([&] { return Count(server.Log(), "in-process unix session #2 started") == 1; }, 5000)) - << "the client never opened a second session:\n" << server.Log(); + << "the client never opened a second session:\n" + << server.Log(); // Both sessions' ends: the lost one's latched exit and the fresh one's clean one. ASSERT_TRUE(WaitFor([&] { return Count(server.Log(), "reaped exit=") == 2; }, 10000)) - << "the second session did not end:\n" << server.Log(); + << "the second session did not end:\n" + << server.Log(); const std::string log = server.Log(); EXPECT_NE(log.find("SessionLatch{BackendDeviceLost}"), std::string::npos) << log; EXPECT_EQ(Count(log, "reaped exit=75"), 1u) << "the lost session did not end latched:\n" << log; @@ -1142,7 +1154,8 @@ namespace { // never created, a create or bind it cannot parse - each Fatal{ProtocolCorruption}. EXPECT_EQ(Count(log, "SessionLatch{"), 1u) << "the recovered session latched too:\n" << log; EXPECT_EQ(log.find("Fatal{ProtocolCorruption"), std::string::npos) - << "the recovered session was sent something it never created:\n" << log; + << "the recovered session was sent something it never created:\n" + << log; EXPECT_TRUE(server.pid > 0 && ::kill(server.pid, 0) == 0) << "the server died"; } }; @@ -1245,7 +1258,8 @@ TEST_F(ClientRecovery, AThreadStillOnTheLostContextReachesNothingOfTheFreshSessi EXPECT_EQ(r.recordsByStaleThread, 0) << "the thread on the lost context published records on the fresh session"; EXPECT_EQ(r.destroyOldOk, 1); EXPECT_EQ(r.pixelAfterStale, kRed) << "the fresh context stopped rendering: " << std::hex << r.pixelAfterStale - << "\n" << diagnostics; + << "\n" + << diagnostics; EXPECT_EQ(r.resetStatusAfterStale, static_cast(GL_NO_ERROR)) << "the fresh session was lost too"; ExpectOneLossAndACleanSecondSession(server); server.Stop(); @@ -1263,13 +1277,16 @@ TEST_F(ClientRecovery, ContextsNamingALostShareContextShareOneNewGroup) { const std::string diagnostics = "server:\n" + server.Log() + "\nclient:\n" + client.Text(); ASSERT_EQ(r.initialized, 1) << r.note << "\n" << diagnostics; ASSERT_EQ(r.firstOk, 1) << "eglCreateContext naming the lost share context failed (0x" << std::hex << r.firstError - << ")\n" << r.note; + << ")\n" + << r.note; ASSERT_EQ(r.secondOk, 1) << r.note; EXPECT_EQ(r.sameGroup, 1) << "the replacements landed in separate share groups"; EXPECT_EQ(r.lostGroupReused, 0) << "a replacement joined the lost share group"; EXPECT_EQ(r.textureSeenAcross, 1) << "a texture of one replacement is not the other's"; EXPECT_EQ(r.drawnAcross, kRed) << "the shared texture did not draw on the other replacement: " << std::hex - << r.drawnAcross << "\n" << r.note << "\n" << diagnostics; + << r.drawnAcross << "\n" + << r.note << "\n" + << diagnostics; ExpectOneLossAndACleanSecondSession(server); server.Stop(); } @@ -1293,11 +1310,10 @@ TEST_F(ClientRecovery, ThePidTargetedKnobLosesOnlyThatClientAndOnlyOnce) { } ClientLog bystanderLog("pid-bystander"); - const BystanderReport bystander = - RunInClient(server, bystanderLog.base, &RunBystanderClient); + const BystanderReport bystander = RunInClient(server, bystanderLog.base, &RunBystanderClient); const TargetedReport r = target.Release(server.endpoint); - const std::string diagnostics = "server:\n" + server.Log() + "\ntarget:\n" + targetLog.Text() + - "\nbystander:\n" + bystanderLog.Text(); + const std::string diagnostics = + "server:\n" + server.Log() + "\ntarget:\n" + targetLog.Text() + "\nbystander:\n" + bystanderLog.Text(); ASSERT_EQ(bystander.initialized, 1) << bystander.note << "\n" << diagnostics; EXPECT_GT(bystander.frames, 5); diff --git a/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp b/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp index 059441c4..2a4f7846 100644 --- a/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp +++ b/MobileGL/MG_Test/Wire/CompositorRecoveryTest.cpp @@ -38,15 +38,16 @@ #include #include -extern "C" { -void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -void glClear(GLbitfield mask); -void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); -GLenum glGetError(void); -GLenum glGetGraphicsResetStatus(void); -void glGenTextures(GLsizei n, GLuint* textures); -void glBindTexture(GLenum target, GLuint texture); -void glFlush(void); +extern "C" +{ + void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void glClear(GLbitfield mask); + void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); + GLenum glGetError(void); + GLenum glGetGraphicsResetStatus(void); + void glGenTextures(GLsizei n, GLuint* textures); + void glBindTexture(GLenum target, GLuint texture); + void glFlush(void); } using namespace MobileGL; @@ -236,8 +237,10 @@ namespace { ::setenv("MOBILEGL_LOG_FILE_PATH", logBase.c_str(), 1); ::setenv("MOBILEGL_TRANSPORT", "spawn", 1); ::setenv("MOBILEGL_IPC_CONTROL", endpoint.c_str(), 1); - if (serverWindow) ::setenv("MOBILEGL_IPC_SURFACE", "server", 1); - else ::unsetenv("MOBILEGL_IPC_SURFACE"); + if (serverWindow) + ::setenv("MOBILEGL_IPC_SURFACE", "server", 1); + else + ::unsetenv("MOBILEGL_IPC_SURFACE"); ::unsetenv("MOBILEGL_BACKEND_TYPE"); ::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT"); ::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_PID"); @@ -265,8 +268,7 @@ namespace { using TargetFn = void (*)(GLenum, void*); const auto target = reinterpret_cast(EGL::GetProcAddress("glEGLImageTargetTexture2DOES")); if (target == nullptr) return 0xFFFFFFFFu; - while (glGetError() != GL_NO_ERROR) { - } + while (glGetError() != GL_NO_ERROR) {} GLuint texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); @@ -398,9 +400,18 @@ namespace { EGLint major = 0; EGLint minor = 0; if (dpy == EGL_NO_DISPLAY || EGL::Initialize(dpy, &major, &minor) != EGL_TRUE) return Note(r, "no display"); - const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + const EGLint configAttribs[] = {EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, EGL_NONE}; EGLConfig config = nullptr; EGLint count = 0; @@ -610,8 +621,8 @@ TEST(CompositorRecovery, ACompositorOnTheServerWindowRecoversOnAFreshSessionAndR compositor.Go(server.endpoint); const CompositorReport c = compositor.Wait(); const OwnerReport o = owner.Wait(); - const std::string diagnostics = "server:\n" + server.Log() + "\ncompositor:\n" + compositorLog.Text() + - "\nowner:\n" + ownerLog.Text(); + const std::string diagnostics = + "server:\n" + server.Log() + "\ncompositor:\n" + compositorLog.Text() + "\nowner:\n" + ownerLog.Text(); // The compositor, before the loss: the server's window, an imported client image, a frame. ASSERT_EQ(c.received, 1) << c.note << "\n" << o.note << "\n" << diagnostics; diff --git a/MobileGL/MG_Test/Wire/ConcurrentContextsTest.cpp b/MobileGL/MG_Test/Wire/ConcurrentContextsTest.cpp index 736ac933..754bfb5a 100644 --- a/MobileGL/MG_Test/Wire/ConcurrentContextsTest.cpp +++ b/MobileGL/MG_Test/Wire/ConcurrentContextsTest.cpp @@ -31,33 +31,34 @@ #include -extern "C" { -void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); -void glClear(GLbitfield mask); -void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); -void glGenTextures(GLsizei n, GLuint* textures); -void glDeleteTextures(GLsizei n, const GLuint* textures); -void glBindTexture(GLenum target, GLuint texture); -void glActiveTexture(GLenum texture); -void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, - GLenum format, GLenum type, const void* pixels); -void glTexParameteri(GLenum target, GLenum pname, GLint param); -GLuint glCreateShader(GLenum type); -void glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); -void glCompileShader(GLuint shader); -GLuint glCreateProgram(void); -void glAttachShader(GLuint program, GLuint shader); -void glLinkProgram(GLuint program); -void glGetProgramiv(GLuint program, GLenum pname, GLint* params); -void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog); -void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); -void glUseProgram(GLuint program); -GLint glGetUniformLocation(GLuint program, const GLchar* name); -void glUniform1i(GLint location, GLint v0); -void glGenVertexArrays(GLsizei n, GLuint* arrays); -void glBindVertexArray(GLuint array); -void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); -void glDrawArrays(GLenum mode, GLint first, GLsizei count); +extern "C" +{ + void glClearColor(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); + void glClear(GLbitfield mask); + void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels); + void glGenTextures(GLsizei n, GLuint* textures); + void glDeleteTextures(GLsizei n, const GLuint* textures); + void glBindTexture(GLenum target, GLuint texture); + void glActiveTexture(GLenum texture); + void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, + GLenum format, GLenum type, const void* pixels); + void glTexParameteri(GLenum target, GLenum pname, GLint param); + GLuint glCreateShader(GLenum type); + void glShaderSource(GLuint shader, GLsizei count, const GLchar* const* string, const GLint* length); + void glCompileShader(GLuint shader); + GLuint glCreateProgram(void); + void glAttachShader(GLuint program, GLuint shader); + void glLinkProgram(GLuint program); + void glGetProgramiv(GLuint program, GLenum pname, GLint* params); + void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, GLchar* infoLog); + void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); + void glUseProgram(GLuint program); + GLint glGetUniformLocation(GLuint program, const GLchar* name); + void glUniform1i(GLint location, GLint v0); + void glGenVertexArrays(GLsizei n, GLuint* arrays); + void glBindVertexArray(GLuint array); + void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); + void glDrawArrays(GLenum mode, GLint first, GLsizei count); } using namespace MobileGL; @@ -90,8 +91,8 @@ namespace { Int32 firstBadFrame = -1; Int32 vertexCompiled = -1; Int32 fragmentCompiled = -1; - Int32 rebinds = 0; // ReleaseOnly: mid-run release + make-current that succeeded - Int32 cycles = 0; // ReadbackRace churners: contexts created, used and destroyed + Int32 rebinds = 0; // ReleaseOnly: mid-run release + make-current that succeeded + Int32 cycles = 0; // ReadbackRace churners: contexts created, used and destroyed Int32 cycleFailures = 0; // DestroyWhileCurrent: what eglDestroyContext answered on the OTHER thread, and what // binding the destroyed context again after this thread released it answered. @@ -110,12 +111,14 @@ namespace { }; // A<<24|B<<16|G<<8|R of the texel each thread draws. - Uint32 ColourOf(int index) { return 0xFF000000u | (0x40u + 0x50u * static_cast(index % 3)) << 8 | - (0x20u + 0x30u * static_cast(index / 3)); } + Uint32 ColourOf(int index) { + return 0xFF000000u | (0x40u + 0x50u * static_cast(index % 3)) << 8 | + (0x20u + 0x30u * static_cast(index / 3)); + } // The clear colour of a churn cycle (ReadbackRace): a different one per thread and cycle. Uint32 ChurnColourOf(int index, int cycle) { - return 0xFF000000u | (0x10u * static_cast(index)) << 16 | (static_cast(cycle * 7) & 0xFFu) << 8 | - 0x80u; + return 0xFF000000u | (0x10u * static_cast(index)) << 16 | + (static_cast(cycle * 7) & 0xFFu) << 8 | 0x80u; } struct Rig { @@ -123,8 +126,8 @@ namespace { EGLConfig config = nullptr; Shape shape = Shape::Unshared; Report* report = nullptr; - EGLContext share = EGL_NO_CONTEXT; // SharedRoot's root - EGLContext given[kThreads] = {}; // contexts made on the main thread (Siblings) + EGLContext share = EGL_NO_CONTEXT; // SharedRoot's root + EGLContext given[kThreads] = {}; // contexts made on the main thread (Siblings) std::atomic published[kThreads] = {}; // DestroyWhileCurrent: each thread's own std::atomic halfway[kThreads] = {}; std::atomic destroyed[kThreads] = {}; @@ -153,9 +156,10 @@ namespace { // The thread's program, vertex array and one-texel texture of its own colour. Drawing BuildDrawing(int index, ThreadReport* out) { - static const char* kVertex = "#version 330 core\n" - "const vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n" - "void main() { gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0); }\n"; + static const char* kVertex = + "#version 330 core\n" + "const vec2 corners[3] = vec2[3](vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0));\n" + "void main() { gl_Position = vec4(corners[gl_VertexID], 0.0, 1.0); }\n"; static const char* kFragment = "#version 330 core\n" "uniform sampler2D image;\n" "out vec4 color;\n" @@ -230,9 +234,15 @@ namespace { const EGLSurface surface = eglCreatePbufferSurface(dpy, rig->config, kPbufferAttribs); EGLContext context = EGL_NO_CONTEXT; switch (rig->shape) { - case Shape::SharedRoot: context = eglCreateContext(dpy, rig->config, rig->share, kContextAttribs); break; - case Shape::Siblings: context = rig->given[index]; break; - default: context = eglCreateContext(dpy, rig->config, EGL_NO_CONTEXT, kContextAttribs); break; + case Shape::SharedRoot: + context = eglCreateContext(dpy, rig->config, rig->share, kContextAttribs); + break; + case Shape::Siblings: + context = rig->given[index]; + break; + default: + context = eglCreateContext(dpy, rig->config, EGL_NO_CONTEXT, kContextAttribs); + break; } if (surface == EGL_NO_SURFACE || context == EGL_NO_CONTEXT || eglMakeCurrent(dpy, surface, surface, context) != EGL_TRUE) { @@ -269,7 +279,8 @@ namespace { // Released, so the destroy the main thread asked for has happened: the handle is gone. out->rebindAnswer = eglMakeCurrent(dpy, surface, surface, context); out->rebindError = eglGetError(); - if (out->rebindAnswer == EGL_TRUE) (void)eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + if (out->rebindAnswer == EGL_TRUE) + (void)eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); break; default: (void)eglDestroyContext(dpy, context); @@ -287,7 +298,8 @@ namespace { eglMakeCurrent(rig->dpy, surface, surface, context) == EGL_TRUE) { out->current = 1; const Drawing drawing = BuildDrawing(index, out); - for (int frame = 0; frame < kRaceReaderFrames; ++frame) DrawFrame(drawing, frame, out); + for (int frame = 0; frame < kRaceReaderFrames; ++frame) + DrawFrame(drawing, frame, out); (void)eglMakeCurrent(rig->dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); (void)eglDestroyContext(rig->dpy, context); } @@ -321,7 +333,8 @@ namespace { glBindVertexArray(vertexArray); const Uint32 colour = ChurnColourOf(index, cycle); glViewport(0, 0, 8, 8); - glClearColor(static_cast(colour & 0xFFu) / 255.0f, static_cast((colour >> 8) & 0xFFu) / 255.0f, + glClearColor(static_cast(colour & 0xFFu) / 255.0f, + static_cast((colour >> 8) & 0xFFu) / 255.0f, static_cast((colour >> 16) & 0xFFu) / 255.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); Uint32 pixel = 0x5A5A5A5Au; @@ -339,8 +352,10 @@ namespace { std::thread threads[kThreads]; if (rig.shape == Shape::ReadbackRace) { threads[0] = std::thread(ReadWithoutPause, &rig, 0); - for (int i = 1; i <= kRaceChurners; ++i) threads[i] = std::thread(ChurnContexts, &rig, i); - for (int i = 0; i <= kRaceChurners; ++i) threads[i].join(); + for (int i = 1; i <= kRaceChurners; ++i) + threads[i] = std::thread(ChurnContexts, &rig, i); + for (int i = 0; i <= kRaceChurners; ++i) + threads[i].join(); return; } if (rig.shape == Shape::SharedRoot) { @@ -352,7 +367,8 @@ namespace { for (int i = 1; i < kThreads; ++i) rig.given[i] = eglCreateContext(rig.dpy, rig.config, rig.given[0], kContextAttribs); } - for (int i = 0; i < kThreads; ++i) threads[i] = std::thread(RenderOnThread, &rig, i); + for (int i = 0; i < kThreads; ++i) + threads[i] = std::thread(RenderOnThread, &rig, i); if (rig.shape == Shape::DestroyWhileCurrent) { // Each context is destroyed from HERE while its own thread still has it current. EGL // answers EGL_TRUE and destroys it once that thread releases it. @@ -368,7 +384,8 @@ namespace { rig.destroyed[i].store(1); } } - for (auto& thread : threads) thread.join(); + for (auto& thread : threads) + thread.join(); if (rig.shape == Shape::ReleaseOnly) { for (int i = 0; i < kThreads; ++i) { const EGLContext context = rig.published[i].load(); @@ -433,12 +450,18 @@ namespace { const char* NameOf(Shape shape) { switch (shape) { - case Shape::SharedRoot: return "concurrent-shared"; - case Shape::Unshared: return "concurrent-unshared"; - case Shape::ReleaseOnly: return "concurrent-release"; - case Shape::Siblings: return "concurrent-siblings"; - case Shape::DestroyWhileCurrent: return "concurrent-destroy-current"; - case Shape::ReadbackRace: return "concurrent-race"; + case Shape::SharedRoot: + return "concurrent-shared"; + case Shape::Unshared: + return "concurrent-unshared"; + case Shape::ReleaseOnly: + return "concurrent-release"; + case Shape::Siblings: + return "concurrent-siblings"; + case Shape::DestroyWhileCurrent: + return "concurrent-destroy-current"; + case Shape::ReadbackRace: + return "concurrent-race"; } return "concurrent"; } @@ -488,16 +511,26 @@ namespace { } // namespace -TEST(ConcurrentContexts, ThreadsSharingOneRootContextEachDrawTheirOwnTexture) { Check(Shape::SharedRoot); } +TEST(ConcurrentContexts, ThreadsSharingOneRootContextEachDrawTheirOwnTexture) { + Check(Shape::SharedRoot); +} -TEST(ConcurrentContexts, ThreadsInSeparateShareGroupsEachDrawTheirOwnTexture) { Check(Shape::Unshared); } +TEST(ConcurrentContexts, ThreadsInSeparateShareGroupsEachDrawTheirOwnTexture) { + Check(Shape::Unshared); +} -TEST(ConcurrentContexts, ReleasingWithoutDestroyingLeavesTheOtherThreadsDrawing) { Check(Shape::ReleaseOnly); } +TEST(ConcurrentContexts, ReleasingWithoutDestroyingLeavesTheOtherThreadsDrawing) { + Check(Shape::ReleaseOnly); +} -TEST(ConcurrentContexts, ShareGroupSiblingsOutliveTheGroupsFirstContext) { Check(Shape::Siblings); } +TEST(ConcurrentContexts, ShareGroupSiblingsOutliveTheGroupsFirstContext) { + Check(Shape::Siblings); +} TEST(ConcurrentContexts, DestroyingAContextCurrentOnAnotherThreadIsDeferredUntilItsRelease) { Check(Shape::DestroyWhileCurrent); } -TEST(ConcurrentContexts, AReadBackRacingOtherThreadsContextTeardownsIsNotLost) { Check(Shape::ReadbackRace); } +TEST(ConcurrentContexts, AReadBackRacingOtherThreadsContextTeardownsIsNotLost) { + Check(Shape::ReadbackRace); +} diff --git a/MobileGL/MG_Test/Wire/EglInfoSequenceTest.cpp b/MobileGL/MG_Test/Wire/EglInfoSequenceTest.cpp index b9c30012..743ed76e 100644 --- a/MobileGL/MG_Test/Wire/EglInfoSequenceTest.cpp +++ b/MobileGL/MG_Test/Wire/EglInfoSequenceTest.cpp @@ -43,11 +43,11 @@ namespace { // One "OpenGL ... profile" block of eglinfo's output, or why it is missing. struct ProfileReport { - Int32 configFound = 0; // eglChooseConfig matched something - Int32 created = 0; // some eglCreateContext in the version walk returned a context - Int32 madeCurrent = 0; // ...and the surfaceless eglMakeCurrent took it - Int32 lastError = 0; // eglGetError after the last failed create / make-current - Int32 drawIsNone = 0; // eglGetCurrentSurface(EGL_DRAW) == EGL_NO_SURFACE while current + Int32 configFound = 0; // eglChooseConfig matched something + Int32 created = 0; // some eglCreateContext in the version walk returned a context + Int32 madeCurrent = 0; // ...and the surfaceless eglMakeCurrent took it + Int32 lastError = 0; // eglGetError after the last failed create / make-current + Int32 drawIsNone = 0; // eglGetCurrentSurface(EGL_DRAW) == EGL_NO_SURFACE while current Int32 major = 0; Int32 minor = 0; char vendor[96] = {}; @@ -77,8 +77,9 @@ namespace { } EGLConfig ChooseLikeEglInfo(EGLDisplay dpy, EGLint apiBit, ProfileReport& out) { - const EGLint attribs[] = {EGL_CONFORMANT, apiBit, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, EGL_BLUE_SIZE, 1, - EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, apiBit, EGL_NONE}; + const EGLint attribs[] = {EGL_CONFORMANT, apiBit, EGL_RED_SIZE, 1, EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, EGL_ALPHA_SIZE, 1, EGL_RENDERABLE_TYPE, apiBit, + EGL_NONE}; EGLConfig configs[64] = {}; EGLint count = 0; (void)EGL::ChooseConfig(dpy, attribs, configs, 64, &count); @@ -125,12 +126,15 @@ namespace { } void DesktopProfile(EGLDisplay dpy, EGLConfig config, bool core, bool khrCreateContext, ProfileReport& out) { - static const int kVersions[][2] = {{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, {3, 3}, - {3, 2}, {3, 1}, {3, 0}, {2, 1}, {2, 0}, {1, 5}, {1, 4}, {1, 3}, - {1, 2}, {1, 1}, {1, 0}}; + static const int kVersions[][2] = {{4, 6}, {4, 5}, {4, 4}, {4, 3}, {4, 2}, {4, 1}, {4, 0}, + {3, 3}, {3, 2}, {3, 1}, {3, 0}, {2, 1}, {2, 0}, {1, 5}, + {1, 4}, {1, 3}, {1, 2}, {1, 1}, {1, 0}}; for (const auto& v : kVersions) { if (core && v[0] == 3 && v[1] == 0) return; - const EGLint withProfile[] = {EGL_CONTEXT_MAJOR_VERSION, v[0], EGL_CONTEXT_MINOR_VERSION, v[1], + const EGLint withProfile[] = {EGL_CONTEXT_MAJOR_VERSION, + v[0], + EGL_CONTEXT_MINOR_VERSION, + v[1], EGL_CONTEXT_OPENGL_PROFILE_MASK, core ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, @@ -189,7 +193,8 @@ namespace { const bool khr = major == 1 && minor >= 4 && HasToken(exts, "EGL_KHR_create_context"); r.khrCreateContext = khr ? 1 : 0; r.surfacelessExtensions = - HasToken(exts, "EGL_KHR_surfaceless_context") && HasToken(exts, "EGL_KHR_no_config_context") ? 1 : 0; + HasToken(exts, "EGL_KHR_surfaceless_context") && HasToken(exts, "EGL_KHR_no_config_context") ? 1 + : 0; if (r.hasOpenGL && EGL::BindAPI(EGL_OPENGL_API) == EGL_TRUE) { const EGLConfig config = ChooseLikeEglInfo(dpy, EGL_OPENGL_BIT, r.core); r.compat.configFound = r.core.configFound; diff --git a/MobileGL/MG_Test/Wire/EventForfeitPeerTest.cpp b/MobileGL/MG_Test/Wire/EventForfeitPeerTest.cpp index 73248906..6953e3bf 100644 --- a/MobileGL/MG_Test/Wire/EventForfeitPeerTest.cpp +++ b/MobileGL/MG_Test/Wire/EventForfeitPeerTest.cpp @@ -242,7 +242,8 @@ namespace { const std::string image = ServerImage(); std::vector env = SupervisorEnvironment(logBase, waitMs, forwardLog, floodLines); std::vector envp; - for (auto& entry : env) envp.push_back(entry.data()); + for (auto& entry : env) + envp.push_back(entry.data()); envp.push_back(nullptr); std::string argv0 = image, argv1 = endpoint, argv2 = "--serve"; char* argv[] = {argv0.data(), argv1.data(), argv2.data(), nullptr}; @@ -289,7 +290,8 @@ namespace { std::size_t Count(const std::string& text, const std::string& needle) { std::size_t n = 0; - for (auto at = text.find(needle); at != std::string::npos; at = text.find(needle, at + needle.size())) ++n; + for (auto at = text.find(needle); at != std::string::npos; at = text.find(needle, at + needle.size())) + ++n; return n; } @@ -300,7 +302,8 @@ namespace { const auto begin = text.rfind('\n', at); const auto end = text.find('\n', at); return text.substr(begin == std::string::npos ? 0 : begin + 1, - (end == std::string::npos ? text.size() : end) - (begin == std::string::npos ? 0 : begin + 1)); + (end == std::string::npos ? text.size() : end) - + (begin == std::string::npos ? 0 : begin + 1)); } // Polls the supervisor's log until `needle` has appeared `times` times, or the budget runs @@ -312,8 +315,8 @@ namespace { const auto start = std::chrono::steady_clock::now(); while (std::chrono::steady_clock::now() - start < budget) { if (Count(ServerLog(logBase), needle) >= times) { - return std::chrono::duration_cast(std::chrono::steady_clock::now() - - start).count(); + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start) + .count(); } std::this_thread::sleep_for(every); } @@ -428,8 +431,8 @@ namespace { desc.StorageKind = static_cast(MobileGL::TextureStorageType::Buffer); desc.BindMask = MGP::kMGPipeBindShaderBuffer; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - if (client.EmitAndWait(MGP::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, nullptr, 0, - &status) == Wire::kInvalidSeq || + if (client.EmitAndWait(MGP::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, nullptr, 0, &status) == + Wire::kInvalidSeq || status != Wire::ReplySink::kStatusOk) { return kBufferCreate; } @@ -469,10 +472,10 @@ namespace { } enum class Scenario { - StopsDraining, // the peer stays, silent: NotDraining after the knob - KilledWhileWaiting, // SIGKILLed while the server waits: PeerGone, at once - KilledWithAControlOpQueued, // the same, with a surface op stuck behind the wait - ControlEndsWhileWaiting, // the peer stays; its control stream ends: Stopped, at once + StopsDraining, // the peer stays, silent: NotDraining after the knob + KilledWhileWaiting, // SIGKILLed while the server waits: PeerGone, at once + KilledWithAControlOpQueued, // the same, with a surface op stuck behind the wait + ControlEndsWhileWaiting, // the peer stays; its control stream ends: Stopped, at once ControlHalfClosedWhileWaiting, // the peer half-closes control (a teardown's first act), // data still open: PeerGone, at once }; @@ -611,11 +614,16 @@ namespace { const char* ScenarioTag(Scenario scenario) { switch (scenario) { - case Scenario::StopsDraining: return "silent"; - case Scenario::KilledWhileWaiting: return "kill"; - case Scenario::KilledWithAControlOpQueued: return "kill-op"; - case Scenario::ControlEndsWhileWaiting: return "ctl-end"; - case Scenario::ControlHalfClosedWhileWaiting: return "ctl-half"; + case Scenario::StopsDraining: + return "silent"; + case Scenario::KilledWhileWaiting: + return "kill"; + case Scenario::KilledWithAControlOpQueued: + return "kill-op"; + case Scenario::ControlEndsWhileWaiting: + return "ctl-end"; + case Scenario::ControlHalfClosedWhileWaiting: + return "ctl-half"; } return "?"; } @@ -628,8 +636,8 @@ namespace { const bool killed = scenario == Scenario::KilledWhileWaiting || scenario == Scenario::KilledWithAControlOpQueued; const std::uint32_t waitMs = scenario == Scenario::StopsDraining ? kEventWaitMs : kLongEventWaitMs; - const std::string tag = std::string(tcp ? "stream-" : "shm-") + ScenarioTag(scenario) + "-" + - std::to_string(::getpid()); + const std::string tag = + std::string(tcp ? "stream-" : "shm-") + ScenarioTag(scenario) + "-" + std::to_string(::getpid()); const std::string logBase = "/tmp/mgl-fz3-" + tag + ".log"; MobileGL::MG_Util::Debug::TruncateRoleLogs(logBase.c_str()); std::string endpoint, control; @@ -649,7 +657,8 @@ namespace { ~Reap() { StopSupervisor(pid); } } reapSupervisor{supervisor}; ASSERT_GE(WaitForLog(logBase, "listening on", 1, std::chrono::milliseconds(10000)), 0) - << "the supervisor never listened\n" << ServerLog(logBase); + << "the supervisor never listened\n" + << ServerLog(logBase); int published[2] = {-1, -1}, release[2] = {-1, -1}; ASSERT_EQ(::pipe(published), 0); @@ -719,21 +728,25 @@ namespace { } const long long stoppedMs = WaitForLog(logBase, "mgl-srv-apply stops on ReverseChannelForfeit", 1, std::chrono::milliseconds(waitMs + 20000)); - const auto stoppedAfterEnd = std::chrono::duration_cast( - std::chrono::steady_clock::now() - endedAt).count(); - const long long reapedMs = WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 1, - std::chrono::milliseconds(waitMs + 20000)); + const auto stoppedAfterEnd = + std::chrono::duration_cast(std::chrono::steady_clock::now() - endedAt) + .count(); + const long long reapedMs = + WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 1, std::chrono::milliseconds(waitMs + 20000)); const std::string log = ServerLog(logBase); ASSERT_GE(stoppedMs, 0) << "the server never stopped on a forfeit\n" << log; ASSERT_GE(reapedMs, 0) << "the supervisor never reaped the session with exit 0\n" << log; const std::string forfeit = LineWith(log, "ReverseChannelForfeit{"); EXPECT_NE(forfeit.find(expected), std::string::npos) - << "the early end of the wait was not named " << expected << "\n" << log; + << "the early end of the wait was not named " << expected << "\n" + << log; EXPECT_NE(forfeit.find(WaitKnobEntry(waitMs)), std::string::npos) - << "the forfeit did not run against the knob this supervisor was given\n" << log; + << "the forfeit did not run against the knob this supervisor was given\n" + << log; if (scenario == Scenario::ControlEndsWhileWaiting) { EXPECT_NE(log.find("control frame is 4 bytes"), std::string::npos) - << "the malformed control frame never reached the server's control loop\n" << log; + << "the malformed control frame never reached the server's control loop\n" + << log; } // WELL INSIDE THE KNOB: from a descriptor or the stop request, not from the 8000 ms // running out, and not from Stop()'s 5000 ms join either. @@ -743,28 +756,32 @@ namespace { } else { // The server waits the knob, forfeits by name, stops the apply thread, ends the session; // the supervisor reaps it. Budget: the knob plus generous slack - and far under 30 s. - const long long stoppedMs = - WaitForLog(logBase, "mgl-srv-apply stops on ReverseChannelForfeit", 1, - std::chrono::milliseconds(waitMs + 20000)); - const long long reapedMs = WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 1, - std::chrono::milliseconds(waitMs + 20000)); - const auto sinceReadbacks = std::chrono::duration_cast( - std::chrono::steady_clock::now() - published_at).count(); + const long long stoppedMs = WaitForLog(logBase, "mgl-srv-apply stops on ReverseChannelForfeit", 1, + std::chrono::milliseconds(waitMs + 20000)); + const long long reapedMs = + WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 1, std::chrono::milliseconds(waitMs + 20000)); + const auto sinceReadbacks = + std::chrono::duration_cast(std::chrono::steady_clock::now() - published_at) + .count(); const std::string log = ServerLog(logBase); ASSERT_GE(stoppedMs, 0) << "the server never stopped on a forfeit\n" << log; ASSERT_GE(reapedMs, 0) << "the supervisor never reaped the forfeited session with exit 0\n" << log; const std::string forfeit = LineWith(log, "ReverseChannelForfeit{"); EXPECT_NE(forfeit.find("ReverseChannelForfeit{NotDraining} - kEventBufferWriteback"), std::string::npos) - << "the forfeit was not named for the event that could not be placed\n" << log; + << "the forfeit was not named for the event that could not be placed\n" + << log; // THE VARIABLE WAS READ: the budget the server names is the one this supervisor was // given, not the 2000 ms default it would fall back to. EXPECT_NE(forfeit.find(WaitKnobEntry(waitMs)), std::string::npos) - << "the server did not wait against the MOBILEGL_IPC_EVENT_WAIT_MS it was given\n" << log; + << "the server did not wait against the MOBILEGL_IPC_EVENT_WAIT_MS it was given\n" + << log; EXPECT_NE(log.find("mgl-srv-apply stops on ReverseChannelForfeit - 1 reverse-channel event(s) dropped"), std::string::npos) - << "the drop was not counted (one event: the fifth writeback)\n" << log; + << "the drop was not counted (one event: the fifth writeback)\n" + << log; EXPECT_NE(log.find("the apply thread has stopped (ReverseChannelForfeit"), std::string::npos) - << "the session child did not end the session from the server's side\n" << log; + << "the session child did not end the session from the server's side\n" + << log; EXPECT_EQ(log.find("Fatal{EventRingOverflow"), std::string::npos) << log; EXPECT_EQ(log.find("reaped signal="), std::string::npos) << "a session died of a signal\n" << log; // THE KNOB, NOT 30 s: the forfeit waited most of MOBILEGL_IPC_EVENT_WAIT_MS (it cannot @@ -782,9 +799,11 @@ namespace { if (next == 0) NextPeer(control); const int nextCode = WaitChild(next, std::chrono::milliseconds(30000)); EXPECT_EQ(nextCode, static_cast(kChildOk)) - << "the next connection was not welcomed (child exit " << nextCode << ")\n" << ServerLog(logBase); + << "the next connection was not welcomed (child exit " << nextCode << ")\n" + << ServerLog(logBase); EXPECT_GE(WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 2, std::chrono::milliseconds(10000)), 0) - << "the welcomed session did not end cleanly\n" << ServerLog(logBase); + << "the welcomed session did not end cleanly\n" + << ServerLog(logBase); EXPECT_EQ(Count(ServerLog(logBase), "Refuse{Busy}"), 0u) << ServerLog(logBase); RemoveRoleLogsIfGreen(logBase); } @@ -842,18 +861,20 @@ namespace { ::close(published[0]); const auto published_at = std::chrono::steady_clock::now(); const int code = WaitChild(peer, std::chrono::milliseconds(kLaunchedEventWaitMs + 20000)); - const auto sinceReadbacks = std::chrono::duration_cast( - std::chrono::steady_clock::now() - published_at).count(); + const auto sinceReadbacks = + std::chrono::duration_cast(std::chrono::steady_clock::now() - published_at) + .count(); const std::string log = ServerLog(logBase); ASSERT_EQ(got, 1) << "the peer did not reach its readbacks (child exit " << code << ")\n" << log; EXPECT_EQ(code, static_cast(kChildOk)) - << "the launched server was not reaped with exit 0 (child exit " << code << ")\n" << log; - const std::string forfeit = LineWith(log, "ReverseChannelForfeit{"); - EXPECT_NE(forfeit.find("ReverseChannelForfeit{NotDraining} - kEventBufferWriteback"), std::string::npos) + << "the launched server was not reaped with exit 0 (child exit " << code << ")\n" << log; + const std::string forfeit = LineWith(log, "ReverseChannelForfeit{"); + EXPECT_NE(forfeit.find("ReverseChannelForfeit{NotDraining} - kEventBufferWriteback"), std::string::npos) << log; EXPECT_NE(forfeit.find(WaitKnobEntry(kLaunchedEventWaitMs)), std::string::npos) << "the launched server did not wait against the knob its client exported - the launcher " - "scrubbed it\n" << log; + "scrubbed it\n" + << log; EXPECT_NE(log.find("the apply thread has stopped (ReverseChannelForfeit"), std::string::npos) << log; EXPECT_EQ(log.find("Fatal{"), std::string::npos) << log; EXPECT_GE(sinceReadbacks, static_cast(kLaunchedEventWaitMs) * 8 / 10) << log; @@ -868,7 +889,8 @@ namespace { if (FILE* file = std::fopen(path.c_str(), "rb")) { char chunk[65536]; std::size_t got = 0; - while ((got = std::fread(chunk, 1, sizeof(chunk), file)) > 0) all.append(chunk, got); + while ((got = std::fread(chunk, 1, sizeof(chunk), file)) > 0) + all.append(chunk, got); std::fclose(file); } return all; @@ -893,7 +915,8 @@ namespace { // The sum of the counts the in-stream LogForward{Dropped} notices name. unsigned long long NamedDrops(const std::string& text) { unsigned long long sum = 0; - for (const auto count : Indices(text, "LogForward{Dropped} - ")) sum += count; + for (const auto count : Indices(text, "LogForward{Dropped} - ")) + sum += count; return sum; } @@ -914,9 +937,8 @@ namespace { if (client.StartSpawned() != MOBILEGL_OK) ::_exit(kNoWelcome); int originalReceiveBuffer = 0; if (alsoStopDraining) { - const int filled = FillTheEventRingWithoutDraining(client, [&] { - return StopReadingControlAndFlood(client, originalReceiveBuffer); - }); + const int filled = FillTheEventRingWithoutDraining( + client, [&] { return StopReadingControlAndFlood(client, originalReceiveBuffer); }); if (filled != kChildOk) ::_exit(filled); } else { const int up = BringUpPbufferContext(); @@ -930,8 +952,8 @@ namespace { (void)::read(release, &go, 1); if (alsoStopDraining) ::_exit(kChildOk); // End the receive-window fault before waiting for the ack behind the queued logs. - if (::setsockopt(client.ControlSocketForTest()->StreamFd(), SOL_SOCKET, SO_RCVBUF, - &originalReceiveBuffer, sizeof(originalReceiveBuffer)) != 0) + if (::setsockopt(client.ControlSocketForTest()->StreamFd(), SOL_SOCKET, SO_RCVBUF, &originalReceiveBuffer, + sizeof(originalReceiveBuffer)) != 0) ::_exit(kControlBuffer); if (!client.PauseControlReaderForTest(false)) ::_exit(kNoControlReader); client.SyncPeerLog(); @@ -944,8 +966,8 @@ namespace { void RunLogFloodArm(bool alsoStopDraining) { std::signal(SIGPIPE, SIG_IGN); // as RunArm: no write in this file may kill the test process PinHeadlessEgl(); - const std::string tag = std::string("stream-flood-") + (alsoStopDraining ? "undrained-" : "") + - std::to_string(::getpid()); + const std::string tag = + std::string("stream-flood-") + (alsoStopDraining ? "undrained-" : "") + std::to_string(::getpid()); const std::string logBase = "/tmp/mgl-fz3-" + tag + ".log"; const std::string peerBase = "/tmp/mgl-fz3-" + tag + "-peer.log"; const std::string peerLogPath = @@ -964,7 +986,8 @@ namespace { ~Reap() { StopSupervisor(pid); } } reapSupervisor{supervisor}; ASSERT_GE(WaitForLog(logBase, "listening on", 1, std::chrono::milliseconds(10000)), 0) - << "the supervisor never listened\n" << ServerLog(logBase); + << "the supervisor never listened\n" + << ServerLog(logBase); int published[2] = {-1, -1}, release[2] = {-1, -1}, synced[2] = {-1, -1}; ASSERT_EQ(::pipe(published), 0); @@ -1021,9 +1044,9 @@ namespace { // own log must reach the flood's last record: every one of the kFloodRecords records is // applied (and logs its ERROR line) while the control connection takes no byte. A forward // that waits on the socket stalls the apply thread a few MiB in, and this never arrives. - const long long reachedMs = WaitForLog(logBase, kFloodErrorNeedle, kFloodRecords, - std::chrono::milliseconds(kFloodWithinMs), - std::chrono::milliseconds(50)); + const long long reachedMs = + WaitForLog(logBase, kFloodErrorNeedle, kFloodRecords, std::chrono::milliseconds(kFloodWithinMs), + std::chrono::milliseconds(50)); ASSERT_GE(reachedMs, 0) << "the apply thread did not get through the flood while its client was not " "reading control - it was held by logging (" << Count(ServerLog(logBase), kFloodErrorNeedle) << " of " << kFloodRecords @@ -1036,7 +1059,8 @@ namespace { const char go = 1; ASSERT_EQ(::write(release[1], &go, 1), 1); struct pollfd wait{synced[0], POLLIN, 0}; - ASSERT_EQ(::poll(&wait, 1, 30000), 1) << "the peer never finished SyncPeerLog\n" << ServerLog(logBase).substr(0, 20000); + ASSERT_EQ(::poll(&wait, 1, 30000), 1) << "the peer never finished SyncPeerLog\n" + << ServerLog(logBase).substr(0, 20000); ASSERT_EQ(::read(synced[0], &byte, 1), 1) << "the peer died in SyncPeerLog (child exit " << WaitChild(peer, std::chrono::milliseconds(5000)) << ")"; ::close(synced[0]); @@ -1090,7 +1114,8 @@ namespace { // ERROR line, not a guess at the socket's buffer growth. EXPECT_LE(Field(summary, "notices"), static_cast(kFloodRecords / 10)) << summary; EXPECT_LT(Field(summary, "max-offer-us"), kMaxOfferUs) - << "one forward held its logging thread for longer than " << kMaxOfferUs << " us\n" << summary; + << "one forward held its logging thread for longer than " << kMaxOfferUs << " us\n" + << summary; EXPECT_EQ(serverLog.find("Fatal{"), std::string::npos); } else { ::close(synced[0]); @@ -1132,7 +1157,8 @@ namespace { if (next == 0) NextPeer(endpoint); const int nextCode = WaitChild(next, std::chrono::milliseconds(30000)); EXPECT_EQ(nextCode, static_cast(kChildOk)) << "the next connection was not welcomed"; - EXPECT_GE(WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 2, std::chrono::milliseconds(10000), every), 0) + EXPECT_GE( + WaitForLog(logBase, "reaped exit=0 sessionsFaulted=0", 2, std::chrono::milliseconds(10000), every), 0) << "the welcomed session did not end cleanly"; } RemoveRoleLogsIfGreen(logBase); @@ -1166,7 +1192,9 @@ TEST(EventForfeitPeer, ShmControlHalfClosedWhileTheServerWaitsIsPeerGone) { RunArm(false, Scenario::ControlHalfClosedWhileWaiting); } -TEST(EventForfeitPeer, ShmLaunchedServerTakesTheWaitKnobThroughTheScrubbedEnvironment) { RunLaunchedArm(); } +TEST(EventForfeitPeer, ShmLaunchedServerTakesTheWaitKnobThroughTheScrubbedEnvironment) { + RunLaunchedArm(); +} TEST(EventForfeitPeer, StreamPeerThatStopsDrainingIsForfeitedAndTheNextConnectionIsWelcomed) { RunArm(true, Scenario::StopsDraining); diff --git a/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp b/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp index fcbaf13e..b4b142b6 100644 --- a/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp +++ b/MobileGL/MG_Test/Wire/FatalFamilyTest.cpp @@ -70,8 +70,7 @@ TEST(FatalFamily, EveryFamilyProjectsToADefinedWireCode) { // projection would say a death was not a death. for (const MGFatalFamily family : kAllFamilies) { const FatalCode code = FatalCodeForFamily(family); - EXPECT_NE(code, FatalCode::None) - << FatalFamilyName(family) << " projects onto FatalCode::None"; + EXPECT_NE(code, FatalCode::None) << FatalFamilyName(family) << " projects onto FatalCode::None"; } } @@ -143,11 +142,9 @@ TEST(FatalFunnelSeam, TheMagmaWireVerbDeathReachesSessionFailAndBumpsTheFaultCou std::signal(SIGABRT, &ReportFaultCountAndExit); MG_Remote::InstallPipeSessionFailHook(); MG_Pipe::MGPipeSessionFail(MG_Pipe::MGPipeFatalFamily::UnmigratedVerb, - "MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", - "unit-probe"); + "MGPipe: Fatal{UnmigratedVerb, \"Magma:%s\"}", "unit-probe"); }, - ::testing::ExitedWithCode(kFaultExitBase + 1), - "Fatal\\{UnmigratedVerb, \"Magma:unit-probe\"\\}") + ::testing::ExitedWithCode(kFaultExitBase + 1), "Fatal\\{UnmigratedVerb, \"Magma:unit-probe\"\\}") << "the Magma wire funnel did not end through SessionFail: either the line is not the " "site's own any more, or SessionFaultCount() did not move - which is what exit gate " "S8 reads and what every telemetry consumer of the funnel depends on"; @@ -186,10 +183,10 @@ TEST(FatalFamily, TheSessionFaultFrameCarriesTheFamilyAndItsCode) { // code the frame carries is the family's own projection. for (const MGFatalFamily family : kAllFamilies) { ::flatbuffers::FlatBufferBuilder builder(256); - const auto fatal = ::MobileGL::Wire::CreateFatalDirect( - builder, FatalCodeForFamily(family), "detail line", FatalFamilyName(family)); - const auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Fatal, fatal.Union()); + const auto fatal = ::MobileGL::Wire::CreateFatalDirect(builder, FatalCodeForFamily(family), "detail line", + FatalFamilyName(family)); + const auto root = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Fatal, fatal.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); ::flatbuffers::Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); @@ -221,8 +218,8 @@ TEST(FatalFunnelSeam, ADeviceLossLatchesAnArmedSessionAndTheProcessLives) { MG_Remote::InstallPipeSessionFailHook(); MG_Remote::ArmSessionLatch(); const bool answered = MG_Pipe::MGPipeSessionLatch( - MG_Pipe::MGPipeFatalFamily::DeviceLost, - "MGPipe: Fatal{BackendDeviceLost, \"Magma:%s\"} - unit probe", "wire-verb-flush"); + MG_Pipe::MGPipeFatalFamily::DeviceLost, "MGPipe: Fatal{BackendDeviceLost, \"Magma:%s\"} - unit probe", + "wire-verb-flush"); const bool ok = !answered && MG_Remote::SessionLatched() && MG_Remote::SessionLatchedFamily() == MG_Remote::MGFatalFamily::BackendDeviceLost && MG_Pipe::MGPipeSessionLatchArmed(); @@ -262,7 +259,8 @@ TEST(FatalFunnelSeam, TheDeviceLossKnobFiresOncePerValueAndReArmsOnANewOne) { { const auto checks = [](int n) { int fired = 0; - for (int i = 0; i < n; ++i) fired += MobileGL::MG_Pipe::MGPipeDebugDeviceLossDue() ? 1 << i : 0; + for (int i = 0; i < n; ++i) + fired += MobileGL::MG_Pipe::MGPipeDebugDeviceLossDue() ? 1 << i : 0; return fired; }; ::unsetenv("MOBILEGL_DEBUG_INJECT_DEVICE_LOST_AT"); diff --git a/MobileGL/MG_Test/Wire/FdPassingTest.cpp b/MobileGL/MG_Test/Wire/FdPassingTest.cpp index 5ca48983..dca99f51 100644 --- a/MobileGL/MG_Test/Wire/FdPassingTest.cpp +++ b/MobileGL/MG_Test/Wire/FdPassingTest.cpp @@ -51,7 +51,9 @@ namespace { } // namespace -TEST(FdPassingTest, IsSupportedOnThisPlatform) { EXPECT_TRUE(FdPassing::Supported()); } +TEST(FdPassingTest, IsSupportedOnThisPlatform) { + EXPECT_TRUE(FdPassing::Supported()); +} TEST(FdPassingTest, ChildSharesASegmentThatTheParentMapsAndVerifies) { int sockets[2] = {-1, -1}; @@ -95,19 +97,16 @@ TEST(FdPassingTest, ChildSharesASegmentThatTheParentMapsAndVerifies) { int fd = -1; std::uint64_t required = 0; MobileGLMutableByteSpan smallSpan{small.data(), small.size()}; - EXPECT_EQ(FdPassing::ReceiveFd(sockets[0], &fd, smallSpan, &required, 5000), - MOBILEGL_ERR_BUFFER_TOO_SMALL); + EXPECT_EQ(FdPassing::ReceiveFd(sockets[0], &fd, smallSpan, &required, 5000), MOBILEGL_ERR_BUFFER_TOO_SMALL); EXPECT_EQ(required, FdPassing::kMaxSidebandBytes); EXPECT_EQ(fd, -1); std::vector sidebandBuffer(FdPassing::kMaxSidebandBytes); std::uint64_t sidebandSize = 0; MobileGLMutableByteSpan sidebandSpan{sidebandBuffer.data(), sidebandBuffer.size()}; - ASSERT_EQ(FdPassing::ReceiveFd(sockets[0], &fd, sidebandSpan, &sidebandSize, 5000), - MOBILEGL_OK); + ASSERT_EQ(FdPassing::ReceiveFd(sockets[0], &fd, sidebandSpan, &sidebandSize, 5000), MOBILEGL_OK); ASSERT_GE(fd, 0); - EXPECT_EQ(std::string(reinterpret_cast(sidebandBuffer.data()), - static_cast(sidebandSize)), + EXPECT_EQ(std::string(reinterpret_cast(sidebandBuffer.data()), static_cast(sidebandSize)), sideband); ShmSegment adopted; @@ -156,8 +155,7 @@ TEST(FdPassingTest, RejectsBadArguments) { const std::vector tooBig(FdPassing::kMaxSidebandBytes + 1, 0); const MobileGLByteSpan oversized{tooBig.data(), tooBig.size()}; EXPECT_EQ(FdPassing::SendFd(sockets[1], sockets[0], oversized), MOBILEGL_ERR_INVALID_ARGUMENT); - EXPECT_EQ(FdPassing::SendFd(sockets[1], -1, MobileGLByteSpan{nullptr, 0}), - MOBILEGL_ERR_INVALID_ARGUMENT); + EXPECT_EQ(FdPassing::SendFd(sockets[1], -1, MobileGLByteSpan{nullptr, 0}), MOBILEGL_ERR_INVALID_ARGUMENT); ::close(sockets[0]); ::close(sockets[1]); @@ -172,8 +170,7 @@ TEST(FdPassingTest, SegmentWithoutASidebandStillCarriesItsDescriptor) { ASSERT_EQ(segment.Map(false), MOBILEGL_OK); static_cast(segment.Data())[0] = 0xA5; - ASSERT_EQ(FdPassing::SendFd(sockets[1], segment.Fd(), MobileGLByteSpan{nullptr, 0}), - MOBILEGL_OK); + ASSERT_EQ(FdPassing::SendFd(sockets[1], segment.Fd(), MobileGLByteSpan{nullptr, 0}), MOBILEGL_OK); std::vector sidebandBuffer(FdPassing::kMaxSidebandBytes); int fd = -1; @@ -208,8 +205,8 @@ TEST(FdPassingTest, SocketDoorbellWakesAParkedWaiter) { std::atomic woke{false}; std::thread waiter([&] { - woke.store(waiterBell.Wait( - parked, [&] { return ready.load(std::memory_order_acquire); }, kDefaultSpinUs, 5000)); + woke.store( + waiterBell.Wait(parked, [&] { return ready.load(std::memory_order_acquire); }, kDefaultSpinUs, 5000)); }); std::this_thread::sleep_for(std::chrono::milliseconds(20)); @@ -234,9 +231,7 @@ TEST(FdPassingTest, SocketDoorbellTimesOutAndRemembersAnEarlyWakeup) { // Nothing rings: the park has to end on its deadline, not hang. const auto start = std::chrono::steady_clock::now(); EXPECT_FALSE(waiterBell.Park(30)); - EXPECT_GE(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 20); // A wakeup that arrives before anyone parks is not lost - it is sitting in @@ -277,12 +272,9 @@ TEST(FdPassingTest, SocketDoorbellStopsParkingWhenThePeerHangsUp) { // ...and a Wait with no deadline at all gives up instead of re-parking. std::atomic parked{0}; - EXPECT_FALSE(waiterBell.Wait( - parked, [] { return false; }, /*spinUs=*/0, kWaitForever)); + EXPECT_FALSE(waiterBell.Wait(parked, [] { return false; }, /*spinUs=*/0, kWaitForever)); EXPECT_EQ(parked.load(), 0u); - EXPECT_LT(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 1000); } diff --git a/MobileGL/MG_Test/Wire/FramingTest.cpp b/MobileGL/MG_Test/Wire/FramingTest.cpp index 69eb9fb3..9c802ec5 100644 --- a/MobileGL/MG_Test/Wire/FramingTest.cpp +++ b/MobileGL/MG_Test/Wire/FramingTest.cpp @@ -171,9 +171,7 @@ TEST(FramingTest, TakeWithNoCompleteMessageDoesNotBlockOrCorrupt) { std::vector message; EXPECT_EQ(reader.TakeMessage(message), MOBILEGL_ERR_TIMEOUT); - ASSERT_EQ(reader.Feed(stream.data() + kFrameHeaderSize + 32, - stream.size() - kFrameHeaderSize - 32), - MOBILEGL_OK); + ASSERT_EQ(reader.Feed(stream.data() + kFrameHeaderSize + 32, stream.size() - kFrameHeaderSize - 32), MOBILEGL_OK); ASSERT_TRUE(reader.HasMessage()); ASSERT_EQ(reader.TakeMessage(message), MOBILEGL_OK); EXPECT_EQ(message, payload); diff --git a/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp b/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp index 09e752af..f0a4c6a9 100644 --- a/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp +++ b/MobileGL/MG_Test/Wire/GlamorEglSequenceTest.cpp @@ -95,10 +95,11 @@ namespace { } void RunGlamor(GlamorReport& r) { - r.platformGbmExtension = HasToken(EGL::QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS), "EGL_MESA_platform_gbm") || - HasToken(EGL::QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS), "EGL_KHR_platform_gbm") - ? 1 - : 0; + r.platformGbmExtension = + HasToken(EGL::QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS), "EGL_MESA_platform_gbm") || + HasToken(EGL::QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS), "EGL_KHR_platform_gbm") + ? 1 + : 0; // Any gbm_device* will do: this process has no GBM loader to ask its backend's name, and the // display's frames come from the server, never from the device. static int fakeGbmDevice[16] = {}; @@ -113,8 +114,13 @@ namespace { HasToken(exts, "EGL_EXT_image_dma_buf_import") && HasToken(exts, "EGL_EXT_image_dma_buf_import_modifiers"); (void)EGL::BindAPI(EGL_OPENGL_API); - const EGLint core31[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, - EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE}; + const EGLint core31[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 1, + EGL_NONE}; const EGLContext ctx = EGL::CreateContext(dpy, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, core31); r.noConfigCoreContext = ctx != EGL_NO_CONTEXT ? 1 : 0; if (!r.noConfigCoreContext) { @@ -142,7 +148,8 @@ namespace { if (std::sscanf(version, "%d.%d", &vmaj, &vmin) == 2) r.glVersion = vmaj * 10 + vmin; } r.coreProfile = !HasGlExtension("GL_ARB_compatibility"); - r.vertexArrayObject = HasGlExtension("GL_ARB_vertex_array_object") || HasGlExtension("GL_OES_vertex_array_object"); + r.vertexArrayObject = + HasGlExtension("GL_ARB_vertex_array_object") || HasGlExtension("GL_OES_vertex_array_object"); r.oesEglImage = HasGlExtension("GL_OES_EGL_image"); EGLint formats[16] = {}; @@ -159,10 +166,19 @@ namespace { auto* backend = MG_Backend::pActiveBackendObject.get(); MG_Backend::SharedImageExport image; if (backend != nullptr && backend->AllocateSharedImage(64, 32, kXrgb8888, &image) && image.Fd >= 0) { - const EGLAttrib attribs[] = {EGL_WIDTH, 64, EGL_HEIGHT, 32, EGL_LINUX_DRM_FOURCC_EXT, kXrgb8888, - EGL_DMA_BUF_PLANE0_FD_EXT, image.Fd, EGL_DMA_BUF_PLANE0_OFFSET_EXT, - static_cast(image.Offset), EGL_DMA_BUF_PLANE0_PITCH_EXT, - static_cast(image.Stride), EGL_NONE}; + const EGLAttrib attribs[] = {EGL_WIDTH, + 64, + EGL_HEIGHT, + 32, + EGL_LINUX_DRM_FOURCC_EXT, + kXrgb8888, + EGL_DMA_BUF_PLANE0_FD_EXT, + image.Fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + static_cast(image.Offset), + EGL_DMA_BUF_PLANE0_PITCH_EXT, + static_cast(image.Stride), + EGL_NONE}; const EGLImage eglImage = EGL::CreateImage(dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs); r.imageCreated = eglImage != EGL_NO_IMAGE ? 1 : 0; if (eglImage != EGL_NO_IMAGE) (void)EGL::DestroyImage(dpy, eglImage); @@ -276,10 +292,19 @@ namespace { const auto clearColor = reinterpret_cast(EGL::GetProcAddress("glClearColor")); const auto clear = reinterpret_cast(EGL::GetProcAddress("glClear")); if (backend != nullptr && backend->AllocateSharedImage(64, 32, kArgb8888, &image) && image.Fd >= 0) { - const EGLAttrib attribs[] = {EGL_WIDTH, 64, EGL_HEIGHT, 32, EGL_LINUX_DRM_FOURCC_EXT, kArgb8888, - EGL_DMA_BUF_PLANE0_FD_EXT, image.Fd, EGL_DMA_BUF_PLANE0_OFFSET_EXT, - static_cast(image.Offset), EGL_DMA_BUF_PLANE0_PITCH_EXT, - static_cast(image.Stride), EGL_NONE}; + const EGLAttrib attribs[] = {EGL_WIDTH, + 64, + EGL_HEIGHT, + 32, + EGL_LINUX_DRM_FOURCC_EXT, + kArgb8888, + EGL_DMA_BUF_PLANE0_FD_EXT, + image.Fd, + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + static_cast(image.Offset), + EGL_DMA_BUF_PLANE0_PITCH_EXT, + static_cast(image.Stride), + EGL_NONE}; eglImage = EGL::CreateImage(dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attribs); } if (eglImage != EGL_NO_IMAGE && genTextures && bindTexture && imageTarget && genFramebuffers && @@ -358,8 +383,13 @@ namespace { r.dmabufImport = HasToken(exts, "EGL_EXT_image_dma_buf_import") ? 1 : 0; r.nativeFence = HasToken(exts, "EGL_ANDROID_native_fence_sync") ? 1 : 0; (void)EGL::BindAPI(EGL_OPENGL_API); - const EGLint core33[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, - EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_NONE}; + const EGLint core33[] = {EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, + EGL_CONTEXT_MAJOR_VERSION, + 3, + EGL_CONTEXT_MINOR_VERSION, + 3, + EGL_NONE}; const EGLContext ctx = EGL::CreateContext(dpy, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, core33); r.current = ctx != EGL_NO_CONTEXT && EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ctx) == EGL_TRUE; if (!r.current) { @@ -489,8 +519,8 @@ TEST(GlamorEglSequence, ChromesGpuProcessGetsANativeFenceOfItsDmaBufWrites) { ASSERT_EQ(r.entryPoints, 1) << "eglCreateSyncKHR & co. or eglDupNativeFenceFDANDROID did not resolve"; EXPECT_EQ(r.noContextRefused, 1); ASSERT_EQ(r.current, 1) << "error 0x" << std::hex << r.eglError; - std::printf("[ chrome ] image bound %d, framebuffer complete %d, fence descriptor %d (error 0x%x)\n", - r.imageBound, r.framebufferComplete, r.dupFd, r.dupError); + std::printf("[ chrome ] image bound %d, framebuffer complete %d, fence descriptor %d (error 0x%x)\n", r.imageBound, + r.framebufferComplete, r.dupFd, r.dupError); // The fence command reaches the server, which publishes the context's image writes and answers. ASSERT_EQ(r.fenceCreated, 1) << "the native fence was refused, error 0x" << std::hex << r.eglError; EXPECT_EQ(r.fenceType, EGL_SYNC_NATIVE_FENCE_ANDROID); diff --git a/MobileGL/MG_Test/Wire/InProcessServerTest.cpp b/MobileGL/MG_Test/Wire/InProcessServerTest.cpp index d54a8c29..abd9cc54 100644 --- a/MobileGL/MG_Test/Wire/InProcessServerTest.cpp +++ b/MobileGL/MG_Test/Wire/InProcessServerTest.cpp @@ -47,9 +47,9 @@ namespace { ::setenv("MOBILEGL_LOG_FILE_PATH", logBase.c_str(), 1); ::setenv("MOBILEGL_IPC_ROLE", "server", 1); ::setenv("MOBILEGL_IPC_DIAL", "no", 1); - for (const char* name : {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", - "MOBILEGL_IPC_STAGE_MB", "MOBILEGL_IPC_CONTROL", "MOBILEGL_IPC_SURFACE", - "MOBILEGL_BACKEND_TYPE"}) + for (const char* name : + {"MOBILEGL_TRANSPORT", "MOBILEGL_IPC_SERVER_PATH", "MOBILEGL_IPC_RING_MB", "MOBILEGL_IPC_STAGE_MB", + "MOBILEGL_IPC_CONTROL", "MOBILEGL_IPC_SURFACE", "MOBILEGL_BACKEND_TYPE"}) ::unsetenv(name); sigset_t stop; sigemptyset(&stop); @@ -147,10 +147,12 @@ namespace { return false; server->pid = launched.pid; bool listening = false; - (void)WaitFor([&] { - if (server->Log().find("listening on") != std::string::npos) return listening = true; - return server->WaitExit(0) && server->pid <= 0; - }, 10000); + (void)WaitFor( + [&] { + if (server->Log().find("listening on") != std::string::npos) return listening = true; + return server->WaitExit(0) && server->pid <= 0; + }, + 10000); // The listener takes the native address; the client control selector includes its // transport prefix. Bare @name is not a supported MOBILEGL_IPC_CONTROL spelling. if (listening) server->endpoint = "unix:" + server->endpoint; @@ -193,8 +195,8 @@ TEST(UnixSupervisor, ConcurrentSharedMemoryClientsHaveSeparateWorkersAndAnExited EXPECT_EQ(excess.started, 0) << "the configured live-session limit was ignored"; // The refusal line reaches the supervisor's log asynchronously; the client can see the refusal // first (one CI run read the log before the line landed), so the log is waited for. - EXPECT_TRUE(WaitFor([&] { return server.Log().find("Refuse{Busy} the session limit is reached") != std::string::npos; }, - 5000)) + EXPECT_TRUE(WaitFor( + [&] { return server.Log().find("Refuse{Busy} the session limit is reached") != std::string::npos; }, 5000)) << server.Log(); // The second worker must not hold the first peer's connection open after its EOF. This also @@ -265,8 +267,8 @@ TEST(InProcessServer, SequentialSessionsShareOneProcessAndALatchedSessionDoesNot // Where headless EGL exists, both healthy sessions rendered - the backend came up, went away with // session A and came up again in the same process. if (a.egl == 0 || c.egl == 0) { - GTEST_SKIP() << "sessions, latch reset and stop proven; the render half needs headless EGL (" << a.note - << " / " << c.note << ")"; + GTEST_SKIP() << "sessions, latch reset and stop proven; the render half needs headless EGL (" << a.note << " / " + << c.note << ")"; } EXPECT_EQ(a.status, Remote::Wire::ReplySink::kStatusOk) << a.note; EXPECT_EQ(a.pixel, 0xFF00FF00u) << "session A's clear did not read back green"; @@ -307,9 +309,8 @@ TEST(InProcessServer, TheFirstSessionsBackendIsPinnedForTheProcessLifetime) { ASSERT_EQ(a.started, 1) << a.note << "\n" << server.Log(); ASSERT_TRUE(WaitFor([&] { return !InProcessReapLine(server, 1).empty(); }, 15000)) << server.Log(); - const PeerReport b = RunPeer(server.endpoint, ApplyOneRecord, /*stopCleanly=*/true, 30000, 0, nullptr, [] { - MobileGL::MG_Config::ActiveBackendType = MobileGL::BackendType::DirectVulkan; - }); + const PeerReport b = RunPeer(server.endpoint, ApplyOneRecord, /*stopCleanly=*/true, 30000, 0, nullptr, + [] { MobileGL::MG_Config::ActiveBackendType = MobileGL::BackendType::DirectVulkan; }); EXPECT_EQ(b.started, 0) << "a Magma session was admitted into a process whose first session was Espryt"; const std::string log = server.Log(); EXPECT_NE(log.find("pinned backend type"), std::string::npos) << log; @@ -336,8 +337,7 @@ TEST(SupervisorChildren, AForkedSessionChildDiesWithItsSupervisor) { ::kill(supervisor.pid, SIGKILL); ASSERT_TRUE(supervisor.WaitExit(5000)); int status = 0; - const bool reaped = WaitFor( - [&] { return ::waitpid(sessionChild, &status, WNOHANG) == sessionChild; }, 5000); + const bool reaped = WaitFor([&] { return ::waitpid(sessionChild, &status, WNOHANG) == sessionChild; }, 5000); EXPECT_TRUE(reaped) << "the session child outlived its supervisor by 5 s with its client still connected"; if (reaped) { EXPECT_TRUE(WIFSIGNALED(status) && WTERMSIG(status) == SIGKILL) diff --git a/MobileGL/MG_Test/Wire/InProcessTransportTest.cpp b/MobileGL/MG_Test/Wire/InProcessTransportTest.cpp index 6c4a120a..9f3ddc1f 100644 --- a/MobileGL/MG_Test/Wire/InProcessTransportTest.cpp +++ b/MobileGL/MG_Test/Wire/InProcessTransportTest.cpp @@ -43,8 +43,7 @@ namespace { if (result != MOBILEGL_OK) { return std::string("(result)) + ">"; } - return std::string(reinterpret_cast(buffer.data()), - static_cast(size)); + return std::string(reinterpret_cast(buffer.data()), static_cast(size)); } } // namespace @@ -112,9 +111,7 @@ TEST(InProcessTransportTest, PollAndTimeoutDoNotBlockForever) { const auto start = std::chrono::steady_clock::now(); EXPECT_EQ(server->ReceiveFrame(span, &size, 30), MOBILEGL_ERR_TIMEOUT); - EXPECT_GE(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 20); } @@ -144,9 +141,7 @@ TEST(InProcessTransportTest, BlockedReceiverWakesOnSendAndOnShutdown) { InProcessTransport::CreatePair(client, server); std::atomic got{false}; - std::thread reader([&] { - got.store(Receive(*server, kWaitForever) == "wake"); - }); + std::thread reader([&] { got.store(Receive(*server, kWaitForever) == "wake"); }); std::this_thread::sleep_for(std::chrono::milliseconds(20)); ASSERT_EQ(client->SendFrame(Span(std::string("wake"))), MOBILEGL_OK); reader.join(); @@ -203,9 +198,7 @@ TEST(InProcessTransportTest, HandsOverADescriptorAndItsSideband) { MobileGLMutableByteSpan bigSpan{big.data(), big.size()}; ASSERT_EQ(server->ReceiveFd(&fd, bigSpan, &sidebandSize, 100), MOBILEGL_OK); ASSERT_GE(fd, 0); - EXPECT_EQ(std::string(reinterpret_cast(big.data()), - static_cast(sidebandSize)), - sideband); + EXPECT_EQ(std::string(reinterpret_cast(big.data()), static_cast(sidebandSize)), sideband); // Same open file description, independent descriptor. const char payload[] = "bytes"; @@ -239,8 +232,7 @@ TEST(InProcessTransportTest, AFrameWakeupIsNotEatenByAWaiterOnDescriptors) { std::uint64_t size = 0; fdWaiterStarted.store(true); // Never offered a descriptor: this one ends on the Shutdown below. - EXPECT_EQ(client->ReceiveFd(&fd, span, &size, kWaitForever), - MOBILEGL_ERR_TRANSPORT_CLOSED); + EXPECT_EQ(client->ReceiveFd(&fd, span, &size, kWaitForever), MOBILEGL_ERR_TRANSPORT_CLOSED); EXPECT_EQ(fd, -1); }); while (!fdWaiterStarted.load()) { @@ -263,9 +255,8 @@ TEST(InProcessTransportTest, AFrameWakeupIsNotEatenByAWaiterOnDescriptors) { const auto start = std::chrono::steady_clock::now(); ASSERT_EQ(server->SendFrame(Span(message)), MOBILEGL_OK); frameWaiter.join(); - const auto elapsedMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(); + const auto elapsedMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); EXPECT_EQ(got, message); // Not "eventually, when the receive timed out and re-checked". @@ -311,8 +302,7 @@ TEST(InProcessTransportTest, DoorbellReturnsImmediatelyWhenAlreadyReady) { std::atomic parked{0}; // No notification is sent at all: a condition that is already true must // never park, or the lost-wakeup window would be reachable. - EXPECT_TRUE(client->SelfDoorbell().Wait( - parked, [] { return true; }, kDefaultSpinUs, 0)); + EXPECT_TRUE(client->SelfDoorbell().Wait(parked, [] { return true; }, kDefaultSpinUs, 0)); EXPECT_EQ(parked.load(), 0u); } @@ -339,8 +329,7 @@ TEST(InProcessTransportTest, DoorbellSpinsWithoutParkingWhenReadyFlipsInsideTheB // Flips on the 8th probe - far inside a 50 us budget on any core that can run this suite, // and deliberately NOT on the first, so the spin loop really iterates. int probes = 0; - EXPECT_TRUE(bell.Wait( - parked, [&probes] { return ++probes >= 8; }, kDefaultSpinUs, 5000)); + EXPECT_TRUE(bell.Wait(parked, [&probes] { return ++probes >= 8; }, kDefaultSpinUs, 5000)); EXPECT_GE(probes, 8); EXPECT_EQ(bell.ParkEntries(), parksBefore) << "the wait blocked although its condition flipped inside the spin budget: the " @@ -358,8 +347,7 @@ TEST(InProcessTransportTest, DoorbellStillParksWhenTheSpinBudgetRunsOut) { Doorbell& bell = client->SelfDoorbell(); const std::uint64_t parksBefore = bell.ParkEntries(); - EXPECT_FALSE(bell.Wait( - parked, [] { return false; }, kDefaultSpinUs, 30)); + EXPECT_FALSE(bell.Wait(parked, [] { return false; }, kDefaultSpinUs, 30)); EXPECT_GT(bell.ParkEntries(), parksBefore) << "the wait never reached the blocking park, so a waiter with no deadline would burn " "a core at full clock for ever"; @@ -385,8 +373,7 @@ TEST(InProcessTransportTest, AWaitThatParksTwiceOnARememberedNotifyIsStillOnePar bell.Notify(); const std::uint64_t parksBefore = bell.ParkEntries(); - EXPECT_FALSE(bell.Wait( - parked, [] { return false; }, kDefaultSpinUs, 30)); + EXPECT_FALSE(bell.Wait(parked, [] { return false; }, kDefaultSpinUs, 30)); EXPECT_EQ(bell.ParkEntries(), parksBefore + 1) << "one wait counted more than one park: the ledger's parks are no longer a subset of " "its waits, and `clipark > cli` reads as 'every verb pays a futex round trip' when " @@ -401,11 +388,8 @@ TEST(InProcessTransportTest, DoorbellTimesOutWhenNothingHappens) { std::atomic parked{0}; const auto start = std::chrono::steady_clock::now(); - EXPECT_FALSE(client->SelfDoorbell().Wait( - parked, [] { return false; }, kDefaultSpinUs, 30)); - EXPECT_GE(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_FALSE(client->SelfDoorbell().Wait(parked, [] { return false; }, kDefaultSpinUs, 30)); + EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 20); EXPECT_EQ(parked.load(), 0u); } @@ -431,8 +415,8 @@ TEST(InProcessTransportTest, ShutdownUnparksAWaiterWithNoDeadline) { InProcessTransport::CreatePair(shared->client, shared->server); std::thread waiter([shared] { - shared->woke.store(shared->server->SelfDoorbell().Wait( - shared->parked, [] { return false; }, kDefaultSpinUs, kWaitForever)); + shared->woke.store( + shared->server->SelfDoorbell().Wait(shared->parked, [] { return false; }, kDefaultSpinUs, kWaitForever)); shared->returned.store(true, std::memory_order_release); }); // Past the spin and announced as parked; a little longer and it is inside @@ -447,8 +431,7 @@ TEST(InProcessTransportTest, ShutdownUnparksAWaiterWithNoDeadline) { shared->client->Shutdown(); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); - while (!shared->returned.load(std::memory_order_acquire) && - std::chrono::steady_clock::now() < deadline) { + while (!shared->returned.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } if (!shared->returned.load(std::memory_order_acquire)) { @@ -467,10 +450,8 @@ TEST(InProcessTransportTest, ShutdownUnparksAWaiterWithNoDeadline) { // Sticky: a wait with no deadline that ARRIVES after the Shutdown returns at // once rather than parking, so a late thread cannot hang either. const auto start = std::chrono::steady_clock::now(); - EXPECT_FALSE(shared->server->SelfDoorbell().Wait( - shared->parked, [] { return false; }, 0, kWaitForever)); - EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - start) - .count(), + EXPECT_FALSE(shared->server->SelfDoorbell().Wait(shared->parked, [] { return false; }, 0, kWaitForever)); + EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 1000); EXPECT_EQ(shared->parked.load(), 0u); } diff --git a/MobileGL/MG_Test/Wire/InitialCapsStartupTest.cpp b/MobileGL/MG_Test/Wire/InitialCapsStartupTest.cpp index 4116979f..9743c64e 100644 --- a/MobileGL/MG_Test/Wire/InitialCapsStartupTest.cpp +++ b/MobileGL/MG_Test/Wire/InitialCapsStartupTest.cpp @@ -82,8 +82,8 @@ namespace { int peerControlFd = -1; const auto accepted = Transport::SocketTransport::AcceptOne(listener, 2000, &peerControlFd); if (accepted == MOBILEGL_OK) - controlPeer = std::make_unique(peerControlFd, -1, - Transport::TransportRole::Server); + controlPeer = + std::make_unique(peerControlFd, -1, Transport::TransportRole::Server); ASSERT_EQ(connected, MOBILEGL_OK); ASSERT_EQ(accepted, MOBILEGL_OK); ASSERT_TRUE(controlClient->IsTcp()); @@ -111,9 +111,9 @@ namespace { sizes.EventRingBytes = 4096; sizes.ReplyBytes = 4096; ASSERT_EQ(client.AttachStreamLink(clientDataFd, sizes), MOBILEGL_OK); - ASSERT_EQ(Transport::CreateStreamLink(peerDataFd, sizes, Transport::TransportRoleTag::ServerConsumer, - dataPeer), - MOBILEGL_OK); + ASSERT_EQ( + Transport::CreateStreamLink(peerDataFd, sizes, Transport::TransportRoleTag::ServerConsumer, dataPeer), + MOBILEGL_OK); dataPeer->InitializeEndpoints(); client.*StartupPeerMember(StartupControlTag{}) = controlClient.get(); client.*StartupPeerMember(StartupInboxTag{}) = std::make_unique(*controlClient); diff --git a/MobileGL/MG_Test/Wire/LogForwardTest.cpp b/MobileGL/MG_Test/Wire/LogForwardTest.cpp index 3743615c..c7e9dd10 100644 --- a/MobileGL/MG_Test/Wire/LogForwardTest.cpp +++ b/MobileGL/MG_Test/Wire/LogForwardTest.cpp @@ -101,7 +101,8 @@ namespace { m_cv.notify_all(); m_cv.wait(lock, [this] { return m_open || m_cut; }); if (m_cut) return MOBILEGL_ERR_TRANSPORT_CLOSED; - m_written.push_back(Decode(static_cast(bytes.data), static_cast(bytes.size))); + m_written.push_back( + Decode(static_cast(bytes.data), static_cast(bytes.size))); m_cv.notify_all(); return MOBILEGL_OK; } @@ -163,7 +164,9 @@ namespace { return line + "\n"; } - bool Contains(const std::string& text, const std::string& needle) { return text.find(needle) != std::string::npos; } + bool Contains(const std::string& text, const std::string& needle) { + return text.find(needle) != std::string::npos; + } // The count a LogForward notice names ("... - N line(s) ..."), or -1. long long NoticeCount(const std::string& text, const char* word) { @@ -270,7 +273,8 @@ namespace { if (FILE* file = std::fopen(path.c_str(), "rb")) { char chunk[4096]; std::size_t got = 0; - while ((got = std::fread(chunk, 1, sizeof(chunk), file)) > 0) all.append(chunk, got); + while ((got = std::fread(chunk, 1, sizeof(chunk), file)) > 0) + all.append(chunk, got); std::fclose(file); } return all; @@ -422,12 +426,14 @@ namespace { long long offeringMs = -1; std::thread caller([&] { const auto start = Clock::now(); - for (int i = 1; i <= kLines; ++i) channel.Offer(MOBILEGL_LOG_LEVEL_WARN, Numbered("stall warn", i, 100).c_str()); + for (int i = 1; i <= kLines; ++i) + channel.Offer(MOBILEGL_LOG_LEVEL_WARN, Numbered("stall warn", i, 100).c_str()); offeringMs = MillisecondsSince(start); done = true; }); const auto start = Clock::now(); - while (!done && MillisecondsSince(start) < 5000) std::this_thread::sleep_for(std::chrono::milliseconds(5)); + while (!done && MillisecondsSince(start) < 5000) + std::this_thread::sleep_for(std::chrono::milliseconds(5)); const bool heldTheCaller = !done; transport.Open(); // lets a held caller finish, so a red is a red and not a hang caller.join(); @@ -452,7 +458,8 @@ namespace { EXPECT_EQ(notice.text[0], '[') << "a notice must carry a header, so the client files it as it came"; EXPECT_EQ(counters.notices, 1u); // Every line is accounted for: written, or counted as dropped. - EXPECT_EQ(SeriesIndices(written, "stall warn").size() + counters.lossyDropped, static_cast(kLines)); + EXPECT_EQ(SeriesIndices(written, "stall warn").size() + counters.lossyDropped, + static_cast(kLines)); EXPECT_EQ(counters.forwarded, SeriesIndices(written, "stall warn").size() + 2); // + primer, + after } @@ -472,8 +479,10 @@ namespace { transport.Open(); counters = channel.Close(); const auto errors = SeriesIndices(transport.Frames(), "mixed error"); - ASSERT_EQ(errors.size(), static_cast(kEach)) << "an ERROR line was lost while WARN lines were dropped"; - for (int i = 0; i < kEach; ++i) EXPECT_EQ(errors[i], i + 1) << "ERROR lines out of order at " << i; + ASSERT_EQ(errors.size(), static_cast(kEach)) + << "an ERROR line was lost while WARN lines were dropped"; + for (int i = 0; i < kEach; ++i) + EXPECT_EQ(errors[i], i + 1) << "ERROR lines out of order at " << i; EXPECT_EQ(counters.errorsCoalesced, 0u); EXPECT_EQ(counters.abandoned, 0u); } @@ -486,7 +495,8 @@ namespace { limits.errorQueueBytes = 4 * 1024; LogForwardChannel channel(transport, &GatedTransport::CutThunk, &transport, limits); constexpr int kLines = 200; - for (int i = 1; i <= kLines; ++i) channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("capped error", i, 60).c_str()); + for (int i = 1; i <= kLines; ++i) + channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("capped error", i, 60).c_str()); auto counters = channel.Counters(); EXPECT_GT(counters.errorsCoalesced, 0u); transport.Open(); @@ -502,7 +512,8 @@ namespace { coalescedNamed += named; } EXPECT_EQ(coalescedNamed, static_cast(counters.errorsCoalesced)); - EXPECT_EQ(SeriesIndices(written, "capped error").size() + counters.errorsCoalesced, static_cast(kLines)) + EXPECT_EQ(SeriesIndices(written, "capped error").size() + counters.errorsCoalesced, + static_cast(kLines)) << "an ERROR line was neither written nor counted"; EXPECT_EQ(written.back().text, "after the error gap\n"); } @@ -515,16 +526,19 @@ namespace { LogForwardChannel channel(transport, &GatedTransport::CutThunk, &transport, limits); constexpr int kLines = 24; const auto start = Clock::now(); - for (int i = 1; i <= kLines; ++i) channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("paced error", i).c_str()); + for (int i = 1; i <= kLines; ++i) + channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("paced error", i).c_str()); EXPECT_LT(MillisecondsSince(start), 200) << "pacing held the caller"; std::this_thread::sleep_for(std::chrono::milliseconds(100)); // A burst of 4 and ~40/s: about 8 by now, and far fewer than all 24. EXPECT_LT(transport.Frames().size(), 18u) << "the pace did not hold anything back"; - ASSERT_TRUE(transport.WaitForWritten(kLines, std::chrono::milliseconds(5000))) << "paced lines never all went out"; + ASSERT_TRUE(transport.WaitForWritten(kLines, std::chrono::milliseconds(5000))) + << "paced lines never all went out"; const auto counters = channel.Close(); const auto indices = SeriesIndices(transport.Frames(), "paced error"); ASSERT_EQ(indices.size(), static_cast(kLines)); - for (int i = 0; i < kLines; ++i) EXPECT_EQ(indices[i], i + 1); + for (int i = 0; i < kLines; ++i) + EXPECT_EQ(indices[i], i + 1); EXPECT_GE(counters.errorsPaced, 10u); EXPECT_EQ(counters.errorsCoalesced, 0u); } @@ -537,7 +551,8 @@ namespace { limits.errorLinesPerSecond = 1; limits.errorBurstLines = 1; LogForwardChannel channel(transport, &GatedTransport::CutThunk, &transport, limits); - for (int i = 1; i <= 10; ++i) channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("before ack", i).c_str()); + for (int i = 1; i <= 10; ++i) + channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("before ack", i).c_str()); flatbuffers::FlatBufferBuilder builder(64); const auto flush = Wire::CreateLogFlush(builder, 77, true); const auto envelope = Wire::CreateCtrlEnvelope(builder, Wire::CtrlMsg::LogFlush, flush.Union()); @@ -598,7 +613,8 @@ namespace { LogForwardLimits limits; limits.closeDrainMs = 200; LogForwardChannel channel(transport, &GatedTransport::CutThunk, &transport, limits); - for (int i = 1; i <= 50; ++i) channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("never read", i).c_str()); + for (int i = 1; i <= 50; ++i) + channel.Offer(MOBILEGL_LOG_LEVEL_ERROR, Numbered("never read", i).c_str()); ASSERT_TRUE(transport.WaitForAttempt(std::chrono::milliseconds(2000))); const auto start = Clock::now(); const auto counters = channel.Close(); @@ -654,14 +670,18 @@ namespace { const auto counters = channel.Close(); const auto errors = SeriesIndices(received, "socket error"); ASSERT_EQ(errors.size(), static_cast(kLines / 100)) << "an ERROR line was lost"; - for (std::size_t i = 0; i < errors.size(); ++i) EXPECT_EQ(errors[i], static_cast((i + 1) * 100)); + for (std::size_t i = 0; i < errors.size(); ++i) + EXPECT_EQ(errors[i], static_cast((i + 1) * 100)); long long droppedNamed = 0; for (const auto& line : received) { if (!line.isLog) continue; const long long named = NoticeCount(line.text, "Dropped"); - if (named >= 0) droppedNamed += named; - else if (Contains(line.text, "socket warn")) EXPECT_EQ(line.level, Wire::LogLevel::Warn); - else if (Contains(line.text, "socket error")) EXPECT_EQ(line.level, Wire::LogLevel::Error); + if (named >= 0) + droppedNamed += named; + else if (Contains(line.text, "socket warn")) + EXPECT_EQ(line.level, Wire::LogLevel::Warn); + else if (Contains(line.text, "socket error")) + EXPECT_EQ(line.level, Wire::LogLevel::Error); } EXPECT_EQ(droppedNamed, static_cast(counters.lossyDropped)); EXPECT_EQ(SeriesIndices(received, "socket warn").size() + counters.lossyDropped, diff --git a/MobileGL/MG_Test/Wire/MultiSessionTest.cpp b/MobileGL/MG_Test/Wire/MultiSessionTest.cpp index 84fa0439..2bbfd4de 100644 --- a/MobileGL/MG_Test/Wire/MultiSessionTest.cpp +++ b/MobileGL/MG_Test/Wire/MultiSessionTest.cpp @@ -65,10 +65,10 @@ namespace { constexpr Uint32 kRed = 0xFF0000FFu; enum PeerOp : Uint32 { - kOpApplyRecord = 1, // one record through the applier (no GL) - kOpCreateContext = 2, // ServerCreateEGLContext(arg) - the session's context token table + kOpApplyRecord = 1, // one record through the applier (no GL) + kOpCreateContext = 2, // ServerCreateEGLContext(arg) - the session's context token table kOpHealthySession = 3, // the rig's whole healthy session, `arg` = the clear colour - kOpStop = 4, // ClientSession::Stop, then the child leaves + kOpStop = 4, // ClientSession::Stop, then the child leaves // P14 S5: mint context `arg` in share group `aux` (a real frontend share-group token, so // two contexts that pass different ones are a NON-shared pair and two that pass the same // one are a sharing pair). @@ -186,14 +186,13 @@ namespace { break; case kOpCreateContext: report.context = Remote::Server::ServerCreateEGLContext(command.arg, /*shareGroupToken=*/0, - /*flags=*/0) + /*flags=*/0) ? 1 : 0; break; case kOpCreateContextInGroup: - report.context = Remote::Server::ServerCreateEGLContext(command.arg, command.aux, /*flags=*/0) - ? 1 - : 0; + report.context = + Remote::Server::ServerCreateEGLContext(command.arg, command.aux, /*flags=*/0) ? 1 : 0; break; case kOpBindContext: { P::MGPBindContext bind{}; @@ -228,8 +227,7 @@ namespace { peer.fromChild = reports[0]; // The handshake's report, taken HERE so that no Step can read it and mistake it for its // own answer. - if (::read(peer.fromChild, &peer.ready, sizeof(peer.ready)) != - static_cast(sizeof(peer.ready))) { + if (::read(peer.fromChild, &peer.ready, sizeof(peer.ready)) != static_cast(sizeof(peer.ready))) { peer.ready = StepReport{}; std::snprintf(peer.ready.note, sizeof(peer.ready.note), "peer died during its handshake"); } @@ -258,7 +256,8 @@ namespace { // THE HEADLINE. Two clients, one server process, both served at once. TEST_F(MultiSessionFixture, TwoShmSessionsInOneProcessAreServedAtOnce) { ServerProcess server; - ASSERT_TRUE(LaunchUnixInProcessServer(&server, "two")) << "the in-process unix server did not start:\n" << server.Log(); + ASSERT_TRUE(LaunchUnixInProcessServer(&server, "two")) << "the in-process unix server did not start:\n" + << server.Log(); const auto serverPid = static_cast(server.pid); Peer a = StartPeer(server.endpoint); @@ -280,9 +279,11 @@ TEST_F(MultiSessionFixture, TwoShmSessionsInOneProcessAreServedAtOnce) { // Both sessions really were started, in this process, and neither latched. EXPECT_NE(SessionStartedLine(server, 1).find("pid=" + std::to_string(server.pid)), std::string::npos) - << SessionStartedLine(server, 1) << "\n" << server.Log(); + << SessionStartedLine(server, 1) << "\n" + << server.Log(); EXPECT_NE(SessionStartedLine(server, 2).find("pid=" + std::to_string(server.pid)), std::string::npos) - << SessionStartedLine(server, 2) << "\n" << server.Log(); + << SessionStartedLine(server, 2) << "\n" + << server.Log(); EXPECT_EQ(Count(server.Log(), "SessionLatch{"), 0u) << "a session latched a fault: " << server.Log(); // THE RENDER HALF, one session at a time, BOTH STILL LIVE: each session's clear colour travels @@ -366,7 +367,8 @@ TEST_F(MultiSessionFixture, ClosingOneSessionLeavesTheOtherServing) { a.Close(); ASSERT_TRUE(WaitFor([&] { return server.Log().find("reaped exit=0") != std::string::npos; }, 5000)) - << "session A did not end cleanly:\n" << server.Log(); + << "session A did not end cleanly:\n" + << server.Log(); EXPECT_EQ(::kill(static_cast(b.pid), 0), 0) << "closing A killed session B's peer"; // B IS STILL SERVED: its records keep reaching its own applier and the server keeps answering. @@ -375,7 +377,8 @@ TEST_F(MultiSessionFixture, ClosingOneSessionLeavesTheOtherServing) { for (int record = 0; record < 3; ++record) { const StepReport after = b.Step(kOpApplyRecord); ASSERT_EQ(after.applied, 1) << "session B stopped applying after session A closed (record " << record - << "): " << after.note << "\n" << server.Log(); + << "): " << after.note << "\n" + << server.Log(); } EXPECT_EQ(Count(server.Log(), "SessionLatch{"), 0u) << "a session latched: " << server.Log(); @@ -408,16 +411,15 @@ TEST_F(MultiSessionFixture, ClosingOneSessionLeavesTheOtherServing) { // one session share one applier and one of the two clears lands on top of the other. TEST_F(MultiSessionFixture, TwoSessionsInterleaveTwoContextsEachWithoutCrossTalk) { ServerProcess server; - ASSERT_TRUE(LaunchUnixInProcessServer(&server, "interleave")) - << "the in-process unix server did not start:\n" << server.Log(); + ASSERT_TRUE(LaunchUnixInProcessServer(&server, "interleave")) << "the in-process unix server did not start:\n" + << server.Log(); Peer a = StartPeer(server.endpoint); Peer b = StartPeer(server.endpoint); ASSERT_GT(a.pid, 0); ASSERT_GT(b.pid, 0); ASSERT_EQ(a.ready.started, 1) << a.ready.note << "\n" << server.Log(); - ASSERT_EQ(b.ready.started, 1) << "a second session was refused (" << b.ready.note << ")\n" - << server.Log(); + ASSERT_EQ(b.ready.started, 1) << "a second session was refused (" << b.ready.note << ")\n" << server.Log(); // THE SAME TWO TOKENS AND THE SAME TWO GROUP TOKENS IN BOTH SESSIONS, on purpose: a name is a // name in a session, and the two group tokens say the two contexts of one session do NOT @@ -445,18 +447,17 @@ TEST_F(MultiSessionFixture, TwoSessionsInterleaveTwoContextsEachWithoutCrossTalk << "round " << round << ": " << aGl.session.note; ASSERT_EQ(bGl.session.status, Remote::Wire::ReplySink::kStatusOk) << "round " << round << ": " << bGl.session.note; - EXPECT_EQ(aGl.session.pixel, kGreen) << "round " << round << ": session A read back " - << std::hex << aGl.session.pixel; - EXPECT_EQ(bGl.session.pixel, kRed) << "round " << round << ": session B read back " - << std::hex << bGl.session.pixel; + EXPECT_EQ(aGl.session.pixel, kGreen) + << "round " << round << ": session A read back " << std::hex << aGl.session.pixel; + EXPECT_EQ(bGl.session.pixel, kRed) + << "round " << round << ": session B read back " << std::hex << bGl.session.pixel; } if (reportedNoEgl) { - GTEST_SKIP() << "the context/session routing is proven; the render half needs headless EGL (" - << a.ready.note << ")"; + GTEST_SKIP() << "the context/session routing is proven; the render half needs headless EGL (" << a.ready.note + << ")"; } - EXPECT_EQ(Count(server.Log(), "SessionLatch{"), 0u) - << "a session latched during the interleave: " << server.Log(); + EXPECT_EQ(Count(server.Log(), "SessionLatch{"), 0u) << "a session latched during the interleave: " << server.Log(); b.Close(); a.Close(); @@ -494,14 +495,16 @@ TEST_F(MultiSessionFixture, ADeviceLossEndsOnlyItsOwnSession) { // Check #1: B's read-back is answered. const StepReport bFirst = b.Step(kOpHealthySession, kGreen); - ASSERT_EQ(bFirst.session.egl, 1) << "no headless EGL for session B: " << bFirst.session.note << "\n" << server.Log(); + ASSERT_EQ(bFirst.session.egl, 1) << "no headless EGL for session B: " << bFirst.session.note << "\n" + << server.Log(); ASSERT_EQ(bFirst.session.pixel, kGreen) << bFirst.session.note << "\n" << server.Log(); // Check #2: A's device is "lost" at its read-back. A's session latches and ends. const StepReport aLost = a.Step(kOpHealthySession, kRed); EXPECT_NE(aLost.session.pixel, kRed) << "session A read back a frame from a lost device"; ASSERT_TRUE(WaitFor([&] { return server.Log().find("reaped exit=75") != std::string::npos; }, 5000)) - << "session A did not end on a latched fault:\n" << server.Log(); + << "session A did not end on a latched fault:\n" + << server.Log(); const std::string log = server.Log(); EXPECT_NE(log.find("Fatal{BackendDeviceLost, \"Espryt:read-pixels\"}"), std::string::npos) << log; EXPECT_NE(log.find("SessionLatch{BackendDeviceLost}"), std::string::npos) << log; @@ -510,8 +513,9 @@ TEST_F(MultiSessionFixture, ADeviceLossEndsOnlyItsOwnSession) { // B IS STILL SERVED, records and read-backs alike. for (int record = 0; record < 3; ++record) { const StepReport after = b.Step(kOpApplyRecord); - ASSERT_EQ(after.applied, 1) << "session B stopped applying after session A's device loss (record " - << record << "): " << after.note << "\n" << server.Log(); + ASSERT_EQ(after.applied, 1) << "session B stopped applying after session A's device loss (record " << record + << "): " << after.note << "\n" + << server.Log(); } const StepReport bAgain = b.Step(kOpHealthySession, kGreen); EXPECT_EQ(bAgain.session.pixel, kGreen) << bAgain.session.note << "\n" << server.Log(); diff --git a/MobileGL/MG_Test/Wire/P12ServerRig.h b/MobileGL/MG_Test/Wire/P12ServerRig.h index 56b89fc0..d0a47e10 100644 --- a/MobileGL/MG_Test/Wire/P12ServerRig.h +++ b/MobileGL/MG_Test/Wire/P12ServerRig.h @@ -118,7 +118,8 @@ namespace { inline Uint32 Count(const std::string& haystack, const std::string& needle) { Uint32 n = 0; - for (auto at = haystack.find(needle); at != std::string::npos; at = haystack.find(needle, at + 1)) ++n; + for (auto at = haystack.find(needle); at != std::string::npos; at = haystack.find(needle, at + 1)) + ++n; return n; } @@ -205,7 +206,8 @@ namespace { // every session, so a session that ends - a device loss among them - leaves it serving. // ------------------------------------------------------------------------------------ - [[noreturn]] inline void RunUnixInProcessServerProcess(const std::string& endpoint, const std::string& logBase) { + [[noreturn]] inline void RunUnixInProcessServerProcess(const std::string& endpoint, + const std::string& logBase) { ::setenv("MOBILEGL_LOG_FILE_PATH", logBase.c_str(), 1); ::setenv("MOBILEGL_IPC_ROLE", "server", 1); ::setenv("MOBILEGL_IPC_DIAL", "no", 1); @@ -275,7 +277,9 @@ namespace { char note[256] = {}; }; - inline void Note(PeerReport& r, const char* text) { std::snprintf(r.note, sizeof(r.note), "%s", text); } + inline void Note(PeerReport& r, const char* text) { + std::snprintf(r.note, sizeof(r.note), "%s", text); + } using PeerBody = std::function; @@ -304,8 +308,10 @@ namespace { const MobileGLResult started = client.StartSpawned(); r.started = started == MOBILEGL_OK ? 1 : 0; r.serverPid = client.PeerServerPid(); - if (r.started) body(client, r); - else std::snprintf(r.note, sizeof(r.note), "start rc=%d", static_cast(started)); + if (r.started) + body(client, r); + else + std::snprintf(r.note, sizeof(r.note), "start rc=%d", static_cast(started)); if (stopCleanly && r.started) client.Stop(); const ssize_t wrote = ::write(fds[1], &r, sizeof(r)); (void)wrote; @@ -378,16 +384,24 @@ namespace { } // The client's virtual EGL handles (EGLImpl mints small integers; ServerLoopEglTest's shape). - inline EGLDisplay Dpy() { return reinterpret_cast(static_cast(0x1)); } - inline EGLSurface Surf() { return reinterpret_cast(static_cast(0x1)); } - inline EGLContext Ctx() { return reinterpret_cast(static_cast(0x1)); } + inline EGLDisplay Dpy() { + return reinterpret_cast(static_cast(0x1)); + } + inline EGLSurface Surf() { + return reinterpret_cast(static_cast(0x1)); + } + inline EGLContext Ctx() { + return reinterpret_cast(static_cast(0x1)); + } inline bool BringUpEgl(PeerReport& r) { EGLint major = -1; EGLint minor = -1; - if (!Remote::Server::ServerInitializeEGLDisplay(Dpy(), &major, &minor)) return Note(r, "no EGL display"), false; + if (!Remote::Server::ServerInitializeEGLDisplay(Dpy(), &major, &minor)) + return Note(r, "no EGL display"), false; if (!Remote::Server::ServerCreateEGLPbufferSurface(Surf(), 8, 8)) return Note(r, "no pbuffer"), false; - if (!Remote::Server::ServerMakeEGLCurrent(Dpy(), Surf(), Surf(), Ctx())) return Note(r, "no make-current"), false; + if (!Remote::Server::ServerMakeEGLCurrent(Dpy(), Surf(), Surf(), Ctx())) + return Note(r, "no make-current"), false; r.egl = 1; return true; } @@ -402,8 +416,7 @@ namespace { // texel little-endian in a Uint32, which is how PeerReport::pixel comes back and how every // expectation on it is written (green (0,1,0,1) is 0xFF00FF00). Decoding it in the other // order would clear the complement of the colour the caller asked for. - inline void HealthySessionWithColor(Remote::Client::ClientSession& client, PeerReport& r, - Uint32 rgba) { + inline void HealthySessionWithColor(Remote::Client::ClientSession& client, PeerReport& r, Uint32 rgba) { P::MGPMemoryBarrier barrier{}; barrier.Bits = 0x2000u; r.applied = Emit(client, P::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)) ? 1 : 0; @@ -416,7 +429,8 @@ namespace { framebuffer.Width = framebuffer.Height = 8; framebuffer.Layers = framebuffer.Samples = 1; framebuffer.FixedSampleLocations = 1; - for (auto& drawBuffer : framebuffer.DrawBuffers) drawBuffer = -1; + for (auto& drawBuffer : framebuffer.DrawBuffers) + drawBuffer = -1; framebuffer.DrawBuffers[0] = 0; framebuffer.ContentHash = 1; P::MGPPixelPackState pack{}; @@ -429,7 +443,8 @@ namespace { const P::MGPipeHandle unboundSampler = P::kMGPipeNullHandle; const P::MGPContextValues context{}; MobileGL::RenderStateParameters params{}; - for (auto& mask : params.ColorMasks) mask = MobileGL::BoolVec4(true, true, true, true); + for (auto& mask : params.ColorMasks) + mask = MobileGL::BoolVec4(true, true, true, true); std::vector pipelineBytes(P::kMGPipePipelineChunkBytes, 0); P::MGPipeGatherPipelineBytes(params, pipelineBytes.data()); P::MGPRenderStateDesc cso{}; @@ -446,7 +461,8 @@ namespace { return Note(r, "the render state was not applied"); if (!Emit(client, P::MGPWireOp::SetFramebufferState, &framebuffer, sizeof(framebuffer)) || !Emit(client, P::MGPWireOp::SetPixelPackState, &pack, sizeof(pack)) || - !Emit(client, P::MGPWireOp::SetSamplerViews, &views, sizeof(views), &unboundView, sizeof(unboundView)) || + !Emit(client, P::MGPWireOp::SetSamplerViews, &views, sizeof(views), &unboundView, + sizeof(unboundView)) || !Emit(client, P::MGPWireOp::BindSamplerStates, &samplers, sizeof(samplers), &unboundSampler, sizeof(unboundSampler)) || !Emit(client, P::MGPWireOp::SetContextValues, &context, sizeof(context))) @@ -457,10 +473,9 @@ namespace { clear.DrawBufferIndex = 0; clear.BufferMask = GL_COLOR_BUFFER_BIT; clear.ValueClass = P::kMGPipeClearValueClassFloat; - const float color[4] = {static_cast(rgba & 0xFFu) / 255.0f, - static_cast((rgba >> 8) & 0xFFu) / 255.0f, - static_cast((rgba >> 16) & 0xFFu) / 255.0f, - static_cast((rgba >> 24) & 0xFFu) / 255.0f}; + const float color[4] = { + static_cast(rgba & 0xFFu) / 255.0f, static_cast((rgba >> 8) & 0xFFu) / 255.0f, + static_cast((rgba >> 16) & 0xFFu) / 255.0f, static_cast((rgba >> 24) & 0xFFu) / 255.0f}; std::memcpy(clear.ColorValue, color, sizeof(color)); if (!Emit(client, P::MGPWireOp::Clear, &clear, sizeof(clear))) return Note(r, "clear not applied"); P::MGPReadbackInfo read{}; diff --git a/MobileGL/MG_Test/Wire/PairAcceptorTest.cpp b/MobileGL/MG_Test/Wire/PairAcceptorTest.cpp index 4ad60838..6e008898 100644 --- a/MobileGL/MG_Test/Wire/PairAcceptorTest.cpp +++ b/MobileGL/MG_Test/Wire/PairAcceptorTest.cpp @@ -51,7 +51,10 @@ using Transport::SocketTransport; namespace { - enum class Kind { Unix, Tcp }; + enum class Kind { + Unix, + Tcp + }; std::string MakeEndpoint(Kind kind) { static int serial = 0; @@ -163,9 +166,11 @@ namespace { *outWho = -1; const std::string down = "server-to-control"; if (server.SendFrame({down.data(), down.size()}) != MOBILEGL_OK) - return ::testing::AssertionFailure() << "the server could not write its control connection (its peer is gone)"; + return ::testing::AssertionFailure() + << "the server could not write its control connection (its peer is gone)"; std::vector fds; - for (const auto& client : clients) fds.push_back({client.control, POLLIN, 0}); + for (const auto& client : clients) + fds.push_back({client.control, POLLIN, 0}); if (::poll(fds.data(), static_cast(fds.size()), 2000) <= 0) return ::testing::AssertionFailure() << "no client's control connection received the server's frame"; int who = -1; @@ -176,24 +181,26 @@ namespace { std::vector frame; if (SocketTransport::ReceiveOneFrame(client.control, 2000, 4096, &frame) != MOBILEGL_OK || std::string(frame.begin(), frame.end()) != down) - return ::testing::AssertionFailure() << "client " << client.name << "'s control connection read something else"; + return ::testing::AssertionFailure() + << "client " << client.name << "'s control connection read something else"; const std::string up = std::string("control-to-server:") + client.name; if (!SendFramed(client.control, up.data(), up.size())) - return ::testing::AssertionFailure() << "client " << client.name << " could not write its control connection"; + return ::testing::AssertionFailure() + << "client " << client.name << " could not write its control connection"; std::vector buffer(4096); std::uint64_t size = 0; if (server.ReceiveFrame({buffer.data(), buffer.size()}, &size, 2000) != MOBILEGL_OK || std::string(buffer.begin(), buffer.begin() + static_cast(size)) != up) - return ::testing::AssertionFailure() << "the server's control connection did not carry client " << client.name - << "'s frame"; + return ::testing::AssertionFailure() + << "the server's control connection did not carry client " << client.name << "'s frame"; const std::string side = std::string("aux:") + client.name; if (::send(client.aux, side.data(), side.size(), MSG_NOSIGNAL) != static_cast(side.size())) return ::testing::AssertionFailure() << "client " << client.name << " could not write its aux connection"; std::string received(side.size(), '\0'); if (!ReadExactly(server.AuxFd(), reinterpret_cast(received.data()), received.size(), 2000) || received != side) - return ::testing::AssertionFailure() << "the server's aux connection is not client " << client.name - << "'s (read \"" << received << "\")"; + return ::testing::AssertionFailure() + << "the server's aux connection is not client " << client.name << "'s (read \"" << received << "\")"; *outWho = who; return ::testing::AssertionSuccess(); } @@ -255,7 +262,8 @@ TEST_P(PairAcceptorTest, TwoClientsWhoseFourConnectionsInterleaveArePairedEachWi EXPECT_TRUE(ServesExactly(*second, clients, &secondWho)); EXPECT_NE(firstWho, secondWho) << "one client was handed out twice"; EXPECT_EQ(pairs.Unpaired(), 0u); - for (int fd : {aControl, bControl, bAux, aAux}) ::close(fd); + for (int fd : {aControl, bControl, bAux, aAux}) + ::close(fd); } // (3) An aux connection whose control connection never arrives: held within the budget, dropped @@ -300,9 +308,9 @@ TEST_P(PairAcceptorTest, AClientWhoseFirstFrameIsAHelloIsRefusedByNameAndNeverPa ::flatbuffers::FlatBufferBuilder builder(512); const std::uint64_t fingerprint = currentWire ? WireFingerprint() : WireFingerprint() ^ 0x1ull; auto terms = ::MobileGL::Wire::CreateLinkTerms(builder); - auto hello = ::MobileGL::Wire::CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, - "an-older-client", 0, 1, nullptr, fingerprint, fingerprint, terms, - nullptr, ::MobileGL::Wire::DialMode::Connect); + auto hello = ::MobileGL::Wire::CreateHelloDirect( + builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, "an-older-client", 0, 1, nullptr, + fingerprint, fingerprint, terms, nullptr, ::MobileGL::Wire::DialMode::Connect); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer( builder, ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union())); ASSERT_TRUE(SendFramed(control, builder.GetBufferPointer(), builder.GetSize())); diff --git a/MobileGL/MG_Test/Wire/PeerLatchTest.cpp b/MobileGL/MG_Test/Wire/PeerLatchTest.cpp index bd101502..c280d5ae 100644 --- a/MobileGL/MG_Test/Wire/PeerLatchTest.cpp +++ b/MobileGL/MG_Test/Wire/PeerLatchTest.cpp @@ -158,8 +158,8 @@ namespace { bool tcp = false; std::string Log() const { - return ReadFile(MobileGL::MG_Util::Debug::RoleLogPath( - logBase.c_str(), MobileGL::MG_Util::Debug::LogRole::Server)); + return ReadFile( + MobileGL::MG_Util::Debug::RoleLogPath(logBase.c_str(), MobileGL::MG_Util::Debug::LogRole::Server)); } // The supervisor's reap line for one session child, or "" while it has not been reaped. std::string ReapLine(Uint32 sessionPid) const { @@ -214,8 +214,7 @@ namespace { ::unlink(sup->endpoint.c_str()); } MobileGL::MG_Util::Debug::TruncateRoleLogs(sup->logBase.c_str()); - if (Server::LaunchServerWithArgs(ServerImage(), sup->endpoint, {"--serve"}, &sup->server) != - MOBILEGL_OK) { + if (Server::LaunchServerWithArgs(ServerImage(), sup->endpoint, {"--serve"}, &sup->server) != MOBILEGL_OK) { return false; } bool listening = false; @@ -242,13 +241,13 @@ namespace { // ------------------------------------------------------------------------------------ struct PeerReport { - Int32 started = 0; // handshake answered Welcome - Int32 forged = 0; // the row's bytes were published - Uint32 serverPid = 0; // Welcome::serverPid - the session child this peer talked to - Int32 status = -99; // a reply status, where the row reads one - Int32 applied = 0; // a follow-up (or the health check's) record applied - Int32 egl = 0; // headless EGL came up in the session - Uint32 pixel = 0; // the health check's read-back texel + Int32 started = 0; // handshake answered Welcome + Int32 forged = 0; // the row's bytes were published + Uint32 serverPid = 0; // Welcome::serverPid - the session child this peer talked to + Int32 status = -99; // a reply status, where the row reads one + Int32 applied = 0; // a follow-up (or the health check's) record applied + Int32 egl = 0; // headless EGL came up in the session + Uint32 pixel = 0; // the health check's read-back texel char note[256] = {}; }; @@ -263,8 +262,8 @@ namespace { // `holdMs` > 0: the peer reports as soon as `body` returns and then HOLDS its connections open // and silent for that long before it exits; the caller gets its pid in `heldPeer` and owes it // a kill + waitpid (the silent-peer cases' shape). - PeerReport RunPeer(const Supervisor& sup, const PeerBody& body, bool stopCleanly, - int timeoutMs = 30000, int holdMs = 0, pid_t* heldPeer = nullptr) { + PeerReport RunPeer(const Supervisor& sup, const PeerBody& body, bool stopCleanly, int timeoutMs = 30000, + int holdMs = 0, pid_t* heldPeer = nullptr) { PeerReport report{}; int fds[2] = {-1, -1}; if (::pipe(fds) != 0) { @@ -291,8 +290,10 @@ namespace { } r.started = started == MOBILEGL_OK ? 1 : 0; r.serverPid = client.PeerServerPid(); - if (r.started) body(client, r); - else std::snprintf(r.note, sizeof(r.note), "start rc=%d", static_cast(started)); + if (r.started) + body(client, r); + else + std::snprintf(r.note, sizeof(r.note), "start rc=%d", static_cast(started)); if (stopCleanly && r.started) client.Stop(); const ssize_t wrote = ::write(fds[1], &r, sizeof(r)); (void)wrote; @@ -350,7 +351,9 @@ namespace { // The raw-record peer driver (F2's, generalised) // ------------------------------------------------------------------------------------ - constexpr Uint64 Align8(Uint64 value) { return (value + 7u) & ~Uint64(7u); } + constexpr Uint64 Align8(Uint64 value) { + return (value + 7u) & ~Uint64(7u); + } template std::vector Bytes(const T& value) { @@ -396,8 +399,7 @@ namespace { // overwritten, and one PublishAndNotify makes them visible to the server together - the shape // of a peer that batches, which is what DrainRing's pre-pop latch check stops mid-batch. // `mutateHeader` applies to the FIRST image's header; `outSeq` is the LAST record's seq. - bool PublishImages(Client::ClientSession& client, const std::vector>& images, - Uint64* outSeq, + bool PublishImages(Client::ClientSession& client, const std::vector>& images, Uint64* outSeq, const std::function& mutateHeader = {}) { if (outSeq != nullptr) *outSeq = Wire::kInvalidSeq; auto* link = client.DataLink(); @@ -416,8 +418,7 @@ namespace { seq = encoder.EncodeRecord(P::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)); } else { const Uint64 base = Align8(sizeof(P::MGPWireRecHeader) + sizeof(P::MGPSamplerStates)); - if (total < base || (total - base) % 8 != 0 || - (total - base) / 8 > P::kMGPipeMaxTextureUnits) + if (total < base || (total - base) % 8 != 0 || (total - base) / 8 > P::kMGPipeMaxTextureUnits) return false; P::MGPSamplerStates filler{}; filler.Start = 0; @@ -444,14 +445,13 @@ namespace { return true; } - bool PublishImage(Client::ClientSession& client, const std::vector& image, - Uint64* outSeq, + bool PublishImage(Client::ClientSession& client, const std::vector& image, Uint64* outSeq, const std::function& mutateHeader = {}) { return PublishImages(client, {image}, outSeq, mutateHeader); } - bool Forge(Client::ClientSession& client, PeerReport& r, P::MGPWireOp op, - const std::vector& payload, const std::vector>& tails = {}, + bool Forge(Client::ClientSession& client, PeerReport& r, P::MGPWireOp op, const std::vector& payload, + const std::vector>& tails = {}, const std::function& mutateHeader = {}) { Uint64 seq = Wire::kInvalidSeq; if (!PublishImage(client, RecordImage(op, payload, tails), &seq, mutateHeader)) { @@ -468,8 +468,7 @@ namespace { const void* tail = nullptr, Uint64 tailBytes = 0) { Int32 status = Wire::ReplySink::kStatusError; const Uint64 seq = client.EmitAndWait(op, payload, bytes, tail, tailBytes, nullptr, 0, &status); - return seq != Wire::kInvalidSeq && - client.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached; + return seq != Wire::kInvalidSeq && client.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached; } // A control frame straight onto the control connection - the SurfaceOp rows' carrier. @@ -479,13 +478,12 @@ namespace { builder, /*seq=*/4242, static_cast<::MobileGL::Wire::SurfaceOpKind>(kind), /*display=*/1, /*surface=*/1, static_cast<::MobileGL::Wire::WindowKind>(windowKind), /*nativeToken=*/0x1234, 16, 16, 0, 1, 1); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); Transport::ITransport* control = client.Control_Plane(); if (control == nullptr || - control->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}) != - MOBILEGL_OK) { + control->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}) != MOBILEGL_OK) { Note(r, "the control frame could not be sent"); return false; } @@ -495,9 +493,15 @@ namespace { } // The client's virtual EGL handles (EGLImpl mints small integers; ServerLoopEglTest's shape). - EGLDisplay Dpy() { return reinterpret_cast(static_cast(0x1)); } - EGLSurface Surf() { return reinterpret_cast(static_cast(0x1)); } - EGLContext Ctx() { return reinterpret_cast(static_cast(0x1)); } + EGLDisplay Dpy() { + return reinterpret_cast(static_cast(0x1)); + } + EGLSurface Surf() { + return reinterpret_cast(static_cast(0x1)); + } + EGLContext Ctx() { + return reinterpret_cast(static_cast(0x1)); + } bool BringUpEgl(PeerReport& r) { EGLint major = -1; @@ -527,7 +531,8 @@ namespace { framebuffer.Width = framebuffer.Height = 8; framebuffer.Layers = framebuffer.Samples = 1; framebuffer.FixedSampleLocations = 1; - for (auto& drawBuffer : framebuffer.DrawBuffers) drawBuffer = -1; + for (auto& drawBuffer : framebuffer.DrawBuffers) + drawBuffer = -1; framebuffer.DrawBuffers[0] = 0; framebuffer.ContentHash = 1; P::MGPPixelPackState pack{}; @@ -543,7 +548,8 @@ namespace { // the bound CSO and nowhere else, so without one a clear writes nothing (the mask is // the struct's zero, not RenderState's all-true constructor). MobileGL::RenderStateParameters params{}; - for (auto& mask : params.ColorMasks) mask = MobileGL::BoolVec4(true, true, true, true); + for (auto& mask : params.ColorMasks) + mask = MobileGL::BoolVec4(true, true, true, true); std::vector pipelineBytes(P::kMGPipePipelineChunkBytes, 0); P::MGPipeGatherPipelineBytes(params, pipelineBytes.data()); P::MGPRenderStateDesc cso{}; @@ -583,8 +589,8 @@ namespace { Uint8 texel[4] = {}; Int32 status = Wire::ReplySink::kStatusError; Uint64 replySize = 0; - const Uint64 seq = client.EmitAndWait(P::MGPWireOp::ReadPixels, &read, sizeof(read), nullptr, - 0, texel, sizeof(texel), &status, &replySize); + const Uint64 seq = client.EmitAndWait(P::MGPWireOp::ReadPixels, &read, sizeof(read), nullptr, 0, texel, + sizeof(texel), &status, &replySize); r.status = status; if (seq == Wire::kInvalidSeq || replySize != sizeof(texel)) return Note(r, "no read-back"); std::memcpy(&r.pixel, texel, sizeof(r.pixel)); @@ -601,9 +607,9 @@ namespace { }; struct Row { - const char* name; // gtest parameter name - const char* file; // the source file of the site (scripts/ci/ph_latch_sites.py reads it) - const char* site; // the quoted word inside the site's Fatal{Family, "word"} marker + const char* name; // gtest parameter name + const char* file; // the source file of the site (scripts/ci/ph_latch_sites.py reads it) + const char* site; // the quoted word inside the site's Fatal{Family, "word"} marker Outcome outcome; const char* marker; // must be the first `Fatal{` line of the server log (Latched/Fatal) bool needsEgl; // the forging session needs a current context first @@ -672,8 +678,8 @@ namespace { desc.Samples = samples; desc.Immutable = immutable; Int32 status = Wire::ReplySink::kStatusError; - const Uint64 seq = client.EmitAndWait(P::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, - nullptr, 0, &status); + const Uint64 seq = + client.EmitAndWait(P::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, nullptr, 0, &status); return seq != Wire::kInvalidSeq && status == Wire::ReplySink::kStatusOk && client.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached; } @@ -697,15 +703,14 @@ namespace { static_cast(MobileGL::TextureUploadTarget::Texture2D)), 0, 4, 4, 1); Int32 status = Wire::ReplySink::kStatusError; - const Uint64 seq = client.EmitAndWait(P::MGPWireOp::ResourceRespecify, &desc, sizeof(desc), nullptr, - 0, nullptr, 0, &status); + const Uint64 seq = + client.EmitAndWait(P::MGPWireOp::ResourceRespecify, &desc, sizeof(desc), nullptr, 0, nullptr, 0, &status); return seq != Wire::kInvalidSeq && status == Wire::ReplySink::kStatusOk && client.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached; } - P::MGPSubData TextureUpload(Client::ClientSession& client, P::MGPipeHandle handle, - P::MGPipeResourceTarget target, MobileGL::TextureUploadTarget upload, - Uint64 bytes) { + P::MGPSubData TextureUpload(Client::ClientSession& client, P::MGPipeHandle handle, P::MGPipeResourceTarget target, + MobileGL::TextureUploadTarget upload, Uint64 bytes) { P::MGPSubData rec{}; rec.Res = handle; rec.Target = P::MGPipePackSubDataTarget(static_cast(target), static_cast(upload)); @@ -726,7 +731,8 @@ namespace { state.Complete = 1; state.Width = state.Height = state.Layers = state.Samples = 1; state.FixedSampleLocations = 1; - for (auto& drawBuffer : state.DrawBuffers) drawBuffer = -1; + for (auto& drawBuffer : state.DrawBuffers) + drawBuffer = -1; state.DrawBuffers[0] = 0; state.Color[0].Res = texture; state.Color[0].InternalFormat = static_cast(MobileGL::TextureInternalFormat::RGBA8); @@ -1512,8 +1518,10 @@ namespace { switch (outcome) { case Outcome::Latched: return "exit=" + std::to_string(kSessionLatchedExitCode) + " (latched fault)"; - case Outcome::Fatal: return "signal=6"; - case Outcome::Refused: return "exit=0 "; + case Outcome::Fatal: + return "signal=6"; + case Outcome::Refused: + return "exit=0 "; } return "?"; } @@ -1522,8 +1530,8 @@ namespace { // The two-session walk every row and the PH-1 (4) headline share. `forgeName` labels the // assertions. - void RunRowAgainst(Supervisor& sup, const char* forgeName, Outcome outcome, const char* marker, - bool needsEgl, bool (*forge)(Client::ClientSession&, PeerReport&)) { + void RunRowAgainst(Supervisor& sup, const char* forgeName, Outcome outcome, const char* marker, bool needsEgl, + bool (*forge)(Client::ClientSession&, PeerReport&)) { // ---- session A: the forged bytes const PeerReport a = RunPeer( sup, @@ -1534,8 +1542,10 @@ namespace { /*stopCleanly=*/outcome == Outcome::Refused); ASSERT_EQ(a.started, 1) << forgeName << ": session A never got Welcome (" << a.note << ")"; if (needsEgl && a.egl == 0) { - GTEST_SKIP() << forgeName << ": the row needs a current context and this host has no " - "headless EGL (" << a.note << ")"; + GTEST_SKIP() << forgeName + << ": the row needs a current context and this host has no " + "headless EGL (" + << a.note << ")"; } ASSERT_EQ(a.forged, 1) << forgeName << ": the peer could not publish its bytes (" << a.note << ")"; ASSERT_GT(a.serverPid, 0u); @@ -1593,8 +1603,10 @@ namespace { // vendor library on an Ubuntu runner; a runner without it shows these rows as Skipped and // PresentFrameSerialZero (which needs a context to reach its site) as skipped outright. if (b.egl == 0) { - GTEST_SKIP() << forgeName << ": latch and next-session Welcome proven; the render half " - "needs headless EGL, absent here (" << b.note << ")"; + GTEST_SKIP() << forgeName + << ": latch and next-session Welcome proven; the render half " + "needs headless EGL, absent here (" + << b.note << ")"; } EXPECT_EQ(b.status, Wire::ReplySink::kStatusOk) << forgeName << ": " << b.note; EXPECT_EQ(b.pixel, 0xFF00FF00u) << forgeName << ": the next session's clear did not read back green"; @@ -1629,15 +1641,15 @@ namespace { TEST(PeerLatchTest, AMalformedRecordLatchesTheSessionAndTheNextUnixConnectionGetsWelcomeAndRenders) { Supervisor sup; ASSERT_TRUE(Launch("headline-unix", /*tcp=*/false, &sup)); - RunRowAgainst(sup, "headline-unix", Outcome::Latched, "Fatal{ProtocolCorruption, \"opcode\"} got=65535", - false, &ForgeUnknownOpcode); + RunRowAgainst(sup, "headline-unix", Outcome::Latched, "Fatal{ProtocolCorruption, \"opcode\"} got=65535", false, + &ForgeUnknownOpcode); } TEST(PeerLatchTest, AMalformedRecordLatchesTheSessionAndTheNextTcpConnectionGetsWelcomeAndRenders) { Supervisor sup; ASSERT_TRUE(Launch("headline-tcp", /*tcp=*/true, &sup)); - RunRowAgainst(sup, "headline-tcp", Outcome::Latched, "Fatal{ProtocolCorruption, \"opcode\"} got=65535", - false, &ForgeUnknownOpcode); + RunRowAgainst(sup, "headline-tcp", Outcome::Latched, "Fatal{ProtocolCorruption, \"opcode\"} got=65535", false, + &ForgeUnknownOpcode); } // A malformed CONTROL frame over TCP: the SurfaceOp arm latches on the main thread (RunSession's @@ -1731,8 +1743,7 @@ namespace { << sup.Log(); EXPECT_TRUE(peerStillConnected) << label << ": the session ended only once the peer had gone, which says nothing about the latch"; - EXPECT_NE(reap.find("exit=" + std::to_string(kSessionLatchedExitCode) + " (latched fault)"), - std::string::npos) + EXPECT_NE(reap.find("exit=" + std::to_string(kSessionLatchedExitCode) + " (latched fault)"), std::string::npos) << label << ": `" << reap << "`"; ExpectARenderingNextSession(sup, label, a.serverPid); } @@ -1748,9 +1759,11 @@ TEST(PeerLatchTest, ALatchedRecordIsTheLastRecordItsBatchApplies) { &ForgeTwoFaultsInOneBatch); const std::string log = sup.Log(); EXPECT_EQ(log.find("\"ObjectDeath.Handle\""), std::string::npos) - << "the record published behind the latched one was applied after the latch:\n" << log; + << "the record published behind the latched one was applied after the latch:\n" + << log; EXPECT_NE(log.find("session closed on a latched fault (ProtocolCorruption; 1 named fault(s)"), std::string::npos) - << "the session did not close on exactly one latched fault:\n" << log; + << "the session did not close on exactly one latched fault:\n" + << log; } // RunSession's sliced control wait: a latch raised on the APPLY thread while the peer sits connected diff --git a/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp b/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp index ae814adc..765c4b92 100644 --- a/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp +++ b/MobileGL/MG_Test/Wire/PersistentMapFaultTest.cpp @@ -96,8 +96,7 @@ namespace { TEST(PersistentMapFaultOwnership, AKernelsUntaggedFaultAddressIsOwnedByATaggedShadowsSlot) { SizeT pageIndex = ~static_cast(0); EXPECT_TRUE(PersistentMapTracker::OwnershipProbeForTest( - kDeviceTaggedShadowBase, kDeviceTaggedShadowBase + kDeviceMappedBytes, - kDeviceKernelFaultAddress, &pageIndex)) + kDeviceTaggedShadowBase, kDeviceTaggedShadowBase + kDeviceMappedBytes, kDeviceKernelFaultAddress, &pageIndex)) << "the handler compared si_addr (untagged, as the kernel delivers it) against a base " "carrying bionic's 0xb4 heap tag, so it owned nothing and chained every fault"; EXPECT_EQ(pageIndex, 0u) << "the fault is on the first page of the mapped range"; @@ -110,30 +109,28 @@ TEST(PersistentMapFaultOwnership, TheOwnershipRowNormalisesBothSidesBeforeItComp SizeT pageIndex = ~static_cast(0); const uintptr_t base = PersistentMapTracker::UntagAddressForTest(kDeviceTaggedShadowBase); // Untagged base against untagged fault: owned, on this host and on the device alike. - EXPECT_TRUE(PersistentMapTracker::OwnershipProbeForTest( - base, base + kDeviceMappedBytes, kDeviceKernelFaultAddress, &pageIndex)); + EXPECT_TRUE(PersistentMapTracker::OwnershipProbeForTest(base, base + kDeviceMappedBytes, kDeviceKernelFaultAddress, + &pageIndex)); EXPECT_EQ(pageIndex, 0u); // A fault one page in is owned, and names that page. - EXPECT_TRUE(PersistentMapTracker::OwnershipProbeForTest( - base, base + kDeviceMappedBytes, kDeviceKernelFaultAddress + kPageBytes + 7, &pageIndex)); + EXPECT_TRUE(PersistentMapTracker::OwnershipProbeForTest(base, base + kDeviceMappedBytes, + kDeviceKernelFaultAddress + kPageBytes + 7, &pageIndex)); EXPECT_EQ(pageIndex, 1u); // And the narrow claim stays narrow: a byte before the base and the first byte past the // end are both foreign, so a genuinely broken access still crashes honestly. - EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest( - base, base + kDeviceMappedBytes, kDeviceKernelFaultAddress - 1, nullptr)); - EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest( - base, base + kDeviceMappedBytes, kDeviceKernelFaultAddress + kDeviceMappedBytes, nullptr)); + EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest(base, base + kDeviceMappedBytes, + kDeviceKernelFaultAddress - 1, nullptr)); + EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest(base, base + kDeviceMappedBytes, + kDeviceKernelFaultAddress + kDeviceMappedBytes, nullptr)); // A free slot (base 0) and one mid-setup (base 1) own nothing, whatever the address. EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest(0, 0, kDeviceKernelFaultAddress, nullptr)); EXPECT_FALSE(PersistentMapTracker::OwnershipProbeForTest(1, 0, kDeviceKernelFaultAddress, nullptr)); } TEST(PersistentMapFaultOwnership, UntagAddressClearsTheTopByteAndNothingBelowIt) { - EXPECT_EQ(PersistentMapTracker::UntagAddressForTest(kDeviceTaggedShadowBase), - kDeviceKernelFaultAddress) + EXPECT_EQ(PersistentMapTracker::UntagAddressForTest(kDeviceTaggedShadowBase), kDeviceKernelFaultAddress) << "bionic's 0xb4 heap tag must come off, or the table and the kernel disagree"; - EXPECT_EQ(PersistentMapTracker::UntagAddressForTest(kDeviceKernelFaultAddress), - kDeviceKernelFaultAddress) + EXPECT_EQ(PersistentMapTracker::UntagAddressForTest(kDeviceKernelFaultAddress), kDeviceKernelFaultAddress) << "normalising an already-normal address is the identity, or the handler would move " "a legitimate fault out of its own slot"; // Bit 55 is ADDRESS, not tag: userspace lives in TTBR0 on arm64 and below TASK_SIZE_MAX @@ -177,8 +174,8 @@ namespace { if (hi <= first || lo > last) continue; if (perms[1] != 'w') { writable = false; - detail = std::string("range [") + std::to_string(lo) + "," + std::to_string(hi) + - ") is '" + perms + "'"; + detail = + std::string("range [") + std::to_string(lo) + "," + std::to_string(hi) + ") is '" + perms + "'"; break; } if (lo <= covered && hi > covered) covered = hi; @@ -208,8 +205,8 @@ TEST(PersistentMapWritability, AMapHandsBackARangeWritableWithoutTheFaultHandler MapAlignedData store(kBytes); ASSERT_EQ(reinterpret_cast(store.data()) % kPageBytes, 0u); constexpr Uint64 kId = ~0ull - 21u; - const auto* slot = PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, - ShadowAllocationBytesFor(store.capacity())); + const auto* slot = + PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, ShadowAllocationBytesFor(store.capacity())); ASSERT_NE(slot, nullptr) << "a page-aligned, page-granular shadow registers on the mprotect arm"; ASSERT_EQ(slot->pageCount, 8u); @@ -218,7 +215,7 @@ TEST(PersistentMapWritability, AMapHandsBackARangeWritableWithoutTheFaultHandler if (child == 0) { // No handler can rescue this process: the disposition is the default and the signal // is blocked in this thread, so a fault here is a kill, not a fault. - struct sigaction dfl {}; + struct sigaction dfl{}; dfl.sa_handler = SIG_DFL; ::sigemptyset(&dfl.sa_mask); ::sigaction(SIGSEGV, &dfl, nullptr); @@ -257,8 +254,8 @@ TEST(PersistentMapWritability, RegistrationLeavesEveryPageOfTheRangeReadWrite) { constexpr SizeT kBytes = 8u * kPageBytes; MapAlignedData store(kBytes); constexpr Uint64 kId = ~0ull - 22u; - const auto* slot = PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, - ShadowAllocationBytesFor(store.capacity())); + const auto* slot = + PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, ShadowAllocationBytesFor(store.capacity())); ASSERT_NE(slot, nullptr); std::string detail; EXPECT_TRUE(EveryPageIsWritable(store.data(), kBytes, detail)) @@ -277,8 +274,8 @@ TEST(PersistentMapWritability, AnArmedPageIsStillAnsweredByTheHandlerAndMadeWrit constexpr SizeT kBytes = 2u * kPageBytes; MapAlignedData store(kBytes); constexpr Uint64 kId = ~0ull - 23u; - const auto* slot = PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, - ShadowAllocationBytesFor(store.capacity())); + const auto* slot = + PersistentMapTracker::TrackForTest(kId, store.data(), 0, kBytes, ShadowAllocationBytesFor(store.capacity())); ASSERT_NE(slot, nullptr); // Arm the first page the way the push does, and write through it: the fault must be // answered (the epoch moves) rather than chained (which would kill this process). @@ -301,9 +298,8 @@ int main(int argc, char** argv) { // Before anything logs, for StagedTextureStoreTest's reason: the file sink caches its // FILE* on the first write and gtest_discover_tests runs every case as its own process. namespace fs = std::filesystem; - const fs::path path = - fs::temp_directory_path() / - ("mobilegl-persistentmapfault-test-" + std::to_string(static_cast(::getpid())) + ".log"); + const fs::path path = fs::temp_directory_path() / ("mobilegl-persistentmapfault-test-" + + std::to_string(static_cast(::getpid())) + ".log"); std::error_code ec; fs::remove(path, ec); const std::string logPath = path.string(); diff --git a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp index 25eb18e6..37c94c55 100644 --- a/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp +++ b/MobileGL/MG_Test/Wire/PipeWireCodecTest.cpp @@ -104,12 +104,11 @@ namespace { Wire2() : m_cmdBytes(kCmdBytes), m_stageBytes(kStageBytes) { Transport::InitRingControl(m_control); - m_cmd = Transport::RingProducer(&m_control, m_cmdBytes.data(), kCmdBytes, - Transport::RingCursorSet::Cmd); - m_stage = Transport::RingProducer(&m_control, m_stageBytes.data(), kStageBytes, - Transport::RingCursorSet::Stage); - m_consumer = Transport::RingConsumer(&m_control, m_cmdBytes.data(), kCmdBytes, - Transport::RingCursorSet::Cmd); + m_cmd = Transport::RingProducer(&m_control, m_cmdBytes.data(), kCmdBytes, Transport::RingCursorSet::Cmd); + m_stage = + Transport::RingProducer(&m_control, m_stageBytes.data(), kStageBytes, Transport::RingCursorSet::Stage); + m_consumer = + Transport::RingConsumer(&m_control, m_cmdBytes.data(), kCmdBytes, Transport::RingCursorSet::Cmd); m_segments.Install(kSegCmd, SegmentView{m_cmdBytes.data(), kCmdBytes}); m_segments.Install(kSegStage, SegmentView{m_stageBytes.data(), kStageBytes}); m_encoder = PipeWireEncoder(&m_control, &m_cmd, &m_stage, &m_segments); @@ -203,8 +202,8 @@ namespace { } return true; } - Bool OnDrawVbo(const MGPDrawInfo& info, const MGPDrawRange* ranges, - const MGHostSpan* userIndices, const MGPDrawIndirect* indirect) override { + Bool OnDrawVbo(const MGPDrawInfo& info, const MGPDrawRange* ranges, const MGHostSpan* userIndices, + const MGPDrawIndirect* indirect) override { Draws.push_back(info); DrawRanges.clear(); for (Uint32 i = 0; i < info.NumDraws; ++i) { @@ -238,8 +237,7 @@ namespace { ImageBinds.push_back(bind); return true; } - Bool OnSetStorageBlockBinding(const MGPStorageBlockBinding& binding, - const char* name) override { + Bool OnSetStorageBlockBinding(const MGPStorageBlockBinding& binding, const char* name) override { StorageBindings.push_back(binding); StorageBlockNames.push_back(name != nullptr ? name : ""); return true; @@ -366,8 +364,7 @@ namespace { if (reply.Status == ReplySink::kStatusDeclined) ++declined; } EXPECT_EQ(ok, wire.Decoder().AcceptedRecords()) - << "the slots say " << ok << " accepted, the decoder's tally says " - << wire.Decoder().AcceptedRecords(); + << "the slots say " << ok << " accepted, the decoder's tally says " << wire.Decoder().AcceptedRecords(); EXPECT_EQ(declined, wire.Decoder().DeclinedRecords()) << "the slots say " << declined << " declined, the decoder's tally says " << wire.Decoder().DeclinedRecords(); @@ -442,8 +439,8 @@ namespace { // Forges ONE record straight into SEG_CMD, bypassing the encoder. Every R-2 arm needs // this: the encoder REFUSES to build a dishonest record, which is the point of it, so a // case that drove the encoder could only ever test the encoder's own check. - void ForgeAndDecode(Wire2& wire, MGPWireOp op, const void* payload, std::uint64_t payloadBytes, - const void* tail, std::uint64_t tailBytes) { + void ForgeAndDecode(Wire2& wire, MGPWireOp op, const void* payload, std::uint64_t payloadBytes, const void* tail, + std::uint64_t tailBytes) { const std::uint64_t body = payloadBytes + tailBytes; void* slot = wire.Cmd().Reserve(static_cast(op), Transport::kRecNone, body); if (slot == nullptr) { @@ -451,8 +448,7 @@ namespace { } std::memcpy(slot, payload, static_cast(payloadBytes)); if (tailBytes != 0) { - std::memcpy(static_cast(slot) + payloadBytes, tail, - static_cast(tailBytes)); + std::memcpy(static_cast(slot) + payloadBytes, tail, static_cast(tailBytes)); } wire.Cmd().Publish(); Transport::RingRecordView view{}; @@ -529,11 +525,9 @@ TEST_F(PipeWireCodecTest, AVarTailRecordIsNotSkippedAsAWrapFiller) { "kReplySlot and kRecVarTail alias"); // Five of the six call-flag bits alias a ring bit; only kOptional is free, and only // because RingRecordFlags has not reached 1<<5. - static_assert((static_cast(kNeedsAck | kHasBlob | kVarTail | kHostSpan | kReplySlot | - kOptional) & - static_cast(Transport::kRecNeedsAck | Transport::kRecHasBlob | - Transport::kRecPad | Transport::kRecBorrowSlot | - Transport::kRecVarTail)) == 0x1Fu, + static_assert((static_cast(kNeedsAck | kHasBlob | kVarTail | kHostSpan | kReplySlot | kOptional) & + static_cast(Transport::kRecNeedsAck | Transport::kRecHasBlob | Transport::kRecPad | + Transport::kRecBorrowSlot | Transport::kRecVarTail)) == 0x1Fu, "the overlap between the two flag spaces moved"); Wire2 wire; @@ -547,8 +541,7 @@ TEST_F(PipeWireCodecTest, AVarTailRecordIsNotSkippedAsAWrapFiller) { tail[1].Res = MakeHandle(12); tail[1].Stride = 32; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetVertexBuffers, &header, sizeof(header), tail, - sizeof(tail)), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetVertexBuffers, &header, sizeof(header), tail, sizeof(tail)), kInvalidSeq); wire.Encoder().Publish(); @@ -571,8 +564,7 @@ TEST_F(PipeWireCodecTest, KNoneRoundTripsAndReachesItsApplier) { bind.Cso = MakeHandle(4); bind.Version = 7; bind.PipelineVersion = 3; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -602,8 +594,7 @@ TEST_F(PipeWireCodecTest, KHasBlobRoundTripsWithARealChunkBlob) { EXPECT_NE(desc.Blob.Size, 0u); EXPECT_EQ(desc.Blob.Seg, static_cast(kSegStage)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateRenderState, &desc, sizeof(desc)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateRenderState, &desc, sizeof(desc)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -621,8 +612,7 @@ TEST_F(PipeWireCodecTest, KVarTailRoundTripsWithItsTailIntact) { tail[i].Texture = MakeHandle(200 + i); tail[i].Unit = 3 + i; } - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetSamplerViews, &header, sizeof(header), tail, - sizeof(tail)), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetSamplerViews, &header, sizeof(header), tail, sizeof(tail)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); @@ -637,12 +627,10 @@ TEST_F(PipeWireCodecTest, AnEmptyVarTailIsALegalRecordAndNeedsNoPlaceholderEntry Wire2 wire; MGPStreamOutputTargets header{}; header.Count = 0; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStreamOutputTargets, &header, sizeof(header)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStreamOutputTargets, &header, sizeof(header)), kInvalidSeq); MGPVertexBuffers buffers{}; buffers.Count = 0; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetVertexBuffers, &buffers, sizeof(buffers)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetVertexBuffers, &buffers, sizeof(buffers)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_FALSE(applied); // no applier for stream output; off the reduced path @@ -656,8 +644,7 @@ TEST_F(PipeWireCodecTest, KReplySlotMapPersistentIsAConstantDecline) { // seedBytes MGPipeApplyMapPersistent takes. Wire2 wire; const MGPHandleOnly handle = HandleOnly(5, MGPipeKind::Buffer); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -688,8 +675,7 @@ TEST_F(PipeWireCodecTest, TierTwoUnderSplitTransportStillDeclinesRatherThanRefus Wire2 wire; const MGPHandleOnly handle = HandleOnly(5, MGPipeKind::Buffer); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MapPersistent, &handle, sizeof(handle)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -713,16 +699,14 @@ TEST_F(PipeWireCodecTest, KNeedsAckRespecifyCarriesItsRedefinitionScope) { create.ArrayLayers = 1; create.Levels = 2; create.Samples = 1; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); MGPResourceDesc respecify = create; MGPipeSetRespecifiedLevel(respecify, 0x0102u, 1u, 4u, 4u, 1u); EXPECT_FALSE(MGPipeRespecifyIsWholeResource(respecify)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); // Two records, two ACCEPTANCE answers, AND THEY NOW RIDE THE REPLY SLOT (ID-31 + R-17). @@ -746,8 +730,7 @@ TEST_F(PipeWireCodecTest, KNeedsAckRespecifyCarriesItsRedefinitionScope) { TEST_F(PipeWireCodecTest, KOptionalUnmapPersistentRoundTrips) { Wire2 wire; const MGPHandleOnly handle = HandleOnly(6, MGPipeKind::Buffer); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::UnmapPersistent, &handle, sizeof(handle)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::UnmapPersistent, &handle, sizeof(handle)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -767,8 +750,7 @@ TEST_F(PipeWireCodecTest, KHostSpanClassIsValidatedEvenThoughP5ProducesNone) { ranges[0].Size = 64; ranges[1].Res = MakeHandle(32); ranges[1].Size = 128; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetShaderBuffers, &header, sizeof(header), - ranges, sizeof(ranges)), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetShaderBuffers, &header, sizeof(header), ranges, sizeof(ranges)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); @@ -812,8 +794,7 @@ TEST_F(PipeWireCodecTest, EveryPayloadByteCrossesIncludingTheOnesSpelledPad) { upload.RegionCount = 0; upload.Blob = wire.Encoder().StageBytes(texels.data(), texels.size()); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceSubData, &upload, sizeof(upload)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceSubData, &upload, sizeof(upload)), kInvalidSeq); wire.Encoder().Publish(); Transport::RingRecordView view{}; @@ -821,8 +802,7 @@ TEST_F(PipeWireCodecTest, EveryPayloadByteCrossesIncludingTheOnesSpelledPad) { ASSERT_TRUE(wire.Consumer().Pop(view, &corrupt)); ASSERT_FALSE(corrupt); ASSERT_GE(view.payloadSize, sizeof(upload)); - EXPECT_EQ(std::memcmp(view.payload, &upload, sizeof(upload)), 0) - << "the payload did not cross byte for byte"; + EXPECT_EQ(std::memcmp(view.payload, &upload, sizeof(upload)), 0) << "the payload did not cross byte for byte"; // And the stamp really did survive somewhere the named fields do not cover, so the case // cannot pass by comparing a struct that has no unclaimed bytes left. @@ -853,8 +833,7 @@ TEST_F(PipeWireCodecTest, ResourceDescPadsCrossToo) { desc.Samples = 1; MGPipeSetRespecifiedLevel(desc, 0x0304u, 2u, 4u, 4u, 1u); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &desc, sizeof(desc)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceRespecify, &desc, sizeof(desc)), kInvalidSeq); wire.Encoder().Publish(); Transport::RingRecordView view{}; @@ -869,7 +848,6 @@ TEST_F(PipeWireCodecTest, ResourceDescPadsCrossToo) { EXPECT_EQ(MGPipeRespecifiedWidthOf(*crossed), 4u); EXPECT_EQ(MGPipeRespecifiedHeightOf(*crossed), 4u); EXPECT_EQ(MGPipeRespecifiedDepthOf(*crossed), 1u); - } // ===================================================================================== @@ -893,9 +871,7 @@ TEST_F(PipeWireCodecTest, SetShaderBuffersCarriesBothTailsWhenTheSpanTailIsPrese spans[1] = spans[0]; const WireTail tails[2] = {{ranges, sizeof(ranges)}, {spans, sizeof(spans)}}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetShaderBuffers, &header, sizeof(header), - tails, 2), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetShaderBuffers, &header, sizeof(header), tails, 2), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); // P5e (sb): applied, for the reason the sibling case above states. BOTH tails still cross @@ -918,8 +894,7 @@ TEST_F(PipeWireCodecTest, ResourceSubDataCarriesABlobAndARegionTailTogether) { create.ArrayLayers = 1; create.Levels = 1; create.Samples = 1; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); @@ -935,8 +910,7 @@ TEST_F(PipeWireCodecTest, ResourceSubDataCarriesABlobAndARegionTailTogether) { region.W = 4; region.H = 4; region.D = 1; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceSubData, &upload, sizeof(upload), - ®ion, sizeof(region)), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceSubData, &upload, sizeof(upload), ®ion, sizeof(region)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -958,8 +932,7 @@ TEST_F(PipeWireCodecTest, ResourceSubDataCarriesABlobAndARegionTailTogether) { // Both records answered - the texture create is declined by the same NoP4aConsumer belt. EXPECT_EQ(wire.Decoder().AcceptedRecords() + wire.Decoder().DeclinedRecords(), 2u); // And the texels themselves crossed intact: the decline is the applier's, not the wire's. - const void* staged = - wire.Segments().Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size); + const void* staged = wire.Segments().Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size); ASSERT_NE(staged, nullptr); EXPECT_EQ(std::memcmp(staged, texels.data(), texels.size()), 0); } @@ -996,21 +969,19 @@ TEST_F(PipeWireCodecTest, TheNoReplyBitSkipsTheAnswerAndKeepsTheAcceptanceRecord // ---- THE NEGATIVE CONTROL FIRST: WITHOUT THE BIT NOTHING CHANGES ---- // The bit is OPT-IN. A case that only checked the skip would pass against a server that // stopped answering everything, which is a different and much worse change. - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); - ASSERT_EQ(wire.Answers().All.size(), 1u) - << "a reply-owning record that did not ask to be skipped was not answered"; + ASSERT_EQ(wire.Answers().All.size(), 1u) << "a reply-owning record that did not ask to be skipped was not answered"; EXPECT_EQ(wire.Answers().All[0].Seq, 1u); // ---- AND WITH IT: NO ANSWER, AND THE SAME ACCEPTANCE RECORD ---- // THE SAME OPCODE, deliberately: the client decides this per CALL (set_texture_params waits // for an unconfirmed object and does not for a confirmed one), so a reader that recovered // "skip" from the opcode alone would get the first record right and this one wrong. - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create), - nullptr, 0, /*noReply=*/true), - kInvalidSeq); + ASSERT_NE( + wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create), nullptr, 0, /*noReply=*/true), + kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_EQ(wire.Answers().All.size(), 1u) << "the bit was set and an answer was written anyway - the 55,903-reply budget the bit " @@ -1035,9 +1006,7 @@ TEST_F(PipeWireCodecTest, TheNoReplyBitSkipsTheAnswerAndKeepsTheAcceptanceRecord // spaces themselves (all six call bits now alias a ring bit), so what is pinned HERE is that // this row's encoded flags carry it rather than a call flag that would be recovered on the // far side as something else entirely. - EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ResourceCreate) & - static_cast(MG_Pipe::kOptional), - 0u) + EXPECT_EQ(MGPipeCallFlagsFor(MGPWireOp::ResourceCreate) & static_cast(MG_Pipe::kOptional), 0u) << "ResourceCreate grew kOptional, whose ring alias is kRecNoReply: a stamped call flag " "would now read as 'do not answer me'"; } @@ -1049,8 +1018,7 @@ TEST_F(PipeWireCodecTest, SetGlobalConstantsCarriesTheDefaultUniformBlock) { record.ShaderCso = MakeHandle(71); record.Version = 2; record.Blob = wire.Encoder().StageBytes(block.data(), block.size()); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetGlobalConstants, &record, sizeof(record)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetGlobalConstants, &record, sizeof(record)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1068,8 +1036,7 @@ TEST_F(PipeWireCodecTest, SetStreamOutputTargetsCarriesItsRangesAndItsOffsets) { ranges[i].Size = 256; } const WireTail tails[2] = {{ranges, sizeof(ranges)}, {offsets, sizeof(offsets)}}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStreamOutputTargets, &header, sizeof(header), - tails, 2), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStreamOutputTargets, &header, sizeof(header), tails, 2), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); @@ -1083,8 +1050,7 @@ TEST_F(PipeWireCodecTest, DrawVboWithoutUserIndicesHasExactlyOneTail) { info.InstanceCount = 1; info.NumDraws = 3; // odd * 12 bytes: the case that makes the alignment rule matter MGPDrawRange ranges[3] = {{0, 3, 0}, {3, 6, 0}, {9, 3, 1}}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), ranges, - sizeof(ranges)), + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), ranges, sizeof(ranges)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); @@ -1123,8 +1089,7 @@ TEST_F(PipeWireCodecTest, DrawVboConditionalSpanTailIsEightAlignedAndSurvives) { EXPECT_EQ(layout.TailOffset[1] % 8, 0u) << "MGPDrawRange is twelve bytes; the span behind an " "odd NumDraws must still be 8-aligned"; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1148,8 +1113,7 @@ TEST_F(PipeWireCodecTest, UserIndexSpanExactlyAtSegmentEndReachesTheSink) { span.Offset = Wire2::kStageBytes - 2; span.Size = 2; const WireTail tails[] = {{&range, sizeof(range)}, {&span, sizeof(span)}}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); ASSERT_TRUE(applied); @@ -1198,8 +1162,7 @@ TEST_F(PipeWireCodecTest, DrawVboIndirectTailCrossesInPlaceOfTheSpan) { EXPECT_EQ(layout.TailOffset[1] % 8, 0u); const WireTail tails[2] = {{nullptr, 0}, {&indirect, sizeof(indirect)}}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1235,8 +1198,7 @@ TEST_F(PipeWireCodecTest, MemoryBarrierReachesTheSink) { MGPMemoryBarrier barrier{}; barrier.Bits = 0x2000u; // GL_SHADER_STORAGE_BARRIER_BIT barrier.ByRegion = 0; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1259,8 +1221,7 @@ TEST_F(PipeWireCodecTest, ResourceCopyRegionReachesTheSinkWithItsTwoNames) { copy.DstLevel = 1; copy.SrcGlName = 1001; copy.DstGlName = 1002; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCopyRegion, ©, sizeof(copy)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCopyRegion, ©, sizeof(copy)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1385,8 +1346,7 @@ TEST_F(PipeWireCodecTest, ApplierResetReachesTheSinkWithItsSerial) { Wire2 wire; MGPApplierReset reset{}; reset.ContextSerial = 0; // 0 = the first make-current (CONTRACT-P5C.md §1) - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1395,8 +1355,7 @@ TEST_F(PipeWireCodecTest, ApplierResetReachesTheSinkWithItsSerial) { // A second edge carries the next serial, and the two records arrive in order. reset.ContextSerial = 1; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); ASSERT_EQ(wire.Sink().ApplierResets.size(), 2u); @@ -1410,15 +1369,13 @@ TEST_F(PipeWireCodecTest, ObjectDeathReachesTheSinkWithItsHandleAndKind) { // seven), because the kind is what the sink dispatches on and a row that widened it // wrong would drop every death of that kind. const MGPipeKind kinds[] = { - MGPipeKind::Texture, MGPipeKind::Framebuffer, MGPipeKind::Renderbuffer, - MGPipeKind::SamplerCso, MGPipeKind::ShaderCso, MGPipeKind::SamplerViewCso, - MGPipeKind::VertexElementsCso, + MGPipeKind::Texture, MGPipeKind::Framebuffer, MGPipeKind::Renderbuffer, MGPipeKind::SamplerCso, + MGPipeKind::ShaderCso, MGPipeKind::SamplerViewCso, MGPipeKind::VertexElementsCso, }; Uint32 slot = 40; for (MGPipeKind kind : kinds) { MGPHandleOnly death = HandleOnly(slot, kind); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), - kInvalidSeq) + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), kInvalidSeq) << static_cast(kind); ++slot; } @@ -1453,14 +1410,14 @@ TEST_F(PipeWireCodecTest, SetContextValuesRoundTripsIntoPipeInputs) { MGPContextValues values{}; values.ActiveTextureUnit = 9; values.MaxTouchedTextureUnit = 41; - for (Uint32 t = 0; t < 15; ++t) values.TouchedBufferBindingPointCount[t] = 100 + t; + for (Uint32 t = 0; t < 15; ++t) + values.TouchedBufferBindingPointCount[t] = 100 + t; values.IsTransformFeedbackActive = 1; values.IsTransformFeedbackPaused = 1; values.TransformFeedbackGeneration = 0xA1A2A3A4A5A6A7A8ull; values.BoundTransformFeedbackLifetimeId = 0xB1B2B3B4B5B6B7B8ull; values.TransformFeedbackCapturedVertices = 0xC1C2C3C4C5C6C7C8ull; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetContextValues, &values, sizeof(values)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetContextValues, &values, sizeof(values)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1473,8 +1430,7 @@ TEST_F(PipeWireCodecTest, SetContextValuesRoundTripsIntoPipeInputs) { EXPECT_EQ(gPipeInputs->GetMaxTouchedTextureUnit(), 41); MGPipeServerStampVerbBoundary(MGPipeVerb::DrawArrays); for (Uint32 t = 0; t < 15; ++t) { - EXPECT_EQ(gPipeInputs->GetTouchedBufferBindingPointCount(static_cast(t)), - SizeT{100 + t}) + EXPECT_EQ(gPipeInputs->GetTouchedBufferBindingPointCount(static_cast(t)), SizeT{100 + t}) << "target " << t; } EXPECT_TRUE(gPipeInputs->IsTransformFeedbackActive()); @@ -1604,8 +1560,7 @@ TEST_F(PipeWireCodecTest, SetStorageBlockBindingCarriesItsNameAsAStagedBlob) { binding.Name = wire.Encoder().StageBytes(name, sizeof(name)); EXPECT_EQ(binding.Name.Size, sizeof(name)); EXPECT_EQ(binding.Name.Seg, static_cast(kSegStage)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStorageBlockBinding, &binding, sizeof(binding)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetStorageBlockBinding, &binding, sizeof(binding)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1633,10 +1588,8 @@ TEST_F(PipeWireCodecTest, CopyFramebufferToTextureReachesTheSinkForBothForms) { sub.InternalFormat = 0; sub.XOffset = 4; sub.YOffset = 8; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CopyFramebufferToTexture, &image, sizeof(image)), - kInvalidSeq); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CopyFramebufferToTexture, &sub, sizeof(sub)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CopyFramebufferToTexture, &image, sizeof(image)), kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CopyFramebufferToTexture, &sub, sizeof(sub)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1671,10 +1624,8 @@ TEST_F(PipeWireCodecTest, PackBufferReadbacksReachTheirOwnArmsAndPostNoReply) { image.Src.DstSize = 4u * 4u * 6u * 4u; image.ImageStride = 4u * 80u; image.SwapGroup = 4; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ReadPixelsToBuffer, &pixels, sizeof(pixels)), - kInvalidSeq); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::GetTextureImageToBuffer, &image, sizeof(image)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ReadPixelsToBuffer, &pixels, sizeof(pixels)), kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::GetTextureImageToBuffer, &image, sizeof(image)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1734,8 +1685,7 @@ TEST_F(PipeWireCodecTest, CreateShaderStateCrossesAsOneArchiveAndSixUndeclaredRu EXPECT_EQ(desc.Spirv[i].Offset, 0u); } - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateShaderState, &desc, sizeof(desc)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateShaderState, &desc, sizeof(desc)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -1767,8 +1717,7 @@ TEST_F(PipeWireCodecTest, ResidualValueBlockCrossesAsItsOwnBlob) { record.Version = 3; record.Blob = wire.Encoder().StageBytes(&eightBytes, sizeof(eightBytes)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetResidualValueState, &record, sizeof(record)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetResidualValueState, &record, sizeof(record)), kInvalidSeq); bool applied = true; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_FALSE(applied) << "a retired row must be declined, not applied"; @@ -1790,14 +1739,12 @@ TEST_F(PipeWireCodecTest, SamplerParametersCrossByteForByteIncludingBorderColorF desc.Parameters = wire.Encoder().StageBytes(¶ms, sizeof(params)); EXPECT_EQ(desc.Parameters.Size, sizeof(SamplerParameters)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &desc, sizeof(desc)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &desc, sizeof(desc)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); - const void* staged = wire.Segments().Resolve(desc.Parameters.Seg, desc.Parameters.Offset, - desc.Parameters.Size); + const void* staged = wire.Segments().Resolve(desc.Parameters.Seg, desc.Parameters.Offset, desc.Parameters.Size); ASSERT_NE(staged, nullptr); SamplerParameters back{}; std::memcpy(&back, staged, sizeof(back)); @@ -1826,8 +1773,7 @@ TEST_F(PipeWireCodecTest, TheFiveClassBVerbsReachTheSinkAndNothingElse) { MGPReadbackInfo readback{}; readback.Box = MGPBox{0, 0, 0, 2, 2, 1}; readback.DstSize = 16; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ReadPixels, &readback, sizeof(readback)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ReadPixels, &readback, sizeof(readback)), kInvalidSeq); MGPPresent present{}; present.FrameSerial = 12; @@ -1894,8 +1840,7 @@ TEST_F(PipeWireCodecTest, TheDecoderWritesNoRingControlFieldOfItsOwn) { // documented. Wire2 wire; MGPPresent present{}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::Present, &present, sizeof(present)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::Present, &present, sizeof(present)), kInvalidSeq); wire.Encoder().Publish(); Transport::RingRecordView view{}; @@ -1972,8 +1917,7 @@ TEST_F(PipeWireCodecTest, TheCommandRingWrapCountIsWhatTheHeadActuallyDid) { // payload up to 8, and a case that restated that arithmetic would be asserting its own // copy of Ring.cpp rather than what the producer did. MGPBindRenderState bind{}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); const Uint64 stride = wire.Encoder().CmdBytesWritten(); @@ -1986,8 +1930,7 @@ TEST_F(PipeWireCodecTest, TheCommandRingWrapCountIsWhatTheHeadActuallyDid) { // there, and not an artefact of a backed-up queue. const Uint64 records = (Wire2::kCmdBytes / stride) + 3; for (Uint64 i = 1; i < records; ++i) { - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq) + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq) << "the ring refused record " << i << " of " << records; ASSERT_TRUE(wire.PumpOne(&applied)); } @@ -2025,8 +1968,7 @@ TEST_F(PipeWireCodecTest, ABigProgramArchiveDoesNotGrowItsRecordAtAll) { MGPProgramDesc desc{}; desc.Cso = MakeHandle(91); desc.Reflection = wire.Encoder().StageBytes(archive.data(), archive.size()); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateShaderState, &desc, sizeof(desc)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateShaderState, &desc, sizeof(desc)), kInvalidSeq); EXPECT_EQ(wire.Encoder().MaxRecordBytesSeen(), 8u + sizeof(MGPProgramDesc)); EXPECT_GE(wire.Encoder().StagedBytesInFlight(), archive.size()); } @@ -2041,8 +1983,7 @@ TEST_F(PipeWireCodecTest, AlreadyRetiredStagingReclamationIsNotAProducerWait) { ASSERT_TRUE(wire.PumpOne(&applied)); // Do not explicitly reclaim: the second real allocation must do that itself. wire.Encoder().StageBytes(payload.data(), payload.size()); - EXPECT_EQ(wire.Encoder().StageReclaimWaits(), 0u) - << "already-retired lazy reclamation is not a producer wait"; + EXPECT_EQ(wire.Encoder().StageReclaimWaits(), 0u) << "already-retired lazy reclamation is not a producer wait"; } TEST_F(PipeWireCodecTest, StagedBytesAreReclaimedOnlyBehindRetiredSeq) { @@ -2054,8 +1995,7 @@ TEST_F(PipeWireCodecTest, StagedBytesAreReclaimedOnlyBehindRetiredSeq) { EXPECT_GE(inFlight, sizeof(payload)); MGPBindRenderState bind{}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); // Nothing is retired yet, so nothing may be released: R-11's whole content on this side. wire.Encoder().ReclaimStagedBytes(); EXPECT_EQ(wire.Encoder().StagedBytesInFlight(), inFlight); @@ -2116,8 +2056,7 @@ TEST_F(PipeWireCodecTest, TheEncoderNeverWritesSegStagesConsumerCursors) { const std::uint8_t payload[128] = {}; (void)wire.Encoder().StageBytes(payload, sizeof(payload)); MGPBindRenderState bind{}; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); wire.Encoder().ReclaimStagedBytes(); @@ -2142,20 +2081,17 @@ TEST_F(PipeWireCodecTest, TheStageMarkQueueStaysBoundedWithOneRecordAlwaysInFlig // queue at every reclaim, which is the one regime the old "clear only when front reaches // size()" code handled - a case written that way stays green against the bug. One record // encoded ahead of the one being applied is what makes `front < size()` permanent. - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); for (int i = 0; i < 4000; ++i) { (void)wire.Encoder().StageBytes(blob, sizeof(blob)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::BindRenderState, &bind, sizeof(bind)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)) << "record " << i; } EXPECT_EQ(wire.Encoder().EmitSeq(), 4001u); EXPECT_EQ(wire.Decoder().AppliedSeq(), 4000u) << "one record must still be in flight"; // Bounded by twice the records actually in flight, not by the records ever encoded. - EXPECT_LE(wire.Encoder().StageMarksHeld(), 4u) - << "the mark queue is tracking history rather than flight"; + EXPECT_LE(wire.Encoder().StageMarksHeld(), 4u) << "the mark queue is tracking history rather than flight"; } // ---- M4: the reply slot is for kReplySlot rows only ---------------------------------------- @@ -2191,15 +2127,13 @@ TEST_F(PipeWireCodecTest, NoReplyIsWrittenForARowTheCatalogueGivesNoReplySlot) { create.ArrayLayers = 1; create.Levels = 1; create.Samples = 1; - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceCreate, &create, sizeof(create)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); ASSERT_EQ(wire.Answers().All.size(), 1u) << "the kReplySlot row did not answer"; const MGPHandleOnly destroy = HandleOnly(131, MGPipeKind::Buffer); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceDestroy, &destroy, sizeof(destroy)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ResourceDestroy, &destroy, sizeof(destroy)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -2213,10 +2147,9 @@ TEST_F(PipeWireCodecTest, EveryRowThatDoesWriteAReplyCarriesKReplySlot) { // this list at ID-31 and R-17 - and they are listed BY NAME rather than derived from the // flags table, because deriving the expectation from the same table the assertion reads // would make this case true by construction. - for (const MGPWireOp op : {MGPWireOp::MapPersistent, MGPWireOp::ResourceReadback, - MGPWireOp::ReadPixels, MGPWireOp::ResourceCreate, - MGPWireOp::ResourceRespecify, MGPWireOp::ResourceSubData, - MGPWireOp::SetTextureParams}) { + for (const MGPWireOp op : + {MGPWireOp::MapPersistent, MGPWireOp::ResourceReadback, MGPWireOp::ReadPixels, MGPWireOp::ResourceCreate, + MGPWireOp::ResourceRespecify, MGPWireOp::ResourceSubData, MGPWireOp::SetTextureParams}) { EXPECT_NE(MGPipeCallFlagsFor(op) & static_cast(kReplySlot), 0u) << WireOpName(op); } } @@ -2235,20 +2168,17 @@ TEST_F(PipeWireCodecTest, TheFourAcceptanceRowsAnswerThroughTheSlotAndNotOnlyThr MGPTextureParams params{}; params.Res = MakeHandle(77); params.BuiltinSampler = MakeHandle(78); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetTextureParams, ¶ms, sizeof(params)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::SetTextureParams, ¶ms, sizeof(params)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied) << "the record must still have CROSSED; acceptance is not 'applied'"; ASSERT_EQ(wire.Answers().All.size(), 1u); EXPECT_EQ(wire.Answers().All[0].Seq, 1u) << "the id is the record's own ordinal (R-3)"; - EXPECT_TRUE(wire.Answers().All[0].Bytes.empty()) - << "an acceptance answer is a STATUS; DECLINED carries no payload"; + EXPECT_TRUE(wire.Answers().All[0].Bytes.empty()) << "an acceptance answer is a STATUS; DECLINED carries no payload"; ASSERT_TRUE(wire.Decoder().LastAcceptanceKnown()); - EXPECT_EQ(wire.Answers().All[0].Status, wire.Decoder().LastAcceptance() - ? ReplySink::kStatusOk - : ReplySink::kStatusDeclined); + EXPECT_EQ(wire.Answers().All[0].Status, + wire.Decoder().LastAcceptance() ? ReplySink::kStatusOk : ReplySink::kStatusDeclined); ExpectRepliesAgreeWithTheAcceptanceTally(wire); } @@ -2264,8 +2194,7 @@ TEST_F(PipeWireCodecTest, TheAuditFillOverwritesExactlyTheRunsTheRecordResolved) MGPSamplerDesc record{}; record.Cso = MakeHandle(91); record.Parameters = wire.Encoder().StageBytes(¶ms, sizeof(params)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &record, sizeof(record)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &record, sizeof(record)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -2283,8 +2212,7 @@ TEST_F(PipeWireCodecTest, TheAuditFillIsOffByDefaultSoTheHotPathPaysNothing) { MGPSamplerDesc record{}; record.Cso = MakeHandle(92); record.Parameters = wire.Encoder().StageBytes(¶ms, sizeof(params)); - ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &record, sizeof(record)), - kInvalidSeq); + ASSERT_NE(wire.Encoder().EncodeRecord(MGPWireOp::CreateSamplerState, &record, sizeof(record)), kInvalidSeq); bool applied = false; ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_EQ(wire.Decoder().PoisonedStageBytes(), 0u); @@ -2298,9 +2226,9 @@ TEST_F(PipeWireCodecTest, TheAuditFillIsOffByDefaultSoTheHotPathPaysNothing) { TEST_F(PipeWireCodecTest, FormatCapabilitiesRoundTrip) { MG_Backend::FormatCapabilityCache cache; - cache.FullCaps[0][1] = MG_Backend::FormatCapabilityFlags( - static_cast(MG_Backend::FormatCapability::Creatable) | - static_cast(MG_Backend::FormatCapability::Sampled)); + cache.FullCaps[0][1] = + MG_Backend::FormatCapabilityFlags(static_cast(MG_Backend::FormatCapability::Creatable) | + static_cast(MG_Backend::FormatCapability::Sampled)); cache.CaveatCaps[2][3] = MG_Backend::FormatCapabilityFlags(static_cast(MG_Backend::FormatCapability::LinearFilter)); cache.SampleCounts[1][4] = Vector{1, 2, 4, 8}; @@ -2349,8 +2277,7 @@ TEST_F(PipeWireCodecTest, RendererInfoRoundTrips) { info.RendererGLInfo.TargetGLVersion = Version{4, 6, 0, Optional(), Optional()}; info.RendererGLInfo.TargetGLSLVersion = Version{4, 60, 0, Optional(String("-dev")), Optional(VersionType::Development)}; - info.RendererGLInfo.Extensions = Vector{static_cast(1), - static_cast(7)}; + info.RendererGLInfo.Extensions = Vector{static_cast(1), static_cast(7)}; info.RendererGLInfo.IsCompatibilityProfile = true; info.StaticBackendCapability.AllowVSOnlyPrograms = true; @@ -2430,8 +2357,7 @@ TEST_F(PipeWireCodecTest, ANonNullHostSpanPointerIsFatal) { std::vector tail(sizeof(range) + sizeof(span)); std::memcpy(tail.data(), &range, sizeof(range)); std::memcpy(tail.data() + sizeof(range), &span, sizeof(span)); - ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), - tail.size()); + ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), tail.size()); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"host-span\"}"), std::string::npos) << r.Log; @@ -2483,8 +2409,7 @@ TEST_F(PipeWireCodecTest, ARecordLargerThanHalfTheRingIsFatalRingOverrun) { Wire2 wire; MGPDrawInfo info{}; // Just over half the ring. Wire2's SEG_CMD is 64 KiB, so the cap is 32 KiB. - const Uint32 draws = - static_cast(((Wire2::kCmdBytes / 2) / sizeof(MGPDrawRange)) + 8); + const Uint32 draws = static_cast(((Wire2::kCmdBytes / 2) / sizeof(MGPDrawRange)) + 8); info.NumDraws = draws; std::vector ranges(draws); (void)wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), ranges.data(), @@ -2495,8 +2420,7 @@ TEST_F(PipeWireCodecTest, ARecordLargerThanHalfTheRingIsFatalRingOverrun) { EXPECT_NE(r.Log.find("exceeds RingProducer::MaxRecordBytes()"), std::string::npos) << r.Log; // The diagnostic has to name the decision it forces, because the person reading it // has to choose between a cut for this row and a bigger ring and neither is a local fix. - EXPECT_NE(r.Log.find("budget cuts blobs, not a record's own bytes"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("budget cuts blobs, not a record's own bytes"), std::string::npos) << r.Log; } TEST_F(PipeWireCodecTest, ARunThatLeavesItsSegmentIsFatal) { @@ -2605,8 +2529,7 @@ TEST_F(PipeWireCodecTest, ATailThatDoesNotMatchItsOwnCountIsFatal) { header.Start = 0; header.Count = 4000; const std::uint64_t eightBytes = 0; - ForgeAndDecode(wire, MGPWireOp::SetSamplerViews, &header, sizeof(header), &eightBytes, - sizeof(eightBytes)); + ForgeAndDecode(wire, MGPWireOp::SetSamplerViews, &header, sizeof(header), &eightBytes, sizeof(eightBytes)); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; // The count is past the unit bound, so the layout refuses it before the length even @@ -2623,8 +2546,7 @@ TEST_F(PipeWireCodecTest, ATailWhoseLengthDisagreesWithItsCountIsFatal) { header.Start = 0; header.Count = 4; // 4 * sizeof(MGPBoundView) == 96 bytes of tail const std::uint64_t eightBytes = 0; - ForgeAndDecode(wire, MGPWireOp::SetSamplerViews, &header, sizeof(header), &eightBytes, - sizeof(eightBytes)); + ForgeAndDecode(wire, MGPWireOp::SetSamplerViews, &header, sizeof(header), &eightBytes, sizeof(eightBytes)); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("its own count fields describe"), std::string::npos) << r.Log; @@ -2638,8 +2560,7 @@ TEST_F(PipeWireCodecTest, VertexAttribDefaultsMustAgreeWithItsOwnMask) { header.Mask = 0x7; // three bits header.Count = 2; // two entries MGPAttribValue tail[2]{}; - ForgeAndDecode(wire, MGPWireOp::SetVertexAttribDefaults, &header, sizeof(header), tail, - sizeof(tail)); + ForgeAndDecode(wire, MGPWireOp::SetVertexAttribDefaults, &header, sizeof(header), tail, sizeof(tail)); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("SetVertexAttribDefaults.Count"), std::string::npos) << r.Log; @@ -2697,8 +2618,7 @@ TEST_F(PipeWireCodecTest, AHostSpanCountThatIsNeitherZeroNorCountIsFatal) { std::vector tail(sizeof(ranges) + sizeof(spans)); std::memcpy(tail.data(), ranges, sizeof(ranges)); std::memcpy(tail.data() + sizeof(ranges), spans, sizeof(spans)); - ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), - tail.size()); + ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), tail.size()); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("SetShaderBuffers.HostSpanCount"), std::string::npos) << r.Log; @@ -2724,13 +2644,11 @@ TEST_F(PipeWireCodecTest, AHostSpanRunPastItsSegmentIsFatalOnSetShaderBuffers) { std::vector tail(sizeof(range) + sizeof(span)); std::memcpy(tail.data(), &range, sizeof(range)); std::memcpy(tail.data() + sizeof(range), &span, sizeof(span)); - ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), - tail.size()); + ForgeAndDecode(wire, MGPWireOp::SetShaderBuffers, &header, sizeof(header), tail.data(), tail.size()); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"host-span\"}"), std::string::npos) << r.Log; - EXPECT_NE(r.Log.find("does not lie inside that segment (R-2.3 arm 4)"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("does not lie inside that segment (R-2.3 arm 4)"), std::string::npos) << r.Log; } TEST_F(PipeWireCodecTest, AHostSpanRunPastItsSegmentIsFatalOnDrawVbo) { @@ -2758,8 +2676,7 @@ TEST_F(PipeWireCodecTest, AHostSpanRunPastItsSegmentIsFatalOnDrawVbo) { ForgeAndDecode(wire, MGPWireOp::DrawVbo, &info, sizeof(info), tail.data(), tail.size()); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("does not lie inside that segment (R-2.3 arm 4)"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("does not lie inside that segment (R-2.3 arm 4)"), std::string::npos) << r.Log; } // The span itself is in bounds, but the driver's index count would leave it. Exercise @@ -2816,17 +2733,30 @@ TEST_F(PipeWireCodecTest, UserIndexSpanRequiresOneIndexedRangeAndNonWrappingExte span.Seg = kSegStage; span.Size = 2; switch (malformed) { - case 0: info.IndexSize = 0; break; // arrays cannot consume user indices - case 1: info.IndexSize = 3; break; - case 2: info.NumDraws = 0; break; - case 3: info.NumDraws = 2; break; // no flattening contract for multi-draw yet - case 4: ranges[0].Start = 1; break; - case 5: info.IndexSize = 4; ranges[0].Count = 0x80000001u; span.Size = 4; break; + case 0: + info.IndexSize = 0; + break; // arrays cannot consume user indices + case 1: + info.IndexSize = 3; + break; + case 2: + info.NumDraws = 0; + break; + case 3: + info.NumDraws = 2; + break; // no flattening contract for multi-draw yet + case 4: + ranges[0].Start = 1; + break; + case 5: + info.IndexSize = 4; + ranges[0].Count = 0x80000001u; + span.Size = 4; + break; } if (side == 0) { - const WireTail tails[] = { - {ranges, static_cast(info.NumDraws) * sizeof(MGPDrawRange)}, - {&span, sizeof(span)}}; + const WireTail tails[] = {{ranges, static_cast(info.NumDraws) * sizeof(MGPDrawRange)}, + {&span, sizeof(span)}}; wire.Encoder().EncodeRecord(MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2); } else { const auto bytes = info.NumDraws * sizeof(MGPDrawRange); @@ -2857,9 +2787,8 @@ TEST_F(PipeWireCodecTest, ADrawThatSetsBothTheSpanAndTheIndirectFlagIsFatal) { info.InstanceCount = 1; info.NumDraws = 0; MGPDrawIndirect indirect{}; - const ChildResult r = RunInChild([&] { - ForgeAndDecode(wire, MGPWireOp::DrawVbo, &info, sizeof(info), &indirect, sizeof(indirect)); - }); + const ChildResult r = + RunInChild([&] { ForgeAndDecode(wire, MGPWireOp::DrawVbo, &info, sizeof(info), &indirect, sizeof(indirect)); }); EXPECT_TRUE(DiedOfAbort(r)) << DescribeStatus(r); EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"DrawVbo.Flags\"}"), std::string::npos) << r.Log; } @@ -2873,9 +2802,8 @@ TEST_F(PipeWireCodecTest, AnIndirectDrawThatDeclaresRangesIsFatal) { info.InstanceCount = 1; info.NumDraws = 1; // the server never reads a count and the client never sends ranges MGPDrawRange range{0, 3, 0}; - const ChildResult r = RunInChild([&] { - ForgeAndDecode(wire, MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); - }); + const ChildResult r = + RunInChild([&] { ForgeAndDecode(wire, MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); }); EXPECT_TRUE(DiedOfAbort(r)) << DescribeStatus(r); EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"DrawVbo.NumDraws\"}"), std::string::npos) << r.Log; } @@ -2930,8 +2858,7 @@ TEST_F(PipeWireCodecTest, APartiallyZeroUploadBoxIsFatalRatherThanReadAsEmpty) { ForgeAndDecode(wire, MGPWireOp::ResourceSubData, &rec, sizeof(rec), nullptr, 0); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("has a zero extent on some axes and not others"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("has a zero extent on some axes and not others"), std::string::npos) << r.Log; } TEST_F(PipeWireCodecTest, ASecondProcessResolverIsFatalRatherThanASilentRace) { @@ -2997,7 +2924,8 @@ namespace { const std::string delta = ReadLog().substr(before.size()); const std::string line = "Refuse{AdoptTierOnStream, \"T" + std::to_string(tier) + "\"}"; std::size_t count = 0; - for (auto at = delta.find(line); at != std::string::npos; at = delta.find(line, at + 1)) ++count; + for (auto at = delta.find(line); at != std::string::npos; at = delta.find(line, at + 1)) + ++count; EXPECT_EQ(count, 1u) << "one refusal at the handshake and none per map_persistent:\n" << delta; EXPECT_EQ(delta.find("Fatal{"), std::string::npos) << delta; } @@ -3027,7 +2955,7 @@ TEST_F(PipeWireCodecTest, AnUnsettledAdoptTierStillDiesByNameOnTheWirePath) { }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("Fatal{UnimplementedAdoptTier, \"T0\"} - MOBILEGL_IPC_ADOPT_TIER=0 " - "reached a map_persistent that no handshake settled"), + "reached a map_persistent that no handshake settled"), std::string::npos) << r.Log; } @@ -3081,7 +3009,6 @@ int main(int argc, char** argv) { return rc; } - namespace { struct FenceProbeBackend final : MG_Backend::DirectGLES::BackendObject_DirectGLES { MG_Backend::GlobalBackendFunctionsTable Table{}; @@ -3097,8 +3024,8 @@ namespace { fenceDeletes = fenceServerWaits = fenceFlags = 0; fenceTimeout = 0; backend.Table.GL.FenceSync = +[]() -> MG_Backend::BackendSyncHandle { return &nativeFenceToken; }; - backend.Table.GL.ClientWaitSync = +[](MG_Backend::BackendSyncHandle native, GLbitfield flags, - GLuint64 timeout) -> GLenum { + backend.Table.GL.ClientWaitSync = + +[](MG_Backend::BackendSyncHandle native, GLbitfield flags, GLuint64 timeout) -> GLenum { EXPECT_EQ(native, &nativeFenceToken); fenceFlags = flags; fenceTimeout = timeout; @@ -3119,7 +3046,7 @@ namespace { ++fenceDeletes; }; } -} +} // namespace TEST(FenceWireRoundTrip, PreservesWaitFlagsTimeoutAnswersAndNativeLifetime) { Wire2 wire; @@ -3217,15 +3144,15 @@ namespace { EXPECT_EQ(query, &nativeQueryToken); ++queryDeletes; }; - backend.Table.GL.ClientWaitSync = +[](MG_Backend::BackendSyncHandle sync, GLbitfield flags, - GLuint64 timeout) -> GLenum { + backend.Table.GL.ClientWaitSync = + +[](MG_Backend::BackendSyncHandle sync, GLbitfield flags, GLuint64 timeout) -> GLenum { EXPECT_EQ(sync, &nativeFenceToken); fenceFlags = flags; fenceTimeout = timeout; return queryGpuReady ? GL_CONDITION_SATISFIED : GL_TIMEOUT_EXPIRED; }; } -} +} // namespace TEST(QueryWireRoundTrip, PrimitiveQueriesPreservePendingStateNativeResultsAndIdentity) { Wire2 wire; @@ -3328,7 +3255,6 @@ TEST(FenceWireRoundTrip, DestroyedAndRecycledWireHandlesNeverReachTheBackend) { } #endif - TEST(FenceWireRoundTrip, MissingConsumerDeclinesWithoutInventingASignaledAnswer) { Wire2 wire; const MGPFenceWait wait{{3, 0}, 0, 0, 0}; @@ -3358,8 +3284,7 @@ TEST(ApplierResetWireRoundTrip, TheSerialSequenceIsAssertedAndTheServerResetRuns wire.Decoder().SetVerbSink(&sink); auto send = [&](Uint64 serial) { const MGPApplierReset reset{serial}; - EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), - kInvalidSeq); + EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ApplierReset, &reset, sizeof(reset)), kInvalidSeq); bool applied = false; EXPECT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -3429,8 +3354,7 @@ TEST(ObjectDeathWireRoundTrip, TheTextureAndFramebufferTwinsReleaseByHandleAndNe // The death crosses and the twin retires: the slot's live generation goes back to 0. const Uint64 deathsBefore = sink.ObjectDeaths(); const MGPHandleOnly death{MakeHandle(one.slot, 1), static_cast(one.kind), 0}; - EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), - kInvalidSeq); + EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), kInvalidSeq); bool applied = false; EXPECT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); @@ -3444,18 +3368,15 @@ TEST(ObjectDeathWireRoundTrip, TheTextureAndFramebufferTwinsReleaseByHandleAndNe // new twin survives it. ASSERT_NE(getOrCreate(MakeHandle(one.slot, 2)), nullptr); ASSERT_EQ(liveGen(one.slot), 2u); - EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), - kInvalidSeq); + EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &death, sizeof(death)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); - EXPECT_EQ(liveGen(one.slot), 2u) - << "a stale handle released the recycled slot's new twin (kind " - << static_cast(one.kind) << ")"; + EXPECT_EQ(liveGen(one.slot), 2u) << "a stale handle released the recycled slot's new twin (kind " + << static_cast(one.kind) << ")"; // And the new object's own death retires it, leaving the table as the case found it. const MGPHandleOnly newDeath{MakeHandle(one.slot, 2), static_cast(one.kind), 0}; - EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &newDeath, sizeof(newDeath)), - kInvalidSeq); + EXPECT_NE(wire.Encoder().EncodeRecord(MGPWireOp::ObjectDeath, &newDeath, sizeof(newDeath)), kInvalidSeq); ASSERT_TRUE(wire.PumpOne(&applied)); EXPECT_TRUE(applied); EXPECT_EQ(liveGen(one.slot), 0u); @@ -3481,9 +3402,7 @@ TEST(CtWireFatals, ASerialTheSessionCannotProveIsProtocolCorruption) { (void)wire.PumpOne(&applied); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"}"), - std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"ApplierReset.ContextSerial\"}"), std::string::npos) << r.Log; } TEST(CtWireFatals, ANullDeathHandleIsProtocolCorruption) { @@ -3499,8 +3418,7 @@ TEST(CtWireFatals, ANullDeathHandleIsProtocolCorruption) { (void)wire.PumpOne(&applied); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"}"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("Fatal{ProtocolCorruption, \"ObjectDeath.Handle\"}"), std::string::npos) << r.Log; } // THE GUARD'S TWO NON-FATAL ARMS, as unit controls. The Fatal arm itself needs a LIVE client diff --git a/MobileGL/MG_Test/Wire/PreAuthGateTest.cpp b/MobileGL/MG_Test/Wire/PreAuthGateTest.cpp index fb6a57a7..8be81b1e 100644 --- a/MobileGL/MG_Test/Wire/PreAuthGateTest.cpp +++ b/MobileGL/MG_Test/Wire/PreAuthGateTest.cpp @@ -38,7 +38,8 @@ namespace Wire = ::MobileGL::Wire; namespace { - std::vector Finish(::flatbuffers::FlatBufferBuilder& builder, ::flatbuffers::Offset root, + std::vector Finish(::flatbuffers::FlatBufferBuilder& builder, + ::flatbuffers::Offset root, const char* identifier = Wire::CtrlEnvelopeIdentifier()) { builder.Finish(root, identifier); return std::vector(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); @@ -47,8 +48,8 @@ namespace { std::vector HelloPayload(const char* identifier = Wire::CtrlEnvelopeIdentifier()) { ::flatbuffers::FlatBufferBuilder builder(512); auto terms = Wire::CreateLinkTerms(builder); - auto hello = Wire::CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, - "build", 0, 1, nullptr, 0, 0, terms, "token", Wire::DialMode::Connect); + auto hello = Wire::CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, "build", + 0, 1, nullptr, 0, 0, terms, "token", Wire::DialMode::Connect); return Finish(builder, Wire::CreateCtrlEnvelope(builder, Wire::CtrlMsg::Hello, hello.Union()), identifier); } @@ -85,11 +86,20 @@ namespace { std::string name, previous; bool had = false; ScopedEnvironment(const char* key, const char* value) : name(key) { - if (const char* old = std::getenv(key)) { previous = old; had = true; } - if (value) ::setenv(key, value, 1); else ::unsetenv(key); + if (const char* old = std::getenv(key)) { + previous = old; + had = true; + } + if (value) + ::setenv(key, value, 1); + else + ::unsetenv(key); } ~ScopedEnvironment() { - if (had) ::setenv(name.c_str(), previous.c_str(), 1); else ::unsetenv(name.c_str()); + if (had) + ::setenv(name.c_str(), previous.c_str(), 1); + else + ::unsetenv(name.c_str()); } }; @@ -101,7 +111,8 @@ namespace { // "did not verify"), and the first EXPECT_EQ names it. TEST(PreAuthGateTest, EachMalformedFirstFrameLandsOnTheShapeItIsNamedFor) { const auto wrongIdentifier = HelloPayload("XXXX"); - EXPECT_EQ(Server::ClassifyFirstFrame(wrongIdentifier.data(), wrongIdentifier.size()), FirstFrameShape::NoIdentifier); + EXPECT_EQ(Server::ClassifyFirstFrame(wrongIdentifier.data(), wrongIdentifier.size()), + FirstFrameShape::NoIdentifier); // Shorter than a root offset plus an identifier: nothing to ask. const std::uint8_t tiny[5] = {1, 2, 3, 4, 5}; EXPECT_EQ(Server::ClassifyFirstFrame(tiny, sizeof(tiny)), FirstFrameShape::NoIdentifier); @@ -119,7 +130,8 @@ TEST(PreAuthGateTest, EachMalformedFirstFrameLandsOnTheShapeItIsNamedFor) { const auto logFlush = Finish(builder, Wire::CreateCtrlEnvelope(builder, Wire::CtrlMsg::LogFlush, flush.Union())); EXPECT_EQ(Server::ClassifyFirstFrame(logFlush.data(), logFlush.size()), FirstFrameShape::NotAHello); ::flatbuffers::FlatBufferBuilder empty(64); - const auto nullHello = Finish(empty, Wire::CreateCtrlEnvelope(empty, Wire::CtrlMsg::Hello, ::flatbuffers::Offset())); + const auto nullHello = + Finish(empty, Wire::CreateCtrlEnvelope(empty, Wire::CtrlMsg::Hello, ::flatbuffers::Offset())); EXPECT_EQ(Server::ClassifyFirstFrame(nullHello.data(), nullHello.size()), FirstFrameShape::NotAHello); // A DataBind is only a DataBind with exactly kDataNonceBytes of nonce. const auto shortBind = DataBindPayload(8); @@ -239,10 +251,12 @@ TEST(PreAuthGateTest, TheBackoffWindowStartsAtTheThresholdDoublesAndIsCapped) { // Off means off. knobs.backoffAfter = 0; Server::AuthBackoff off(knobs); - for (int i = 0; i < 50; ++i) EXPECT_EQ(off.NoteFailure(peer, t0), 0u); + for (int i = 0; i < 50; ++i) + EXPECT_EQ(off.NoteFailure(peer, t0), 0u); EXPECT_EQ(off.RetryAfterMs(peer, t0), 0u); // Bounded, however many addresses fail. - for (int i = 0; i < 1000; ++i) backoff.NoteFailure("198.51.100." + std::to_string(i), t0); + for (int i = 0; i < 1000; ++i) + backoff.NoteFailure("198.51.100." + std::to_string(i), t0); EXPECT_LE(backoff.Tracked(), Server::kAuthBackoffTrackedPeers); } diff --git a/MobileGL/MG_Test/Wire/ProtocolSmokeTest.cpp b/MobileGL/MG_Test/Wire/ProtocolSmokeTest.cpp index 677515eb..4795c90a 100644 --- a/MobileGL/MG_Test/Wire/ProtocolSmokeTest.cpp +++ b/MobileGL/MG_Test/Wire/ProtocolSmokeTest.cpp @@ -29,9 +29,9 @@ namespace { std::vector BuildHello() { ::flatbuffers::FlatBufferBuilder builder(1024); const std::vector config{1, 2, 3, 4}; - auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, - MOBILEGL_PROTOCOL_ABI_MINOR, "mobilegl-test-build", - /*backendType=*/2, /*pid=*/4242, &config); + auto hello = + CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, "mobilegl-test-build", + /*backendType=*/2, /*pid=*/4242, &config); auto envelope = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, envelope); const std::uint8_t* begin = builder.GetBufferPointer(); @@ -161,10 +161,9 @@ TEST(ProtocolSmokeTest, TravelsAcrossTheTransportUnchanged) { TEST(ProtocolSmokeTest, StreamHandshakeCarriesIndependentWireBuildAndServerWindows) { ::flatbuffers::FlatBufferBuilder builder(512); - auto terms = CreateLinkTerms(builder, DataPlane::Stream, WireForm::StructImage, - 1048560, 8388608, 33554432, 262144); - auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, - "commit-123", 1, 42, nullptr, 0, 0x12345678, terms, "pairing-token", DialMode::Connect); + auto terms = CreateLinkTerms(builder, DataPlane::Stream, WireForm::StructImage, 1048560, 8388608, 33554432, 262144); + auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, "commit-123", 1, + 42, nullptr, 0, 0x12345678, terms, "pairing-token", DialMode::Connect); auto envelope = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, envelope); ::flatbuffers::Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); diff --git a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp index 10f14fc1..5a91eaaf 100644 --- a/MobileGL/MG_Test/Wire/RemoteClientTest.cpp +++ b/MobileGL/MG_Test/Wire/RemoteClientTest.cpp @@ -198,8 +198,7 @@ TEST(RemoteEmitTable, TheThreeClassesPartitionAllSeventyOneSlots) { EXPECT_EQ(LocallyAnsweredSlotCount(), 2u); EXPECT_EQ(ImplementedVerbCount(), 69u); EXPECT_EQ(UnmigratedSlotCount(), 0u); - EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(), - kRemoteEmitSlotCount); + EXPECT_EQ(LocallyAnsweredSlotCount() + ImplementedVerbCount() + UnmigratedSlotCount(), kRemoteEmitSlotCount); } TEST(RemoteEmitTable, TheSixXfbAndPatchSlotsAreNonNullAndDistinct) { @@ -262,13 +261,11 @@ TEST(RemoteEmitTable, EachXfbAndPatchSlotIsClassBAndDemandsASessionByItsOwnName) for (const Slot& slot : kSlots) { const ChildResult r = RunInChild([&slot] { slot.Call(); }); ASSERT_TRUE(DiedOfAbort(r)) << slot.Name << ": " << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find(std::string("Fatal{NoClientSession, \"") + slot.Name + "\"}"), - std::string::npos) + EXPECT_NE(r.Log.find(std::string("Fatal{NoClientSession, \"") + slot.Name + "\"}"), std::string::npos) << slot.Name << " did not reach the class-B emitter's session demand:\n" << r.Log; - EXPECT_EQ(r.Log.find("Fatal{UnmigratedVerb"), std::string::npos) - << slot.Name << " is still class C:\n" - << r.Log; + EXPECT_EQ(r.Log.find("Fatal{UnmigratedVerb"), std::string::npos) << slot.Name << " is still class C:\n" + << r.Log; } } @@ -285,8 +282,7 @@ TEST(RemoteEmitTable, DeleteTransformFeedbackHasNoRowAndStillAbortsByItsOwnName) // failed. const ChildResult r = RunInChild([] { RemoteEmitTable().GL.DeleteTransformFeedback(7); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"DeleteTransformFeedback\"}"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"DeleteTransformFeedback\"}"), std::string::npos) << r.Log; EXPECT_EQ(r.Log.find("Fatal{UnmigratedVerb"), std::string::npos) << r.Log; } #endif // MGTEST_HAVE_FORK @@ -311,10 +307,9 @@ TEST(RemoteEmitTable, NoSlotIsNull) { for (SizeT i = 0; i < cellCount; ++i) { if (cells[i] == nullptr) ++nullCells; } - EXPECT_LE(nullCells, 1u) - << "a slot in the remote emit table is null (" << nullCells << " null cells out of " - << cellCount - << "). R-4 forbids it: 91 MG_Impl sites call through this table with no null check at all"; + EXPECT_LE(nullCells, 1u) << "a slot in the remote emit table is null (" << nullCells << " null cells out of " + << cellCount + << "). R-4 forbids it: 91 MG_Impl sites call through this table with no null check at all"; } TEST(RemoteEmitTable, TheFiveEmittersAreTheOnesTheCensusMeasured) { @@ -329,8 +324,7 @@ TEST(RemoteEmitTable, TheFiveEmittersAreTheOnesTheCensusMeasured) { // And the two R-15 answers them locally, so they are not the same pointer as any Fatal one. EXPECT_NE(table.GL.GetIntegeri_v, nullptr); EXPECT_NE(table.GL.IsTimerQuerySupported, nullptr); - EXPECT_NE(reinterpret_cast(table.GL.DrawArrays), - reinterpret_cast(table.GL.DrawElements)) + EXPECT_NE(reinterpret_cast(table.GL.DrawArrays), reinterpret_cast(table.GL.DrawElements)) << "DrawArrays is class B and DrawElements is class C; they cannot share a thunk"; } @@ -344,11 +338,9 @@ TEST(RemoteEmitTable, AnUnmigratedShapeRefusalAbortsAndNamesItself) { // unrepresentable SHAPE (RefuseDrawByName, EmitF1Clear's default arm), and every refusal // left on it is spelled "+" - so that is what is driven here, through the // funnel itself, because every emitter that reaches it needs a session and a context first. - const ChildResult r = - RunInChild([] { UnmigratedVerbFatal("MultiDrawElementsBaseVertex+CLIENT_INDICES"); }); + const ChildResult r = RunInChild([] { UnmigratedVerbFatal("MultiDrawElementsBaseVertex+CLIENT_INDICES"); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"MultiDrawElementsBaseVertex+CLIENT_INDICES\"}"), - std::string::npos) + EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"MultiDrawElementsBaseVertex+CLIENT_INDICES\"}"), std::string::npos) << r.Log; } @@ -357,8 +349,7 @@ TEST(RemoteEmitTable, EachUnmigratedShapeRefusalNamesItsOwnShape) { // caller's and not a constant. Two different shapes, two different names. const ChildResult r = RunInChild([] { UnmigratedVerbFatal("DrawArraysIndirect+CLIENT_COMMANDS"); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; - EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"DrawArraysIndirect+CLIENT_COMMANDS\"}"), std::string::npos) - << r.Log; + EXPECT_NE(r.Log.find("Fatal{UnmigratedVerb, \"DrawArraysIndirect+CLIENT_COMMANDS\"}"), std::string::npos) << r.Log; EXPECT_EQ(r.Log.find("MultiDrawElementsBaseVertex"), std::string::npos) << "the Fatal message names a shape other than the one that was refused:\n" << r.Log; @@ -369,10 +360,8 @@ TEST(RemoteEmitTable, EachUnmigratedShapeRefusalNamesItsOwnShape) { // the stub it replaced, by behaviour rather than by pointer. Red once by: moving DrawElements // back into MGR_UNMIGRATED_D1_SLOTS - the log then reads UnmigratedVerb. TEST(RemoteEmitTable, AFlippedDrawSlotDemandsASessionRatherThanNamingItselfUnmigrated) { - const ChildResult r = RunInChild([] { - RemoteEmitTable().GL.DrawElements(0x0004 /*GL_TRIANGLES*/, 3, 0x1405 /*GL_UNSIGNED_INT*/, - nullptr); - }); + const ChildResult r = RunInChild( + [] { RemoteEmitTable().GL.DrawElements(0x0004 /*GL_TRIANGLES*/, 3, 0x1405 /*GL_UNSIGNED_INT*/, nullptr); }); ASSERT_TRUE(DiedOfAbort(r)) << DescribeStatus(r) << "\n" << r.Log; EXPECT_NE(r.Log.find("Fatal{NoClientSession, \"DrawElements\"}"), std::string::npos) << r.Log; EXPECT_EQ(r.Log.find("Fatal{UnmigratedVerb"), std::string::npos) @@ -417,10 +406,9 @@ TEST(RemoteEmitTable, TheSevenI1SlotsAreClassBAndAreNotTheFatalThunk) { reinterpret_cast(table.GL.CopyImageSubData), reinterpret_cast(table.GL.ShaderStorageBlockBinding), }; - static const char* const kNames[] = {"BindImageTexture", "DispatchCompute", - "DispatchComputeIndirect", "MemoryBarrier", - "MemoryBarrierByRegion", "CopyImageSubData", - "ShaderStorageBlockBinding"}; + static const char* const kNames[] = { + "BindImageTexture", "DispatchCompute", "DispatchComputeIndirect", "MemoryBarrier", + "MemoryBarrierByRegion", "CopyImageSubData", "ShaderStorageBlockBinding"}; for (SizeT i = 0; i < sizeof(i1) / sizeof(i1[0]); ++i) { EXPECT_NE(i1[i], nullptr) << kNames[i] << " is null"; } @@ -455,39 +443,31 @@ TEST(RemoteEmitTable, EachI1SlotReachesRequireSessionUnderItsOwnName) { // MemoryBarrier arm below then finds Fatal{UnmigratedVerb, "MemoryBarrier"} instead. const ChildResult barrier = RunInChild([] { RemoteEmitTable().GL.MemoryBarrier(0x2000); }); ASSERT_TRUE(DiedOfAbort(barrier)) << DescribeStatus(barrier) << "\n" << barrier.Log; - EXPECT_NE(barrier.Log.find("Fatal{NoClientSession, \"MemoryBarrier\"}"), std::string::npos) - << barrier.Log; + EXPECT_NE(barrier.Log.find("Fatal{NoClientSession, \"MemoryBarrier\"}"), std::string::npos) << barrier.Log; EXPECT_EQ(barrier.Log.find("Fatal{UnmigratedVerb"), std::string::npos) << "MemoryBarrier is class B from P5b i1 on:\n" << barrier.Log; const ChildResult dispatch = RunInChild([] { RemoteEmitTable().GL.DispatchCompute(1, 1, 1); }); ASSERT_TRUE(DiedOfAbort(dispatch)) << DescribeStatus(dispatch) << "\n" << dispatch.Log; - EXPECT_NE(dispatch.Log.find("Fatal{NoClientSession, \"DispatchCompute\"}"), std::string::npos) - << dispatch.Log; + EXPECT_NE(dispatch.Log.find("Fatal{NoClientSession, \"DispatchCompute\"}"), std::string::npos) << dispatch.Log; - const ChildResult bind = RunInChild( - [] { RemoteEmitTable().GL.BindImageTexture(0, 1, 0, GL_FALSE, 0, 0x88BA, 0x8058); }); + const ChildResult bind = + RunInChild([] { RemoteEmitTable().GL.BindImageTexture(0, 1, 0, GL_FALSE, 0, 0x88BA, 0x8058); }); ASSERT_TRUE(DiedOfAbort(bind)) << DescribeStatus(bind) << "\n" << bind.Log; - EXPECT_NE(bind.Log.find("Fatal{NoClientSession, \"BindImageTexture\"}"), std::string::npos) - << bind.Log; + EXPECT_NE(bind.Log.find("Fatal{NoClientSession, \"BindImageTexture\"}"), std::string::npos) << bind.Log; - const ChildResult ssbo = RunInChild( - [] { RemoteEmitTable().GL.ShaderStorageBlockBinding(1, "Blk", 2); }); + const ChildResult ssbo = RunInChild([] { RemoteEmitTable().GL.ShaderStorageBlockBinding(1, "Blk", 2); }); ASSERT_TRUE(DiedOfAbort(ssbo)) << DescribeStatus(ssbo) << "\n" << ssbo.Log; - EXPECT_NE(ssbo.Log.find("Fatal{NoClientSession, \"ShaderStorageBlockBinding\"}"), - std::string::npos) - << ssbo.Log; + EXPECT_NE(ssbo.Log.find("Fatal{NoClientSession, \"ShaderStorageBlockBinding\"}"), std::string::npos) << ssbo.Log; const ChildResult copy = RunInChild([] { const MG_Backend::CopyImageEndpoint src{}; const MG_Backend::CopyImageEndpoint dst{}; - RemoteEmitTable().GL.CopyImageSubData(src, 0x0DE1, 0, 0, 0, 0, dst, 0x0DE1, 0, 0, 0, 0, 1, - 1, 1); + RemoteEmitTable().GL.CopyImageSubData(src, 0x0DE1, 0, 0, 0, 0, dst, 0x0DE1, 0, 0, 0, 0, 1, 1, 1); }); ASSERT_TRUE(DiedOfAbort(copy)) << DescribeStatus(copy) << "\n" << copy.Log; - EXPECT_NE(copy.Log.find("Fatal{NoClientSession, \"CopyImageSubData\"}"), std::string::npos) - << copy.Log; + EXPECT_NE(copy.Log.find("Fatal{NoClientSession, \"CopyImageSubData\"}"), std::string::npos) << copy.Log; } #endif // MGTEST_HAVE_FORK @@ -569,9 +549,7 @@ TEST(CapsMirrorTest, TheParallelShaderCompileEntryIsTheClientsCompileSwitchNotTh // AsyncOff.ExtensionStringMatchesTheConfiguration, whose fixture server compiles // asynchronously while the client does not. Red once by removing the reconcile call from // CapsMirror::Adopt: the first two expectations below fail. - auto count = [](const Vector& list, GLExtension e) { - return std::count(list.begin(), list.end(), e); - }; + auto count = [](const Vector& list, GLExtension e) { return std::count(list.begin(), list.end(), e); }; Snapshot s = MakeSnapshot(kMGPipeSubsystemResources, 0); { // The server compiles asynchronously and says so; this client compiles inline. @@ -690,14 +668,12 @@ TEST(CapsMirrorTest, APlaceholderMirrorRefusesToAnswerAndNamesTheFamily) { ::_exit(0); }); ASSERT_TRUE(DiedOfAbort(result)) << DescribeStatus(result) << "\n" << result.Log; - EXPECT_NE(result.Log.find("Fatal{CapsBeforeFirstSnapshot, \"TextureResources\"}"), - std::string::npos) + EXPECT_NE(result.Log.find("Fatal{CapsBeforeFirstSnapshot, \"TextureResources\"}"), std::string::npos) << "the refusal must name the family that asked; a death with no word is invisible to " "the census, to run_trace_case.cmake and to every red-once:\n" << result.Log; - EXPECT_NE(result.Log.find("0x400"), std::string::npos) - << "the subsystem bit belongs in the line beside its word:\n" - << result.Log; + EXPECT_NE(result.Log.find("0x400"), std::string::npos) << "the subsystem bit belongs in the line beside its word:\n" + << result.Log; #else GTEST_SKIP() << "the Fatal arm is only observable through a fork"; #endif @@ -745,9 +721,8 @@ TEST(CapsMirrorTest, ThePlaceholderWaitsForASnapshotAnotherThreadIsFetching) { reader.join(); EXPECT_TRUE(answered.load()) << "the reader never returned from ServerConsumes"; - EXPECT_TRUE(consumes.load()) - << "a reader that asked DURING the bring-up must get the server's real answer - " - "Magma publishes the resource family - rather than the placeholder's false"; + EXPECT_TRUE(consumes.load()) << "a reader that asked DURING the bring-up must get the server's real answer - " + "Magma publishes the resource family - rather than the placeholder's false"; EXPECT_GT(PublishedCapsGeneration(), 0u); ClientSessionInstance().Stop(); MG_Remote::Server::ServerLoopInstance().Stop(); @@ -873,8 +848,9 @@ TEST(CapsMirrorTest, MagmaTransportPublishesRealBufferConsumersWithoutRunAhead) EXPECT_NE(ops->Readback, nullptr); EXPECT_NE(ops->Destroy, nullptr); EXPECT_NE(ops->MapPersistent, nullptr); - if (ops->MapPersistent) EXPECT_EQ(ops->MapPersistent({7, 1}, 64, nullptr), nullptr) - << "Magma must not donate a server address as a client persistent map"; + if (ops->MapPersistent) + EXPECT_EQ(ops->MapPersistent({7, 1}, 64, nullptr), nullptr) + << "Magma must not donate a server address as a client persistent map"; // P7 wave 2 package C, OQ-10 (CONTRACT-P7 §5.4): THE TABLE DECIDES THE BIT, and // this is the assertion that says so rather than asserting a constant. Magma // registers a SubDataResident arm (VkBufferManager.cpp's g_vulkanWireResourceOps), @@ -912,8 +888,8 @@ TEST(CapsMirrorTest, TheConsumerBlockDoesNotCollideWithTheFeatureBits) { // The two halves of CallMask, asserted against each other rather than against a constant: // bits 0..8 are MGPCapBit and bits 32..47 are the consumer mask, and the whole reason R-8 // became implementable is that they do not overlap. - AdoptSnapshot(MakeSnapshot(kMGPipeSubsystemResources | kMGPipeSubsystemPrograms, - kCapTimerQuery | kCapOcclusionQuery)); + AdoptSnapshot( + MakeSnapshot(kMGPipeSubsystemResources | kMGPipeSubsystemPrograms, kCapTimerQuery | kCapOcclusionQuery)); EXPECT_TRUE(CapsMirrorInstance().HasCap(kCapTimerQuery)); EXPECT_TRUE(CapsMirrorInstance().HasCap(kCapOcclusionQuery)); EXPECT_FALSE(CapsMirrorInstance().HasCap(kCapXfbPrimitivesQuery)); @@ -1020,8 +996,7 @@ TEST(RemoteDrawPlan, AClientIndexArrayPlansFromIndexZeroWithNoHandle) { TEST(RemoteDrawPlan, AnArraysRangeIsFirstAndCountWithNoBias) { RemoteDrawBindings b{}; MGPDrawRange range{}; - ASSERT_TRUE(PlanDrawRange(b, 0, reinterpret_cast(static_cast(56064)), - 16128, 0, range)); + ASSERT_TRUE(PlanDrawRange(b, 0, reinterpret_cast(static_cast(56064)), 16128, 0, range)); EXPECT_EQ(range.Start, 56064u); EXPECT_EQ(range.Count, 16128u); EXPECT_EQ(range.IndexBias, 0); @@ -1059,7 +1034,7 @@ TEST(RemoteDrawPlan, TheThreeIndexTypesSizeAndNothingElseDoes) { EXPECT_EQ(RemoteIndexSizeFor(0x1403 /*GL_UNSIGNED_SHORT*/), 2u); EXPECT_EQ(RemoteIndexSizeFor(0x1405 /*GL_UNSIGNED_INT*/), 4u); EXPECT_EQ(RemoteIndexSizeFor(0x1406 /*GL_FLOAT*/), 0u) << "not an index type: the emitter refuses " - "\"+INDEX_TYPE\""; + "\"+INDEX_TYPE\""; } TEST(RemoteEmitTable, TheNineteenDrawSlotsAreEmittersDistinctFromEveryStub) { @@ -1106,8 +1081,7 @@ TEST(RemoteEmitTable, TheE2DropSwitchStartsDisarmed) { EXPECT_EQ(DroppedClearEmissions(), 0u); SetDropClearEmissionForNegativeControl(true); SetDropClearEmissionForNegativeControl(false); - EXPECT_EQ(DroppedClearEmissions(), 0u) - << "arming and disarming the control must not, by itself, drop anything"; + EXPECT_EQ(DroppedClearEmissions(), 0u) << "arming and disarming the control must not, by itself, drop anything"; } // ===================================================================================== @@ -1193,7 +1167,8 @@ TEST(RemoteReadback, TheTightRowsAreScatteredWhereThePackStateSaysAndTheGapsAreL // THE INPUT, not the state under test: every source byte is distinct, so a scatter that // wrote the right COUNT of bytes from the wrong offset cannot look correct. std::vector tight(static_cast(kW) * kH * kBpp); - for (size_t i = 0; i < tight.size(); ++i) tight[i] = static_cast(i); + for (size_t i = 0; i < tight.size(); ++i) + tight[i] = static_cast(i); std::vector destination(512, kSentinel); ScatterTightReadbackIntoPackState(tight.data(), destination.data(), kW, kH, kBpp, pack); @@ -1202,8 +1177,7 @@ TEST(RemoteReadback, TheTightRowsAreScatteredWhereThePackStateSaysAndTheGapsAreL const size_t first = 1 * stride + 2 * 4; for (size_t row = 0; row < static_cast(kH); ++row) { for (size_t byte = 0; byte < static_cast(kW) * kBpp; ++byte) { - EXPECT_EQ(destination[first + row * stride + byte], - tight[row * static_cast(kW) * kBpp + byte]) + EXPECT_EQ(destination[first + row * stride + byte], tight[row * static_cast(kW) * kBpp + byte]) << "row " << row << " byte " << byte << " landed somewhere else"; } } @@ -1214,9 +1188,8 @@ TEST(RemoteReadback, TheTightRowsAreScatteredWhereThePackStateSaysAndTheGapsAreL // here; widening the per-row copy to the stride passes both loops above and fails here. size_t touched = 0; for (size_t i = 0; i < destination.size(); ++i) { - const bool inWrittenRow = - i >= first && ((i - first) % stride) < static_cast(kW) * kBpp && - ((i - first) / stride) < static_cast(kH); + const bool inWrittenRow = i >= first && ((i - first) % stride) < static_cast(kW) * kBpp && + ((i - first) / stride) < static_cast(kH); if (!inWrittenRow) { EXPECT_EQ(destination[i], kSentinel) << "byte " << i << " is outside the rectangle GL names and was overwritten"; @@ -1241,11 +1214,12 @@ TEST(RemoteReadback, PackSkipImagesIsIgnoredForATwoDimensionalRead) { constexpr Uint8 kSentinel = 0xEE; PixelStoreParameters pack{}; - pack.SkipImages = 1; // the parameter under test - pack.ImageHeight = kH; // and its companion; both must be ignored + pack.SkipImages = 1; // the parameter under test + pack.ImageHeight = kH; // and its companion; both must be ignored std::vector tight(static_cast(kW) * kH * kBpp); - for (size_t i = 0; i < tight.size(); ++i) tight[i] = static_cast(i + 1); + for (size_t i = 0; i < tight.size(); ++i) + tight[i] = static_cast(i + 1); std::vector destination(96, kSentinel); ScatterTightReadbackIntoPackState(tight.data(), destination.data(), kW, kH, kBpp, pack); @@ -1253,9 +1227,9 @@ TEST(RemoteReadback, PackSkipImagesIsIgnoredForATwoDimensionalRead) { for (size_t i = 0; i < tight.size(); ++i) EXPECT_EQ(destination[i], tight[i]) << "byte " << i << " should hold the reply at offset 0"; for (size_t i = tight.size(); i < destination.size(); ++i) - EXPECT_EQ(destination[i], kSentinel) - << "byte " << i << " is past the read's own extent and SKIP_IMAGES must not have moved " - "the write there"; + EXPECT_EQ(destination[i], kSentinel) << "byte " << i + << " is past the read's own extent and SKIP_IMAGES must not have moved " + "the write there"; // And the fast path takes it: an otherwise-neutral read with only SKIP_IMAGES set is tight. EXPECT_TRUE(ReadbackPackStateIsTightForTest(kW, kBpp, pack)) << "SKIP_IMAGES alone must not force the bounce path for a 2-D read"; @@ -1271,7 +1245,8 @@ TEST(RemoteReadback, TheFastPathIsTakenExactlyWhenTheScatterWouldChangeNothing) constexpr GLsizei kW = 5; constexpr GLsizei kH = 3; std::vector tight(static_cast(kW) * kH * kBpp); - for (size_t i = 0; i < tight.size(); ++i) tight[i] = static_cast(i * 7 + 1); + for (size_t i = 0; i < tight.size(); ++i) + tight[i] = static_cast(i * 7 + 1); // Six layouts, chosen so both answers appear: a bare default, an explicit equal row // length, an alignment the row already satisfies, an alignment it does not, a skip, and a @@ -1279,8 +1254,8 @@ TEST(RemoteReadback, TheFastPathIsTakenExactlyWhenTheScatterWouldChangeNothing) // count of each answer is asserted too. std::vector layouts(6); layouts[1].RowLength = kW; - layouts[2].Alignment = 4; // 5*4 = 20, already a multiple of 4 - layouts[3].Alignment = 8; // 20 is not a multiple of 8 - the rows gain padding + layouts[2].Alignment = 4; // 5*4 = 20, already a multiple of 4 + layouts[3].Alignment = 8; // 20 is not a multiple of 8 - the rows gain padding layouts[4].SkipPixels = 1; layouts[5].RowLength = 8; @@ -1291,8 +1266,7 @@ TEST(RemoteReadback, TheFastPathIsTakenExactlyWhenTheScatterWouldChangeNothing) ScatterTightReadbackIntoPackState(tight.data(), destination.data(), kW, kH, kBpp, pack); destination.resize(tight.size()); const bool scatterChangedNothing = (destination == tight); - const bool predicateSaysTight = - ReadbackPackStateIsTightForTest(kW, kBpp, pack) != 0; + const bool predicateSaysTight = ReadbackPackStateIsTightForTest(kW, kBpp, pack) != 0; EXPECT_EQ(predicateSaysTight, scatterChangedNothing) << "layout " << i << ": the fast-path predicate and the scatter disagree"; if (predicateSaysTight) ++tightCount; @@ -1337,9 +1311,9 @@ TEST(RemoteReadback, TheCtsReadOfExactlyTwoMiBIsTwoBandsThatEachFitASlot) { for (const ReadbackBand& band : bands) { EXPECT_EQ(band.FirstColumn, 0u); EXPECT_EQ(band.Columns, 256u); - EXPECT_LE(TightReadbackByteCount(static_cast(band.Columns), static_cast(band.Rows), - 0x1908, 0x1406), - kCap); + EXPECT_LE( + TightReadbackByteCount(static_cast(band.Columns), static_cast(band.Rows), 0x1908, 0x1406), + kCap); } // A read that already fits is ONE record, unchanged from before: exactly the cap, and E2's // 640x480 RGBA8 snapshot. @@ -1353,16 +1327,18 @@ TEST(RemoteReadback, EveryPlanCoversTheReadOnceInRowMajorOrderWithBandsThatFitAn // The plan's invariants over a sweep that includes both shapes - whole-width row bands and, // for a row wider than a reply, single-row column pieces - at small caps so every boundary is // hit: the cap exactly one row, one byte short of a row, one pixel, one pixel short of two. - struct Shape { Uint64 w, h, bpp, cap; }; + struct Shape { + Uint64 w, h, bpp, cap; + }; const Shape shapes[] = { - {7, 5, 4, 28}, {7, 5, 4, 27}, {7, 5, 4, 4}, {7, 5, 4, 7}, {7, 5, 16, 1000}, - {20, 3, 16, 100}, {1, 9, 3, 3}, {9, 1, 3, 26}, {256, 512, 16, 2097136}, {5, 5, 8, 8}, - {33, 17, 4, 131}, {1024, 513, 4, 2097136}, + {7, 5, 4, 28}, {7, 5, 4, 27}, {7, 5, 4, 4}, {7, 5, 4, 7}, + {7, 5, 16, 1000}, {20, 3, 16, 100}, {1, 9, 3, 3}, {9, 1, 3, 26}, + {256, 512, 16, 2097136}, {5, 5, 8, 8}, {33, 17, 4, 131}, {1024, 513, 4, 2097136}, }; int rowShapes = 0, pieceShapes = 0; for (const Shape& s : shapes) { - SCOPED_TRACE(std::to_string(s.w) + "x" + std::to_string(s.h) + " bpp " + std::to_string(s.bpp) + - " cap " + std::to_string(s.cap)); + SCOPED_TRACE(std::to_string(s.w) + "x" + std::to_string(s.h) + " bpp " + std::to_string(s.bpp) + " cap " + + std::to_string(s.cap)); Bool planned = false; const auto bands = CollectReadbackBands(s.w, s.h, s.bpp, s.cap, &planned); ASSERT_TRUE(planned); @@ -1386,7 +1362,8 @@ TEST(RemoteReadback, EveryPlanCoversTheReadOnceInRowMajorOrderWithBandsThatFitAn ++covered[static_cast((band.FirstRow + r) * s.w + band.FirstColumn + c)]; } EXPECT_EQ(previousTightEnd, s.w * s.h); - for (size_t i = 0; i < covered.size(); ++i) ASSERT_EQ(covered[i], 1) << "pixel " << i; + for (size_t i = 0; i < covered.size(); ++i) + ASSERT_EQ(covered[i], 1) << "pixel " << i; // AS FEW RECORDS AS THE SHAPE ALLOWS: a whole-width band takes every row a reply holds. if (!pieces) { const Uint64 rowsPerReply = s.cap / (s.w * s.bpp); @@ -1428,7 +1405,8 @@ TEST(RemoteReadback, ABandedScatterWritesExactlyTheBytesTheWholeScatterWrites) { constexpr Uint8 kSentinel = 0xEE; for (const Uint64 bpp : {Uint64{3}, Uint64{4}, Uint64{16}}) { std::vector tight(static_cast(kW) * kH * bpp); - for (size_t i = 0; i < tight.size(); ++i) tight[i] = static_cast(i * 13 + 5); + for (size_t i = 0; i < tight.size(); ++i) + tight[i] = static_cast(i * 13 + 5); std::vector layouts(6); layouts[1].Alignment = 8; layouts[2].RowLength = kW + 3; @@ -1649,8 +1627,7 @@ TEST(PipeRouting, TheInstalledClientArmIsWireAndNotMonolithAndEveryRoutedRowMove "thread under split) or an unrouted row was overwritten"; const SizeT movedEscapes = CountDifferingCells(gMGPipeRouteEscapes, MGPipeMonolithEscapes()); - EXPECT_EQ(movedEscapes, 5u) - << "the five escape routes must move off the monolith escapes too"; + EXPECT_EQ(movedEscapes, 5u) << "the five escape routes must move off the monolith escapes too"; // Restore the monolith arm for the sibling cases that assert it (and for a clean binary). MGPipeInstallMonolithTables(); @@ -1714,8 +1691,7 @@ TEST(RemoteReadback, AReplyIsScatteredOnlyWhenItIsOkAndExactlyTheReadsExtent) { EXPECT_FALSE(ReadbackReplyIsComplete(kOk, tight - 16, tight)) << "a SHORT OK reply (one row missing) must not be scattered - the missing rows would be " "whatever the destination held"; - EXPECT_FALSE(ReadbackReplyIsComplete(kDeclined, 0, tight)) - << "a DECLINED reply carries no pixels"; + EXPECT_FALSE(ReadbackReplyIsComplete(kDeclined, 0, tight)) << "a DECLINED reply carries no pixels"; EXPECT_FALSE(ReadbackReplyIsComplete(kError, 0, tight)) << "an ERROR reply carries no pixels"; EXPECT_FALSE(ReadbackReplyIsComplete(kOk, 0, tight)) << "an OK reply of zero bytes is not the extent"; } @@ -1729,38 +1705,59 @@ TEST(RemoteReadback, AReplyIsScatteredOnlyWhenItIsOkAndExactlyTheReadsExtent) { // f1: the installed emitters are decoded by a peer on the apply thread. #if MGTEST_HAVE_FORK namespace { -struct F1Peer : Codec::WireVerbSink { - MGPClear clear{}; - MGPCopyFromFramebuffer copy{}; - MGPMipPlan mip{}; - MGPBlit blit{}; - unsigned calls = 0; - Bool OnClear(const MGPClear& v) override { clear = v; ++calls; return true; } - Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer& v) override { copy = v; ++calls; return true; } - Bool OnGenerateMipmap(const MGPMipPlan& v) override { mip = v; ++calls; return true; } - Bool OnBlit(const MGPBlit& v) override { blit = v; ++calls; return true; } - void Install() { - if (Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void* self) { - auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{}); - decoder.SetVerbSink(static_cast(self)); - return MOBILEGL_OK; - }, this) != MOBILEGL_OK) ::_exit(82); - } -}; -} + struct F1Peer : Codec::WireVerbSink { + MGPClear clear{}; + MGPCopyFromFramebuffer copy{}; + MGPMipPlan mip{}; + MGPBlit blit{}; + unsigned calls = 0; + Bool OnClear(const MGPClear& v) override { + clear = v; + ++calls; + return true; + } + Bool OnCopyFramebufferToTexture(const MGPCopyFromFramebuffer& v) override { + copy = v; + ++calls; + return true; + } + Bool OnGenerateMipmap(const MGPMipPlan& v) override { + mip = v; + ++calls; + return true; + } + Bool OnBlit(const MGPBlit& v) override { + blit = v; + ++calls; + return true; + } + void Install() { + if (Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + [](void* self) { + auto& decoder = Srv::ServerSessionInstance().Applier().*PeerMember(DecoderTag{}); + decoder.SetVerbSink(static_cast(self)); + return MOBILEGL_OK; + }, + this) != MOBILEGL_OK) + ::_exit(82); + } + }; +} // namespace TEST(RemoteF1, ClearBufferfvFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferfv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLfloat value[4] = {1, 7, 13, 23}; RemoteEmitTable().GL.ClearBufferfv(GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassFloat || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - !MGPipeHandleIsNull(r.Fbo)) ::_exit(101); + !MGPipeHandleIsNull(r.Fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1771,14 +1768,16 @@ TEST(RemoteF1, ClearNamedFramebufferfvFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferfv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLfloat value[4] = {1, 7, 13, 23}; const auto fbo = MakeShared(73); RemoteEmitTable().GL.ClearNamedFramebufferfv(fbo, GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassFloat || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101); + r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1789,14 +1788,16 @@ TEST(RemoteF1, ClearBufferivFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferiv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLint value[4] = {1, 7, 13, 23}; RemoteEmitTable().GL.ClearBufferiv(GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassInt || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - !MGPipeHandleIsNull(r.Fbo)) ::_exit(101); + !MGPipeHandleIsNull(r.Fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1807,14 +1808,16 @@ TEST(RemoteF1, ClearNamedFramebufferivFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferiv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLint value[4] = {1, 7, 13, 23}; const auto fbo = MakeShared(73); RemoteEmitTable().GL.ClearNamedFramebufferiv(fbo, GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassInt || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101); + r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1825,14 +1828,16 @@ TEST(RemoteF1, ClearBufferuivFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferuiv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLuint value[4] = {1, 7, 13, 23}; RemoteEmitTable().GL.ClearBufferuiv(GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassUint || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - !MGPipeHandleIsNull(r.Fbo)) ::_exit(101); + !MGPipeHandleIsNull(r.Fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1843,14 +1848,16 @@ TEST(RemoteF1, ClearNamedFramebufferuivFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferuiv.fields fails. const auto child = RunInChild([] { StartControlSession(); - F1Peer peer; peer.Install(); + F1Peer peer; + peer.Install(); const GLuint value[4] = {1, 7, 13, 23}; const auto fbo = MakeShared(73); RemoteEmitTable().GL.ClearNamedFramebufferuiv(fbo, GL_COLOR, 3, value); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindColor || r.ValueClass != kMGPipeClearValueClassUint || r.DrawBufferIndex != 3 || std::memcmp(r.ColorValue, value, sizeof(value)) != 0 || - r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101); + r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1860,13 +1867,15 @@ TEST(RemoteF1, ClearNamedFramebufferuivFieldsCross) { TEST(RemoteF1, ClearBufferfiFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearBufferfi.fields fails. const auto child = RunInChild([] { - StartControlSession(); F1Peer peer; peer.Install(); + StartControlSession(); + F1Peer peer; + peer.Install(); RemoteEmitTable().GL.ClearBufferfi(GL_DEPTH_STENCIL, 0, 0.375f, 91); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindDepthStencil || r.DrawBufferIndex != 0 || - r.DepthValue != 0.375f || r.StencilValue != 91 || - !MGPipeHandleIsNull(r.Fbo)) ::_exit(101); + r.DepthValue != 0.375f || r.StencilValue != 91 || !MGPipeHandleIsNull(r.Fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1876,13 +1885,15 @@ TEST(RemoteF1, ClearBufferfiFieldsCross) { TEST(RemoteF1, ClearNamedFramebufferfiFieldsCross) { // Red once (executed, reverted): zero the emitted clear values; F1.ClearNamedFramebufferfi.fields fails. const auto child = RunInChild([] { - StartControlSession(); F1Peer peer; peer.Install(); + StartControlSession(); + F1Peer peer; + peer.Install(); const auto fbo = MakeShared(73); RemoteEmitTable().GL.ClearNamedFramebufferfi(fbo, GL_DEPTH_STENCIL, 0, 0.375f, 91); const auto& r = peer.clear; if (peer.calls != 1 || r.Kind != kMGPipeClearKindDepthStencil || r.DrawBufferIndex != 0 || - r.DepthValue != 0.375f || r.StencilValue != 91 || - r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) ::_exit(101); + r.DepthValue != 0.375f || r.StencilValue != 91 || r.Fbo != MGPipeFramebufferEmitter::HandleFor(*fbo)) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1890,32 +1901,36 @@ TEST(RemoteF1, ClearNamedFramebufferfiFieldsCross) { } namespace { -SharedPtr F1Texture() { - MG_State::pGLContext = MakeUnique(); - auto tex = MakeShared(91); - tex->SetInternalFormat(TextureInternalFormat::RGBA8); - for (Uint level = 0; level != 3; ++level) { - const Int size = 8 >> level; - tex->AllocateStorage(TextureUploadTarget::Texture2D, level, - MG_State::GLState::MipmapInput{IntVec3{size, size, 1}, static_cast(size * size * 4)}); + SharedPtr F1Texture() { + MG_State::pGLContext = MakeUnique(); + auto tex = MakeShared(91); + tex->SetInternalFormat(TextureInternalFormat::RGBA8); + for (Uint level = 0; level != 3; ++level) { + const Int size = 8 >> level; + tex->AllocateStorage( + TextureUploadTarget::Texture2D, level, + MG_State::GLState::MipmapInput{IntVec3{size, size, 1}, static_cast(size * size * 4)}); + } + tex->SetBaseLevel(1); + MGPipeTextureEmitterInstance().AcquireTexture(tex->GetLifetimeId(), tex.get()); + MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).Bind(tex); + return tex; } - tex->SetBaseLevel(1); - MGPipeTextureEmitterInstance().AcquireTexture(tex->GetLifetimeId(), tex.get()); - MG_State::pGLContext->GetTextureUnitObject(0).GetBindingSlot(TextureTarget::Texture2D).Bind(tex); - return tex; -} -} +} // namespace TEST(RemoteF1, CopyTexImage2DFieldsCross) { // Red once (executed, reverted): increment the emitted copy level; F1.CopyTexImage2D.fields fails. const auto child = RunInChild([] { - StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture(); + StartControlSession(); + F1Peer peer; + peer.Install(); + const auto tex = F1Texture(); RemoteEmitTable().GL.CopyTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, -3, 4, 11, 13, 0); const auto& r = peer.copy; - if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) || - MGPipeHandleIsNull(r.Dst) || r.Target != GL_TEXTURE_2D || r.Level != 2 || - r.InternalFormat != GL_RGBA8 || r.X != -3 || r.Y != 4 || r.Width != 11 || r.Height != 13 || - r.XOffset != 0 || r.YOffset != 0 || r.SubImage != 0) ::_exit(101); + if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) || MGPipeHandleIsNull(r.Dst) || + r.Target != GL_TEXTURE_2D || r.Level != 2 || r.InternalFormat != GL_RGBA8 || r.X != -3 || r.Y != 4 || + r.Width != 11 || r.Height != 13 || r.XOffset != 0 || r.YOffset != 0 || r.SubImage != 0) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1925,13 +1940,16 @@ TEST(RemoteF1, CopyTexImage2DFieldsCross) { TEST(RemoteF1, CopyTexSubImage2DFieldsCross) { // Red once (executed, reverted): increment the emitted copy level; F1.CopyTexSubImage2D.fields fails. const auto child = RunInChild([] { - StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture(); + StartControlSession(); + F1Peer peer; + peer.Install(); + const auto tex = F1Texture(); RemoteEmitTable().GL.CopyTexSubImage2D(GL_TEXTURE_2D, 2, 5, 7, -3, 4, 11, 13); const auto& r = peer.copy; - if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) || - MGPipeHandleIsNull(r.Dst) || r.Target != GL_TEXTURE_2D || r.Level != 2 || - r.InternalFormat != 0 || r.X != -3 || r.Y != 4 || r.Width != 11 || r.Height != 13 || - r.XOffset != 5 || r.YOffset != 7 || r.SubImage != 1) ::_exit(101); + if (peer.calls != 1 || r.Dst != MGPipeTextureEmitterInstance().FindTexture(*tex) || MGPipeHandleIsNull(r.Dst) || + r.Target != GL_TEXTURE_2D || r.Level != 2 || r.InternalFormat != 0 || r.X != -3 || r.Y != 4 || + r.Width != 11 || r.Height != 13 || r.XOffset != 5 || r.YOffset != 7 || r.SubImage != 1) + ::_exit(101); ClientSessionInstance().Stop(); }); EXPECT_TRUE(WIFEXITED(child.Status) && WEXITSTATUS(child.Status) == 0) @@ -1941,7 +1959,10 @@ TEST(RemoteF1, CopyTexSubImage2DFieldsCross) { TEST(RemoteF1, GenerateMipmapFieldsCross) { // Red once (executed, reverted): increment the emitted base level; F1.GenerateMipmap.fields fails. const auto child = RunInChild([] { - StartControlSession(); F1Peer peer; peer.Install(); const auto tex = F1Texture(); + StartControlSession(); + F1Peer peer; + peer.Install(); + const auto tex = F1Texture(); RemoteEmitTable().GL.GenerateMipmap(GL_TEXTURE_2D); // THE FENCE. Under MOBILEGL_IPC_BATCH_WAITS (default on) GenerateMipmap is kCtxObject - // a value-class record whose emit no longer waits for its own apply (production-safe: @@ -1952,8 +1973,8 @@ TEST(RemoteF1, GenerateMipmapFieldsCross) { const GLfloat fence[4] = {0, 0, 0, 0}; RemoteEmitTable().GL.ClearBufferfv(GL_COLOR, 0, fence); const auto& r = peer.mip; - if (peer.calls != 2 || r.Res != MGPipeTextureEmitterInstance().FindTexture(*tex) || - MGPipeHandleIsNull(r.Res) || r.Target != GL_TEXTURE_2D || r.BaseLevel != 1 || r.LevelCount != 3) + if (peer.calls != 2 || r.Res != MGPipeTextureEmitterInstance().FindTexture(*tex) || MGPipeHandleIsNull(r.Res) || + r.Target != GL_TEXTURE_2D || r.BaseLevel != 1 || r.LevelCount != 3) ::_exit(101); ClientSessionInstance().Stop(); }); @@ -1962,8 +1983,6 @@ TEST(RemoteF1, GenerateMipmapFieldsCross) { } #endif - - #if MGTEST_HAVE_FORK namespace { @@ -2032,39 +2051,45 @@ namespace { clear.DepthValue = 0.375f; clear.StencilValue = 91; if (!sink.OnClear(clear) || observedNamedClearCalls != 1) ::_exit(101); - if (observedNamedClear != clear.Fbo || !observedNamedClearRecord || - observedNamedClearRead != originalRead) ::_exit(102); - if (MGPipeApplier().BoundFramebuffer[0] != originalDraw || - MGPipeApplier().BoundFramebuffer[1] != originalRead) ::_exit(103); + if (observedNamedClear != clear.Fbo || !observedNamedClearRecord || observedNamedClearRead != originalRead) + ::_exit(102); + if (MGPipeApplier().BoundFramebuffer[0] != originalDraw || MGPipeApplier().BoundFramebuffer[1] != originalRead) + ::_exit(103); if (observedNamedClearIndex != clear.DrawBufferIndex) ::_exit(104); if (kind == kMGPipeClearKindDepthStencil) { if (observedNamedClearTarget != GL_DEPTH_STENCIL || observedNamedClearDepth != clear.DepthValue || - observedNamedClearStencil != clear.StencilValue) ::_exit(105); + observedNamedClearStencil != clear.StencilValue) + ::_exit(105); } else if (observedNamedClearTarget != GL_COLOR || - std::memcmp(observedNamedClearValues, clear.ColorValue, sizeof(bits))) ::_exit(106); + std::memcmp(observedNamedClearValues, clear.ColorValue, sizeof(bits))) + ::_exit(106); MGPipeSetResourceOps(nullptr); sink.SetBackend(nullptr); } -} +} // namespace TEST(RemoteF1, UnboundNamedfvRefusesByName) { // Historical name retained; a named target now reaches the native clear hook. - const auto child = RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassFloat); }); + const auto child = + RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassFloat); }); ExpectChildSuccess(child); } TEST(RemoteF1, UnboundNamedivRefusesByName) { - const auto child = RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassInt); }); + const auto child = + RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassInt); }); ExpectChildSuccess(child); } TEST(RemoteF1, UnboundNameduivRefusesByName) { - const auto child = RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassUint); }); + const auto child = + RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindColor, kMGPipeClearValueClassUint); }); ExpectChildSuccess(child); } TEST(RemoteF1, UnboundNamedfiRefusesByName) { - const auto child = RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindDepthStencil, kMGPipeClearValueClassFloat); }); + const auto child = + RunInChild([] { CheckNamedClearScopedTarget(kMGPipeClearKindDepthStencil, kMGPipeClearValueClassFloat); }); ExpectChildSuccess(child); } #endif @@ -2096,19 +2121,20 @@ TEST(RemoteF1, BlitNamedCarriesHandlesAndLeavesTheClientBindingsAlone) { MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Read).Bind(boundRead); MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Draw).Bind(boundDraw); - RemoteEmitTable().GL.BlitNamedFramebuffer(namedRead, namedDraw, 0, 0, 8, 8, 0, 0, 8, 8, - GL_COLOR_BUFFER_BIT, GL_NEAREST); + RemoteEmitTable().GL.BlitNamedFramebuffer(namedRead, namedDraw, 0, 0, 8, 8, 0, 0, 8, 8, GL_COLOR_BUFFER_BIT, + GL_NEAREST); const auto& r = peer.blit; if (peer.calls != 1 || r.ReadFbo != MGPipeFramebufferEmitter::HandleFor(*namedRead) || - r.DrawFbo != MGPipeFramebufferEmitter::HandleFor(*namedDraw)) ::_exit(101); + r.DrawFbo != MGPipeFramebufferEmitter::HandleFor(*namedDraw)) + ::_exit(101); // The client's own bindings are untouched: the server resolves the named pair from the // record, not from a staged binding override (T3). - if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Read) - .GetBoundObject() - .get() != boundRead.get()) ::_exit(102); - if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Draw) - .GetBoundObject() - .get() != boundDraw.get()) ::_exit(103); + if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Read).GetBoundObject().get() != + boundRead.get()) + ::_exit(102); + if (MG_State::pGLContext->GetFramebufferBindingSlot(MobileGL::FramebufferTarget::Draw).GetBoundObject().get() != + boundDraw.get()) + ::_exit(103); ClientSessionInstance().Stop(); }); ExpectChildSuccess(child); @@ -2124,8 +2150,8 @@ namespace { g_observedBlitRead = st.VerbBlitReadFbo; g_observedBlitDraw = st.VerbBlitDrawFbo; } - void StubBlitFramebufferConsuming(GLint x0, GLint y0, GLint x1, GLint y1, GLint dx0, GLint dy0, - GLint dx1, GLint dy1, GLbitfield mask, GLenum filter) { + void StubBlitFramebufferConsuming(GLint x0, GLint y0, GLint x1, GLint y1, GLint dx0, GLint dy0, GLint dx1, + GLint dy1, GLbitfield mask, GLenum filter) { StubBlitFramebuffer(x0, y0, x1, y1, dx0, dy0, dx1, dy1, mask, filter); MG_Pipe::MGPipeApplier().VerbBlitNamedConsumed = true; } @@ -2135,7 +2161,9 @@ namespace { g_observedCopyDst = MG_Pipe::MGPipeApplier().VerbCopyTexDst; } MGPipeHandle g_observedMipRes = kMGPipeNullHandle; - void StubGenerateMipmap(GLenum) { g_observedMipRes = MG_Pipe::MGPipeApplier().VerbMipRes; } + void StubGenerateMipmap(GLenum) { + g_observedMipRes = MG_Pipe::MGPipeApplier().VerbMipRes; + } MGPipeHandle g_observedIndirect = kMGPipeNullHandle; MGPipeHandle g_observedParameter = kMGPipeNullHandle; void StubMultiDrawArraysIndirectCount(GLenum, const void*, GLintptr, GLsizei, GLsizei) { @@ -2202,8 +2230,7 @@ TEST(RemoteF1, BoundBlitCarriesNoHandlesAndIsAppliedAsBefore) { r.Mask = GL_COLOR_BUFFER_BIT; r.Filter = GL_NEAREST; if (!sink.OnBlit(r)) ::_exit(101); - if (!MGPipeHandleIsNull(g_observedBlitRead) || !MGPipeHandleIsNull(g_observedBlitDraw)) - ::_exit(102); + if (!MGPipeHandleIsNull(g_observedBlitRead) || !MGPipeHandleIsNull(g_observedBlitDraw)) ::_exit(102); ::_exit(0); }); ExpectChildSuccess(child); @@ -2269,8 +2296,7 @@ TEST(RemoteF1, AnIndirectDrawStashesTheCommandAndParameterBuffers) { indirect.DrawCount = 3; indirect.Stride = 16; if (!sink.OnDrawVbo(info, nullptr, nullptr, &indirect)) ::_exit(101); - if (g_observedIndirect != indirect.Buffer || g_observedParameter != indirect.ParameterBuffer) - ::_exit(102); + if (g_observedIndirect != indirect.Buffer || g_observedParameter != indirect.ParameterBuffer) ::_exit(102); ::_exit(0); }); ExpectChildSuccess(child); @@ -2301,10 +2327,12 @@ TEST(RemoteF1, DispatchIndirectStashesTheCommandBuffer) { TEST(RemoteGuards, AllocatorAcquireFromTheApplyThreadIsFatalByName) { const auto child = RunInChild([] { StartControlSession(); - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void*) { - MGPipeSlots().Acquire(MGPipeKind::Texture, 424242); - return MOBILEGL_OK; - }, nullptr); + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + [](void*) { + MGPipeSlots().Acquire(MGPipeKind::Texture, 424242); + return MOBILEGL_OK; + }, + nullptr); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}"); @@ -2313,10 +2341,12 @@ TEST(RemoteGuards, AllocatorAcquireFromTheApplyThreadIsFatalByName) { TEST(RemoteGuards, AllocatorFindByLifetimeIdFromTheApplyThreadIsFatalByName) { const auto child = RunInChild([] { StartControlSession(); - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void*) { - MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, 424242); - return MOBILEGL_OK; - }, nullptr); + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + [](void*) { + MGPipeSlots().FindByLifetimeId(MGPipeKind::Texture, 424242); + return MOBILEGL_OK; + }, + nullptr); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}"); @@ -2325,10 +2355,12 @@ TEST(RemoteGuards, AllocatorFindByLifetimeIdFromTheApplyThreadIsFatalByName) { TEST(RemoteGuards, AllocatorFreeFromTheApplyThreadIsFatalByName) { const auto child = RunInChild([] { StartControlSession(); - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void*) { - MGPipeSlots().Free(MGPipeKind::Texture, {7, 1}); - return MOBILEGL_OK; - }, nullptr); + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + [](void*) { + MGPipeSlots().Free(MGPipeKind::Texture, {7, 1}); + return MOBILEGL_OK; + }, + nullptr); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}"); @@ -2339,14 +2371,28 @@ namespace { int marker = 0; }; using TestTextureTable = - MG_Backend::DirectGLES::BackendSlotTable; + MG_Backend::DirectGLES::BackendSlotTable; } // namespace // The four accessor drives share one shape: the BufferObject is constructed on the CLIENT // thread (its own resource_create publication is a legal client-side emit), and only the // accessor runs on the apply thread, where it must die at the accessor's own guard. -#define MGL_BUFFER_GUARD_TEST(Name, Id, Call) TEST(RemoteGuards, Name) { const auto child = RunInChild([] { StartControlSession(); MG_State::GLState::BufferObject buffer(Id); Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting([](void* self) { auto& buffer = *static_cast(self); Call; return MOBILEGL_OK; }, &buffer); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"buffer-legacy-arm\"}"); } +#define MGL_BUFFER_GUARD_TEST(Name, Id, Call) \ + TEST(RemoteGuards, Name) { \ + const auto child = RunInChild([] { \ + StartControlSession(); \ + MG_State::GLState::BufferObject buffer(Id); \ + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( \ + [](void* self) { \ + auto& buffer = *static_cast(self); \ + Call; \ + return MOBILEGL_OK; \ + }, \ + &buffer); \ + ClientSessionInstance().Stop(); \ + }); \ + ExpectNamedAbort(child, "Fatal{RoleViolation, \"buffer-legacy-arm\"}"); \ + } MGL_BUFFER_GUARD_TEST(BufferMappedDataFromTheApplyThreadIsFatalByName, 703, (void)buffer.MappedData()) MGL_BUFFER_GUARD_TEST(BufferIsMappedFromTheApplyThreadIsFatalByName, 704, (void)buffer.IsMapped()) MGL_BUFFER_GUARD_TEST(BufferChangeSerialFromTheApplyThreadIsFatalByName, 705, (void)buffer.GetChangeSerial()) @@ -2378,13 +2424,15 @@ TEST(RemoteGuards, BarrieredLegacyScopesCannotExemptAllocator) { const auto child = RunInChild([backend] { StartControlSession(); auto selectedBackend = backend; - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void* self) -> MobileGLResult { - MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true); - MG_Config::ActiveBackendType = *static_cast(self); - // HighWater had no per-method guard in the old implementation. - (void)MGPipeSlots().HighWater(MGPipeKind::Texture); - return MOBILEGL_OK; - }, &selectedBackend); + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + +[](void* self) -> MobileGLResult { + MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true); + MG_Config::ActiveBackendType = *static_cast(self); + // HighWater had no per-method guard in the old implementation. + (void)MGPipeSlots().HighWater(MGPipeKind::Texture); + return MOBILEGL_OK; + }, + &selectedBackend); ClientSessionInstance().Stop(); }); ExpectNamedAbort(child, "Fatal{RoleViolation, \"MGPipeSlots\"}"); @@ -2394,25 +2442,27 @@ TEST(RemoteGuards, BarrieredLegacyScopesCannotExemptAllocator) { TEST(RemoteGuards, HandleRegistryMembersWorkOnBarrieredAndUnbarrieredApply) { const auto child = RunInChild([] { StartControlSession(); - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting(+[](void*) -> MobileGLResult { - TestTextureTable table; - for (const bool barriered : {true, false}) { - MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(barriered); - const MGPipeHandle handle{71, barriered ? 1u : 2u}; - auto& twin = table.GetOrCreate(handle); - twin = MakeShared(); - twin->marker = 19; - auto* found = table.FindByHandle(handle); - if (!found || !*found || (*found)->marker != 19) _exit(81); - unsigned live = 0; - table.ForEachLive([&](MGPipeHandle actual, const auto& backend) { - if (actual.Slot != handle.Slot || actual.Gen != handle.Gen || backend->marker != 19) _exit(82); - ++live; - }); - if (live != 1 || !table.ReleaseByHandle(handle) || table.FindByHandle(handle)) _exit(83); - } - return MOBILEGL_OK; - }, nullptr); + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( + +[](void*) -> MobileGLResult { + TestTextureTable table; + for (const bool barriered : {true, false}) { + MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(barriered); + const MGPipeHandle handle{71, barriered ? 1u : 2u}; + auto& twin = table.GetOrCreate(handle); + twin = MakeShared(); + twin->marker = 19; + auto* found = table.FindByHandle(handle); + if (!found || !*found || (*found)->marker != 19) _exit(81); + unsigned live = 0; + table.ForEachLive([&](MGPipeHandle actual, const auto& backend) { + if (actual.Slot != handle.Slot || actual.Gen != handle.Gen || backend->marker != 19) _exit(82); + ++live; + }); + if (live != 1 || !table.ReleaseByHandle(handle) || table.FindByHandle(handle)) _exit(83); + } + return MOBILEGL_OK; + }, + nullptr); ClientSessionInstance().Stop(); }); ASSERT_TRUE(WIFEXITED(child.Status)) << child.Log; @@ -2458,25 +2508,24 @@ TEST(RemoteGuards, CapsMirrorFallbackWithNoServerBackendIsFatalByName) { // parked in its own wait. Still NOT in the list: GetUploadTargets / GetTarget / IsComplete, // which are TextureObject's own and not MipmapStorage's - their rows retire with the object // class in P7/P9. -#define MGL_TEXTURE_GUARD_TEST(Name, Id, Call) \ - TEST(RemoteGuards, Name) { \ - const auto child = RunInChild([] { \ - StartControlSession(); \ - MG_State::GLState::TextureObject2D texture(Id); \ - Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( \ - [](void* self) { \ - auto& texture = *static_cast(self); \ - Call; \ - return MOBILEGL_OK; \ - }, \ - &texture); \ - ClientSessionInstance().Stop(); \ - }); \ - ExpectNamedAbort(child, "Fatal{RoleViolation, \"texture-legacy-arm\"}"); \ +#define MGL_TEXTURE_GUARD_TEST(Name, Id, Call) \ + TEST(RemoteGuards, Name) { \ + const auto child = RunInChild([] { \ + StartControlSession(); \ + MG_State::GLState::TextureObject2D texture(Id); \ + Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( \ + [](void* self) { \ + auto& texture = *static_cast(self); \ + Call; \ + return MOBILEGL_OK; \ + }, \ + &texture); \ + ClientSessionInstance().Stop(); \ + }); \ + ExpectNamedAbort(child, "Fatal{RoleViolation, \"texture-legacy-arm\"}"); \ } MGL_TEXTURE_GUARD_TEST(TextureAllocateStorageFromTheApplyThreadIsFatalByName, 711, - texture.AllocateStorage(TextureUploadTarget::Texture2D, 0, - {{4, 4, 1}, 64})) + texture.AllocateStorage(TextureUploadTarget::Texture2D, 0, {{4, 4, 1}, 64})) MGL_TEXTURE_GUARD_TEST(TextureTruncateMipmapLevelsFromTheApplyThreadIsFatalByName, 712, texture.TruncateMipmapLevels(TextureUploadTarget::Texture2D, 1)) MGL_TEXTURE_GUARD_TEST(TextureUpdateMipmapSubDataFromTheApplyThreadIsFatalByName, 713, @@ -2486,19 +2535,16 @@ MGL_TEXTURE_GUARD_TEST(TextureMapMipmapDataFromTheApplyThreadIsFatalByName, 714, MGL_TEXTURE_GUARD_TEST(TextureMarkStorageDirtyFromTheApplyThreadIsFatalByName, 715, texture.MarkStorageDirty(TextureUploadTarget::Texture2D, 0, true)) MGL_TEXTURE_GUARD_TEST(TextureMarkStorageDirtyRegionFromTheApplyThreadIsFatalByName, 716, - texture.MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, - {0, 0, 0}, {1, 1, 1})) + texture.MarkStorageDirtyRegion(TextureUploadTarget::Texture2D, 0, {0, 0, 0}, {1, 1, 1})) MGL_TEXTURE_GUARD_TEST(TextureIsStorageDirtyFromTheApplyThreadIsFatalByName, 717, (void)texture.IsStorageDirty(TextureUploadTarget::Texture2D, 0)) MGL_TEXTURE_GUARD_TEST(TextureGetStorageDirtyRegionFromTheApplyThreadIsFatalByName, 718, (void)texture.GetStorageDirtyRegion(TextureUploadTarget::Texture2D, 0)) MGL_TEXTURE_GUARD_TEST(TextureGetStorageDirtyRectsFromTheApplyThreadIsFatalByName, 719, - (void)texture.GetStorageDirtyRects(TextureUploadTarget::Texture2D, 0, - nullptr, 0)) + (void)texture.GetStorageDirtyRects(TextureUploadTarget::Texture2D, 0, nullptr, 0)) // P11 M2: dev 9524c688's exact dirty footprint joins the dirty model's rows. The server's // footprint is the record's region list (Managers.cpp's handle arm), never this accessor. -MGL_TEXTURE_GUARD_TEST(TextureGetStorageDirtyFootprintFromTheApplyThreadIsFatalByName, 720, - ([&texture] { +MGL_TEXTURE_GUARD_TEST(TextureGetStorageDirtyFootprintFromTheApplyThreadIsFatalByName, 720, ([&texture] { Vector footprint; texture.GetStorageDirtyFootprint(TextureUploadTarget::Texture2D, 0, footprint); }())) @@ -2590,8 +2636,7 @@ void StartRunAheadSession() { // The run-ahead guards below are the SHARED-block rules; the dual block (the default since // P15) makes a barriered fill the client's own and is covered by DualBlockScenario. MG_Config::Ipc.RoleSplitState = false; - Srv::ServerSessionInstance().SetCapabilityBits( - static_cast(MG_Pipe::kCapRunAheadApply)); + Srv::ServerSessionInstance().SetCapabilityBits(static_cast(MG_Pipe::kCapRunAheadApply)); Srv::ServerSessionInstance().SetConsumedSubsystems(kMGPipeSubsystemsMigratedAtP4a); // The test peer publishes the first snapshot through the real handshake. Srv::ServerSessionInstance().SetBackend(ControlCapsPeer()); @@ -2652,8 +2697,7 @@ TEST(RemoteRunAhead, ClearingTheVerbBarrierDisarmsRunAheadEvenWithTheCapBit) { MG_Config::Ipc.VerbBarrier = 0; ::alarm(15); MG_Config::Transport = MG_Config::TransportMode::InProcess; - Srv::ServerSessionInstance().SetCapabilityBits( - static_cast(MG_Pipe::kCapRunAheadApply)); + Srv::ServerSessionInstance().SetCapabilityBits(static_cast(MG_Pipe::kCapRunAheadApply)); Srv::ServerSessionInstance().SetConsumedSubsystems(kMGPipeSubsystemsMigratedAtP4a); Srv::ServerSessionInstance().SetBackend(ControlCapsPeer()); if (ClientSessionInstance().Start(MG_Config::TransportMode::InProcess, {}) != MOBILEGL_OK) { @@ -2689,8 +2733,7 @@ TEST(RemoteRunAhead, ACreditOneClientPaysTheCreditAtEveryPresentAfterTheFirst) { if (session.PresentCreditWaits() != 2) ::_exit(103); // ONE CREDIT PER SWAP, in the client's own 1-based space: the server returned the // second present's credit, so the watermark is at least 2. - if (session.Control() == nullptr || - session.Control()->Progress.presentAckSerial.load() < 2u) { + if (session.Control() == nullptr || session.Control()->Progress.presentAckSerial.load() < 2u) { ::_exit(104); } session.Stop(); @@ -3020,7 +3063,8 @@ TEST(RemoteRunAhead, AnEventBurstBehindAnUnwaitedSequenceFlowControlsInsteadOfAb Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void*) -> MobileGLResult { MG_Pipe::MGPRange ranges[64]; - for (Uint32 r = 0; r < 64; ++r) ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; + for (Uint32 r = 0; r < 64; ++r) + ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; for (Uint32 i = 0; i < 3000; ++i) { MG_Pipe::gMGPipeCallbacks.OnGpuWritten(MG_Pipe::MGPipeHandle{7, 1}, 64, ranges); } @@ -3107,9 +3151,11 @@ TEST(RemoteRunAhead, ARepublishedServerWindowExtentResizesTheServerOwnedSurfaceA if (ClientSessionInstance().DrainPublishedEvents() < 1u) ::_exit(92); if (query(EGL_WIDTH) != 2560 || query(EGL_HEIGHT) != 1600) ::_exit(93); const IntVec3 colour = - static_cast(defaultFramebuffer->colorAttachment.get())->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0); + static_cast(defaultFramebuffer->colorAttachment.get()) + ->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0); const IntVec3 depth = - static_cast(defaultFramebuffer->depthAttachment.get())->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0); + static_cast(defaultFramebuffer->depthAttachment.get()) + ->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0); if (colour.x() != 2560 || colour.y() != 1600 || depth.x() != 2560 || depth.y() != 1600) ::_exit(94); // A format-only event (a pbuffer's, a client-named window's) says nothing about the size. @@ -3165,9 +3211,8 @@ namespace { } Uint64 MillisecondsSince(std::chrono::steady_clock::time_point start) { - return static_cast(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count()); + return static_cast( + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count()); } } // namespace @@ -3187,7 +3232,8 @@ TEST(RemoteRunAhead, AStonewallingClientForfeitsTheReverseChannelWithinTheWaitKn Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void*) -> MobileGLResult { MG_Pipe::MGPRange ranges[64]; - for (Uint32 r = 0; r < 64; ++r) ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; + for (Uint32 r = 0; r < 64; ++r) + ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; for (Uint32 i = 0; i < 3000; ++i) { MG_Pipe::gMGPipeCallbacks.OnGpuWritten(MG_Pipe::MGPipeHandle{7, 1}, 64, ranges); } @@ -3209,8 +3255,7 @@ TEST(RemoteRunAhead, AStonewallingClientForfeitsTheReverseChannelWithinTheWaitKn ClientSessionInstance().Stop(); }); ExpectChildSuccess(child); - EXPECT_NE(child.Log.find("ReverseChannelForfeit{NotDraining} - kEventGpuWritten"), std::string::npos) - << child.Log; + EXPECT_NE(child.Log.find("ReverseChannelForfeit{NotDraining} - kEventGpuWritten"), std::string::npos) << child.Log; EXPECT_NE(child.Log.find("mgl-srv-apply stops on ReverseChannelForfeit"), std::string::npos) << child.Log; EXPECT_EQ(child.Log.find("Fatal{EventRingOverflow"), std::string::npos) << child.Log; } @@ -3255,7 +3300,8 @@ TEST(RemoteRunAhead, ATricklingClientGetsTheWholeWaitThenForfeitsByNameInsteadOf MG_Pipe::gMGPipeCallbacks.OnGpuWritten(MG_Pipe::MGPipeHandle{7, 1}, 1, &one); } MG_Pipe::MGPRange ranges[64]; - for (Uint32 r = 0; r < 64; ++r) ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; + for (Uint32 r = 0; r < 64; ++r) + ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; for (Uint32 i = 0; i < 16; ++i) { MG_Pipe::gMGPipeCallbacks.OnGpuWritten(MG_Pipe::MGPipeHandle{7, 1}, 64, ranges); } @@ -3277,8 +3323,7 @@ TEST(RemoteRunAhead, ATricklingClientGetsTheWholeWaitThenForfeitsByNameInsteadOf ClientSessionInstance().Stop(); }); ExpectChildSuccess(child); - EXPECT_NE(child.Log.find("ReverseChannelForfeit{TooSlow} - kEventGpuWritten"), std::string::npos) - << child.Log; + EXPECT_NE(child.Log.find("ReverseChannelForfeit{TooSlow} - kEventGpuWritten"), std::string::npos) << child.Log; EXPECT_EQ(child.Log.find("Fatal{EventRingOverflow"), std::string::npos) << child.Log; } @@ -3301,7 +3346,8 @@ TEST(RemoteRunAhead, AStopWhileTheServerWaitsForADrainEndsTheWaitByNameInsteadOf (void)Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void*) -> MobileGLResult { MG_Pipe::MGPRange ranges[64]; - for (Uint32 r = 0; r < 64; ++r) ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; + for (Uint32 r = 0; r < 64; ++r) + ranges[r] = MG_Pipe::MGPRange{r * 64ull, 64ull}; for (Uint32 i = 0; i < 3000; ++i) { MG_Pipe::gMGPipeCallbacks.OnGpuWritten(MG_Pipe::MGPipeHandle{7, 1}, 64, ranges); } @@ -3329,8 +3375,7 @@ TEST(RemoteRunAhead, AStopWhileTheServerWaitsForADrainEndsTheWaitByNameInsteadOf ClientSessionInstance().Stop(); }); ExpectChildSuccess(child); - EXPECT_NE(child.Log.find("ReverseChannelForfeit{Stopped} - kEventGpuWritten"), std::string::npos) - << child.Log; + EXPECT_NE(child.Log.find("ReverseChannelForfeit{Stopped} - kEventGpuWritten"), std::string::npos) << child.Log; EXPECT_EQ(child.Log.find("Fatal{ApplyThreadJoinTimeout"), std::string::npos) << child.Log; EXPECT_EQ(child.Log.find("Fatal{EventRingOverflow"), std::string::npos) << child.Log; } @@ -3344,8 +3389,7 @@ TEST(RemoteGuards, ADeferredDestroyFromAnUnbarrieredApplyIsFatalUnderStrict) { Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void*) -> MobileGLResult { MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(false); - (void)MG_Pipe::MGPipeDeferDestroyAndFreeIfOnApplyThread(MG_Pipe::MGPipeKind::Texture, - 4242); + (void)MG_Pipe::MGPipeDeferDestroyAndFreeIfOnApplyThread(MG_Pipe::MGPipeKind::Texture, 4242); return MOBILEGL_OK; }, nullptr); @@ -3364,8 +3408,7 @@ TEST(RemoteGuards, ADeferredDestroyFromABarrieredApplyIsStillServed) { Srv::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void*) -> MobileGLResult { MG_Pipe::MGPipeApplierSetCurrentRecordBarriered(true); - if (!MG_Pipe::MGPipeDeferDestroyAndFreeIfOnApplyThread(MG_Pipe::MGPipeKind::Texture, - 4243)) { + if (!MG_Pipe::MGPipeDeferDestroyAndFreeIfOnApplyThread(MG_Pipe::MGPipeKind::Texture, 4243)) { ::_exit(101); } return MOBILEGL_OK; @@ -3389,13 +3432,11 @@ TEST(RemoteRunAhead, OnlyTheTextureHalfOfResourceSubDataWantsItsReply) { EXPECT_FALSE(MG_Pipe::MGPipeSubDataWantsItsReply(buffer)); MG_Pipe::MGPSubData texture{}; - texture.Target = MG_Pipe::MGPipePackSubDataTarget( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), - static_cast(TextureUploadTarget::Texture2D)); + texture.Target = MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), + static_cast(TextureUploadTarget::Texture2D)); EXPECT_TRUE(MG_Pipe::MGPipeSubDataWantsItsReply(texture)); } - #include "RemoteClientE1Controls.inc" int main(int argc, char** argv) { diff --git a/MobileGL/MG_Test/Wire/RingTest.cpp b/MobileGL/MG_Test/Wire/RingTest.cpp index 5ff2a6f7..0b62cb20 100644 --- a/MobileGL/MG_Test/Wire/RingTest.cpp +++ b/MobileGL/MG_Test/Wire/RingTest.cpp @@ -102,8 +102,7 @@ TEST(RingTest, ControlPageLayoutIsTheSharedContract) { // Each contended group on its own cache line. const auto offset = [&control](const void* member) { - return reinterpret_cast(member) - - reinterpret_cast(&control); + return reinterpret_cast(member) - reinterpret_cast(&control); }; EXPECT_EQ(offset(&control.cmdHead) % 64, 0); EXPECT_EQ(offset(&control.cmdAppliedTail) % 64, 0); @@ -205,8 +204,7 @@ TEST(RingTest, WrapsWithoutSplittingARecord) { std::uint8_t seed = 0; for (int i = 0; i < 200; ++i) { const std::uint64_t size = 24 + (i % 5) * 8; - ASSERT_TRUE(ring.WriteRecord(static_cast(1 + (i % 3)), size, seed)) - << "record " << i; + ASSERT_TRUE(ring.WriteRecord(static_cast(1 + (i % 3)), size, seed)) << "record " << i; RingRecordView view{}; bool corrupt = false; ASSERT_TRUE(ring.Consumer().Pop(view, &corrupt)) << "record " << i; @@ -432,8 +430,8 @@ TEST(RingTest, SpscProducerConsumerThreadsAgreeOnEveryRecord) { for (int i = 0; i < kRecords; ++i) { const std::uint64_t payloadSize = sizeof(std::uint32_t) + (i % 4) * 8; void* payload = nullptr; - while ((payload = ring.Producer().Reserve(static_cast(1 + (i % 7)), - kRecNone, payloadSize)) == nullptr) { + while ((payload = ring.Producer().Reserve(static_cast(1 + (i % 7)), kRecNone, payloadSize)) == + nullptr) { if (failed.load()) { break; } @@ -479,10 +477,7 @@ TEST(RingTest, DoorbellHandoffWakesBothSidesOnEveryPublish) { while (seen < kRecords) { const bool woke = consumerBell.Wait( ring.Control().consumerParked, - [&] { - return ring.Control().cmdHead.load(std::memory_order_acquire) != - ring.Consumer().LocalTail(); - }, + [&] { return ring.Control().cmdHead.load(std::memory_order_acquire) != ring.Consumer().LocalTail(); }, kDefaultSpinUs, 5000); if (!woke) { ok.store(false); // a wakeup was lost, or the producer stalled @@ -515,9 +510,8 @@ TEST(RingTest, DoorbellHandoffWakesBothSidesOnEveryPublish) { break; } if (!producerBell.Wait( - ring.Control().producerParked, - [&] { return ring.Producer().FreeBytes() >= kRecordBytes; }, kDefaultSpinUs, - 5000)) { + ring.Control().producerParked, [&] { return ring.Producer().FreeBytes() >= kRecordBytes; }, + kDefaultSpinUs, 5000)) { ok.store(false); break; } @@ -590,8 +584,7 @@ TEST(RingTest, AppliedSeqAdvancesOncePerRecordAndIsNeverBatchedInP5) { RingFixture ring(1024); constexpr int kRecords = 12; for (int i = 0; i < kRecords; ++i) { - ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), 16, - static_cast(i))); + ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), 16, static_cast(i))); } std::uint64_t applied = 0; @@ -616,8 +609,7 @@ TEST(RingTest, ALazyWatermarkMayTrailTheWorkButMustNeverLeadIt) { RingFixture ring(1024); constexpr int kRecords = 8; for (int i = 0; i < kRecords; ++i) { - ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), 16, - static_cast(i))); + ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), 16, static_cast(i))); } std::uint64_t drained = 0; @@ -651,8 +643,7 @@ TEST(RingTest, AWrapFillerDoesNotAdvanceTheRecordSequence) { // it lands exactly at the boundary. The fifth is what forces the filler. std::uint64_t written = 0; for (int i = 0; i < 3; ++i) { - ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), kPayload, - static_cast(i))); + ASSERT_TRUE(ring.WriteRecord(static_cast(i + 1), kPayload, static_cast(i))); ++written; } const std::uint64_t headAfterThree = ring.Producer().LocalHead(); @@ -674,8 +665,7 @@ TEST(RingTest, AWrapFillerDoesNotAdvanceTheRecordSequence) { // the head advances by MORE than the records' own bytes, and that surplus is the // pad. The record count still has to match. for (int i = 0; i < 3; ++i) { - ASSERT_TRUE(ring.WriteRecord(static_cast(i + 10), kPayload, - static_cast(i + 10))); + ASSERT_TRUE(ring.WriteRecord(static_cast(i + 10), kPayload, static_cast(i + 10))); ++written; } const std::uint64_t headAfterSix = ring.Producer().LocalHead(); @@ -685,8 +675,7 @@ TEST(RingTest, AWrapFillerDoesNotAdvanceTheRecordSequence) { EXPECT_EQ(view.flags & kRecPad, 0u) << "a wrap filler reached the record counter"; ++popped; } - EXPECT_EQ(popped, written) - << "the two sides' sequence spaces have drifted by the fillers between them"; + EXPECT_EQ(popped, written) << "the two sides' sequence spaces have drifted by the fillers between them"; EXPECT_TRUE(ring.Invariants()); } @@ -707,7 +696,9 @@ namespace { constexpr std::uint32_t Call(MobileGL::MG_Pipe::MGPipeCallFlags f) { return static_cast(f); } - constexpr std::uint32_t Rec(RingRecordFlags f) { return static_cast(f); } + constexpr std::uint32_t Rec(RingRecordFlags f) { + return static_cast(f); + } } // namespace TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { @@ -745,8 +736,7 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { // kRecNoReply; extend it again rather than reusing the count. static_assert(kMGPipeCallFlagsAllBits == 0x3Fu, "MGPipeCallFlags grew or shrank"); constexpr std::uint32_t kAllRingFlags = - Rec(kRecNeedsAck) | Rec(kRecHasBlob) | Rec(kRecPad) | Rec(kRecBorrowSlot) | - Rec(kRecVarTail) | Rec(kRecNoReply); + Rec(kRecNeedsAck) | Rec(kRecHasBlob) | Rec(kRecPad) | Rec(kRecBorrowSlot) | Rec(kRecVarTail) | Rec(kRecNoReply); static_assert(kAllRingFlags == 0x3Fu, "RingRecordFlags grew or shrank"); // ---- the runtime half, and it is NOT the story the collision table alone suggests ---- @@ -761,10 +751,10 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { ASSERT_NE(drawVboCallFlags & Call(kVarTail), 0u) << "draw_vbo stopped being a var-tail call"; ASSERT_NE(drawVboCallFlags & Call(kHostSpan), 0u) << "draw_vbo stopped being a host-span call"; - void* payload = ring.Producer().Reserve( - static_cast(MGPWireOp::DrawVbo), - static_cast(drawVboCallFlags), // the mistake, made on purpose - 16); + void* payload = + ring.Producer().Reserve(static_cast(MGPWireOp::DrawVbo), + static_cast(drawVboCallFlags), // the mistake, made on purpose + 16); ASSERT_NE(payload, nullptr); std::memset(payload, 0xAB, 16); ring.Producer().Publish(); @@ -796,16 +786,13 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { // `&& header.kind == kRingPadRecordKind` half of Pop's condition and this record vanishes // into the wrap-filler skip again, exactly as it did before the pair was required, and this // case goes red on the ASSERT's own message. That perturbation was run. - void* second = ring.Producer().Reserve(static_cast(MGPWireOp::DrawVbo), - kRecNone, 16); + void* second = ring.Producer().Reserve(static_cast(MGPWireOp::DrawVbo), kRecNone, 16); ASSERT_NE(second, nullptr); std::memset(second, 0xAB, 16); RingRecordHeader stamped{}; - std::memcpy(&stamped, static_cast(second) - sizeof(RingRecordHeader), - sizeof(stamped)); + std::memcpy(&stamped, static_cast(second) - sizeof(RingRecordHeader), sizeof(stamped)); stamped.flags = static_cast(drawVboCallFlags); // no mask in this path - std::memcpy(static_cast(second) - sizeof(RingRecordHeader), &stamped, - sizeof(stamped)); + std::memcpy(static_cast(second) - sizeof(RingRecordHeader), &stamped, sizeof(stamped)); ring.Producer().Publish(); ASSERT_TRUE(ring.Consumer().Pop(view)) @@ -842,11 +829,9 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { ASSERT_NE(filler, nullptr); std::memset(filler, 0xEF, 16); RingRecordHeader asFiller{}; - std::memcpy(&asFiller, static_cast(filler) - sizeof(RingRecordHeader), - sizeof(asFiller)); + std::memcpy(&asFiller, static_cast(filler) - sizeof(RingRecordHeader), sizeof(asFiller)); asFiller.flags = static_cast(kRecPad); - std::memcpy(static_cast(filler) - sizeof(RingRecordHeader), &asFiller, - sizeof(asFiller)); + std::memcpy(static_cast(filler) - sizeof(RingRecordHeader), &asFiller, sizeof(asFiller)); ring.Producer().Publish(); EXPECT_FALSE(ring.Consumer().Pop(view)) @@ -856,8 +841,7 @@ TEST(RingTest, TheTwoFlagSpacesAreDisjointByTranslation) { // And the same record framed the way the encoder actually frames it - translated, with the // ring's own var-tail bit - round-trips intact. - void* honest = ring.Producer().Reserve(static_cast(MGPWireOp::DrawVbo), - kRecVarTail, 16); + void* honest = ring.Producer().Reserve(static_cast(MGPWireOp::DrawVbo), kRecVarTail, 16); ASSERT_NE(honest, nullptr); std::memset(honest, 0xCD, 16); ring.Producer().Publish(); diff --git a/MobileGL/MG_Test/Wire/ServerContextLiveTest.cpp b/MobileGL/MG_Test/Wire/ServerContextLiveTest.cpp index 589bc9ce..1a63e7f1 100644 --- a/MobileGL/MG_Test/Wire/ServerContextLiveTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerContextLiveTest.cpp @@ -25,7 +25,9 @@ using namespace MobileGL; namespace { thread_local MG_Pipe::PipeInputs* t_sessionBlock = nullptr; - MG_Pipe::PipeInputs* ResolveTestSessionBlock() { return t_sessionBlock; } + MG_Pipe::PipeInputs* ResolveTestSessionBlock() { + return t_sessionBlock; + } // A fresh thread, bound to `block` before its first touch of gPipeInputs - the order // ThreadSessionScope keeps on a real session thread. diff --git a/MobileGL/MG_Test/Wire/ServerDisplayTest.cpp b/MobileGL/MG_Test/Wire/ServerDisplayTest.cpp index 4281f9ac..cdb42ae1 100644 --- a/MobileGL/MG_Test/Wire/ServerDisplayTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerDisplayTest.cpp @@ -115,7 +115,9 @@ namespace { }; int g_windowStorage[4]; - void* FakeWindow(int index) { return &g_windowStorage[index]; } + void* FakeWindow(int index) { + return &g_windowStorage[index]; + } } // namespace @@ -125,9 +127,7 @@ class EmbeddedServerTest : public ::testing::Test { protected: FakePlatform platform; - mobilegl_server_display_hooks Hooks() { - return {&FakeAcquire, &FakeRelease, &FakeGeometry, &platform}; - } + mobilegl_server_display_hooks Hooks() { return {&FakeAcquire, &FakeRelease, &FakeGeometry, &platform}; } void TearDown() override { ServerDisplayInstance().EndLease(this); (void)mobilegl_server_display_uninstall(0); @@ -198,7 +198,8 @@ TEST(ServerDisplayTest, AProcessThatInstalledNothingHasNoDisplay) { EXPECT_FALSE(display.HasDisplay()); ServerWindowLease lease; const auto started = std::chrono::steady_clock::now(); - EXPECT_EQ(display.AcquireFor(64, 48, 5000, this, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::NoDisplay); + EXPECT_EQ(display.AcquireFor(64, 48, 5000, this, nullptr, nullptr, nullptr, &lease), + ServerWindowAcquire::NoDisplay); EXPECT_LT(std::chrono::steady_clock::now() - started, std::chrono::milliseconds(1000)) << "a process with no display must not wait for a window"; // And the process singleton is that process: nothing on the host installs one. @@ -213,7 +214,8 @@ TEST(ServerDisplayTest, AWaitForAWindowThatNeverComesEndsByNameAfterItsBound) { EXPECT_TRUE(display.HasDisplay()); ServerWindowLease lease; const auto started = std::chrono::steady_clock::now(); - EXPECT_EQ(display.AcquireFor(640, 480, 200, this, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::NoWindow); + EXPECT_EQ(display.AcquireFor(640, 480, 200, this, nullptr, nullptr, nullptr, &lease), + ServerWindowAcquire::NoWindow); const auto waited = std::chrono::steady_clock::now() - started; EXPECT_GE(waited, std::chrono::milliseconds(150)); EXPECT_LT(waited, std::chrono::milliseconds(3000)); @@ -288,7 +290,8 @@ TEST(ServerDisplayTest, TheWindowsOwnSizeAfterAFixedSizeIsTheLayoutsNotTheStaleF display.Attach(FakeWindow(0), 640, 480); }); ServerWindowLease lease; - ASSERT_EQ(display.AcquireFor(640, 480, 5000, this, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::Acquired); + ASSERT_EQ(display.AcquireFor(640, 480, 5000, this, nullptr, nullptr, nullptr, &lease), + ServerWindowAcquire::Acquired); fixer.join(); display.EndLease(this); // Session 2 asks for the window's own size: setSizeFromLayout answers later, at the layout's size. @@ -435,9 +438,9 @@ TEST(ServerDisplayTest, ALeaseWhoseSessionIsEndingIsWaitedForAndThenHandedOver) const auto refusedAt = std::chrono::steady_clock::now(); EXPECT_EQ(display.AcquireFor(0, 0, 2000, &third, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::LeasedElsewhere); - EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - refusedAt) - .count(), - 500); + EXPECT_LT( + std::chrono::duration_cast(std::chrono::steady_clock::now() - refusedAt).count(), + 500); display.EndLease(&fresh); EXPECT_EQ(platform.releases.load(), 0) << "a lease that changed hands released the window"; } @@ -460,8 +463,7 @@ TEST(ServerDisplayTest, AnEndingLeaseIsWaitedForOnlyAsLongAsTheRequestWaitsAndTh const auto started = std::chrono::steady_clock::now(); EXPECT_EQ(display.AcquireFor(0, 0, 200, &second, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::LeasedElsewhere); - EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - started) - .count(), + EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - started).count(), 150) << "the ending lease was not waited for"; @@ -473,9 +475,9 @@ TEST(ServerDisplayTest, AnEndingLeaseIsWaitedForOnlyAsLongAsTheRequestWaitsAndTh const auto refusedAt = std::chrono::steady_clock::now(); EXPECT_EQ(display.AcquireFor(0, 0, 2000, &second, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::LeasedElsewhere); - EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - refusedAt) - .count(), - 500); + EXPECT_LT( + std::chrono::duration_cast(std::chrono::steady_clock::now() - refusedAt).count(), + 500); display.EndLease(&first); } @@ -490,7 +492,8 @@ TEST(ServerDisplayTest, InterruptAndTheCancelPredicateEndAWaitByName) { display.Interrupt(); }); ServerWindowLease lease; - EXPECT_EQ(display.AcquireFor(0, 0, 5000, this, nullptr, nullptr, nullptr, &lease), ServerWindowAcquire::Interrupted); + EXPECT_EQ(display.AcquireFor(0, 0, 5000, this, nullptr, nullptr, nullptr, &lease), + ServerWindowAcquire::Interrupted); interrupter.join(); static std::atomic cancelled{false}; @@ -532,11 +535,13 @@ TEST(ServerDisplayTest, ASuspendedHolderIsToldAboutTheNextNewWindowUntilItLeases const auto onLost = [](void*) { lost.fetch_add(1); }; int holder = 0; ServerWindowLease lease; - ASSERT_EQ(display.AcquireFor(64, 48, 100, &holder, onLost, nullptr, nullptr, &lease), ServerWindowAcquire::Acquired); + ASSERT_EQ(display.AcquireFor(64, 48, 100, &holder, onLost, nullptr, nullptr, &lease), + ServerWindowAcquire::Acquired); // The screen goes off; the "apply thread" suspends instead of ending. std::thread session([&] { - while (lost.load() == 0) std::this_thread::sleep_for(std::chrono::milliseconds(1)); + while (lost.load() == 0) + std::this_thread::sleep_for(std::chrono::milliseconds(1)); display.SuspendLease(&holder, onAttached); }); EXPECT_EQ(display.Detach(3000), ServerWindowDetach::ReleasedBySession); diff --git a/MobileGL/MG_Test/Wire/ServerLoopTest.cpp b/MobileGL/MG_Test/Wire/ServerLoopTest.cpp index f5b94a21..6f5d971a 100644 --- a/MobileGL/MG_Test/Wire/ServerLoopTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerLoopTest.cpp @@ -157,13 +157,13 @@ namespace { auto stamp = builder.CreateString(GIT_COMMIT_HASH_SHORT); auto hello = ::MobileGL::Wire::CreateHello( builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, stamp, - /*backendType=*/0u, /*pid=*/0u, /*configBlob=*/0, CapsAbiFingerprint(), - CapsAbiFingerprint(), ::MobileGL::Wire::CreateLinkTerms(builder)); - auto root = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); + /*backendType=*/0u, /*pid=*/0u, /*configBlob=*/0, CapsAbiFingerprint(), CapsAbiFingerprint(), + ::MobileGL::Wire::CreateLinkTerms(builder)); + auto root = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, hello.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, root); - if (clientTransport->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), - builder.GetSize()}) != MOBILEGL_OK) { + if (clientTransport->SendFrame(MobileGLByteSpan{builder.GetBufferPointer(), builder.GetSize()}) != + MOBILEGL_OK) { return false; } } @@ -174,18 +174,17 @@ namespace { session->SetCapabilityBits(0); session->SetConsumedSubsystems(MG_Pipe::kMGPipeSubsystemsMigratedAtP4a); if (session->Accept(*serverTransport) != MOBILEGL_OK) return false; - if (clientSegments.AttachInProcess(session->Shm(), Transport::MemoryRole::Client) != - MOBILEGL_OK) { + if (clientSegments.AttachInProcess(session->Shm(), Transport::MemoryRole::Client) != MOBILEGL_OK) { return false; } Transport::RingControl* control = clientSegments.CmdControl(); - cmd = Transport::RingProducer(control, clientSegments.CmdRingBase(), - clientSegments.CmdRingCapacity(), Transport::RingCursorSet::Cmd); + cmd = Transport::RingProducer(control, clientSegments.CmdRingBase(), clientSegments.CmdRingCapacity(), + Transport::RingCursorSet::Cmd); if (!cmd.Valid()) return false; - producer.Attach(control, &cmd, &clientTransport->PeerDoorbell(), - &clientTransport->SelfDoorbell(), MG_Config::Ipc.SpinUs); - clientTable.Install(Codec::kSegCmd, Codec::SegmentView{clientSegments.CmdRingBase(), - clientSegments.CmdRingCapacity()}); + producer.Attach(control, &cmd, &clientTransport->PeerDoorbell(), &clientTransport->SelfDoorbell(), + MG_Config::Ipc.SpinUs); + clientTable.Install(Codec::kSegCmd, + Codec::SegmentView{clientSegments.CmdRingBase(), clientSegments.CmdRingCapacity()}); clientTable.Install(Codec::kSegStage, Codec::SegmentView{clientSegments.StageBase(), clientSegments.StageBytes()}); clientTable.Install(Codec::kSegReply, @@ -194,15 +193,12 @@ namespace { return true; } - bool StartLoop() { - return Server::ServerLoopInstance().Start(*session) == MOBILEGL_OK; - } + bool StartLoop() { return Server::ServerLoopInstance().Start(*session) == MOBILEGL_OK; } // Emit one record and wait for the server to apply it. This is the verb barrier's own // wait (R-3/R-5) - appliedSeq, not a sleep - so a case that goes green here has really // seen the apply thread move the watermark. - bool EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - Uint32 timeoutMs = 5000) { + bool EmitAndWait(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, Uint32 timeoutMs = 5000) { const Uint64 seq = encoder.EncodeRecord(op, payload, payloadBytes); if (seq == Codec::kInvalidSeq) return false; encoder.Publish(); @@ -210,8 +206,8 @@ namespace { return producer.WaitForApplied(seq, timeoutMs) == Transport::SessionWait::Reached; } - bool EmitAndWaitWithTail(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, - const void* tail, Uint64 tailBytes, Uint32 timeoutMs = 5000) { + bool EmitAndWaitWithTail(MG_Pipe::MGPWireOp op, const void* payload, Uint64 payloadBytes, const void* tail, + Uint64 tailBytes, Uint32 timeoutMs = 5000) { const Uint64 seq = encoder.EncodeRecord(op, payload, payloadBytes, tail, tailBytes); if (seq == Codec::kInvalidSeq) return false; encoder.Publish(); @@ -391,10 +387,8 @@ TEST(ServerLoopTest, TheControlShadowIsClearedWhenThePumpTakesTheRequest) { +[](void* user) -> MobileGLResult { Gate* g = static_cast(user); g->running.store(true, std::memory_order_release); - const auto gateDeadline = - std::chrono::steady_clock::now() + std::chrono::seconds(5); - while (!g->release.load(std::memory_order_acquire) && - std::chrono::steady_clock::now() < gateDeadline) { + const auto gateDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); + while (!g->release.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < gateDeadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } return MOBILEGL_OK; @@ -405,8 +399,7 @@ TEST(ServerLoopTest, TheControlShadowIsClearedWhenThePumpTakesTheRequest) { // Wait until the work is really running, i.e. until the pump has taken the request. const auto startDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); - while (!gate.running.load(std::memory_order_acquire) && - std::chrono::steady_clock::now() < startDeadline) { + while (!gate.running.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < startDeadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } const Bool started = gate.running.load(std::memory_order_acquire); @@ -490,14 +483,14 @@ namespace { // Posts `hook` from a helper thread (N-9's reason: a lost post must be said in the case's own // words, not as ctest's timeout) and waits for its answer within `deadlineMs`. - bool PostProbeAndWait(Server::ServerLoop::ControlProbeHook hook, void* user, Uint32 deadlineMs, - MobileGLResult* rc, long long* waitedMs) { + bool PostProbeAndWait(Server::ServerLoop::ControlProbeHook hook, void* user, Uint32 deadlineMs, MobileGLResult* rc, + long long* waitedMs) { std::atomic answered{false}; std::thread poster([&] { const auto start = std::chrono::steady_clock::now(); *rc = Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting(hook, user); - *waitedMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count(); + *waitedMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); answered.store(true, std::memory_order_release); }); const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(deadlineMs); @@ -606,7 +599,8 @@ TEST(ServerLoopTest, AFramePostedButNotYetTakenIsNotReportedHoweverLongItWaits) // THE WINDOW WAS REAL: the probe waited behind the drain for several slices. EXPECT_GE(waitedMs, static_cast(2 * Server::ServerLoop::kControlProgressIntervalMs)) - << "the probe was answered after " << waitedMs << " ms, so it was never posted-and-waiting " + << "the probe was answered after " << waitedMs + << " ms, so it was never posted-and-waiting " "long enough for this case to say anything"; EXPECT_TRUE(log.Snapshot().empty()) << log.Snapshot().size() << " progress report(s) for a frame the apply thread had NOT " @@ -652,10 +646,9 @@ TEST(ServerLoopTest, WireSequenceSurvivesRealDispatchAndFailureReply) { ASSERT_TRUE(fixture.Handshake()); ASSERT_TRUE(fixture.StartLoop()); flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp( - builder, 4242, ::MobileGL::Wire::SurfaceOpKind::SetSwapInterval); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, 4242, ::MobileGL::Wire::SurfaceOpKind::SetSwapInterval); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); Server::SurfaceControlFrame reply; EXPECT_EQ(MG_Remote::ServerApplyWireSurfaceOp( @@ -685,7 +678,8 @@ TEST(ServerLoopTest, ConcurrentProbesKeepTheirArgumentsAcrossNestedProbes) { std::vector posters; for (auto& probe : probes) { posters.emplace_back([&go, &probe] { - while (!go.load(std::memory_order_acquire)) std::this_thread::yield(); + while (!go.load(std::memory_order_acquire)) + std::this_thread::yield(); for (int i = 0; i < kCalls; ++i) { const auto rc = Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting( +[](void* user) -> MobileGLResult { @@ -696,14 +690,17 @@ TEST(ServerLoopTest, ConcurrentProbesKeepTheirArgumentsAcrossNestedProbes) { +[](void* nestedUser) -> MobileGLResult { static_cast(nestedUser)->nested.fetch_add(1); return MOBILEGL_OK; - }, p); - }, &probe); + }, + p); + }, + &probe); if (rc != MOBILEGL_OK) probe.errors.fetch_add(1); } }); } go.store(true, std::memory_order_release); - for (auto& poster : posters) poster.join(); + for (auto& poster : posters) + poster.join(); for (const auto& probe : probes) { EXPECT_EQ(probe.calls.load(), kCalls) << "another poster or nested probe replaced this hook/user pair"; EXPECT_EQ(probe.nested.load(), kCalls); @@ -749,8 +746,7 @@ TEST(ServerLoopTest, TheAffinityStringResolvesToAMaskAndTheMaskIsLogged) { ASSERT_TRUE(fixture.Handshake()); ASSERT_TRUE(fixture.StartLoop()); ASSERT_TRUE(fixture.WaitUntilTrulyParked()); - EXPECT_EQ(Server::ServerLoopInstance().ResolvedAffinityMask(), 0u) - << "`off` did not resolve to no-affinity"; + EXPECT_EQ(Server::ServerLoopInstance().ResolvedAffinityMask(), 0u) << "`off` did not resolve to no-affinity"; const std::string log = ReadLog(); EXPECT_NE(log.find("RESOLVED mask"), std::string::npos) @@ -791,8 +787,7 @@ TEST(ServerLoopTest, TheExplicitAffinityMaskResolvesToItselfAndIsLogged) { << "an explicit mask the box can honour did not resolve to itself (0x3); the effective " "mask read back from the kernel differs from the request, or the resolver is broken"; const std::string log = ReadLog(); - EXPECT_NE(log.find("RESOLVED mask 0x3"), std::string::npos) - << "the logged RESOLVED mask is not the effective 0x3"; + EXPECT_NE(log.find("RESOLVED mask 0x3"), std::string::npos) << "the logged RESOLVED mask is not the effective 0x3"; fixture.Stop(); MG_Config::Ipc.ServerAffinity = saved; @@ -924,16 +919,14 @@ TEST(ServerLoopTest, TheAuditPoisonFillsExactlyTheStagedRunAfterTheApplierReturn MG_Pipe::MGPResourceDesc respecify = create; respecify.HasDefinedContent = 1; MG_Pipe::MGPipeSetRespecifiedLevel( - respecify, - MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), 0u), - 0, 4, 4, 1); + respecify, MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), 0u), 0, + 4, 4, 1); ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify))); Vector texels(4 * 4 * 4, 0x5A); MG_Pipe::MGPSubData upload{}; upload.Res = create.Resource; - upload.Target = MG_Pipe::MGPipePackSubDataTarget( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), 0u); + upload.Target = MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), 0u); upload.Level = 0; upload.UnionBox = MG_Pipe::MGPBox{0, 0, 0, 4, 4, 1}; upload.RegionCount = 1; @@ -942,8 +935,7 @@ TEST(ServerLoopTest, TheAuditPoisonFillsExactlyTheStagedRunAfterTheApplierReturn // The bytes are in SEG_STAGE and readable through the CLIENT's own mapping right now - the // record has not been published yet, so nothing has retired it. - const void* stagedBefore = - fixture.clientTable.Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size); + const void* stagedBefore = fixture.clientTable.Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size); ASSERT_NE(stagedBefore, nullptr); ASSERT_EQ(std::memcmp(stagedBefore, texels.data(), texels.size()), 0); @@ -951,18 +943,19 @@ TEST(ServerLoopTest, TheAuditPoisonFillsExactlyTheStagedRunAfterTheApplierReturn region.W = 4; region.H = 4; region.D = 1; - ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::ResourceSubData, &upload, - sizeof(upload), ®ion, sizeof(region))); + ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::ResourceSubData, &upload, sizeof(upload), ®ion, + sizeof(region))); EXPECT_GT(fixture.session->Applier().PoisonedStageBytes(), 0u) << "MOBILEGL_IPC_AUDIT=1 poisoned nothing, so R-2.5's only mechanical control against a " "retained SEG_STAGE pointer never ran"; - const auto* poisoned = static_cast( - fixture.clientTable.Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size)); + const auto* poisoned = + static_cast(fixture.clientTable.Resolve(upload.Blob.Seg, upload.Blob.Offset, upload.Blob.Size)); ASSERT_NE(poisoned, nullptr); for (Uint64 i = 0; i < upload.Blob.Size; ++i) { - ASSERT_EQ(poisoned[i], 0xDD) << "staged byte " << i << " was not poisoned; an applier " + ASSERT_EQ(poisoned[i], 0xDD) << "staged byte " << i + << " was not poisoned; an applier " "that kept this pointer would still read real bytes and " "the control would prove nothing"; } @@ -1000,7 +993,8 @@ TEST(StagedShadowTest, TheSplitArmCopiesAndSurvivesTheSourceBeingPoisoned) { std::fill(staged.begin(), staged.end(), Uint8{0xDD}); for (SizeT i = 0; i < 32; ++i) { - EXPECT_EQ(splitBase[16 + i], 0xAB) << "byte " << i << " of the server's copy is the " + EXPECT_EQ(splitBase[16 + i], 0xAB) << "byte " << i + << " of the server's copy is the " "poison, so the copy never happened"; } // And the control: the monolith arm reads the poison, which is what makes the assertion @@ -1089,8 +1083,8 @@ TEST(StagedShadowTest, ADrainOutsideTheStagedCoverageIsFatalByName) { // it to the Fatal's abort() (a segfault is SIGSEGV and fails this), and the log grep names the // exact wording. The log flush is pinned: Log.cpp's WriteToFile fflushes after every write and // MGLOG_F logs before abort(), so the line is on disk in the forked child before it dies. - EXPECT_EXIT(store.RequireCoverage(&key, base, 0, 64, "unit_out_of_range"), - ::testing::KilledBySignal(SIGABRT), ".*"); + EXPECT_EXIT(store.RequireCoverage(&key, base, 0, 64, "unit_out_of_range"), ::testing::KilledBySignal(SIGABRT), + ".*"); const std::string log = ReadLog(); EXPECT_NE(log.find("Fatal{StageSnapshotTooNarrow, \"unit_out_of_range\"}"), std::string::npos) << "the abort happened but not for this rule's reason; the log says: " << log; @@ -1139,9 +1133,9 @@ TEST(StagedShadowProductionTest, SubDataThroughTheRealOpsTableCopiesAndSurvivesT // client's staging run is dead. A server that copied still reads the original bytes. std::fill(src.begin(), src.end(), Uint8{0xDD}); for (SizeT i = 0; i < src.size(); ++i) { - ASSERT_EQ(found->hostBytes[i], 0xAB) - << "byte " << i << " of the server base is the poison: Ops_H_SubData kept a pointer " - "into SEG_STAGE instead of copying"; + ASSERT_EQ(found->hostBytes[i], 0xAB) << "byte " << i + << " of the server base is the poison: Ops_H_SubData kept a pointer " + "into SEG_STAGE instead of copying"; } MG_Backend::DirectGLES::BufferImpl::UnregisterBufferBackendOps(); } @@ -1269,12 +1263,11 @@ TEST(ServerLoopTest, AThreadCreatedAfterTheLoopStoppedIsNotMistakenForTheApplyTh // has been joined and is therefore the likeliest candidate to receive the recycled block. for (int i = 0; i < 8; ++i) { std::atomic onApplyThread{true}; - std::thread probe([&] { - onApplyThread.store(Server::ServerLoop::OnApplyThread(), std::memory_order_release); - }); + std::thread probe([&] { onApplyThread.store(Server::ServerLoop::OnApplyThread(), std::memory_order_release); }); probe.join(); ASSERT_FALSE(onApplyThread.load(std::memory_order_acquire)) - << "thread " << i << " created after the apply thread was joined answers " + << "thread " << i + << " created after the apply thread was joined answers " "OnApplyThread() TRUE - it inherited the exited thread's TLS block and the stale " "key still names it. Every role guard in the tree is now inverted on that thread"; } @@ -1352,8 +1345,8 @@ TEST(ServerLoopTest, MakeCurrentClassifiesRepeatAndReleaseWithoutRebinding) { const EGLContext ctx = reinterpret_cast(0x3); // Nothing current yet: a genuine bind. - EXPECT_EQ(Server::ClassifyEglMakeCurrent(false, EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT, dpy, draw, read, ctx), + EXPECT_EQ(Server::ClassifyEglMakeCurrent(false, EGL_NO_DISPLAY, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT, dpy, + draw, read, ctx), Server::EglBindAction::NativeBind); // The SAME tuple already held: a no-op, no native bind, no owner write. EXPECT_EQ(Server::ClassifyEglMakeCurrent(true, dpy, draw, read, ctx, dpy, draw, read, ctx), @@ -1365,9 +1358,9 @@ TEST(ServerLoopTest, MakeCurrentClassifiesRepeatAndReleaseWithoutRebinding) { EXPECT_EQ(Server::ClassifyEglMakeCurrent(true, dpy, draw, read, ctx, dpy, draw, read, otherCtx), Server::EglBindAction::NativeBind); // A release request, whatever is current: recorded, not forwarded (the context is held). - EXPECT_EQ(Server::ClassifyEglMakeCurrent(true, dpy, draw, read, ctx, dpy, EGL_NO_SURFACE, - EGL_NO_SURFACE, EGL_NO_CONTEXT), - Server::EglBindAction::ClientRelease); + EXPECT_EQ( + Server::ClassifyEglMakeCurrent(true, dpy, draw, read, ctx, dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), + Server::EglBindAction::ClientRelease); } // ===================================================================================== @@ -1529,8 +1522,8 @@ TEST(ServerLoopTest, TheSevenFormatCapabilityReadsAnswerFromTheServersBackendNot TextureTarget::Texture2D); EXPECT_EQ(internalFormat, kGlRgb8Snorm) << "the format info followed the global (null, so the fallback normalisation widened RGB8_SNORM) " - "instead of the server's backend (present, so native RGB8_SNORM); got 0x" << std::hex - << internalFormat; + "instead of the server's backend (present, so native RGB8_SNORM); got 0x" + << std::hex << internalFormat; EXPECT_FALSE(TextureImpl::BackendTextureFormatAddsAlpha(TextureInternalFormat::RGB8Snorm, TextureTarget::Texture2D)) << "the adds-alpha answer followed the global (null, so the fallback adds alpha) instead of " "the server's backend"; @@ -1674,19 +1667,19 @@ namespace { }; // FAIL() and GTEST_SKIP() both return from the enclosing void test body. -#define MGL_EGL_BRING_UP_OR_BAIL(fixture) \ - do { \ - const std::string why = (fixture).BringUp(); \ - if (!why.empty()) { \ - (fixture).Stop(); \ - MG_State::pGLContext = Move((fixture).savedContext); \ - if (RequireGpuFromEnvironment()) { \ - FAIL() << "MOBILEGL_ITEST_REQUIRE_GPU is set, so the server's EGL bring-up failing is " \ - "a failure, not a skip. It failed at: " \ - << why; \ - } \ - GTEST_SKIP() << "no usable headless EGL for the server backend: " << why; \ - } \ +#define MGL_EGL_BRING_UP_OR_BAIL(fixture) \ + do { \ + const std::string why = (fixture).BringUp(); \ + if (!why.empty()) { \ + (fixture).Stop(); \ + MG_State::pGLContext = Move((fixture).savedContext); \ + if (RequireGpuFromEnvironment()) { \ + FAIL() << "MOBILEGL_ITEST_REQUIRE_GPU is set, so the server's EGL bring-up failing is " \ + "a failure, not a skip. It failed at: " \ + << why; \ + } \ + GTEST_SKIP() << "no usable headless EGL for the server backend: " << why; \ + } \ } while (0) // The production shape of a buffer under split, step by step through the applier's own entry @@ -1743,22 +1736,22 @@ TEST(ServerLoopEglTest, SuccessfulVoidWireOperationsReplyWithSuccessAndOriginalS EglServerFixture fixture; MGL_EGL_BRING_UP_OR_BAIL(fixture); // Release last: it deliberately destroys the native context used by the preceding ops. - const auto kinds = {::MobileGL::Wire::SurfaceOpKind::SetSwapInterval, - ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle, - ::MobileGL::Wire::SurfaceOpKind::ReleaseSurface, - ::MobileGL::Wire::SurfaceOpKind::ReleaseResources}; + const auto kinds = { + ::MobileGL::Wire::SurfaceOpKind::SetSwapInterval, ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle, + ::MobileGL::Wire::SurfaceOpKind::ReleaseSurface, ::MobileGL::Wire::SurfaceOpKind::ReleaseResources}; Uint64 seq = 9000; for (const auto kind : kinds) { flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp( - builder, ++seq, kind, 0, 0, ::MobileGL::Wire::WindowKind::None, 0, 0, 0, 1); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, ++seq, kind, 0, 0, + ::MobileGL::Wire::WindowKind::None, 0, 0, 0, 1); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); Server::SurfaceControlFrame reply; EXPECT_EQ(MG_Remote::ServerApplyWireSurfaceOp( *::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(), &reply), - MOBILEGL_OK) << static_cast(kind); + MOBILEGL_OK) + << static_cast(kind); flatbuffers::FlatBufferBuilder replies(256); MG_Remote::EncodeSurfaceReplyFrame(reply, &replies); const auto* wireReply = ::MobileGL::Wire::GetCtrlEnvelope(replies.GetBufferPointer())->msg_as_SurfaceReply(); @@ -1918,22 +1911,23 @@ TEST(ServerLoopEglTest, RenderShadowCannotSkipAnUnchangedVersionAfterContextEpoc ASSERT_TRUE(fixture.MakeCurrent()); Bool nativeRestored = false, servedRestored = false; ASSERT_EQ(OnApply([&] { - using namespace MG_Backend::DirectGLES; - MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); - RenderStateImpl::SyncRenderState(true); - // Simulate the successor driver's default/foreign state while retaining exactly - // the same parameter bytes and version. Epoch, not a value diff, must force it. - g_GLESFuncs.glEnable(GL_BLEND); - ++g_backendContextGeneration; - RenderStateImpl::SyncRenderState(true); - nativeRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE; - g_GLESFuncs.glEnable(GL_BLEND); - MG_Pipe::MGPipeApplierReset(); - MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); - RenderStateImpl::SyncRenderState(true); - servedRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE; - MG_Pipe::MGPipeServerClearVerbBoundary(); - }), MOBILEGL_OK); + using namespace MG_Backend::DirectGLES; + MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); + RenderStateImpl::SyncRenderState(true); + // Simulate the successor driver's default/foreign state while retaining exactly + // the same parameter bytes and version. Epoch, not a value diff, must force it. + g_GLESFuncs.glEnable(GL_BLEND); + ++g_backendContextGeneration; + RenderStateImpl::SyncRenderState(true); + nativeRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE; + g_GLESFuncs.glEnable(GL_BLEND); + MG_Pipe::MGPipeApplierReset(); + MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); + RenderStateImpl::SyncRenderState(true); + servedRestored = g_GLESFuncs.glIsEnabled(GL_BLEND) == GL_FALSE; + MG_Pipe::MGPipeServerClearVerbBoundary(); + }), + MOBILEGL_OK); EXPECT_TRUE(nativeRestored) << "unchanged render version hid a new native context"; EXPECT_TRUE(servedRestored) << "unchanged render version hid a new served context"; fixture.TearDown(); @@ -2001,8 +1995,8 @@ TEST(ServerLoopEglTest, TheServersNativeContextAsksTheDriverToReportResets) { ASSERT_EQ(OnApply([&] { const auto& egl = MG_Backend::DirectGLES::g_EGLFuncs; const char* extensions = egl.eglQueryString(egl.eglGetCurrentDisplay(), EGL_EXTENSIONS); - robustness = extensions != nullptr && - std::strstr(extensions, "EGL_EXT_create_context_robustness") != nullptr; + robustness = + extensions != nullptr && std::strstr(extensions, "EGL_EXT_create_context_robustness") != nullptr; MG_Backend::DirectGLES::g_GLESFuncs.glGetIntegerv(0x8256 /*GL_RESET_NOTIFICATION_STRATEGY*/, &strategy); }), @@ -2019,7 +2013,9 @@ TEST(ServerLoopEglTest, TheServersNativeContextAsksTheDriverToReportResets) { namespace { // The driver's reset status, as the backend sees it through its function table. std::atomic g_fakeResetStatus{GL_NO_ERROR}; - GLenum FakeGetGraphicsResetStatus() { return g_fakeResetStatus.exchange(GL_NO_ERROR); } + GLenum FakeGetGraphicsResetStatus() { + return g_fakeResetStatus.exchange(GL_NO_ERROR); + } // THE WINDOW, HELD OPEN. The server acknowledges a record before it looks for the next one, so // a record the client publishes right after that acknowledgement can be popped by the SAME @@ -2050,9 +2046,9 @@ namespace { } enum : int { - kResetInnocentEnded = 1, // an INNOCENT report latched the session (the compositor would die with a client) - kResetGuiltyApplied = 2, // a record published after the GUILTY report was applied - kResetGuiltyNotLatched = 4, // GUILTY did not latch BackendDeviceLost through Espryt's apply-loop site + kResetInnocentEnded = 1, // an INNOCENT report latched the session (the compositor would die with a client) + kResetGuiltyApplied = 2, // a record published after the GUILTY report was applied + kResetGuiltyNotLatched = 4, // GUILTY did not latch BackendDeviceLost through Espryt's apply-loop site kResetHealthyNotApplied = 8, // a record before the GUILTY report was not applied }; @@ -2107,7 +2103,8 @@ namespace { if (!applied(healthy)) failed |= kResetHealthyNotApplied; if (applied(guilty)) failed |= kResetGuiltyApplied; Server::ServerLoopInstance().SetBetweenRecordsHookForTesting(nullptr); - if (!MG_Remote::SessionLatched() || MG_Remote::SessionLatchedFamily() != MG_Remote::MGFatalFamily::BackendDeviceLost || + if (!MG_Remote::SessionLatched() || + MG_Remote::SessionLatchedFamily() != MG_Remote::MGFatalFamily::BackendDeviceLost || std::strstr(MG_Remote::SessionLatchedLine(), "Espryt:apply") == nullptr) failed |= kResetGuiltyNotLatched; std::fprintf(stderr, "[reset] latched=%d line=%s\n", MG_Remote::SessionLatched() ? 1 : 0, @@ -2121,10 +2118,10 @@ namespace { // bits 2|4), and with LatchIfDeviceLost latching every non-NO_ERROR status (bit 1). TEST(ServerLoopEglLatchTest, AGuiltyResetEndsTheSessionBeforeItsNextRecordAndAnInnocentOneDoesNot) { PinHeadlessEglEnvironment(); - EXPECT_EXIT(ResetReportsAndExit(), ::testing::ExitedWithCode(0), - ".*") + EXPECT_EXIT(ResetReportsAndExit(), ::testing::ExitedWithCode(0), ".*") << "bits: 1 = INNOCENT latched; 2 = a record after GUILTY was applied; 4 = GUILTY did not latch " - "BackendDeviceLost at Espryt:apply; 8 = a healthy record was not applied (98 = no headless EGL while MOBILEGL_ITEST_REQUIRE_GPU is set)"; + "BackendDeviceLost at Espryt:apply; 8 = a healthy record was not applied (98 = no headless EGL while " + "MOBILEGL_ITEST_REQUIRE_GPU is set)"; } // ID-49's tight-size half, gated (review v2 N-5: "nothing, unit and joint"). The client's DstSize is @@ -2154,11 +2151,11 @@ TEST(ServerLoopEglTest, AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClient framebuffer.Width = framebuffer.Height = 64; framebuffer.Layers = framebuffer.Samples = 1; framebuffer.FixedSampleLocations = 1; - for (auto& drawBuffer : framebuffer.DrawBuffers) drawBuffer = -1; + for (auto& drawBuffer : framebuffer.DrawBuffers) + drawBuffer = -1; framebuffer.DrawBuffers[0] = 0; framebuffer.ContentHash = 1; - ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::SetFramebufferState, - &framebuffer, sizeof(framebuffer))); + ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::SetFramebufferState, &framebuffer, sizeof(framebuffer))); MG_Pipe::MGPPixelPackState pack{}; pack.Pack.Alignment = 4; @@ -2169,13 +2166,13 @@ TEST(ServerLoopEglTest, AReadPixelsReplyIsTheTightExtentWhateverDstSizeTheClient MG_Pipe::MGPSamplerViews views{}; views.Count = 1; MG_Pipe::MGPBoundView unboundView{}; - ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::SetSamplerViews, - &views, sizeof(views), &unboundView, sizeof(unboundView))); + ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::SetSamplerViews, &views, sizeof(views), &unboundView, + sizeof(unboundView))); MG_Pipe::MGPSamplerStates samplers{}; samplers.Count = 1; const MG_Pipe::MGPipeHandle unboundSampler = MG_Pipe::kMGPipeNullHandle; - ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::BindSamplerStates, - &samplers, sizeof(samplers), &unboundSampler, sizeof(unboundSampler))); + ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::BindSamplerStates, &samplers, sizeof(samplers), + &unboundSampler, sizeof(unboundSampler))); MG_Pipe::MGPContextValues context{}; // unit zero is unbound; no open XFB capture ASSERT_TRUE(fixture.EmitAndWait(MG_Pipe::MGPWireOp::SetContextValues, &context, sizeof(context))); @@ -2324,7 +2321,8 @@ TEST(StagedShadowProductionTest, AQueuedRangeOutsideTheStagedCoverageIsFatalAtTh const std::string log = ReadLog(); EXPECT_NE(log.find("Fatal{StageSnapshotTooNarrow, \"ensure_flush_pending\"}"), std::string::npos) << "the abort was not the pending-coverage clamp's (M-1, RequireStagedCoverageForPendingRanges); " - "the log says: " << log; + "the log says: " + << log; fixture.TearDown(); } @@ -2392,8 +2390,9 @@ TEST(StagedShadowProductionTest, TheEnsurePathUploadsTheServerShadowNotTheClient // Keep this object coherently mapped: Ensure's retained backend sync site must not // re-enter the client producer and replace the staged 0x5B bytes with this 0xA5 map. - ASSERT_NE(buffer->AcquireMemoryRange({0, 64}, BufferMappingAccessBit::Write | - BufferMappingAccessBit::Persistent | BufferMappingAccessBit::Coherent), nullptr); + ASSERT_NE(buffer->AcquireMemoryRange({0, 64}, BufferMappingAccessBit::Write | BufferMappingAccessBit::Persistent | + BufferMappingAccessBit::Coherent), + nullptr); ASSERT_TRUE(buffer->IsMapped()); // The server shadow for the twin the draw will use: B, staged the way the wire delivers it. @@ -2450,7 +2449,10 @@ namespace { // (the respecify reader, or the pool-reuse reader when a 64-byte id is waiting in the pool) // must go through and leave 16 bytes of pattern and 48 of zero in the driver's store. Exit 0 // when it did, 7 when the wrong bytes landed, 8 when the child's bring-up failed. - enum class IdiomArm { Respecify, PoolReuse }; + enum class IdiomArm { + Respecify, + PoolReuse + }; [[noreturn]] void RunTheStreamingIdiomAndExit(IdiomArm arm) { EglServerFixture fixture; @@ -2508,7 +2510,8 @@ TEST(StagedShadowProductionTest, TheStreamingIdiomOrphanThenPartialSubDataIsUplo EXPECT_EQ(appended.find("Fatal{StageSnapshotTooNarrow"), std::string::npos) << "the orphan-then-partial-subdata idiom was refused by name (M-3's whole-store refusal must " "key on the descriptor's HasDefinedContent, not on the frontend flag a partial subdata " - "flips); the log says: " << appended; + "flips); the log says: " + << appended; } // The same idiom through the POOL-REUSE reader: a 64-byte id retired to the pool by a previous @@ -2568,11 +2571,10 @@ TEST(ServerLoopTest, AnInstancedBaseVertexDrawRecordReachesTheSinkWithItsFieldsI ASSERT_TRUE(fixture.StartLoop()); MG_Pipe::MGPDrawInfo info = D1DrawInfo(/*indexSize=*/2, /*numDraws=*/1); - info.InstanceCount = 7; // DrawElementsInstancedBaseVertex(..., 7, 5): the Minecraft slot + info.InstanceCount = 7; // DrawElementsInstancedBaseVertex(..., 7, 5): the Minecraft slot info.IndexResource = D1Handle(31); const MG_Pipe::MGPDrawRange range{/*Start=*/12, /*Count=*/36, /*IndexBias=*/5}; - ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, - sizeof(range))); + ASSERT_TRUE(fixture.EmitAndWaitWithTail(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range))); const Server::ServerVerbSink& verbs = fixture.session->Applier().Verbs(); EXPECT_EQ(verbs.DrawRecords(), 1u) << "the record did not reach OnDrawVbo"; @@ -2613,8 +2615,7 @@ TEST(ServerLoopTest, AClientIndexArrayCrossesAsAStagedSpanAndTheSinkSeesItsByteC info.Flags = MG_Pipe::kDrawHasUserIndices; const MG_Pipe::MGPDrawRange range{0, 6, 0}; const Codec::WireTail tails[2] = {{&range, sizeof(range)}, {&span, sizeof(span)}}; - const Uint64 seq = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), - tails, 2); + const Uint64 seq = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2); ASSERT_NE(seq, Codec::kInvalidSeq); fixture.encoder.Publish(); fixture.producer.PublishAndNotify(seq); @@ -2646,8 +2647,7 @@ TEST(ServerLoopTest, AnIndirectDrawRecordReachesTheSinkWithItsBlockIntact) { block.Stride = 16; block.DrawCount = 3; const Codec::WireTail tails[2] = {{nullptr, 0}, {&block, sizeof(block)}}; - const Uint64 seq = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), - tails, 2); + const Uint64 seq = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), tails, 2); ASSERT_NE(seq, Codec::kInvalidSeq); fixture.encoder.Publish(); fixture.producer.PublishAndNotify(seq); @@ -2693,7 +2693,7 @@ namespace { struct FvSession : ServerFixture { ~FvSession() { Stop(); } }; -} +} // namespace namespace { // Exercise the real hidden-resource constructors without adding a production test API. @@ -2707,12 +2707,13 @@ namespace { using type = Bool (FvRenderer::*)(); friend type FvPrivateMember(FvMipmapInitTag); }; - template struct FvMemberAccess { + template + struct FvMemberAccess { friend typename Tag::type FvPrivateMember(Tag) { return Member; } }; template struct FvMemberAccess; template struct FvMemberAccess; -} +} // namespace TEST(P5fReverseChannel, TransportDoesNotConstructHiddenFrontendPrograms) { // No Vulkan device, program factory or client GLContext is supplied. A transport @@ -2731,7 +2732,7 @@ TEST(P5fReverseChannel, GlErrorsUseTheOwnedCallbackWithoutAResidualPull) { MG_Pipe::MGPipeResetResidualPullCountForTesting(); MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); MG_Pipe::gPipeInputs->RecordError(ErrorCode::InvalidOperation, - MakeUnique("fv", "draw", "driver message")); + MakeUnique("fv", "draw", "driver message")); EXPECT_EQ(g_fvErrorCalls, 1u); EXPECT_EQ(g_fvErrorCode, static_cast(ErrorCode::InvalidOperation)); EXPECT_EQ(g_fvErrorMessage, "[fv] [draw] driver message"); @@ -2767,54 +2768,62 @@ TEST(P5fReverseChannel, CloseDoesNotEraseAnotherGlErrorOwner) { #if !defined(_WIN32) TEST(P5fReverseChannel, GlErrorCallbackRejectsDoubleInstallation) { - EXPECT_EXIT({ - MG_Pipe::gMGPipeCallbacks.OnGlError = &CaptureFvError; - FvSession fixture; - (void)fixture.Handshake(); - std::_Exit(9); - }, ::testing::KilledBySignal(SIGABRT), ""); + EXPECT_EXIT( + { + MG_Pipe::gMGPipeCallbacks.OnGlError = &CaptureFvError; + FvSession fixture; + (void)fixture.Handshake(); + std::_Exit(9); + }, + ::testing::KilledBySignal(SIGABRT), ""); EXPECT_NE(ReadLog().find("callback-double-install"), std::string::npos); } TEST(P5fReverseChannel, ASecondSessionCannotClaimTheSameReverseChannel) { - EXPECT_EXIT({ - FvSession owner; - if (!owner.Handshake()) std::_Exit(7); - std::unique_ptr client; - std::unique_ptr server; - Transport::InProcessTransport::CreatePair(client, server); - Server::ServerSession other; - // The owner refusal precedes receiving a Hello or replacing the segment resolver. - (void)other.Accept(*server); - std::_Exit(9); - }, ::testing::KilledBySignal(SIGABRT), ""); + EXPECT_EXIT( + { + FvSession owner; + if (!owner.Handshake()) std::_Exit(7); + std::unique_ptr client; + std::unique_ptr server; + Transport::InProcessTransport::CreatePair(client, server); + Server::ServerSession other; + // The owner refusal precedes receiving a Hello or replacing the segment resolver. + (void)other.Accept(*server); + std::_Exit(9); + }, + ::testing::KilledBySignal(SIGABRT), ""); EXPECT_NE(ReadLog().find("another ServerSession already owns"), std::string::npos); } TEST(P5fReverseChannel, RecordErrorWithoutCallbackIsNamedFatal) { - EXPECT_EXIT({ - MG_Config::Ipc.StrictErrors = true; - MG_Config::Ipc.RoleSplitState = true; - MG_Pipe::gMGPipeCallbacks.OnGlError = nullptr; - MG_Pipe::gPipeInputs->RecordError(ErrorCode::InvalidOperation, nullptr); - std::_Exit(9); - }, ::testing::KilledBySignal(SIGABRT), ""); + EXPECT_EXIT( + { + MG_Config::Ipc.StrictErrors = true; + MG_Config::Ipc.RoleSplitState = true; + MG_Pipe::gMGPipeCallbacks.OnGlError = nullptr; + MG_Pipe::gPipeInputs->RecordError(ErrorCode::InvalidOperation, nullptr); + std::_Exit(9); + }, + ::testing::KilledBySignal(SIGABRT), ""); EXPECT_NE(ReadLog().find("OnGlError.callback-missing"), std::string::npos); } TEST(P5fReverseChannel, LegacyGpuWrittenNeverReadsAFrontendObjectInTransport) { for (Bool installed : {false, true}) { - EXPECT_EXIT({ - MG_Pipe::gMGPipeCallbacks.OnGpuWritten = installed ? - +[](MG_Pipe::MGPipeHandle, Uint, const MG_Pipe::MGPRange*) {} : nullptr; - // Deliberately unreadable object: a guard after a lifetime-id probe or - // either MarkGpuWritten fallback would SIGSEGV instead of the named abort. - SharedPtr foreign( - reinterpret_cast(std::uintptr_t{1}), - [](MG_State::GLState::BufferObject*) {}); - MG_Pipe::MGPipeAnnounceBufferGpuWritten(foreign); - std::_Exit(9); - }, ::testing::KilledBySignal(SIGABRT), ""); + EXPECT_EXIT( + { + MG_Pipe::gMGPipeCallbacks.OnGpuWritten = + installed ? +[](MG_Pipe::MGPipeHandle, Uint, const MG_Pipe::MGPRange*) {} : nullptr; + // Deliberately unreadable object: a guard after a lifetime-id probe or + // either MarkGpuWritten fallback would SIGSEGV instead of the named abort. + SharedPtr foreign( + reinterpret_cast(std::uintptr_t{1}), + [](MG_State::GLState::BufferObject*) {}); + MG_Pipe::MGPipeAnnounceBufferGpuWritten(foreign); + std::_Exit(9); + }, + ::testing::KilledBySignal(SIGABRT), ""); } EXPECT_NE(ReadLog().find("gpu-written-legacy-object"), std::string::npos); } @@ -2829,32 +2838,36 @@ TEST(P5fReverseChannel, GlErrorMessagesShareFifoWithOtherReverseEvents) { ASSERT_TRUE(fixture.Handshake()); ASSERT_TRUE(fixture.StartLoop()); ASSERT_EQ(Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting( - +[](void*) -> MobileGLResult { - MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); - MG_Pipe::gPipeInputs->RecordError(ErrorCode::InvalidOperation, - MakeUnique("fv", "driver error")); - const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer}; - MG_Pipe::gMGPipeCallbacks.OnGpuWritten({17, 3}, 1, &whole); - Uint8 bytes[] = {11, 22, 33, 44}; - MG_Pipe::gMGPipeCallbacks.OnBufferWriteback({17, 3}, 24, - MG_Pipe::MGPBlobRef{reinterpret_cast(bytes), sizeof(bytes), - MG_Pipe::kMGHostSpanSegNone, 0}); - std::memset(bytes, 0xDD, sizeof(bytes)); - String longMessage(Transport::kEventGlErrorMaxMessageBytes * 2, 'Q'); - MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidValue), - longMessage.c_str()); - std::fill(longMessage.begin(), longMessage.end(), '?'); - MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidEnum), nullptr); - MG_Pipe::MGPipeServerClearVerbBoundary(); - return MOBILEGL_OK; - }, nullptr), MOBILEGL_OK); + +[](void*) -> MobileGLResult { + MG_Pipe::MGPipeServerStampVerbBoundary(MG_Pipe::MGPipeVerb::Clear); + MG_Pipe::gPipeInputs->RecordError(ErrorCode::InvalidOperation, + MakeUnique("fv", "driver error")); + const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer}; + MG_Pipe::gMGPipeCallbacks.OnGpuWritten({17, 3}, 1, &whole); + Uint8 bytes[] = {11, 22, 33, 44}; + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback({17, 3}, 24, + MG_Pipe::MGPBlobRef{reinterpret_cast(bytes), + sizeof(bytes), + MG_Pipe::kMGHostSpanSegNone, 0}); + std::memset(bytes, 0xDD, sizeof(bytes)); + String longMessage(Transport::kEventGlErrorMaxMessageBytes * 2, 'Q'); + MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidValue), + longMessage.c_str()); + std::fill(longMessage.begin(), longMessage.end(), '?'); + MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidEnum), nullptr); + MG_Pipe::MGPipeServerClearVerbBoundary(); + return MOBILEGL_OK; + }, + nullptr), + MOBILEGL_OK); - Transport::EventRingConsumer events(fixture.clientSegments.EventControl(), - fixture.clientSegments.CmdControl(), fixture.clientSegments.EventRingBase(), - fixture.clientSegments.EventRingCapacity(), fixture.clientSegments.EventSegmentBase()); + Transport::EventRingConsumer events(fixture.clientSegments.EventControl(), fixture.clientSegments.CmdControl(), + fixture.clientSegments.EventRingBase(), + fixture.clientSegments.EventRingCapacity(), + fixture.clientSegments.EventSegmentBase()); ASSERT_TRUE(events.Valid()); - const Uint16 kinds[] = {Transport::kEventGlError, Transport::kEventGpuWritten, - Transport::kEventBufferWriteback, Transport::kEventGlError, Transport::kEventGlError}; + const Uint16 kinds[] = {Transport::kEventGlError, Transport::kEventGpuWritten, Transport::kEventBufferWriteback, + Transport::kEventGlError, Transport::kEventGlError}; for (SizeT i = 0; i < 5; ++i) { Transport::RingRecordView event; ASSERT_TRUE(events.Pop(event)) << i; @@ -2909,29 +2922,33 @@ TEST(P5fReverseChannel, ANonSingletonSessionOwnsAllFourReverseCallbacks) { ASSERT_NE(fixture.session, &Server::ServerSessionInstance()); ASSERT_TRUE(fixture.StartLoop()); ASSERT_EQ(Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting( - +[](void*) -> MobileGLResult { - MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidOperation), - "non-singleton owner"); - const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer}; - MG_Pipe::gMGPipeCallbacks.OnGpuWritten({41, 7}, 1, &whole); - const Uint8 bytes[] = {17, 34, 51, 68}; - MG_Pipe::gMGPipeCallbacks.OnBufferWriteback({41, 7}, 20, - MG_Pipe::MGPBlobRef{reinterpret_cast(bytes), sizeof(bytes), - MG_Pipe::kMGHostSpanSegNone, 0}); - MG_Pipe::MGPSurfaceInfo surface{}; - surface.Width = 321; - surface.Height = 123; - surface.InternalFormat = static_cast(TextureInternalFormat::RGBA8); - surface.Samples = 4; - surface.Layers = 2; - surface.IsDefault = 1; - MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged(&surface); - return MOBILEGL_OK; - }, nullptr), MOBILEGL_OK); + +[](void*) -> MobileGLResult { + MG_Pipe::gMGPipeCallbacks.OnGlError(static_cast(ErrorCode::InvalidOperation), + "non-singleton owner"); + const MG_Pipe::MGPRange whole{0, MG_Pipe::kMGPipeWholeBuffer}; + MG_Pipe::gMGPipeCallbacks.OnGpuWritten({41, 7}, 1, &whole); + const Uint8 bytes[] = {17, 34, 51, 68}; + MG_Pipe::gMGPipeCallbacks.OnBufferWriteback({41, 7}, 20, + MG_Pipe::MGPBlobRef{reinterpret_cast(bytes), + sizeof(bytes), + MG_Pipe::kMGHostSpanSegNone, 0}); + MG_Pipe::MGPSurfaceInfo surface{}; + surface.Width = 321; + surface.Height = 123; + surface.InternalFormat = static_cast(TextureInternalFormat::RGBA8); + surface.Samples = 4; + surface.Layers = 2; + surface.IsDefault = 1; + MG_Pipe::gMGPipeCallbacks.OnSurfaceChanged(&surface); + return MOBILEGL_OK; + }, + nullptr), + MOBILEGL_OK); - Transport::EventRingConsumer events(fixture.clientSegments.EventControl(), - fixture.clientSegments.CmdControl(), fixture.clientSegments.EventRingBase(), - fixture.clientSegments.EventRingCapacity(), fixture.clientSegments.EventSegmentBase()); + Transport::EventRingConsumer events(fixture.clientSegments.EventControl(), fixture.clientSegments.CmdControl(), + fixture.clientSegments.EventRingBase(), + fixture.clientSegments.EventRingCapacity(), + fixture.clientSegments.EventSegmentBase()); ASSERT_TRUE(events.Valid()); Transport::RingRecordView event; ASSERT_TRUE(events.Pop(event)); @@ -2986,15 +3003,25 @@ TEST(P5fReverseChannel, CachedReverseCallbacksRejectAMissingSessionOwner) { const char* names[] = {"OnGlError", "OnGpuWritten", "OnBufferWriteback", "OnSurfaceChanged"}; for (Uint32 callback = 0; callback < 4; ++callback) { const auto logSize = ReadLog().size(); - EXPECT_EXIT({ - switch (callback) { - case 0: callbacks.OnGlError(0, "retired"); break; - case 1: callbacks.OnGpuWritten(MG_Pipe::kMGPipeNullHandle, 0, nullptr); break; - case 2: callbacks.OnBufferWriteback(MG_Pipe::kMGPipeNullHandle, 0, MG_Pipe::MGPBlobRef{}); break; - case 3: callbacks.OnSurfaceChanged(nullptr); break; - } - std::_Exit(9); - }, ::testing::KilledBySignal(SIGABRT), ""); + EXPECT_EXIT( + { + switch (callback) { + case 0: + callbacks.OnGlError(0, "retired"); + break; + case 1: + callbacks.OnGpuWritten(MG_Pipe::kMGPipeNullHandle, 0, nullptr); + break; + case 2: + callbacks.OnBufferWriteback(MG_Pipe::kMGPipeNullHandle, 0, MG_Pipe::MGPBlobRef{}); + break; + case 3: + callbacks.OnSurfaceChanged(nullptr); + break; + } + std::_Exit(9); + }, + ::testing::KilledBySignal(SIGABRT), ""); const auto marker = String(names[callback]) + ".session-missing"; EXPECT_NE(ReadLog().substr(logSize).find(marker), String::npos) << marker; } @@ -3048,8 +3075,7 @@ namespace { barrier.Bits = 0x2000u; (void)fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::ObjectDeath, &kind, sizeof(kind)); (void)fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::ObjectDeath, &nullHandle, sizeof(nullHandle)); - const Uint64 last = - fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)); + const Uint64 last = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier)); if (last == Codec::kInvalidSeq) ::_exit(66); fixture.encoder.Publish(); fixture.producer.PublishAndNotify(last); @@ -3140,8 +3166,9 @@ namespace { "MGPipe: Fatal{ProtocolCorruption, \"unit.first\"} - the first " "named fault (%d)", 1); - const bool second = Remote::SessionLatch(Remote::MGFatalFamily::UnmigratedVerb, - "MGPipe: Fatal{UnmigratedVerb, \"unit.second\"} - a later one (%d)", 2); + const bool second = + Remote::SessionLatch(Remote::MGFatalFamily::UnmigratedVerb, + "MGPipe: Fatal{UnmigratedVerb, \"unit.second\"} - a later one (%d)", 2); int failed = 0; if (first || second) failed |= kSemReturned; if (!Remote::SessionLatched()) failed |= kSemNotLatched; @@ -3193,8 +3220,7 @@ namespace { const Uint64 mask = fixture.clientSegments.CmdRingCapacity() - 1; auto* header = reinterpret_cast(ring + ((fixture.cmd.LocalHead() - total) & mask)); - if (header->size != total || header->kind != static_cast(MG_Pipe::MGPWireOp::Clear)) - ::_exit(68); + if (header->size != total || header->kind != static_cast(MG_Pipe::MGPWireOp::Clear)) ::_exit(68); header->size = 8; fixture.encoder.Publish(); fixture.producer.PublishAndNotify(seq); @@ -3258,8 +3284,8 @@ namespace { const MG_Pipe::MGPDrawRange range{0, 3, 0}; Uint64 last = Codec::kInvalidSeq; for (int i = 0; i < 3; ++i) { - last = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, - sizeof(range)); + last = + fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); if (last == Codec::kInvalidSeq) ::_exit(66); } fixture.encoder.Publish(); @@ -3327,9 +3353,10 @@ TEST(ServerLoopLatchTest, ARecordShorterThanItsTypeIsLatchedBeforeTheApplierStam } // Codex closeout finding 6: a latch ANOTHER thread stores between two records of one batch, before the -// next pop's check, stops that pop (the check-to-pop span itself stays open - narrowed, not closed). Red with DrainRing's pre-pop check put back to the F2 shape (a check at the function's -// top and one under `++applied;`, the hook where it is now): the latch lands after the first -// record's checks, the drain pops and applies the second draw, and only then looks again (bits 2|8). +// next pop's check, stops that pop (the check-to-pop span itself stays open - narrowed, not closed). Red with +// DrainRing's pre-pop check put back to the F2 shape (a check at the function's top and one under `++applied;`, the +// hook where it is now): the latch lands after the first record's checks, the drain pops and applies the second draw, +// and only then looks again (bits 2|8). TEST(ServerLoopLatchTest, ALatchFromAnotherThreadBetweenTwoRecordsStopsTheNextPop) { EXPECT_EXIT(LatchFromAnotherThreadBetweenTwoRecordsAndExit(), ::testing::ExitedWithCode(0), ".*") << "bits: 1 = the apply thread was still running 3 s after the latch; 2 = a record behind the " @@ -3405,7 +3432,8 @@ namespace { ServerWindowProbe probe; probe.width = 64; probe.height = 48; - if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) ::_exit(66); + if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) + ::_exit(66); int failed = 0; if (probe.rc != MOBILEGL_OK || probe.lease.window != &g_fakeServerWindow || !probe.onApplyThread) failed |= kLostNotLeased; @@ -3461,7 +3489,8 @@ namespace { ServerWindowProbe probe; probe.width = 64; probe.height = 48; - if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) ::_exit(66); + if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) + ::_exit(66); int failed = 0; if (probe.rc != MOBILEGL_OK || probe.lease.window != &g_fakeServerWindow) failed |= kEndingNotLeased; if (!fixture.WaitUntilTrulyParked()) ::_exit(67); @@ -3475,8 +3504,8 @@ namespace { std::atomic fresh{-1}; std::thread freshSession([&] { Server::ServerWindowLease lease; - fresh.store(static_cast(display.AcquireFor(0, 0, 5000, &g_freshServerWindowHolder, nullptr, nullptr, - nullptr, &lease))); + fresh.store(static_cast( + display.AcquireFor(0, 0, 5000, &g_freshServerWindowHolder, nullptr, nullptr, nullptr, &lease))); }); std::this_thread::sleep_for(std::chrono::milliseconds(200)); if (fresh.load() == static_cast(Server::ServerWindowAcquire::LeasedElsewhere)) failed |= kEndingRefused; @@ -3509,12 +3538,12 @@ namespace { constexpr int kStreamRecords = 200; constexpr int kStreamRecordMs = 20; enum : int { - kStreamNotLeased = 1, // the probe could not take the lease - kStreamDetach = 2, // Detach did not answer ReleasedBySession - kStreamDetachSlow = 4, // Detach took 1 s or more (the batch ran on under it) - kStreamDrainedOn = 8, // more than two records were applied (the stream was not cut) - kStreamNotLatched = 16, // the session did not latch ServerWindowLost - kStreamThreadStayed = 32, // the apply thread was still running 3 s after the latch + kStreamNotLeased = 1, // the probe could not take the lease + kStreamDetach = 2, // Detach did not answer ReleasedBySession + kStreamDetachSlow = 4, // Detach took 1 s or more (the batch ran on under it) + kStreamDrainedOn = 8, // more than two records were applied (the stream was not cut) + kStreamNotLatched = 16, // the session did not latch ServerWindowLost + kStreamThreadStayed = 32, // the apply thread was still running 3 s after the latch }; std::atomic g_streamRecords{0}; @@ -3527,10 +3556,10 @@ namespace { g_streamDetachThread = std::thread([] { const auto started = std::chrono::steady_clock::now(); const Server::ServerWindowDetach detached = Server::ServerDisplayInstance().Detach(3000); - g_streamDetachMs.store(std::chrono::duration_cast( - std::chrono::steady_clock::now() - started) - .count(), - std::memory_order_release); + g_streamDetachMs.store( + std::chrono::duration_cast(std::chrono::steady_clock::now() - started) + .count(), + std::memory_order_release); g_streamDetachResult.store(static_cast(detached), std::memory_order_release); }); // Detach clears the window and calls the lost hook under one lock hold, so once the window @@ -3552,7 +3581,8 @@ namespace { ServerWindowProbe probe; probe.width = 64; probe.height = 48; - if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) ::_exit(66); + if (loop.RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe) != MOBILEGL_OK) + ::_exit(66); int failed = 0; if (probe.rc != MOBILEGL_OK || probe.lease.window != &g_fakeServerWindow) failed |= kStreamNotLeased; if (!fixture.WaitUntilTrulyParked()) ::_exit(67); @@ -3568,8 +3598,8 @@ namespace { const MG_Pipe::MGPDrawRange range{0, 3, 0}; Uint64 last = Codec::kInvalidSeq; for (int i = 0; i < kStreamRecords; ++i) { - last = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, - sizeof(range)); + last = + fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); if (last == Codec::kInvalidSeq) ::_exit(68); } fixture.encoder.Publish(); @@ -3584,11 +3614,10 @@ namespace { if (loop.DrainedRecords() > 2) failed |= kStreamDrainedOn; if (!Remote::SessionLatched() || Remote::SessionLatchedFamily() != Remote::MGFatalFamily::ServerWindowLost) failed |= kStreamNotLatched; - std::fprintf(stderr, "[stream] Detach answered %s in %lld ms after %llu of %d streamed records\n", - Server::ServerWindowDetachName( - static_cast(g_streamDetachResult.load())), - g_streamDetachMs.load(), static_cast(loop.DrainedRecords()), - kStreamRecords); + std::fprintf( + stderr, "[stream] Detach answered %s in %lld ms after %llu of %d streamed records\n", + Server::ServerWindowDetachName(static_cast(g_streamDetachResult.load())), + g_streamDetachMs.load(), static_cast(loop.DrainedRecords()), kStreamRecords); loop.SetBetweenRecordsHookForTesting(nullptr); fixture.Stop(); ::_exit(failed); @@ -3662,7 +3691,8 @@ namespace { [[noreturn]] void ApplyNothingAfterAGpuFaultAndExit() { Remote::ArmSessionLatch(); - Remote::InstallPipeSessionFailHook(); // the MG_Pipe latch seam the backends latch through (InitServerRoleCommon) + Remote::InstallPipeSessionFailHook(); // the MG_Pipe latch seam the backends latch through + // (InitServerRoleCommon) ServerFixture fixture; if (!fixture.Handshake()) ::_exit(64); if (Server::ServerLoopInstance().InstallBackendForTesting(MakeUnique()) != MOBILEGL_OK) @@ -3699,8 +3729,9 @@ namespace { std::strstr(Remote::SessionLatchedLine(), "\"unit:gpu-hang\"") == nullptr) failed |= kFaultNotLatched; if (GpuFaultBackendDouble::asked.load() == 0) failed |= kFaultNotAsked; - std::fprintf(stderr, "[gpu-fault] presents %d, asked %d, drained %llu\n", GpuFaultBackendDouble::presents.load(), - GpuFaultBackendDouble::asked.load(), static_cast(loop.DrainedRecords())); + std::fprintf(stderr, "[gpu-fault] presents %d, asked %d, drained %llu\n", + GpuFaultBackendDouble::presents.load(), GpuFaultBackendDouble::asked.load(), + static_cast(loop.DrainedRecords())); fixture.Stop(); ::_exit(failed); } @@ -3777,7 +3808,8 @@ namespace { static void Present() { presents.fetch_add(1, std::memory_order_acq_rel); - if (window.load(std::memory_order_acquire) == nullptr) presentsWithoutWindow.fetch_add(1, std::memory_order_acq_rel); + if (window.load(std::memory_order_acquire) == nullptr) + presentsWithoutWindow.fetch_add(1, std::memory_order_acq_rel); } static inline std::atomic window{nullptr}; @@ -3859,13 +3891,14 @@ namespace { } enum : int { - kSuspendDetach = 1, // Detach did not answer ReleasedBySession - kSuspendLatched = 2, // the session latched, or the apply thread left - kSuspendBackend = 4, // the backend was not asked to suspend exactly once, on the apply thread, for the lost window - kSuspendState = 8, // the loop's counters / the display's lease and wait are not the suspended state - kSuspendRecords = 16, // presents after the loss were not applied (all of them, none reaching a window) - kSuspendResume = 32, // a new window did not resume the session on it, on the apply thread - kSuspendAgain = 64, // the resumed session did not suspend again on the next loss + kSuspendDetach = 1, // Detach did not answer ReleasedBySession + kSuspendLatched = 2, // the session latched, or the apply thread left + kSuspendBackend = + 4, // the backend was not asked to suspend exactly once, on the apply thread, for the lost window + kSuspendState = 8, // the loop's counters / the display's lease and wait are not the suspended state + kSuspendRecords = 16, // presents after the loss were not applied (all of them, none reaching a window) + kSuspendResume = 32, // a new window did not resume the session on it, on the apply thread + kSuspendAgain = 64, // the resumed session did not suspend again on the next loss kSuspendNotCancelled = 128, // the session's end left it waiting for a window }; @@ -3881,7 +3914,8 @@ namespace { if (display.Detach(3000) != Server::ServerWindowDetach::ReleasedBySession) failed |= kSuspendDetach; if (CountingWindowHooks::releases.load() != 1) failed |= kSuspendDetach; if (Remote::SessionLatched() || !loop.Running()) failed |= kSuspendLatched; - if (WindowBackendDouble::suspends.load() != 1 || WindowBackendDouble::suspendedOn.load() != &g_fakeServerWindow || + if (WindowBackendDouble::suspends.load() != 1 || + WindowBackendDouble::suspendedOn.load() != &g_fakeServerWindow || !WindowBackendDouble::suspendOnApplyThread.load() || WindowBackendDouble::releases.load() != 0) failed |= kSuspendBackend; if (loop.ServerWindowsSuspended() != 1 || loop.ServerWindowsLost() != 0 || !loop.ServerWindowSuspended() || @@ -3897,7 +3931,8 @@ namespace { // The screen comes back: a NEW window. display.Attach(&g_secondFakeServerWindow, 64, 48); if (!PollUntil([&] { return loop.ServerWindowsResumed() == 1; }, 3000) || - WindowBackendDouble::resumes.load() != 1 || WindowBackendDouble::window.load() != &g_secondFakeServerWindow || + WindowBackendDouble::resumes.load() != 1 || + WindowBackendDouble::window.load() != &g_secondFakeServerWindow || WindowBackendDouble::resumeWidth.load() != 64 || WindowBackendDouble::resumeHeight.load() != 48 || !WindowBackendDouble::resumeOnApplyThread.load() || loop.ServerWindowSuspended() || !display.Leased() || display.SuspendedHolders() != 0) @@ -3909,8 +3944,8 @@ namespace { if (!fixture.WaitUntilTrulyParked()) ::_exit(251); if (display.Detach(3000) != Server::ServerWindowDetach::ReleasedBySession || WindowBackendDouble::suspends.load() != 2 || - WindowBackendDouble::suspendedOn.load() != &g_secondFakeServerWindow || loop.ServerWindowsSuspended() != 2 || - Remote::SessionLatched()) + WindowBackendDouble::suspendedOn.load() != &g_secondFakeServerWindow || + loop.ServerWindowsSuspended() != 2 || Remote::SessionLatched()) failed |= kSuspendAgain; fixture.Stop(); @@ -3962,10 +3997,10 @@ namespace { g_suspendStreamDetachThread = std::thread([] { const auto started = std::chrono::steady_clock::now(); const Server::ServerWindowDetach detached = Server::ServerDisplayInstance().Detach(3000); - g_suspendStreamDetachMs.store(std::chrono::duration_cast( - std::chrono::steady_clock::now() - started) - .count(), - std::memory_order_release); + g_suspendStreamDetachMs.store( + std::chrono::duration_cast(std::chrono::steady_clock::now() - started) + .count(), + std::memory_order_release); g_suspendStreamDetachResult.store(static_cast(detached), std::memory_order_release); }); (void)PollUntil([] { return !Server::ServerDisplayInstance().Attached(); }, 1000); @@ -3974,10 +4009,10 @@ namespace { } enum : int { - kSuspendStreamDetach = 1, // Detach did not answer ReleasedBySession - kSuspendStreamSlow = 2, // Detach took 1 s or more - kSuspendStreamCut = 4, // the stream was not applied to its end - kSuspendStreamLatched = 8, // the session latched or the apply thread left + kSuspendStreamDetach = 1, // Detach did not answer ReleasedBySession + kSuspendStreamSlow = 2, // Detach took 1 s or more + kSuspendStreamCut = 4, // the stream was not applied to its end + kSuspendStreamLatched = 8, // the session latched or the apply thread left kSuspendStreamNotSuspended = 16, // the session is not suspended exactly once }; @@ -3990,7 +4025,8 @@ namespace { const Uint64 drainedBefore = loop.DrainedRecords(); int failed = 0; if (!PresentAndWait(fixture, 1, kStreamRecords, 15000)) failed |= kSuspendStreamCut; - if (!PollUntil([] { return g_suspendStreamDetachResult.load(std::memory_order_acquire) >= 0; }, 8000)) ::_exit(251); + if (!PollUntil([] { return g_suspendStreamDetachResult.load(std::memory_order_acquire) >= 0; }, 8000)) + ::_exit(251); if (g_suspendStreamDetachThread.joinable()) g_suspendStreamDetachThread.join(); if (g_suspendStreamDetachResult.load() != static_cast(Server::ServerWindowDetach::ReleasedBySession)) failed |= kSuspendStreamDetach; @@ -4000,11 +4036,11 @@ namespace { failed |= kSuspendStreamCut; if (Remote::SessionLatched() || !loop.Running()) failed |= kSuspendStreamLatched; if (loop.ServerWindowsSuspended() != 1 || !loop.ServerWindowSuspended()) failed |= kSuspendStreamNotSuspended; - std::fprintf(stderr, "[suspend-stream] Detach answered %s in %lld ms; %llu of %d presents applied\n", - Server::ServerWindowDetachName( - static_cast(g_suspendStreamDetachResult.load())), - g_suspendStreamDetachMs.load(), - static_cast(loop.DrainedRecords() - drainedBefore), kStreamRecords); + std::fprintf( + stderr, "[suspend-stream] Detach answered %s in %lld ms; %llu of %d presents applied\n", + Server::ServerWindowDetachName(static_cast(g_suspendStreamDetachResult.load())), + g_suspendStreamDetachMs.load(), static_cast(loop.DrainedRecords() - drainedBefore), + kStreamRecords); loop.SetBetweenRecordsHookForTesting(nullptr); fixture.Stop(); ::_exit(failed); @@ -4037,7 +4073,8 @@ TEST(ServerLoopSuspendTest, ABackendThatCannotSuspendStillLatchesServerWindowLos // the suspend arm deleted: the stream is cut at the latch (bits 4|8|16). namespace { enum : int { - kResizeNotPublished = 1, // a resize with no present was not handed to the backend at the new size, on the apply thread + kResizeNotPublished = + 1, // a resize with no present was not handed to the backend at the new size, on the apply thread kResizeExtent = 2, // the extent a backend publishes (MGPipe's) did not follow the display kResizeRepeated = 4, // an unchanged report was published again kResizePresented = 8, // a present ran (the case is about publishing without one) @@ -4160,8 +4197,8 @@ namespace { const MG_Pipe::MGPDrawRange range{0, 3, 0}; Uint64 last = Codec::kInvalidSeq; for (int i = 0; i < 200; ++i) { - last = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, - sizeof(range)); + last = + fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); if (last == Codec::kInvalidSeq) ::_exit(66); } fixture.encoder.Publish(); @@ -4210,8 +4247,8 @@ namespace { info.MaxIndex = ~0u; info.NumDraws = 1; const MG_Pipe::MGPDrawRange range{0, 3, 0}; - const Uint64 last = fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, - sizeof(range)); + const Uint64 last = + fixture.encoder.EncodeRecord(MG_Pipe::MGPWireOp::DrawVbo, &info, sizeof(info), &range, sizeof(range)); if (last == Codec::kInvalidSeq) ::_exit(66); fixture.encoder.Publish(); fixture.producer.PublishAndNotify(last); @@ -4251,8 +4288,9 @@ TEST(ServerLoopTest, AServerWindowRequestOnAServerWithNoDisplayIsRefusedByNameWi ServerWindowProbe probe; probe.width = 640; probe.height = 480; - ASSERT_EQ(Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe), - MOBILEGL_OK); + ASSERT_EQ( + Server::ServerLoopInstance().RunProbeOnApplyThreadForTesting(&AcquireServerWindowOnTheApplyThread, &probe), + MOBILEGL_OK); EXPECT_EQ(probe.rc, MOBILEGL_ERR_UNSUPPORTED); EXPECT_EQ(probe.refusal, Server::SurfaceRefusalCode::NoServerDisplay); EXPECT_FALSE(MobileGL::MG_Remote::SessionLatched()); diff --git a/MobileGL/MG_Test/Wire/ServerOwnedSurfaceTest.cpp b/MobileGL/MG_Test/Wire/ServerOwnedSurfaceTest.cpp index 86fed7cd..c3565458 100644 --- a/MobileGL/MG_Test/Wire/ServerOwnedSurfaceTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerOwnedSurfaceTest.cpp @@ -34,9 +34,9 @@ namespace { namespace EGL = MobileGL::MG_Impl::EGLImpl; enum class Scenario { - ServerOwnedFirst, // the ServerOwned create, then a pbuffer - NullWindowWithoutKnob, // the knob unset: a NULL window, then a pbuffer - PbufferThenServerOwned, // a pbuffer first (offscreen), then the ServerOwned create + ServerOwnedFirst, // the ServerOwned create, then a pbuffer + NullWindowWithoutKnob, // the knob unset: a NULL window, then a pbuffer + PbufferThenServerOwned, // a pbuffer first (offscreen), then the ServerOwned create SurfacelessThenServerOwned, // a surfaceless make-current first, then the ServerOwned create }; @@ -66,8 +66,10 @@ namespace { ::setenv("MOBILEGL_TRANSPORT", "spawn", 1); ::setenv("MOBILEGL_IPC_CONTROL", server.endpoint.c_str(), 1); ::setenv("MOBILEGL_BACKEND_TYPE", "DirectGLES", 1); - if (surfaceKnob) ::setenv("MOBILEGL_IPC_SURFACE", "server", 1); - else ::unsetenv("MOBILEGL_IPC_SURFACE"); + if (surfaceKnob) + ::setenv("MOBILEGL_IPC_SURFACE", "server", 1); + else + ::unsetenv("MOBILEGL_IPC_SURFACE"); EglPeerReport r{}; const auto note = [&r](const char* text) { std::snprintf(r.note, sizeof(r.note), "%s", text); }; const EGLDisplay dpy = EGL::GetDisplay(EGL_DEFAULT_DISPLAY); @@ -102,8 +104,7 @@ namespace { r.madeCurrent = EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) == EGL_TRUE; (void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); tryWindow(); - r.madeCurrentAfter = - EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) == EGL_TRUE; + r.madeCurrentAfter = EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) == EGL_TRUE; } else if (scenario == Scenario::PbufferThenServerOwned) { tryPbuffer(); tryWindow(); @@ -133,7 +134,8 @@ namespace { } pollfd pfd{fds[0], POLLIN, 0}; if (::poll(&pfd, 1, 60000) > 0) { - if (::read(fds[0], &report, sizeof(report)) != static_cast(sizeof(report))) report = EglPeerReport{}; + if (::read(fds[0], &report, sizeof(report)) != static_cast(sizeof(report))) + report = EglPeerReport{}; } else { ::kill(pid, SIGKILL); } @@ -199,12 +201,15 @@ TEST(ServerOwnedSurface, ADisplaylessServerRefusesItByNameOnBothSidesAndTheSessi ASSERT_TRUE(WaitFor([&] { return !(reap = ReapLine(server, r.serverPid)).empty(); }, 15000)) << server.Log(); const std::string serverLog = server.Log(); EXPECT_NE(serverLog.find("Refuse ServerOwned: this server owns no display"), std::string::npos) - << "the server did not name its refusal:\n" << serverLog; - EXPECT_EQ(serverLog.find("Fatal{"), std::string::npos) << "the refusal latched or killed the session:\n" << serverLog; + << "the server did not name its refusal:\n" + << serverLog; + EXPECT_EQ(serverLog.find("Fatal{"), std::string::npos) << "the refusal latched or killed the session:\n" + << serverLog; EXPECT_NE(reap.find("exit=0 "), std::string::npos) << "the session did not end cleanly: " << reap; const std::string clientLog = client.Own(); EXPECT_NE(clientLog.find("Refuse ServerOwned (NoServerDisplay)"), std::string::npos) - << "the client did not name the refusal in its own log:\n" << clientLog; + << "the client did not name the refusal in its own log:\n" + << clientLog; // Not latched: the same session went on to its pbuffer. Its make-current needs headless EGL. EXPECT_EQ(r.pbufferCreated, 1) << "the session did not carry on after the refusal"; @@ -223,7 +228,8 @@ TEST(ServerOwnedSurface, WithoutTheKnobANullWindowIsTheClientsOwnRefusal) { EXPECT_EQ(r.windowCreated, 0); EXPECT_EQ(r.windowError, EGL_BAD_NATIVE_WINDOW); ASSERT_TRUE(WaitFor([&] { return !ReapLine(server, r.serverPid).empty(); }, 15000)) << server.Log(); - EXPECT_EQ(server.Log().find("ServerOwned"), std::string::npos) << "a NULL window without the knob reached the server"; + EXPECT_EQ(server.Log().find("ServerOwned"), std::string::npos) + << "a NULL window without the knob reached the server"; EXPECT_EQ(r.pbufferCreated, 1); } @@ -273,9 +279,11 @@ TEST(ServerOwnedSurface, ASurfacelessBindDoesNotLatchTheSessionOffscreen) { ASSERT_TRUE(WaitFor([&] { return !(reap = ReapLine(server, r.serverPid)).empty(); }, 15000)) << server.Log(); const std::string serverLog = server.Log(); EXPECT_NE(serverLog.find("surface=pbuffer 1x1 (surfaceless stand-in"), std::string::npos) - << "no stand-in arm proof:\n" << serverLog; + << "no stand-in arm proof:\n" + << serverLog; EXPECT_EQ(serverLog.find("SurfaceModeMismatch"), std::string::npos) - << "the surfaceless bind latched the session offscreen:\n" << serverLog; + << "the surfaceless bind latched the session offscreen:\n" + << serverLog; EXPECT_NE(serverLog.find("Refuse ServerOwned: this server owns no display"), std::string::npos) << serverLog; EXPECT_EQ(serverLog.find("Fatal{"), std::string::npos) << serverLog; EXPECT_NE(reap.find("exit=0 "), std::string::npos) << reap; diff --git a/MobileGL/MG_Test/Wire/ServerProbeTest.cpp b/MobileGL/MG_Test/Wire/ServerProbeTest.cpp index 549bf9a2..dc1f080c 100644 --- a/MobileGL/MG_Test/Wire/ServerProbeTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerProbeTest.cpp @@ -187,12 +187,12 @@ namespace { // ---- the entry points, each in a fresh process --------------------------------------------- struct EntryReport { - Int32 getDisplay = -1; // 1: eglGetDisplay gave a display - Int32 vendorDisplay = -1; // 1: the glvnd vendor's getPlatformDisplay did - Int32 vendorDevices = -1; // the vendor's eglQueryDevicesEXT count - Int32 glxLoaded = -1; // __glx_Main's answer - Int32 clientQueries = -1; // 1: client extensions and a proc address answered (no display needed) - Int64 elapsedMs = -1; // all of the above, end to end + Int32 getDisplay = -1; // 1: eglGetDisplay gave a display + Int32 vendorDisplay = -1; // 1: the glvnd vendor's getPlatformDisplay did + Int32 vendorDevices = -1; // the vendor's eglQueryDevicesEXT count + Int32 glxLoaded = -1; // __glx_Main's answer + Int32 clientQueries = -1; // 1: client extensions and a proc address answered (no display needed) + Int64 elapsedMs = -1; // all of the above, end to end }; EntryReport RunEntryPoints(const std::string& control, const std::string& logBase, bool software = false) { @@ -207,8 +207,10 @@ namespace { ::setenv("MOBILEGL_TRANSPORT", "spawn", 1); ::setenv("MOBILEGL_IPC_CONTROL", control.c_str(), 1); ::setenv("MOBILEGL_BACKEND_TYPE", "DirectGLES", 1); - if (software) ::setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); - else ::unsetenv("LIBGL_ALWAYS_SOFTWARE"); + if (software) + ::setenv("LIBGL_ALWAYS_SOFTWARE", "1", 1); + else + ::unsetenv("LIBGL_ALWAYS_SOFTWARE"); EntryReport r{}; const auto start = std::chrono::steady_clock::now(); @@ -220,7 +222,8 @@ namespace { EGL::QueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS) != nullptr && imports.getDispatchAddress("glClear") != nullptr && EGL::GetProcAddress("eglQueryDmaBufModifiersEXT") != nullptr; - r.vendorDisplay = vendor && imports.getPlatformDisplay(EGL_NONE, EGL_DEFAULT_DISPLAY, nullptr) != EGL_NO_DISPLAY; + r.vendorDisplay = + vendor && imports.getPlatformDisplay(EGL_NONE, EGL_DEFAULT_DISPLAY, nullptr) != EGL_NO_DISPLAY; r.getDisplay = EGL::GetDisplay(EGL_DEFAULT_DISPLAY) != EGL_NO_DISPLAY; if (vendor) { using QueryDevices = EGLBoolean (*)(EGLint, EGLDeviceEXT*, EGLint*); diff --git a/MobileGL/MG_Test/Wire/ServerSpawnTest.cpp b/MobileGL/MG_Test/Wire/ServerSpawnTest.cpp index a6ab9283..7ef7351f 100644 --- a/MobileGL/MG_Test/Wire/ServerSpawnTest.cpp +++ b/MobileGL/MG_Test/Wire/ServerSpawnTest.cpp @@ -100,8 +100,10 @@ namespace { ::setenv(key, value.c_str(), 1); } ~ScopedEnvironment() { - if (hadOldValue) ::setenv(name.c_str(), oldValue.c_str(), 1); - else ::unsetenv(name.c_str()); + if (hadOldValue) + ::setenv(name.c_str(), oldValue.c_str(), 1); + else + ::unsetenv(name.c_str()); } }; @@ -110,16 +112,13 @@ namespace { // valid; selected payload fields bypass the encoder's client-side checks. template bool EncodeThenMutateHeaderAndPublish(Client::ClientSession& session, MobileGL::MG_Pipe::MGPWireOp op, - const Payload& payload, Mutator&& mutate, - std::uint64_t* outSeq = nullptr, - const Wire::WireTail* tails = nullptr, - MobileGL::Uint32 tailCount = 0) { + const Payload& payload, Mutator&& mutate, std::uint64_t* outSeq = nullptr, + const Wire::WireTail* tails = nullptr, MobileGL::Uint32 tailCount = 0) { if (outSeq != nullptr) *outSeq = Wire::kInvalidSeq; auto* link = session.DataLink(); if (link == nullptr || !link->Attached()) return false; Wire::WireRecordLayout layout{}; - if (!Wire::MGPipeWireRecordLayout(op, &payload, layout) || - layout.PayloadBytes != sizeof(payload)) return false; + if (!Wire::MGPipeWireRecordLayout(op, &payload, layout) || layout.PayloadBytes != sizeof(payload)) return false; auto& encoder = session.Encoder(); const std::uint64_t seq = tailCount == 0 ? encoder.EncodeRecord(op, &payload, sizeof(payload)) @@ -128,13 +127,12 @@ namespace { auto& commands = link->CommandsOut(); const auto* arena = link->RecordArena(); const std::uint64_t head = commands.LocalHead(); - if (arena == nullptr || arena->Base == nullptr || arena->Capacity == 0 || - head < layout.TotalBytes) return false; + if (arena == nullptr || arena->Base == nullptr || arena->Capacity == 0 || head < layout.TotalBytes) + return false; const std::uint64_t start = head - layout.TotalBytes; auto* bytes = static_cast(arena->Base) + (start & arena->Mask); auto* header = reinterpret_cast(bytes); - if (header->kind != static_cast(op) || header->size != layout.TotalBytes) - return false; + if (header->kind != static_cast(op) || header->size != layout.TotalBytes) return false; mutate(*header, bytes + sizeof(Transport::RingRecordHeader)); // Use the real session publisher after mutation so both the ring head // and the producer-owned submittedSeq stay monotone and teardown knows @@ -157,27 +155,24 @@ namespace { return session.WaitForApplied(seq, 5000) == Transport::SessionWait::Reached; } - MobileGL::MG_Pipe::MGPFramebufferState RawColorAttachmentFramebuffer( - MobileGL::MG_Pipe::MGPipeHandle fbo, MobileGL::MG_Pipe::MGPipeHandle texture, - MobileGL::Uint64 contentHash) { + MobileGL::MG_Pipe::MGPFramebufferState RawColorAttachmentFramebuffer(MobileGL::MG_Pipe::MGPipeHandle fbo, + MobileGL::MG_Pipe::MGPipeHandle texture, + MobileGL::Uint64 contentHash) { MobileGL::MG_Pipe::MGPFramebufferState state{}; state.Fbo = fbo; - state.Target = static_cast( - MobileGL::MG_Pipe::MGPipeFramebufferTarget::Both); + state.Target = static_cast(MobileGL::MG_Pipe::MGPipeFramebufferTarget::Both); state.IsDefault = 0; state.Complete = 1; state.Width = state.Height = state.Layers = state.Samples = 1; state.FixedSampleLocations = 1; - for (auto& drawBuffer : state.DrawBuffers) drawBuffer = -1; + for (auto& drawBuffer : state.DrawBuffers) + drawBuffer = -1; state.DrawBuffers[0] = 0; state.Color[0].Res = texture; - state.Color[0].InternalFormat = - static_cast(MobileGL::TextureInternalFormat::RGBA8); + state.Color[0].InternalFormat = static_cast(MobileGL::TextureInternalFormat::RGBA8); state.Color[0].Kind = MobileGL::MG_Pipe::kMGPipeSurfaceKindTexture; - state.Color[0].UploadTarget = - static_cast(MobileGL::TextureUploadTarget::Texture2D); - state.Color[0].TextureTarget = - static_cast(MobileGL::TextureTarget::Texture2D); + state.Color[0].UploadTarget = static_cast(MobileGL::TextureUploadTarget::Texture2D); + state.Color[0].TextureTarget = static_cast(MobileGL::TextureTarget::Texture2D); state.ContentHash = contentHash; return state; } @@ -262,10 +257,8 @@ namespace { return Client::ClientSessionInstance().StartOverSocket(std::move(session.client)); } - std::string Exchange(Transport::ITransport& transport, const std::string& message, - std::uint32_t timeoutMs = 4000) { - const MobileGLResult sent = - transport.SendFrame(MobileGLByteSpan{message.data(), message.size()}); + std::string Exchange(Transport::ITransport& transport, const std::string& message, std::uint32_t timeoutMs = 4000) { + const MobileGLResult sent = transport.SendFrame(MobileGLByteSpan{message.data(), message.size()}); if (sent != MOBILEGL_OK) { return ""; } @@ -276,8 +269,7 @@ namespace { if (got != MOBILEGL_OK) { return std::string("(got)) + ">"; } - return std::string(reinterpret_cast(buffer.data()), - static_cast(size)); + return std::string(reinterpret_cast(buffer.data()), static_cast(size)); } } // namespace @@ -322,8 +314,7 @@ TEST(ServerSpawnTest, ARawPeerMutatesARecordAfterTheClientEncoderAcceptedIt) { const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str()); EXPECT_NE(log.find("Fatal{ProtocolCorruption, \"opcode\"} got=65535"), std::string::npos) << "the server process exited without the named opcode fault"; - EXPECT_NE(log.find("SessionLatch{ProtocolCorruption}"), std::string::npos) - << "the fault was not latched"; + EXPECT_NE(log.find("SessionLatch{ProtocolCorruption}"), std::string::npos) << "the fault was not latched"; EXPECT_EQ(log.find("protocol corruption applying"), std::string::npos) << "the record reached the generated gate's unnamed death"; client.Stop(); @@ -351,7 +342,8 @@ TEST(ServerSpawnTest, RawPeerOutOfRangeRenderStateCsoSlotGetsNamedProtocolCorrup [](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) { auto* wireDesc = reinterpret_cast(payloadBytes); wireDesc->Cso.Slot = MobileGL::MG_Pipe::kMGPipeMaxRenderStateCsoSlots; - })) << "the test peer failed to mutate and publish the encoder-accepted record bytes"; + })) + << "the test peer failed to mutate and publish the encoder-accepted record bytes"; EXPECT_LT(encoderAcceptedSlot, MobileGL::MG_Pipe::kMGPipeMaxRenderStateCsoSlots); int exitCode = -1; @@ -366,8 +358,7 @@ TEST(ServerSpawnTest, RawPeerOutOfRangeRenderStateCsoSlotGetsNamedProtocolCorrup ASSERT_EQ(reaped, MOBILEGL_OK) << "server did not reject the mutated record promptly"; EXPECT_EQ(exitCode, -SIGABRT) << "the malformed CSO slot did not reach the server applier"; const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str()); - EXPECT_NE(log.find("Fatal{ProtocolCorruption, \"CreateRenderState.Cso.Slot\"}"), - std::string::npos) + EXPECT_NE(log.find("Fatal{ProtocolCorruption, \"CreateRenderState.Cso.Slot\"}"), std::string::npos) << "the server process exited without the named protocol corruption diagnostic"; client.Stop(); } @@ -400,7 +391,9 @@ TEST(ServerSpawnTest, RawPeerOversizedReadbackGetsErrorAndSessionContinues) { wireInfo->Box.W = width; wireInfo->Box.H = 1; wireInfo->DstSize = static_cast(width) * 4; - }, &readSeq)) << "the peer failed to mutate and publish the encoder-accepted readback"; + }, + &readSeq)) + << "the peer failed to mutate and publish the encoder-accepted readback"; ASSERT_NE(readSeq, Wire::kInvalidSeq); ASSERT_EQ(client.WaitForApplied(readSeq, 5000), Transport::SessionWait::Reached); @@ -414,9 +407,8 @@ TEST(ServerSpawnTest, RawPeerOversizedReadbackGetsErrorAndSessionContinues) { // server session or stranding the command consumer. MobileGL::MG_Pipe::MGPMemoryBarrier barrier{}; barrier.Bits = 0x2000u; - const auto followupSeq = client.EmitAndWait( - MobileGL::MG_Pipe::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier), - nullptr, 0, nullptr, 0, nullptr); + const auto followupSeq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::MemoryBarrier, &barrier, sizeof(barrier), + nullptr, 0, nullptr, 0, nullptr); ASSERT_NE(followupSeq, Wire::kInvalidSeq); EXPECT_EQ(client.WaitForApplied(followupSeq, 5000), Transport::SessionWait::Reached); Client::ClientSessionInstance().Stop(); @@ -448,8 +440,8 @@ TEST(ServerSpawnTest, RawPeerTextureRunPastDeclaredLevelBoundIsNamedProtocolCorr desc.Samples = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -457,12 +449,13 @@ TEST(ServerSpawnTest, RawPeerTextureRunPastDeclaredLevelBoundIsNamedProtocolCorr MobileGL::MG_Pipe::MGPResourceDesc respecify = desc; respecify.HasDefinedContent = 1; MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel( - respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget( - static_cast(respecify.Target), - static_cast(MobileGL::TextureUploadTarget::Texture2D)), + respecify, + MobileGL::MG_Pipe::MGPipePackSubDataTarget( + static_cast(respecify.Target), + static_cast(MobileGL::TextureUploadTarget::Texture2D)), 0, 4, 4, 1); - seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, - sizeof(respecify), nullptr, 0, nullptr, 0, &status); + seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -502,7 +495,8 @@ TEST(ServerSpawnTest, RawPeerTextureRunPastDeclaredLevelBoundIsNamedProtocolCorr payloadBytes + sizeof(MobileGL::MG_Pipe::MGPSubData)); wireRegion->SrcRowStride = 32; wireRegion->SrcSliceStride = 96; - }, &uploadSeq, &tail, 1)) + }, + &uploadSeq, &tail, 1)) << "the peer failed to publish the encoder-accepted texture run"; ASSERT_NE(uploadSeq, Wire::kInvalidSeq); @@ -520,10 +514,8 @@ TEST(ServerSpawnTest, RawPeerTextureRunPastDeclaredLevelBoundIsNamedProtocolCorr const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str()); const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption"); const auto boundFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.CopyRunInto\"}"); - EXPECT_NE(boundFatal, std::string::npos) - << "the server did not reject the run at the declared level byte bound"; - EXPECT_EQ(firstProtocolFatal, boundFatal) - << "the run was refused before it reached the staged-level byte bound"; + EXPECT_NE(boundFatal, std::string::npos) << "the server did not reject the run at the declared level byte bound"; + EXPECT_EQ(firstProtocolFatal, boundFatal) << "the run was refused before it reached the staged-level byte bound"; client.Stop(); } @@ -549,8 +541,8 @@ TEST(ServerSpawnTest, RawPeerTextureSubDataMustMatchTheAcceptedMutableLevelExten desc.Levels = 1; desc.Samples = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -558,12 +550,13 @@ TEST(ServerSpawnTest, RawPeerTextureSubDataMustMatchTheAcceptedMutableLevelExten MobileGL::MG_Pipe::MGPResourceDesc respecify = desc; respecify.HasDefinedContent = 1; MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel( - respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget( - static_cast(respecify.Target), - static_cast(MobileGL::TextureUploadTarget::Texture2D)), + respecify, + MobileGL::MG_Pipe::MGPipePackSubDataTarget( + static_cast(respecify.Target), + static_cast(MobileGL::TextureUploadTarget::Texture2D)), 0, 4, 4, 1); - seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, - sizeof(respecify), nullptr, 0, nullptr, 0, &status); + seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -588,7 +581,8 @@ TEST(ServerSpawnTest, RawPeerTextureSubDataMustMatchTheAcceptedMutableLevelExten // Still within the device limit and still contains the dirty box; the server must // refuse it because ResourceRespecify accepted 4x4 for this exact level. wireUpload->LevelWidth = 8; - }, &uploadSeq)) + }, + &uploadSeq)) << "the peer failed to publish the encoder-accepted texture record"; ASSERT_NE(uploadSeq, Wire::kInvalidSeq); @@ -608,8 +602,7 @@ TEST(ServerSpawnTest, RawPeerTextureSubDataMustMatchTheAcceptedMutableLevelExten const auto extentFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.LevelExtent\"}"); EXPECT_NE(extentFatal, std::string::npos) << "server did not refuse the upload extent that disagreed with ResourceRespecify"; - EXPECT_EQ(firstProtocolFatal, extentFatal) - << "the record was refused before staged-level extent matching"; + EXPECT_EQ(firstProtocolFatal, extentFatal) << "the record was refused before staged-level extent matching"; client.Stop(); } @@ -636,16 +629,16 @@ TEST(ServerSpawnTest, RawPeerMultisampleTextureSubDataIsNamedProtocolCorruption) desc.Samples = 4; desc.Immutable = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); MobileGL::MG_Pipe::MGPResourceDesc respecify = desc; respecify.HasDefinedContent = 1; - seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, - sizeof(respecify), nullptr, 0, nullptr, 0, &status); + seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceRespecify, &respecify, sizeof(respecify), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -684,8 +677,7 @@ TEST(ServerSpawnTest, RawPeerMultisampleTextureSubDataIsNamedProtocolCorruption) const auto targetFatal = log.find("Fatal{ProtocolCorruption, \"StagedTextureStore.Target\"}"); EXPECT_NE(targetFatal, std::string::npos) << "server did not refuse multisample bytes through the staged texture target gate"; - EXPECT_EQ(firstProtocolFatal, targetFatal) - << "multisample record was refused before the staged target gate"; + EXPECT_EQ(firstProtocolFatal, targetFatal) << "multisample record was refused before the staged target gate"; client.Stop(); } @@ -711,8 +703,8 @@ TEST(ServerSpawnTest, RawPeerNoncanonicalMutableExtentCarrierIsRefused) { desc.Levels = 1; desc.Samples = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -720,9 +712,10 @@ TEST(ServerSpawnTest, RawPeerNoncanonicalMutableExtentCarrierIsRefused) { MobileGL::MG_Pipe::MGPResourceDesc respecify = desc; respecify.HasDefinedContent = 1; MobileGL::MG_Pipe::MGPipeSetRespecifiedLevel( - respecify, MobileGL::MG_Pipe::MGPipePackSubDataTarget( - static_cast(respecify.Target), - static_cast(MobileGL::TextureUploadTarget::Texture2D)), + respecify, + MobileGL::MG_Pipe::MGPipePackSubDataTarget( + static_cast(respecify.Target), + static_cast(MobileGL::TextureUploadTarget::Texture2D)), 0, 4, 4, 1); std::uint64_t respecifySeq = Wire::kInvalidSeq; ASSERT_TRUE(EncodeThenMutateHeaderAndPublish( @@ -730,7 +723,8 @@ TEST(ServerSpawnTest, RawPeerNoncanonicalMutableExtentCarrierIsRefused) { [](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) { auto* wireDesc = reinterpret_cast(payloadBytes); wireDesc->BufSize |= (std::uint64_t{1} << 32); - }, &respecifySeq)); + }, + &respecifySeq)); ASSERT_NE(respecifySeq, Wire::kInvalidSeq); int exitCode = -1; @@ -749,8 +743,7 @@ TEST(ServerSpawnTest, RawPeerNoncanonicalMutableExtentCarrierIsRefused) { const auto carrierFatal = log.find("Fatal{ProtocolCorruption, \"ResourceRespecify.ExtentCarrier\"}"); EXPECT_NE(carrierFatal, std::string::npos) << "server did not reject high BufSize bits in the mutable extent carrier"; - EXPECT_EQ(firstProtocolFatal, carrierFatal) - << "carrier was refused before the canonicality gate"; + EXPECT_EQ(firstProtocolFatal, carrierFatal) << "carrier was refused before the canonicality gate"; client.Stop(); } @@ -776,8 +769,8 @@ TEST(ServerSpawnTest, RawPeerWholeImageRespecifyCannotSmuggleBufferRangeFields) desc.Levels = 1; desc.Samples = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); @@ -790,7 +783,8 @@ TEST(ServerSpawnTest, RawPeerWholeImageRespecifyCannotSmuggleBufferRangeFields) [](Transport::RingRecordHeader&, std::uint8_t* payloadBytes) { auto* wireDesc = reinterpret_cast(payloadBytes); wireDesc->BufOffset = 1; - }, &respecifySeq)); + }, + &respecifySeq)); ASSERT_NE(respecifySeq, Wire::kInvalidSeq); int exitCode = -1; @@ -807,10 +801,8 @@ TEST(ServerSpawnTest, RawPeerWholeImageRespecifyCannotSmuggleBufferRangeFields) const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str()); const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption"); const auto rangeFatal = log.find("Fatal{ProtocolCorruption, \"ResourceRespecify.BufferRange\"}"); - EXPECT_NE(rangeFatal, std::string::npos) - << "server did not reject BufOffset/BufSize on the whole image descriptor"; - EXPECT_EQ(firstProtocolFatal, rangeFatal) - << "descriptor was refused before the canonical buffer-range gate"; + EXPECT_NE(rangeFatal, std::string::npos) << "server did not reject BufOffset/BufSize on the whole image descriptor"; + EXPECT_EQ(firstProtocolFatal, rangeFatal) << "descriptor was refused before the canonical buffer-range gate"; client.Stop(); } @@ -827,13 +819,12 @@ TEST(ServerSpawnTest, RawPeerFramebufferAttachmentPastBackendSlotLimitIsNamedPro const MobileGL::MG_Pipe::MGPipeHandle fbo{20u, 1u}; const MobileGL::MG_Pipe::MGPipeHandle outOfRange{1u << 20, 1u}; const auto framebuffer = RawColorAttachmentFramebuffer(fbo, outOfRange, 1); - ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, - framebuffer)); + ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, framebuffer)); const auto clear = RawColorClear(fbo); std::uint64_t clearSeq = Wire::kInvalidSeq; ASSERT_TRUE(EncodeThenMutateHeaderAndPublish( - client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear, - [](Transport::RingRecordHeader&, std::uint8_t*) {}, &clearSeq)); + client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear, [](Transport::RingRecordHeader&, std::uint8_t*) {}, + &clearSeq)); ASSERT_NE(clearSeq, Wire::kInvalidSeq); // This final clear is expected to kill the server, so there is no applied watermark to wait on. @@ -853,8 +844,7 @@ TEST(ServerSpawnTest, RawPeerFramebufferAttachmentPastBackendSlotLimitIsNamedPro const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption"); const auto slotFatal = log.find("Fatal{ProtocolCorruption, \"BackendSlotTable.HandleSlot\"}"); EXPECT_NE(slotFatal, std::string::npos) << "server did not name the backend texture slot bound"; - EXPECT_EQ(firstProtocolFatal, slotFatal) - << "the peer record was refused before the backend twin adoption guard"; + EXPECT_EQ(firstProtocolFatal, slotFatal) << "the peer record was refused before the backend twin adoption guard"; client.Stop(); } @@ -878,16 +868,15 @@ TEST(ServerSpawnTest, RawPeerFramebufferAttachmentBackwardGenerationIsNamedProto desc.Width = desc.Height = desc.Depth = 1; desc.ArrayLayers = desc.Levels = desc.Samples = 1; MobileGL::Int32 status = Wire::ReplySink::kStatusError; - auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), - nullptr, 0, nullptr, 0, &status); + auto seq = client.EmitAndWait(MobileGL::MG_Pipe::MGPWireOp::ResourceCreate, &desc, sizeof(desc), nullptr, 0, + nullptr, 0, &status); ASSERT_NE(seq, Wire::kInvalidSeq); ASSERT_EQ(status, Wire::ReplySink::kStatusOk); ASSERT_EQ(client.WaitForApplied(seq, 5000), Transport::SessionWait::Reached); const MobileGL::MG_Pipe::MGPipeHandle fbo{20u, 1u}; auto current = RawColorAttachmentFramebuffer(fbo, texture, 1); - ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, - current)); + ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, current)); const auto clear = RawColorClear(fbo); ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear)); @@ -895,12 +884,11 @@ TEST(ServerSpawnTest, RawPeerFramebufferAttachmentBackwardGenerationIsNamedProto // the only fact that lets the apply-side adoption guard identify the stale generation. current.Color[0].Res.Gen = 1; current.ContentHash = 2; - ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, - current)); + ASSERT_TRUE(PublishRawRecordAndWait(client, MobileGL::MG_Pipe::MGPWireOp::SetFramebufferState, current)); std::uint64_t badClearSeq = Wire::kInvalidSeq; ASSERT_TRUE(EncodeThenMutateHeaderAndPublish( - client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear, - [](Transport::RingRecordHeader&, std::uint8_t*) {}, &badClearSeq)); + client, MobileGL::MG_Pipe::MGPWireOp::Clear, clear, [](Transport::RingRecordHeader&, std::uint8_t*) {}, + &badClearSeq)); ASSERT_NE(badClearSeq, Wire::kInvalidSeq); int exitCode = -1; @@ -912,14 +900,12 @@ TEST(ServerSpawnTest, RawPeerFramebufferAttachmentBackwardGenerationIsNamedProto (void)Server::ReapServer(session.server, 5000, &exitCode); } } - ASSERT_EQ(reaped, MOBILEGL_OK) - << "server did not reject the FBO attachment whose generation moved backwards"; + ASSERT_EQ(reaped, MOBILEGL_OK) << "server did not reject the FBO attachment whose generation moved backwards"; EXPECT_EQ(exitCode, -SIGABRT); const std::string log = MobileGL::MG_Util::Debug::ReadRoleLogs(logBase.c_str()); const auto firstProtocolFatal = log.find("Fatal{ProtocolCorruption"); const auto generationFatal = log.find("Fatal{ProtocolCorruption, \"BackendSlotTable.Generation\"}"); - EXPECT_NE(generationFatal, std::string::npos) - << "server did not name the stale attachment generation"; + EXPECT_NE(generationFatal, std::string::npos) << "server did not name the stale attachment generation"; EXPECT_EQ(firstProtocolFatal, generationFatal) << "the peer record was refused before the backend twin generation guard"; client.Stop(); @@ -932,8 +918,7 @@ TEST(ServerSpawnTest, StartsAServerProcessAndHandshakesAcrossIt) { Session session; ASSERT_TRUE(Bring("handshake", &session)); ASSERT_GT(session.server.pid, 0); - EXPECT_NE(session.server.pid, static_cast(::getpid())) - << "that is this process, not a separate one"; + EXPECT_NE(session.server.pid, static_cast(::getpid())) << "that is this process, not a separate one"; EXPECT_EQ(Server::CountOwnChildren(), before + 1); // THE WHOLE POINT. Hello crosses, Welcome comes back with four SegmentRefs, @@ -1046,8 +1031,7 @@ namespace { } long long MillisecondsSince(std::chrono::steady_clock::time_point start) { - return std::chrono::duration_cast(std::chrono::steady_clock::now() - start) - .count(); + return std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); } } // namespace @@ -1083,10 +1067,9 @@ TEST(ServerSpawnTest, AColdBringUpThatOutlastsTheReplyBudgetIsWaitedForBecauseTh // THE ROUND TRIP, not the EGL answer: whether a headless runner can make a pbuffer is not // this case's question (`ok` is whatever the driver said); whether the reply was WAITED FOR is. - EXPECT_EQ(rc, MOBILEGL_OK) - << "the op came back rc=" << static_cast(rc) << " after " << elapsedMs - << " ms: the client gave up on a server that was still running it - the retrace-split " - "failure (\"ALIVE BUT SILENT - no SurfaceReply for CreatePbufferSurface seq 2\")"; + EXPECT_EQ(rc, MOBILEGL_OK) << "the op came back rc=" << static_cast(rc) << " after " << elapsedMs + << " ms: the client gave up on a server that was still running it - the retrace-split " + "failure (\"ALIVE BUT SILENT - no SurfaceReply for CreatePbufferSurface seq 2\")"; if (rc == MOBILEGL_OK) { EXPECT_GE(elapsedMs, 3000) << "the reply came back before the server's lever released the op, so the lever did not " @@ -1115,11 +1098,10 @@ TEST(ServerSpawnTest, AFrozenServerReportsNoProgressAndTheOpStillTimesOutAtTheBu const long long elapsedMs = MillisecondsSince(start); (void)::kill(session.server.pid, SIGCONT); - EXPECT_EQ(rc, MOBILEGL_ERR_TIMEOUT) - << "a frozen server was not timed out (rc=" << static_cast(rc) << ")"; - EXPECT_LT(elapsedMs, 10000) - << "took " << elapsedMs << " ms against a 1000 ms budget: something other than a progress " - "report restarted the wait"; + EXPECT_EQ(rc, MOBILEGL_ERR_TIMEOUT) << "a frozen server was not timed out (rc=" << static_cast(rc) << ")"; + EXPECT_LT(elapsedMs, 10000) << "took " << elapsedMs + << " ms against a 1000 ms budget: something other than a progress " + "report restarted the wait"; EXPECT_FALSE(Client::ClientSession::DeviceLost()) << "a frozen server is not a lost device (5.4)"; } @@ -1198,8 +1180,7 @@ TEST(ServerSpawnTest, AKilledServerLatchesDeviceLostFromTheHangupAndNotFromADead Session session; ASSERT_TRUE(Bring("devicelost", &session)); ASSERT_EQ(Handshake(session), MOBILEGL_OK); - EXPECT_FALSE(Client::ClientSession::DeviceLost()) - << "a healthy session must not read as a lost device"; + EXPECT_FALSE(Client::ClientSession::DeviceLost()) << "a healthy session must not read as a lost device"; Transport::Doorbell* bell = Client::ClientSessionInstance().SelfDoorbellForTest(); ASSERT_NE(bell, nullptr); @@ -1214,16 +1195,16 @@ TEST(ServerSpawnTest, AKilledServerLatchesDeviceLostFromTheHangupAndNotFromADead for (int attempt = 0; attempt < 200 && !bell->PeerHungUp(); ++attempt) { (void)bell->Park(10); } - const auto elapsedMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start).count(); + const auto elapsedMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); EXPECT_TRUE(bell->PeerHungUp()) << "the witness never reported the hangup; the bell is watching a descriptor this side " "also holds, which is the exact defect D5c names"; EXPECT_TRUE(bell->Dead()) << "a peer that hung up is a dead bell"; - EXPECT_LT(elapsedMs, 3000) - << "took " << elapsedMs << " ms: too slow to be a hangup and fast enough only for a " - "descriptor, so this assertion is what separates the two mechanisms"; + EXPECT_LT(elapsedMs, 3000) << "took " << elapsedMs + << " ms: too slow to be a hangup and fast enough only for a " + "descriptor, so this assertion is what separates the two mechanisms"; // AND THE LATCH ITSELF. PeerHungUp is the cause; DeviceLost is the session-scoped // consequence, and it is what glGetGraphicsResetStatus reports. @@ -1248,7 +1229,8 @@ TEST(ServerSpawnTest, AReadbackOnALostDeviceWritesNothingAndDoesNotAbort) { Transport::Doorbell* bell = Client::ClientSessionInstance().SelfDoorbellForTest(); ASSERT_NE(bell, nullptr); ASSERT_EQ(::kill(session.server.pid, SIGKILL), 0); - for (int attempt = 0; attempt < 200 && !bell->PeerHungUp(); ++attempt) (void)bell->Park(10); + for (int attempt = 0; attempt < 200 && !bell->PeerHungUp(); ++attempt) + (void)bell->Park(10); ASSERT_TRUE(bell->PeerHungUp()); Client::ClientSessionInstance().LatchDeviceLost("the test killed the server"); ASSERT_TRUE(Client::ClientSession::DeviceLost()); @@ -1284,8 +1266,7 @@ TEST(ServerSpawnTest, AnOrderlyStopIsNotADeviceLoss) { ASSERT_EQ(Server::ReapServer(session.server, 5000, &exitCode), MOBILEGL_OK); EXPECT_EQ(exitCode, 0) << "the server must have exited cleanly, not been killed"; - EXPECT_FALSE(bell->PeerHungUp()) - << "an orderly Stop() must not look like a peer that died"; + EXPECT_FALSE(bell->PeerHungUp()) << "an orderly Stop() must not look like a peer that died"; EXPECT_FALSE(Client::ClientSession::DeviceLost()) << "a clean teardown armed the device-lost latch; every exit would report a reset"; } @@ -1337,15 +1318,13 @@ TEST(ServerSpawnTest, AnAbstractEndpointNeedsNoWritableDirectoryAndLeavesNoFile) Session session; ASSERT_EQ(Server::LaunchServer(ServerImage(), endpoint, &session.server), MOBILEGL_OK); ASSERT_EQ(Transport::SocketTransport::ConnectTo(endpoint, 10000, session.client), MOBILEGL_OK); - ASSERT_EQ(Handshake(session), MOBILEGL_OK) - << "an abstract endpoint carried the connection but not the session"; + ASSERT_EQ(Handshake(session), MOBILEGL_OK) << "an abstract endpoint carried the connection but not the session"; // A leading '@' is a NAME, not a path. If FillAddress had treated it as one, the // bind would have created a file literally called "@mgl-..." in the working // directory - which would still have worked here, and would have failed on the // one platform this exists for. - EXPECT_NE(::access(endpoint.c_str(), F_OK), 0) - << "an abstract name must leave no filesystem node: " << endpoint; + EXPECT_NE(::access(endpoint.c_str(), F_OK), 0) << "an abstract name must leave no filesystem node: " << endpoint; Client::ClientSessionInstance().Stop(); int exitCode = -1; @@ -1360,8 +1339,7 @@ TEST(ServerSpawnTest, AnUnresolvableImageIsANamedRefusalAndStartsNothing) { // ConfigLoader.cpp names in its own comment: a lane that asked for spawn, // silently got monolith, and went green on the wrong arm. Server::LaunchedServer server; - EXPECT_EQ(Server::LaunchServer("/nonexistent/libMobileGLServer.so", Endpoint("missing"), - &server), + EXPECT_EQ(Server::LaunchServer("/nonexistent/libMobileGLServer.so", Endpoint("missing"), &server), MOBILEGL_ERR_INVALID_ARGUMENT); EXPECT_EQ(server.pid, -1); EXPECT_EQ(Server::CountOwnChildren(), before) << "a refused launch must leave no process"; @@ -1375,9 +1353,7 @@ TEST(ServerSpawnTest, ConnectingToNothingIsANamedRefusalNotAHang) { const auto start = std::chrono::steady_clock::now(); EXPECT_NE(Transport::SocketTransport::ConnectTo(Endpoint("nobody"), 300, client), MOBILEGL_OK); EXPECT_FALSE(client); - EXPECT_LT(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 4000); } @@ -1397,9 +1373,7 @@ TEST(ServerSpawnTest, ADescriptorCrossesBetweenTheTwoProcesses) { int pipeFds[2] = {-1, -1}; ASSERT_EQ(::pipe(pipeFds), 0); const std::string sideband = "SegmentRef{id=1,kind=Cmd}"; - EXPECT_EQ(session.client->ShareFd(pipeFds[0], - MobileGLByteSpan{sideband.data(), sideband.size()}), - MOBILEGL_OK); + EXPECT_EQ(session.client->ShareFd(pipeFds[0], MobileGLByteSpan{sideband.data(), sideband.size()}), MOBILEGL_OK); ::close(pipeFds[0]); ::close(pipeFds[1]); diff --git a/MobileGL/MG_Test/Wire/SessionHandshakeTest.cpp b/MobileGL/MG_Test/Wire/SessionHandshakeTest.cpp index 5eb2bc7f..535c5f0c 100644 --- a/MobileGL/MG_Test/Wire/SessionHandshakeTest.cpp +++ b/MobileGL/MG_Test/Wire/SessionHandshakeTest.cpp @@ -97,8 +97,7 @@ namespace { // table->Verify(*this)`. std::vector BuildNullUnionFrame(::MobileGL::Wire::CtrlMsg tag) { ::flatbuffers::FlatBufferBuilder builder(256); - auto envelope = - ::MobileGL::Wire::CreateCtrlEnvelope(builder, tag, ::flatbuffers::Offset()); + auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, tag, ::flatbuffers::Offset()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); const Uint8* begin = builder.GetBufferPointer(); return std::vector(begin, begin + builder.GetSize()); @@ -134,9 +133,8 @@ TEST(SessionHandshakeTest, TheAbiFingerprintChangesWhenAnyOfItsInputsDoes) { EXPECT_EQ(inputs.ProgramArtifactsSchema, MG_State::GLState::ProgramArtifactsSchemaFingerprint()); EXPECT_EQ(inputs.OpCount, static_cast(MG_Pipe::MGPWireOp::kOpCount)); #if MOBILEGL_BUILD_DISAGGREGATED - EXPECT_EQ(inputs.ControlSchemaRevision, - (static_cast(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) | - MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision); + EXPECT_EQ(inputs.ControlSchemaRevision, (static_cast(MOBILEGL_PROTOCOL_CONTROL_REVISION) << 32) | + MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision); #else EXPECT_EQ(inputs.ControlSchemaRevision, static_cast(MOBILEGL_PROTOCOL_CONTROL_REVISION)); #endif @@ -153,9 +151,11 @@ TEST(SessionHandshakeTest, TheAbiFingerprintChangesWhenAnyOfItsInputsDoes) { EXPECT_NE(production, perturbed([](auto& i) { ++i.RenderStateLayout; })) << "RenderStateLayout"; EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilityTargets; })) << "FormatCapabilityTargets"; EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilityFormats; })) << "FormatCapabilityFormats"; - EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilitiesCodecVersion; })) << "FormatCapabilitiesCodecVersion"; + EXPECT_NE(production, perturbed([](auto& i) { ++i.FormatCapabilitiesCodecVersion; })) + << "FormatCapabilitiesCodecVersion"; EXPECT_NE(production, perturbed([](auto& i) { ++i.RendererInfoCodecVersion; })) << "RendererInfoCodecVersion"; - EXPECT_NE(production, perturbed([](auto& i) { ++i.ProgramArtifactsCodecVersion; })) << "ProgramArtifactsCodecVersion"; + EXPECT_NE(production, perturbed([](auto& i) { ++i.ProgramArtifactsCodecVersion; })) + << "ProgramArtifactsCodecVersion"; EXPECT_NE(production, perturbed([](auto& i) { ++i.ProgramArtifactsSchema; })) << "ProgramArtifactsSchema"; EXPECT_NE(production, perturbed([](auto& i) { ++i.OpCount; })) << "OpCount"; EXPECT_NE(production, perturbed([](auto& i) { ++i.ControlSchemaRevision; })) << "ControlSchemaRevision"; @@ -166,8 +166,7 @@ TEST(SessionHandshakeTest, TheAbiFingerprintChangesWhenAnyOfItsInputsDoes) { TEST(SessionHandshakeTest, SameWidthFieldReorderingChangesTheWireDigestWithoutABuildStamp) { using namespace MG_Pipe; - std::vector fields(std::begin(kMGPipeWireLayoutMembers), - std::end(kMGPipeWireLayoutMembers)); + std::vector fields(std::begin(kMGPipeWireLayoutMembers), std::end(kMGPipeWireLayoutMembers)); // MGPRange.Offset and Size have the same width: sizeof-only checks cannot see this. SizeT offsetIndex = 0, sizeIndex = 0; for (SizeT i = 0; i < fields.size(); ++i) { @@ -194,12 +193,16 @@ namespace { Set(value); } ~ScopedHandshakeEnvironment() { Set(m_present ? m_previous.c_str() : nullptr); } + private: void Set(const char* value) { #if defined(_WIN32) ::_putenv_s(m_name, value == nullptr ? "" : value); #else - if (value == nullptr) ::unsetenv(m_name); else ::setenv(m_name, value, 1); + if (value == nullptr) + ::unsetenv(m_name); + else + ::setenv(m_name, value, 1); #endif } const char* m_name; @@ -215,22 +218,24 @@ namespace { return frame; } - void CheckHandshake(Uint32 major, Uint64 wire, const char* build, - ::MobileGL::Wire::DialMode dial, ::MobileGL::Wire::RefuseCode expected) { + void CheckHandshake(Uint32 major, Uint64 wire, const char* build, ::MobileGL::Wire::DialMode dial, + ::MobileGL::Wire::RefuseCode expected) { using namespace ::MobileGL::Wire; std::unique_ptr client, server; Transport::InProcessTransport::CreatePair(client, server); ::flatbuffers::FlatBufferBuilder builder(512); auto terms = CreateLinkTerms(builder); - auto hello = CreateHelloDirect(builder, major, MOBILEGL_PROTOCOL_ABI_MINOR, build, - 0, 1, nullptr, wire, wire, terms, nullptr, dial); + auto hello = CreateHelloDirect(builder, major, MOBILEGL_PROTOCOL_ABI_MINOR, build, 0, 1, nullptr, wire, wire, + terms, nullptr, dial); auto root = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, root); std::vector first(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); Server::ServerSession session; Transport::SessionSegmentSizes sizes; - sizes.CmdRingBytes = 4096; sizes.StageBytes = 4096; - sizes.ReplyBytes = 4096; sizes.EventRingBytes = 4096; + sizes.CmdRingBytes = 4096; + sizes.StageBytes = 4096; + sizes.ReplyBytes = 4096; + sizes.EventRingBytes = 4096; session.SetSegmentSizes(sizes); const auto result = session.Accept(*server, &first); EXPECT_EQ(result, expected == RefuseCode::None ? MOBILEGL_OK : MOBILEGL_ERR_PROTOCOL_MISMATCH); @@ -251,7 +256,7 @@ namespace { } session.Close(); } -} +} // namespace TEST(SessionHandshakeTest, WireMajorMismatchReturnsNamedRefuseWithoutAborting) { CheckHandshake(99, WireFingerprint(), BuildFingerprint(), ::MobileGL::Wire::DialMode::No, @@ -272,7 +277,8 @@ TEST(SessionHandshakeTest, ARevisionFivePeerIsRefusedByNameAtRevisionSix) { static_assert(MOBILEGL_PROTOCOL_CONTROL_REVISION == 6, "S1 bumped the control revision 5 -> 6"); auto inputs = CapsAbiFingerprintInputs(); #if MOBILEGL_BUILD_DISAGGREGATED - inputs.ControlSchemaRevision = (static_cast(5) << 32) | MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision; + inputs.ControlSchemaRevision = + (static_cast(5) << 32) | MG_Pipe::kMGPipeResourceRespecifyExtentCarrierRevision; #else inputs.ControlSchemaRevision = 5; #endif @@ -294,16 +300,18 @@ TEST(SessionHandshakeTest, AHelloAskingAnAdoptTierThatIsNotAnAskIsRefusedByName) Transport::InProcessTransport::CreatePair(client, server); ::flatbuffers::FlatBufferBuilder builder(512); auto terms = CreateLinkTerms(builder, DataPlane::SharedSegments, WireForm::StructImage, 0, 0, 0, 0, ask); - auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, - BuildFingerprint(), 0, 1, nullptr, WireFingerprint(), WireFingerprint(), - terms, nullptr, DialMode::No); + auto hello = + CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, BuildFingerprint(), 0, + 1, nullptr, WireFingerprint(), WireFingerprint(), terms, nullptr, DialMode::No); auto root = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, root); std::vector first(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); Server::ServerSession session; Transport::SessionSegmentSizes sizes; - sizes.CmdRingBytes = 4096; sizes.StageBytes = 4096; - sizes.ReplyBytes = 4096; sizes.EventRingBytes = 4096; + sizes.CmdRingBytes = 4096; + sizes.StageBytes = 4096; + sizes.ReplyBytes = 4096; + sizes.EventRingBytes = 4096; session.SetSegmentSizes(sizes); const Bool legal = ask == 0 || ask == 2 || ask == 0x80; EXPECT_EQ(session.Accept(*server, &first), legal ? MOBILEGL_OK : MOBILEGL_ERR_PROTOCOL_MISMATCH); @@ -331,8 +339,8 @@ TEST(SessionHandshakeTest, ConnectAcceptsDifferentBuildWithIdenticalWire) { ::MobileGL::Wire::DialMode::Connect, ::MobileGL::Wire::RefuseCode::None); } TEST(SessionHandshakeTest, ForkRefusesDifferentBuildWithIdenticalWire) { - CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint(), "different-commit", - ::MobileGL::Wire::DialMode::Fork, ::MobileGL::Wire::RefuseCode::BuildFingerprint); + CheckHandshake(MOBILEGL_PROTOCOL_ABI_MAJOR, WireFingerprint(), "different-commit", ::MobileGL::Wire::DialMode::Fork, + ::MobileGL::Wire::RefuseCode::BuildFingerprint); } // PH-8. THE SERVER SIZES THE SESSION; THE CLIENT'S HELLO ONLY ASKS. // @@ -359,23 +367,26 @@ TEST(SessionHandshakeTest, AHelloAskingFor64GiBIsClampedToTheServersTermsAndAllo Transport::InProcessTransport::CreatePair(client, server); ::flatbuffers::FlatBufferBuilder builder(512); auto terms = CreateLinkTerms(builder, DataPlane::SharedSegments, WireForm::StructImage, kAsk, kAsk, kAsk, kAsk); - auto hello = CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, - BuildFingerprint(), 0, 1, nullptr, WireFingerprint(), WireFingerprint(), - terms, nullptr, DialMode::No); + auto hello = + CreateHelloDirect(builder, MOBILEGL_PROTOCOL_ABI_MAJOR, MOBILEGL_PROTOCOL_ABI_MINOR, BuildFingerprint(), 0, 1, + nullptr, WireFingerprint(), WireFingerprint(), terms, nullptr, DialMode::No); auto root = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, root); std::vector first(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); Server::ServerSession session; Transport::SessionSegmentSizes sizes; - sizes.CmdRingBytes = 4096; sizes.StageBytes = 4096; - sizes.ReplyBytes = 4096; sizes.EventRingBytes = 4096; + sizes.CmdRingBytes = 4096; + sizes.StageBytes = 4096; + sizes.ReplyBytes = 4096; + sizes.EventRingBytes = 4096; session.SetSegmentSizes(sizes); const std::string before = ReadLog(); ASSERT_EQ(session.Accept(*server, &first), MOBILEGL_OK); const std::string delta = ReadLog().substr(before.size()); EXPECT_TRUE(Contains(delta, "MG_Remote server: Hello asked for windows the server does not grant")) << "the 64 GiB ask was ignored silently; an operator comparing the client's configured " - "windows with the server's would see no line. Log delta:\n" << delta; + "windows with the server's would see no line. Log delta:\n" + << delta; const auto reply = ReadHandshakeFrame(*client); ASSERT_FALSE(reply.empty()); ::flatbuffers::Verifier verifier(reply.data(), reply.size()); @@ -386,8 +397,8 @@ TEST(SessionHandshakeTest, AHelloAskingFor64GiBIsClampedToTheServersTermsAndAllo EXPECT_EQ(welcome->linkTerms()->cmdWindowBytes(), 4096u) << "the command window echoed the Hello"; EXPECT_EQ(welcome->linkTerms()->stageWindowBytes(), 4096u) << "the stage window echoed the Hello"; EXPECT_EQ(welcome->linkTerms()->eventWindowBytes(), 4096u) << "the event window echoed the Hello"; - EXPECT_EQ(welcome->linkTerms()->maxReplyBytes(), - 4096u / sizes.ReplySlotCount - sizeof(Transport::ReplySlotHeader)) << "the reply bound echoed the Hello"; + EXPECT_EQ(welcome->linkTerms()->maxReplyBytes(), 4096u / sizes.ReplySlotCount - sizeof(Transport::ReplySlotHeader)) + << "the reply bound echoed the Hello"; // The segments that EXIST are the server's: each announced size is a few pages at most, never // the ask. A 64 GiB request that had been honoured would show here even if Welcome's terms // had been rewritten afterwards. @@ -424,16 +435,18 @@ TEST(SessionHandshakeTest, AnUnauthenticatedHelloLearnsNeitherTheFingerprintNorT ASSERT_EQ(Transport::SocketTransport::CreatePair(client, server), MOBILEGL_OK); ::flatbuffers::FlatBufferBuilder builder(512); auto terms = CreateLinkTerms(builder); - auto hello = CreateHelloDirect(builder, 99, MOBILEGL_PROTOCOL_ABI_MINOR, "some-other-build", 0, 1, nullptr, - WireFingerprint() ^ 1, WireFingerprint() ^ 1, terms, "not-the-servers-token", - DialMode::Fork); + auto hello = + CreateHelloDirect(builder, 99, MOBILEGL_PROTOCOL_ABI_MINOR, "some-other-build", 0, 1, nullptr, + WireFingerprint() ^ 1, WireFingerprint() ^ 1, terms, "not-the-servers-token", DialMode::Fork); auto root = CreateCtrlEnvelope(builder, CtrlMsg::Hello, hello.Union()); FinishCtrlEnvelopeBuffer(builder, root); std::vector first(builder.GetBufferPointer(), builder.GetBufferPointer() + builder.GetSize()); Server::ServerSession session; Transport::SessionSegmentSizes sizes; - sizes.CmdRingBytes = 4096; sizes.StageBytes = 4096; - sizes.ReplyBytes = 4096; sizes.EventRingBytes = 4096; + sizes.CmdRingBytes = 4096; + sizes.StageBytes = 4096; + sizes.ReplyBytes = 4096; + sizes.EventRingBytes = 4096; session.SetSegmentSizes(sizes); EXPECT_EQ(session.Accept(*server, &first), MOBILEGL_ERR_PROTOCOL_MISMATCH); EXPECT_FALSE(session.Accepted()); @@ -511,8 +524,7 @@ TEST(SessionHandshakeTest, ANullUnionWelcomeIsRefusedByStartRatherThanDereferenc ASSERT_FALSE(serverSession.Accepted()); const std::string before = ReadLog(); - EXPECT_EQ(session.StartOverTransportPair(std::move(client), std::move(server)), - MOBILEGL_ERR_PROTOCOL_MISMATCH) + EXPECT_EQ(session.StartOverTransportPair(std::move(client), std::move(server)), MOBILEGL_ERR_PROTOCOL_MISMATCH) << "a null-union Welcome was not refused by the handshake"; EXPECT_FALSE(session.Started()); EXPECT_EQ(Client::ClientSession::Active(), nullptr); @@ -522,9 +534,8 @@ TEST(SessionHandshakeTest, ANullUnionWelcomeIsRefusedByStartRatherThanDereferenc EXPECT_EQ(Server::ServerSession::Active(), nullptr); const std::string delta = ReadLog().substr(before.size()); - EXPECT_TRUE(Contains(delta, - "MG_Remote client: the server's first control frame is not a verifiable " - "Welcome")) + EXPECT_TRUE(Contains(delta, "MG_Remote client: the server's first control frame is not a verifiable " + "Welcome")) << "Start refused, but not with the guard's own line. Log delta:\n" << delta; EXPECT_FALSE(Contains(delta, "Fatal{AbiMismatch")) @@ -543,8 +554,8 @@ int main(int argc, char** argv) { // write, and caches the FILE*. The name carries this process's pid, because // gtest_discover_tests runs every case as its own process, in parallel under ctest -j. namespace fs = std::filesystem; - const fs::path path = fs::temp_directory_path() / - ("mobilegl-sessionhandshake-test-" + std::to_string(ProcessId()) + ".log"); + const fs::path path = + fs::temp_directory_path() / ("mobilegl-sessionhandshake-test-" + std::to_string(ProcessId()) + ".log"); std::error_code ec; fs::remove(path, ec); g_logPath = path.string(); diff --git a/MobileGL/MG_Test/Wire/SessionTest.cpp b/MobileGL/MG_Test/Wire/SessionTest.cpp index 3ccad43a..38b66219 100644 --- a/MobileGL/MG_Test/Wire/SessionTest.cpp +++ b/MobileGL/MG_Test/Wire/SessionTest.cpp @@ -92,10 +92,10 @@ namespace { // shape spawn has, and the reason the fixture does not share one pointer. RingControl* clientControl = clientSegments.CmdControl(); RingControl* serverControl = serverSegments.CmdControl(); - cmdProducer = RingProducer(clientControl, clientSegments.CmdRingBase(), - clientSegments.CmdRingCapacity(), RingCursorSet::Cmd); - cmdConsumer = RingConsumer(serverControl, serverSegments.CmdRingBase(), - serverSegments.CmdRingCapacity(), RingCursorSet::Cmd); + cmdProducer = RingProducer(clientControl, clientSegments.CmdRingBase(), clientSegments.CmdRingCapacity(), + RingCursorSet::Cmd); + cmdConsumer = RingConsumer(serverControl, serverSegments.CmdRingBase(), serverSegments.CmdRingCapacity(), + RingCursorSet::Cmd); if (!cmdProducer.Valid() || !cmdConsumer.Valid()) { return false; } @@ -105,16 +105,13 @@ namespace { &clientTransport->SelfDoorbell(), kDefaultSpinUs); consumer.Attach(serverControl, &cmdConsumer, &serverTransport->PeerDoorbell(), &serverTransport->SelfDoorbell(), kDefaultSpinUs); - replies = ReplySlotPool(serverSegments.ReplyBase(), serverSegments.ReplyBytes(), - serverSegments.ReplySlotCount()); + replies = + ReplySlotPool(serverSegments.ReplyBase(), serverSegments.ReplyBytes(), serverSegments.ReplySlotCount()); replies.Clear(); - eventOut = EventRingProducer(serverSegments.EventControl(), serverControl, - serverSegments.EventRingBase(), + eventOut = EventRingProducer(serverSegments.EventControl(), serverControl, serverSegments.EventRingBase(), serverSegments.EventRingCapacity()); - eventIn = EventRingConsumer(clientSegments.EventControl(), clientControl, - clientSegments.EventRingBase(), - clientSegments.EventRingCapacity(), - clientSegments.EventSegmentBase()); + eventIn = EventRingConsumer(clientSegments.EventControl(), clientControl, clientSegments.EventRingBase(), + clientSegments.EventRingCapacity(), clientSegments.EventSegmentBase()); return replies.Valid() && eventOut.Valid() && eventIn.Valid(); } @@ -202,14 +199,12 @@ TEST(SessionTest, TheDefaultGeometryIsTheFourContractRingSizes) { EXPECT_EQ(segments.ReplyBytes(), 16ull * 1024 * 1024); EXPECT_EQ(segments.EventRingCapacity(), 256ull * 1024); - EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Cmd), - 8ull * 1024 * 1024 + sizeof(RingControl)); + EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Cmd), 8ull * 1024 * 1024 + sizeof(RingControl)); // SEG_STAGE is not a ring at all - no control page, no cursor triple, no power-of-two // rounding - and neither is SEG_REPLY, so both announce exactly what was asked for. EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Stage), 32ull * 1024 * 1024); EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Reply), 16ull * 1024 * 1024); - EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Event), - 256ull * 1024 + sizeof(RingControl)); + EXPECT_EQ(segments.AnnouncedSize(SessionSegmentSlot::Event), 256ull * 1024 + sizeof(RingControl)); segments.Close(); } @@ -305,8 +300,7 @@ TEST(SessionTest, TheLedgersPeakIsItsOwnRunningMaxAndNeverTheKernelsHighWaterMar // The CI shape: the kernel says peak 100, current 200. RoleMemorySample sample = SampleRoleMemoryInto(runningPeak, MemoryRole::Client, 100, 200); - EXPECT_EQ(sample.PeakRssBytes, 200u) - << "the kernel's peak was reported verbatim although its current exceeded it"; + EXPECT_EQ(sample.PeakRssBytes, 200u) << "the kernel's peak was reported verbatim although its current exceeded it"; EXPECT_GE(sample.PeakRssBytes, sample.CurrentRssBytes); EXPECT_EQ(sample.CurrentRssBytes, 200u); EXPECT_EQ(runningPeak.load(), 200u); @@ -390,8 +384,7 @@ TEST(SessionTest, TwoThreadsMoveTwentyThousandRecordsAndAgreeOnEveryOne) { std::uint64_t emitted = 0; for (std::uint32_t index = 0; index < kRecords && ok.load(); ++index) { void* payload = nullptr; - while ((payload = session.cmdProducer.Reserve(1, kRecNone, sizeof(std::uint32_t))) == - nullptr) { + while ((payload = session.cmdProducer.Reserve(1, kRecNone, sizeof(std::uint32_t))) == nullptr) { // Never "wait" on a nullptr with enough free bytes - RingProducer::Reserve (Ring.h) says that can // only mean "too big, chunk", and a producer that waited there would stall for ever. ASSERT_LT(session.cmdProducer.FreeBytes(), 16u); @@ -417,8 +410,7 @@ TEST(SessionTest, TwoThreadsMoveTwentyThousandRecordsAndAgreeOnEveryOne) { // fillers. A side that counted a kRecPad would land here off by the number of wraps. EXPECT_EQ(session.Control().Progress.appliedSeq.load(), static_cast(kRecords)); EXPECT_EQ(session.consumer.AppliedSeq(), static_cast(kRecords)); - EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, - session.serverSegments.CmdRingCapacity())); + EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, session.serverSegments.CmdRingCapacity())); } // --------------------------------------------------------------------------- @@ -504,8 +496,7 @@ TEST(SessionTest, RetiredSeqMayTrailTheApplyButCanNeverOvertakeIt) { ASSERT_NE(session.cmdProducer.Reserve(1, kRecNone, 8), nullptr); } session.producer.PublishAndNotify(3); - while (session.consumer.ApplyOne([](const RingRecordView&) {})) { - } + while (session.consumer.ApplyOne([](const RingRecordView&) {})) {} ASSERT_EQ(session.Control().Progress.appliedSeq.load(), 3u); // Trailing is legal and is what "late" means. @@ -528,8 +519,7 @@ TEST(SessionTest, CompletedFrameSerialIsTheServersAndIsIndependentOfAppliedSeq) ASSERT_NE(session.cmdProducer.Reserve(1, kRecNone, 8), nullptr); } session.producer.PublishAndNotify(5); - while (session.consumer.ApplyOne([](const RingRecordView&) {})) { - } + while (session.consumer.ApplyOne([](const RingRecordView&) {})) {} EXPECT_EQ(session.Control().Progress.appliedSeq.load(), 5u); // Five records applied, no frame completed: the two are not the same number and nothing // may derive one from the other. @@ -764,8 +754,7 @@ TEST(SessionTest, AWrapFillerDoesNotAdvanceTheSessionsAppliedSeq) { EXPECT_EQ(view.flags & kRecPad, 0u) << "a wrap filler reached the record counter"; EXPECT_NE(view.kind, kRingPadRecordKind); ++applied; - })) { - } + })) {} session.consumer.RetireThrough(session.consumer.AppliedSeq()); continue; } @@ -780,8 +769,7 @@ TEST(SessionTest, AWrapFillerDoesNotAdvanceTheSessionsAppliedSeq) { while (session.consumer.ApplyOne([&](const RingRecordView& view) { EXPECT_EQ(view.flags & kRecPad, 0u) << "a wrap filler reached the record counter"; ++applied; - })) { - } + })) {} EXPECT_GT(fillerBytes, 0u) << "the ring never wrapped, so this case proved nothing"; EXPECT_EQ(applied, emitted); @@ -799,8 +787,7 @@ TEST(SessionTest, AReplyIsAddressedBySeqAndCarriesItsSeqBackForSelfCheck) { ASSERT_TRUE(session.Build(TestSizes())); ASSERT_EQ(session.replies.SlotCount(), 8u); EXPECT_EQ(session.replies.SlotBytes(), 64ull * 1024 / 8); - EXPECT_EQ(session.replies.MaxReplyBytes(), - session.replies.SlotBytes() - sizeof(ReplySlotHeader)); + EXPECT_EQ(session.replies.MaxReplyBytes(), session.replies.SlotBytes() - sizeof(ReplySlotHeader)); const std::uint32_t pixels[4] = {1, 2, 3, 4}; session.replies.Post(5, kReplyStatusOk, pixels, sizeof(pixels)); @@ -929,8 +916,8 @@ TEST(SessionTest, TheEventRingCarriesTheThreeReverseCallbacks) { // pointer (R-2's rule B). const std::uint8_t written[8] = {9, 8, 7, 6, 5, 4, 3, 2}; { - void* slot = session.eventOut.Reserve(kEventBufferWriteback, - sizeof(EventBufferWritebackHead) + sizeof(written)); + void* slot = + session.eventOut.Reserve(kEventBufferWriteback, sizeof(EventBufferWritebackHead) + sizeof(written)); ASSERT_NE(slot, nullptr); EventBufferWritebackHead head{}; head.Resource = EventHandle{7, 1}; @@ -941,8 +928,7 @@ TEST(SessionTest, TheEventRingCarriesTheThreeReverseCallbacks) { } // OnGpuWritten: a count and a tail of ranges. { - void* slot = session.eventOut.Reserve(kEventGpuWritten, - sizeof(EventGpuWrittenHead) + 2 * sizeof(EventRange)); + void* slot = session.eventOut.Reserve(kEventGpuWritten, sizeof(EventGpuWrittenHead) + 2 * sizeof(EventRange)); ASSERT_NE(slot, nullptr); EventGpuWrittenHead head{}; head.Resource = EventHandle{9, 2}; @@ -961,8 +947,7 @@ TEST(SessionTest, TheEventRingCarriesTheThreeReverseCallbacks) { head.IsDefault = 1; std::memcpy(slot, &head, sizeof(head)); } - session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), - session.Control().producerParked); + session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), session.Control().producerParked); RingRecordView view{}; ASSERT_TRUE(session.eventIn.Pop(view)); @@ -1010,8 +995,7 @@ TEST(SessionTest, TheEventRingCarriesAGlError) { const std::uint32_t messageBytes = static_cast(sizeof(message)); // NUL included ASSERT_LT(messageBytes, kEventGlErrorMaxMessageBytes); { - void* slot = session.eventOut.Reserve(kEventGlError, - sizeof(EventGlErrorHead) + messageBytes); + void* slot = session.eventOut.Reserve(kEventGlError, sizeof(EventGlErrorHead) + messageBytes); ASSERT_NE(slot, nullptr); EventGlErrorHead head{}; head.Code = 4; // ErrorCode::InvalidOperation, widened @@ -1019,8 +1003,7 @@ TEST(SessionTest, TheEventRingCarriesAGlError) { std::memcpy(slot, &head, sizeof(head)); std::memcpy(static_cast(slot) + sizeof(head), message, messageBytes); } - session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), - session.Control().producerParked); + session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), session.Control().producerParked); RingRecordView view{}; ASSERT_TRUE(session.eventIn.Pop(view)); @@ -1063,8 +1046,7 @@ TEST(SessionTest, AFullEventRingLatchesTheFlagRatherThanDecidingWhatToDoAboutIt) session.eventOut.CountDrop(); EXPECT_EQ(session.eventIn.DroppedEvents(), 1u); - session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), - session.Control().producerParked); + session.eventOut.PublishAndNotify(session.clientTransport->SelfDoorbell(), session.Control().producerParked); RingRecordView view{}; std::uint64_t drained = 0; while (session.eventIn.Pop(view)) { @@ -1105,8 +1087,7 @@ TEST(SessionTest, ShutdownUnparksTheApplyThreadAndTheJoinIsBounded) { shared->returned.store(true, std::memory_order_release); }); - while (shared->session.Control().consumerParked.load() == 0 && - !shared->returned.load(std::memory_order_acquire)) { + while (shared->session.Control().consumerParked.load() == 0 && !shared->returned.load(std::memory_order_acquire)) { std::this_thread::yield(); } std::this_thread::sleep_for(std::chrono::milliseconds(50)); @@ -1116,8 +1097,7 @@ TEST(SessionTest, ShutdownUnparksTheApplyThreadAndTheJoinIsBounded) { shared->session.clientTransport->Shutdown(); const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5); - while (!shared->returned.load(std::memory_order_acquire) && - std::chrono::steady_clock::now() < deadline) { + while (!shared->returned.load(std::memory_order_acquire) && std::chrono::steady_clock::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } if (!shared->returned.load(std::memory_order_acquire)) { @@ -1135,9 +1115,7 @@ TEST(SessionTest, ShutdownUnparksTheApplyThreadAndTheJoinIsBounded) { // late thread cannot hang either. const auto start = std::chrono::steady_clock::now(); EXPECT_EQ(shared->session.consumer.WaitForWork(kWaitForever), SessionWait::ShutDown); - EXPECT_LT(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_LT(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 1000); // And so does a producer blocked in the verb barrier: without this, a client waiting for // appliedSeq when the server died would sit in the barrier for ever. @@ -1193,16 +1171,14 @@ TEST(SessionTest, TheInprocPeerGetsItsOwnMappingOfTheSameSharedMemory) { #if !defined(_WIN32) // Different descriptors, different virtual addresses... - EXPECT_NE(peer.DescriptorFor(SessionSegmentSlot::Cmd), - owner.DescriptorFor(SessionSegmentSlot::Cmd)); + EXPECT_NE(peer.DescriptorFor(SessionSegmentSlot::Cmd), owner.DescriptorFor(SessionSegmentSlot::Cmd)); EXPECT_GE(peer.DescriptorFor(SessionSegmentSlot::Cmd), 0); EXPECT_NE(static_cast(peer.CmdControl()), static_cast(owner.CmdControl())); EXPECT_NE(peer.CmdRingBase(), owner.CmdRingBase()); #endif // ... over the SAME physical page, which is the whole point: a store through one mapping is // visible through the other, exactly as it is across two processes under spawn. - EXPECT_EQ(peer.AnnouncedSize(SessionSegmentSlot::Cmd), - owner.AnnouncedSize(SessionSegmentSlot::Cmd)); + EXPECT_EQ(peer.AnnouncedSize(SessionSegmentSlot::Cmd), owner.AnnouncedSize(SessionSegmentSlot::Cmd)); owner.CmdControl()->Progress.appliedSeq.store(4242, std::memory_order_release); EXPECT_EQ(peer.CmdControl()->Progress.appliedSeq.load(std::memory_order_acquire), 4242u); peer.CmdControl()->Progress.presentAckSerial.store(77, std::memory_order_release); @@ -1244,8 +1220,7 @@ TEST(SessionTest, ABorrowedSlotIsNotHandedBackUntilItIsReleased) { sawBorrow = true; } ++seen; - })) { - } + })) {} ASSERT_EQ(seen, 3); ASSERT_TRUE(sawBorrow) << "the producer dropped kRecBorrowSlot, so this case proved nothing"; @@ -1258,14 +1233,12 @@ TEST(SessionTest, ABorrowedSlotIsNotHandedBackUntilItIsReleased) { EXPECT_EQ(session.Control().cmdRetiredTail.load(), firstRecordEnd); EXPECT_LT(session.Control().cmdRetiredTail.load(), session.Control().cmdAppliedTail.load()) << "a borrowed slot was handed back to the producer while the GPU may still read it"; - EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, - session.serverSegments.CmdRingCapacity())); + EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, session.serverSegments.CmdRingCapacity())); // completedFrameSerial passed it: now the rest comes back. session.consumer.RetireBorrowedUpTo(session.cmdConsumer.LocalTail()); EXPECT_EQ(session.Control().cmdRetiredTail.load(), session.Control().cmdAppliedTail.load()); - EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, - session.serverSegments.CmdRingCapacity())); + EXPECT_TRUE(RingCursorsValid(session.Control(), RingCursorSet::Cmd, session.serverSegments.CmdRingCapacity())); } // q-1. Teardown's drain keys on the PRODUCER's own last-published seq, not on @@ -1316,8 +1289,7 @@ TEST(SessionTest, TheProducersParkLedgerCountsItsOwnWaitsAndNotTheBellsOtherWait // A SECOND SUBSYSTEM waits on the same bell, exactly as the encoder does. spinUs 0 and a // condition that never flips, so it goes straight to the blocking park. std::atomic parked{0}; - EXPECT_FALSE(session.clientTransport->SelfDoorbell().Wait( - parked, [] { return false; }, 0, 20)); + EXPECT_FALSE(session.clientTransport->SelfDoorbell().Wait(parked, [] { return false; }, 0, 20)); EXPECT_EQ(session.producer.Waits(), waitsBefore) << "a foreign wait on the shared bell moved the producer's wait count"; EXPECT_EQ(session.producer.Parks(), parksBefore) @@ -1348,8 +1320,8 @@ TEST(SessionTest, TheProducersParkLedgerCountsItsOwnWaitsAndNotTheBellsOtherWait // simplified away"; it did not, and s1-v3.md says so. TEST(SessionTest, ANullUnionFrameVerifiesWhichIsThePremiseOfBothHandshakeGuards) { ::flatbuffers::FlatBufferBuilder builder(256); - auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, - ::flatbuffers::Offset()); + auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::Hello, ::flatbuffers::Offset()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); ::flatbuffers::Verifier verifier(builder.GetBufferPointer(), builder.GetSize()); @@ -1357,8 +1329,7 @@ TEST(SessionTest, ANullUnionFrameVerifiesWhichIsThePremiseOfBothHandshakeGuards) << "if this ever starts failing, the guard in both handshakes may be relaxed"; ASSERT_TRUE(::MobileGL::Wire::CtrlEnvelopeBufferHasIdentifier(builder.GetBufferPointer())); - const ::MobileGL::Wire::CtrlEnvelope* parsed = - ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer()); + const ::MobileGL::Wire::CtrlEnvelope* parsed = ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer()); ASSERT_NE(parsed, nullptr); EXPECT_EQ(parsed->msg_type(), ::MobileGL::Wire::CtrlMsg::Hello); // The whole finding, in one line: the tag says Hello and there is no Hello. @@ -1419,15 +1390,13 @@ TEST(SessionTest, TheStageCursorTripleStaysDeadAcrossAWholeSession) { for (int index = 0; index < kRecords; ++index) { void* payload = nullptr; while ((payload = session.cmdProducer.Reserve(1, kRecNone, 32)) == nullptr) { - while (session.consumer.ApplyOne([&](const RingRecordView&) { ++applied; })) { - } + while (session.consumer.ApplyOne([&](const RingRecordView&) { ++applied; })) {} session.consumer.RetireThrough(session.consumer.AppliedSeq()); } std::memset(payload, index & 0xFF, 32); session.producer.PublishAndNotify(static_cast(index + 1)); } - while (session.consumer.ApplyOne([&](const RingRecordView&) { ++applied; })) { - } + while (session.consumer.ApplyOne([&](const RingRecordView&) { ++applied; })) {} session.consumer.RetireThrough(session.consumer.AppliedSeq()); ASSERT_EQ(applied, kRecords); @@ -1484,8 +1453,7 @@ TEST(SessionTest, ARecordWearingThePadBitIsDeliveredRatherThanEatenAsAFiller) { seenKind = view.kind; seenFlags = view.flags; ++seen; - })) { - } + })) {} EXPECT_EQ(seen, 1) << "the record was skipped as a wrap filler because it wore bit 2 - Pop's " "kind check (a filler is kRecPad AND kind == kRingPadRecordKind) is the " "only thing between a stamped header and a silently deleted record"; @@ -1504,8 +1472,7 @@ TEST(SessionTest, ARecordWearingThePadBitIsDeliveredRatherThanEatenAsAFiller) { stamped.flags = static_cast(stamped.flags | kRecPad); std::memcpy(headerBytes, &stamped, sizeof(stamped)); session.producer.PublishAndNotify(2); - while (session.consumer.ApplyOne([&](const RingRecordView&) { ++seen; })) { - } + while (session.consumer.ApplyOne([&](const RingRecordView&) { ++seen; })) {} EXPECT_EQ(seen, 1) << "a genuine filler was delivered as a record: Pop's skip was removed, " "not narrowed"; EXPECT_EQ(session.Control().Progress.appliedSeq.load(), 1u) << "a filler advanced appliedSeq (R-9)"; @@ -1522,8 +1489,7 @@ TEST(SessionTest, ABorrowSlotSightingIsCountedRatherThanJustActedOn) { ASSERT_NE(session.cmdProducer.Reserve(3, kRecBorrowSlot, 16), nullptr); session.producer.PublishAndNotify(1); - while (session.consumer.ApplyOne([](const RingRecordView&) {})) { - } + while (session.consumer.ApplyOne([](const RingRecordView&) {})) {} EXPECT_EQ(session.consumer.BorrowedRecordsSeen(), 1u) << "a kRecBorrowSlot record went by unnamed; in P5 that bit can only be kHostSpan"; } diff --git a/MobileGL/MG_Test/Wire/SharedImageTest.cpp b/MobileGL/MG_Test/Wire/SharedImageTest.cpp index da2d37fd..adee1f35 100644 --- a/MobileGL/MG_Test/Wire/SharedImageTest.cpp +++ b/MobileGL/MG_Test/Wire/SharedImageTest.cpp @@ -224,13 +224,15 @@ TEST(SharedImageSync, PendingReadsAreWaitedForAndSignaledOnesAreDropped) { SI::PublishRead(*image, second.Dup()); Vector pending = SI::DupPendingReadFences(*image); EXPECT_EQ(pending.size(), 2u); - for (const int fd : pending) ::close(fd); + for (const int fd : pending) + ::close(fd); EXPECT_FALSE(SI::WaitForReads(*image, 10)); first.Signal(); pending = SI::DupPendingReadFences(*image); EXPECT_EQ(pending.size(), 1u); - for (const int fd : pending) ::close(fd); + for (const int fd : pending) + ::close(fd); second.Signal(); EXPECT_TRUE(SI::WaitForReads(*image, 10)); EXPECT_TRUE(SI::DupPendingReadFences(*image).empty()); @@ -309,9 +311,15 @@ namespace { SI::PlaneLayout Layout; SizeT Size = 0; }; - Uint8 LumaAt(Uint32 x, Uint32 y) { return static_cast(x * 3 + y * 7); } - Uint8 CbAt(Uint32 x, Uint32 y) { return static_cast(100 + x + y * 5); } - Uint8 CrAt(Uint32 x, Uint32 y) { return static_cast(200 - x - y * 3); } + Uint8 LumaAt(Uint32 x, Uint32 y) { + return static_cast(x * 3 + y * 7); + } + Uint8 CbAt(Uint32 x, Uint32 y) { + return static_cast(100 + x + y * 5); + } + Uint8 CrAt(Uint32 x, Uint32 y) { + return static_cast(200 - x - y * 3); + } ForeignFrame MakeForeignNv12(Uint32 width, Uint32 height, Uint8 bias) { ForeignFrame frame; @@ -351,7 +359,8 @@ namespace { wrong += map[y * image.Stride + x] != static_cast(LumaAt(x, y) + bias); for (Uint32 y = 0; y < (image.Height + 1) / 2; ++y) { for (Uint32 x = 0; x < (image.Width + 1) / 2; ++x) { - wrong += map[image.Plane1Offset + y * image.Plane1Stride + 2 * x] != static_cast(CbAt(x, y) + bias); + wrong += + map[image.Plane1Offset + y * image.Plane1Stride + 2 * x] != static_cast(CbAt(x, y) + bias); wrong += map[image.Plane1Offset + y * image.Plane1Stride + 2 * x + 1] != static_cast(CrAt(x, y) + bias); } @@ -396,7 +405,8 @@ TEST(SharedImageYuv, AForeignBufferIsCopiedIntoAnImageOfItsOwn) { { ForeignFrame next = MakeForeignNv12(48, 30, 9); auto* source = static_cast(::mmap(nullptr, next.Size, PROT_READ, MAP_SHARED, next.Fd, 0)); - auto* target = static_cast(::mmap(nullptr, frame.Size, PROT_READ | PROT_WRITE, MAP_SHARED, frame.Fd, 0)); + auto* target = + static_cast(::mmap(nullptr, frame.Size, PROT_READ | PROT_WRITE, MAP_SHARED, frame.Fd, 0)); std::memcpy(target, source, frame.Size); ::munmap(source, next.Size); ::munmap(target, frame.Size); @@ -439,7 +449,8 @@ TEST(SharedImageYuv, ThePlaneCopyFillsEveryDestinationLayout) { constexpr Uint32 W = 6, H = 4; Uint8 y[H][W], cbcr[H / 2][W]; for (Uint32 r = 0; r < H; ++r) - for (Uint32 c = 0; c < W; ++c) y[r][c] = static_cast(r * 10 + c); + for (Uint32 c = 0; c < W; ++c) + y[r][c] = static_cast(r * 10 + c); for (Uint32 r = 0; r < H / 2; ++r) for (Uint32 c = 0; c < W / 2; ++c) { cbcr[r][2 * c] = static_cast(100 + r * 10 + c); @@ -447,7 +458,8 @@ TEST(SharedImageYuv, ThePlaneCopyFillsEveryDestinationLayout) { } const auto check = [&](const SI::Yuv::PlanarLayout& dst) { for (Uint32 r = 0; r < H; ++r) - for (Uint32 c = 0; c < W; ++c) EXPECT_EQ(dst.Y.Data[r * dst.Y.RowStride + c * dst.Y.PixelStride], y[r][c]); + for (Uint32 c = 0; c < W; ++c) + EXPECT_EQ(dst.Y.Data[r * dst.Y.RowStride + c * dst.Y.PixelStride], y[r][c]); for (Uint32 r = 0; r < H / 2; ++r) for (Uint32 c = 0; c < W / 2; ++c) { EXPECT_EQ(dst.Cb.Data[r * dst.Cb.RowStride + c * dst.Cb.PixelStride], cbcr[r][2 * c]); @@ -471,9 +483,11 @@ TEST(SharedImageYuv, ThePlaneCopyFillsEveryDestinationLayout) { // 16-bit samples (P010): two bytes each, Cb Cr pairs of four. Uint16 y16[H][W], c16[H / 2][W]; for (Uint32 r = 0; r < H; ++r) - for (Uint32 c = 0; c < W; ++c) y16[r][c] = static_cast((r * 10 + c) << 6); + for (Uint32 c = 0; c < W; ++c) + y16[r][c] = static_cast((r * 10 + c) << 6); for (Uint32 r = 0; r < H / 2; ++r) - for (Uint32 c = 0; c < W; ++c) c16[r][c] = static_cast((500 + r * 10 + c) << 6); + for (Uint32 c = 0; c < W; ++c) + c16[r][c] = static_cast((500 + r * 10 + c) << 6); std::vector luma16(16 * H), chroma16(16 * H); SI::Yuv::PlanarLayout p010{{reinterpret_cast(luma16.data()), 32, 2}, {reinterpret_cast(chroma16.data()), 32, 4}, @@ -481,9 +495,11 @@ TEST(SharedImageYuv, ThePlaneCopyFillsEveryDestinationLayout) { ASSERT_TRUE(SI::Yuv::CopySemiPlanar(reinterpret_cast(&y16[0][0]), W * 2, reinterpret_cast(&c16[0][0]), W * 2, W, H, 2, p010)); for (Uint32 r = 0; r < H; ++r) - for (Uint32 c = 0; c < W; ++c) EXPECT_EQ(luma16[r * 16 + c], y16[r][c]); + for (Uint32 c = 0; c < W; ++c) + EXPECT_EQ(luma16[r * 16 + c], y16[r][c]); for (Uint32 r = 0; r < H / 2; ++r) - for (Uint32 c = 0; c < W; ++c) EXPECT_EQ(chroma16[r * 16 + c], c16[r][c]); + for (Uint32 c = 0; c < W; ++c) + EXPECT_EQ(chroma16[r * 16 + c], c16[r][c]); } // The conversion an import's hints select: BT.709 narrow-range codes of saturated colours decode @@ -493,7 +509,8 @@ TEST(SharedImageYuv, TheConversionDecodesTheHintedColourSpace) { const double in[3] = {y / max - c.Offset[0], cb / max - c.Offset[1], cr / max - c.Offset[2]}; std::array rgb{}; for (int row = 0; row < 3; ++row) - for (int col = 0; col < 3; ++col) rgb[row] += c.Matrix[col * 3 + row] * in[col]; + for (int col = 0; col < 3; ++col) + rgb[row] += c.Matrix[col * 3 + row] * in[col]; return rgb; }; SI::Yuv::Hints bt709; @@ -505,14 +522,16 @@ TEST(SharedImageYuv, TheConversionDecodesTheHintedColourSpace) { const double want[4][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {1, 1, 1}}; for (int i = 0; i < 4; ++i) { const auto rgb = decode(c709, codes[i][0], codes[i][1], codes[i][2], 255.0); - for (int k = 0; k < 3; ++k) EXPECT_NEAR(rgb[k], want[i][k], 0.02) << "colour " << i << " channel " << k; + for (int k = 0; k < 3; ++k) + EXPECT_NEAR(rgb[k], want[i][k], 0.02) << "colour " << i << " channel " << k; } // Full range: Y passes through. SI::Yuv::Hints full; full.Range = SI::Yuv::kEglYuvFullRange; const auto cFull = SI::Yuv::ConversionFor(full, 255.0); const auto grey = decode(cFull, 128, 128, 128, 255.0); - for (int k = 0; k < 3; ++k) EXPECT_NEAR(grey[k], 128.0 / 255.0, 0.005); + for (int k = 0; k < 3; ++k) + EXPECT_NEAR(grey[k], 128.0 / 255.0, 0.005); EXPECT_EQ(SI::Yuv::Hints{}.EffectiveColorSpace(), SI::Yuv::kEglItuRec601); EXPECT_FALSE(SI::Yuv::Hints{}.FullRange()); } diff --git a/MobileGL/MG_Test/Wire/SocketTransportTest.cpp b/MobileGL/MG_Test/Wire/SocketTransportTest.cpp index 4e27c3b9..2b4d709e 100644 --- a/MobileGL/MG_Test/Wire/SocketTransportTest.cpp +++ b/MobileGL/MG_Test/Wire/SocketTransportTest.cpp @@ -35,10 +35,10 @@ // `BufferTooSmallKeepsTheMessage` matters more here: the message it must keep // is one this side already reassembled out of bytes nobody else can re-deliver. -#include // PH-7 (4): EncodeDataBind / DecodeDataBind +#include // PH-7 (4): EncodeDataBind / DecodeDataBind #include // RefuseCode, for the word below -#include // PH-7 (3): the minimum and the comparison -#include // kWaitForever +#include // PH-7 (3): the minimum and the comparison +#include // kWaitForever #include #include @@ -93,8 +93,9 @@ TEST(SocketTransportTest, TcpCarriesControlAndIndependentDataWithKeepalive) { ASSERT_EQ(SocketTransport::MintNonce(nonce, sizeof(nonce)), MOBILEGL_OK); const auto bind = MobileGL::MG_Remote::EncodeDataBind(nonce, sizeof(nonce)); int clientData = -1, serverData = -1; - ASSERT_EQ(SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, - &clientData), MOBILEGL_OK); + ASSERT_EQ( + SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, &clientData), + MOBILEGL_OK); ASSERT_EQ(SocketTransport::AcceptOne(listener, 1000, &serverData), MOBILEGL_OK); ::close(listener); std::vector bound; @@ -149,8 +150,7 @@ TEST(SocketTransportTest, TcpRejectsMalformedNamesAndUnauthenticatedWildcard) { // no peer could ever receive. fatal_census.py's rule 4 refuses an invented word statically; // this pins the word that gate checks against to the enumerator the peer actually reads, so // renaming the enumerator without the log line fails here rather than silently. - EXPECT_STREQ(MobileGL::Wire::EnumNameRefuseCode(MobileGL::Wire::RefuseCode::Authentication), - "Authentication"); + EXPECT_STREQ(MobileGL::Wire::EnumNameRefuseCode(MobileGL::Wire::RefuseCode::Authentication), "Authentication"); if (wasSet) ::setenv("MOBILEGL_IPC_TOKEN", saved.c_str(), 1); } @@ -169,8 +169,7 @@ TEST(SocketTransportTest, TcpRefusesToListenWithATokenShorterThanTheMinimum) { const std::string tooShort(kMinimumAuthTokenBytes - 1, 'x'); ::setenv("MOBILEGL_IPC_TOKEN", tooShort.c_str(), 1); int listener = -1; - EXPECT_EQ(SocketTransport::Listen("tcp://127.0.0.1:40697", &listener), - MOBILEGL_ERR_PROTOCOL_MISMATCH); + EXPECT_EQ(SocketTransport::Listen("tcp://127.0.0.1:40697", &listener), MOBILEGL_ERR_PROTOCOL_MISMATCH); EXPECT_EQ(listener, -1); // One more byte and the same endpoint comes up, so the refusal above is the LENGTH and not // the port, the address family or the environment variable's mere presence. @@ -179,8 +178,10 @@ TEST(SocketTransportTest, TcpRefusesToListenWithATokenShorterThanTheMinimum) { EXPECT_EQ(SocketTransport::Listen("tcp://127.0.0.1:40697", &listener), MOBILEGL_OK); EXPECT_GE(listener, 0); if (listener >= 0) ::close(listener); - if (wasSet) ::setenv("MOBILEGL_IPC_TOKEN", saved.c_str(), 1); - else ::unsetenv("MOBILEGL_IPC_TOKEN"); + if (wasSet) + ::setenv("MOBILEGL_IPC_TOKEN", saved.c_str(), 1); + else + ::unsetenv("MOBILEGL_IPC_TOKEN"); } // The comparison itself. `std::strcmp` and `std::string::operator==` - the two this replaced - @@ -197,8 +198,7 @@ TEST(SocketTransportTest, TheTokenComparisonAnswersLengthAndPrefixTheSameWay) { EXPECT_FALSE(ConstantTimeTokenMatch(expected, "", 0)); // An embedded NUL is compared, not truncated at: `presented` carries its own length because // a flatbuffers string can hold one and c_str()-plus-strcmp would have stopped there. - const char embedded[] = {'0', '\0', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + const char embedded[] = {'0', '\0', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; EXPECT_FALSE(ConstantTimeTokenMatch(expected, embedded, sizeof(embedded))); EXPECT_FALSE(ConstantTimeTokenMatch(nullptr, "anything", 8)); // F fix round: the loop runs a fixed kAuthTokenCompareBytes (64) so its trip count does not @@ -241,8 +241,9 @@ TEST(SocketTransportTest, TheDataBindIsReadExactlyAndTheStreamBehindItIsLeftOnTh ASSERT_EQ(SocketTransport::MintNonce(nonce, sizeof(nonce)), MOBILEGL_OK); const auto bind = MobileGL::MG_Remote::EncodeDataBind(nonce, sizeof(nonce)); int client = -1; - ASSERT_EQ(SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, - &client), MOBILEGL_OK); + ASSERT_EQ( + SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, &client), + MOBILEGL_OK); const char tail[] = "MGLD-the-first-stream-bytes"; ASSERT_EQ(::send(client, tail, sizeof(tail), MSG_NOSIGNAL), static_cast(sizeof(tail))); @@ -270,8 +271,9 @@ TEST(SocketTransportTest, TheDataBindIsReadExactlyAndTheStreamBehindItIsLeftOnTh // A frame over the caller's bound is refused before its payload is read. int second = -1; - ASSERT_EQ(SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, - &second), MOBILEGL_OK); + ASSERT_EQ( + SocketTransport::ConnectDataConnection(endpoint, 1000, MobileGLByteSpan{bind.data(), bind.size()}, &second), + MOBILEGL_OK); int big = -1; ASSERT_EQ(SocketTransport::AcceptOne(listener, 1000, &big), MOBILEGL_OK); EXPECT_EQ(SocketTransport::ReceiveOneFrame(big, 1000, bind.size() - 1, &frame), MOBILEGL_ERR_PROTOCOL_MISMATCH); @@ -295,8 +297,7 @@ namespace { if (result != MOBILEGL_OK) { return std::string("(result)) + ">"; } - return std::string(reinterpret_cast(buffer.data()), - static_cast(size)); + return std::string(reinterpret_cast(buffer.data()), static_cast(size)); } struct Pair { @@ -396,9 +397,7 @@ TEST(SocketTransportTest, PollAndTimeoutDoNotBlockForever) { const auto start = std::chrono::steady_clock::now(); EXPECT_EQ(pair.server->ReceiveFrame(span, &size, 30), MOBILEGL_ERR_TIMEOUT); - EXPECT_GE(std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(), + EXPECT_GE(std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(), 20); } @@ -482,9 +481,7 @@ TEST(SocketTransportTest, HandsOverADescriptorAndItsSideband) { MobileGLMutableByteSpan bigSpan{big.data(), big.size()}; ASSERT_EQ(pair.server->ReceiveFd(&fd, bigSpan, &sidebandSize, 100), MOBILEGL_OK); ASSERT_GE(fd, 0); - EXPECT_EQ(std::string(reinterpret_cast(big.data()), - static_cast(sidebandSize)), - sideband); + EXPECT_EQ(std::string(reinterpret_cast(big.data()), static_cast(sidebandSize)), sideband); // Same open file description, independent descriptor - which is exactly // what the four segments will ride on in `sm`. @@ -544,9 +541,8 @@ TEST(SocketTransportTest, AFrameWakeupIsNotEatenByAWaiterOnDescriptors) { const auto start = std::chrono::steady_clock::now(); ASSERT_EQ(pair.server->SendFrame(Span(message)), MOBILEGL_OK); frameWaiter.join(); - const auto elapsedMs = std::chrono::duration_cast( - std::chrono::steady_clock::now() - start) - .count(); + const auto elapsedMs = + std::chrono::duration_cast(std::chrono::steady_clock::now() - start).count(); EXPECT_EQ(got, message); // Not "eventually, when the receive timed out and re-checked". @@ -574,9 +570,7 @@ TEST(SocketTransportTest, ReassemblesAMessageSplitAcrossReads) { sender.join(); EXPECT_EQ(size, big.size()); - EXPECT_EQ(std::string(reinterpret_cast(buffer.data()), - static_cast(size)), - big); + EXPECT_EQ(std::string(reinterpret_cast(buffer.data()), static_cast(size)), big); } TEST(SocketTransportTest, AGarbageStreamIsLatchedFailedAndNeverRecovers) { @@ -587,10 +581,8 @@ TEST(SocketTransportTest, AGarbageStreamIsLatchedFailedAndNeverRecovers) { // must not be retry-able past: the alternative is a reader that resyncs // onto a byte boundary it invented, which is how one bad frame becomes a // silently misparsed session. - const std::uint8_t garbage[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0, - 0xFF, 0xFF, 0xFF, 0xFF, 1, 2, 3, 4}; - ASSERT_EQ(::write(pair.client->StreamFd(), garbage, sizeof(garbage)), - static_cast(sizeof(garbage))); + const std::uint8_t garbage[16] = {0xDE, 0xAD, 0xBE, 0xEF, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF, 1, 2, 3, 4}; + ASSERT_EQ(::write(pair.client->StreamFd(), garbage, sizeof(garbage)), static_cast(sizeof(garbage))); std::vector buffer(64); std::uint64_t size = 0; diff --git a/MobileGL/MG_Test/Wire/StagedTextureStoreTest.cpp b/MobileGL/MG_Test/Wire/StagedTextureStoreTest.cpp index 6cf250df..f396b556 100644 --- a/MobileGL/MG_Test/Wire/StagedTextureStoreTest.cpp +++ b/MobileGL/MG_Test/Wire/StagedTextureStoreTest.cpp @@ -105,8 +105,7 @@ TEST(StagedTextureStoreTest, TheSplitArmCopiesAndSurvivesTheSourceBeingPoisoned) const IntVec3 extent{4, 4, 1}; splitStore.NoteLevelDefined(key, kTex2DTarget, 0, extent); const Uint8* splitBase = splitStore.Adopt(key, kTex2DTarget, 0, extent, staged.data(), staged.size()); - const Uint8* monolithBase = - monolithStore.Adopt(key, kTex2DTarget, 0, extent, staged.data(), staged.size()); + const Uint8* monolithBase = monolithStore.Adopt(key, kTex2DTarget, 0, extent, staged.data(), staged.size()); ASSERT_NE(splitBase, nullptr); EXPECT_EQ(monolithBase, nullptr) @@ -165,8 +164,7 @@ TEST(StagedTextureStoreTest, AMutableNoncanonicalLevelKeepsItsExactDeclaredExten const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(41, 1)); const IntVec3 noncanonical{3, 2, 1}; - store.NoteLevelDefined(key, kTex2DTarget, 2, noncanonical, - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), + store.NoteLevelDefined(key, kTex2DTarget, 2, noncanonical, static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), static_cast(TextureInternalFormat::RGBA8)); EXPECT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 2)); EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 2), noncanonical) @@ -235,14 +233,13 @@ TEST(StagedTextureStoreTest, AZeroExtentCarrierIsCanonicalAndAHighDepthWordIsNot } TEST(StagedTextureStoreTest, MultisampleAndTextureBufferTargetsCannotAcceptStagedTexelBytes) { - EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DMS))); - EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DMSArray))); - EXPECT_FALSE(Server::StagedTextureTargetSupportsSubData( - static_cast(MG_Pipe::MGPipeResourceTarget::TexBuffer))); - EXPECT_TRUE(Server::StagedTextureTargetSupportsSubData( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D))); + EXPECT_FALSE( + Server::StagedTextureTargetSupportsSubData(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DMS))); + EXPECT_FALSE( + Server::StagedTextureTargetSupportsSubData(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DMSArray))); + EXPECT_FALSE( + Server::StagedTextureTargetSupportsSubData(static_cast(MG_Pipe::MGPipeResourceTarget::TexBuffer))); + EXPECT_TRUE(Server::StagedTextureTargetSupportsSubData(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D))); } // Keys are independent, ResetLevels drops one resource's whole chain, Drop one key and @@ -326,8 +323,7 @@ TEST(StagedTextureStoreTest, TwoSessionsHoldTheSameHandleAndOneSessionsDropAllSp store.Adopt(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, kwinBytes.data(), kwinBytes.size()); EnterBucket(/*session=*/2, /*group=*/20); - EXPECT_FALSE(store.IsLevelDefined(key, kTex2DTarget, 0)) - << "another session's {slot, gen} answered for this one's"; + EXPECT_FALSE(store.IsLevelDefined(key, kTex2DTarget, 0)) << "another session's {slot, gen} answered for this one's"; store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{2, 2, 1}); store.Adopt(key, kTex2DTarget, 0, IntVec3{2, 2, 1}, appBytes.data(), appBytes.size()); @@ -343,8 +339,7 @@ TEST(StagedTextureStoreTest, TwoSessionsHoldTheSameHandleAndOneSessionsDropAllSp ASSERT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 0)) << "a neighbour session's teardown dropped this session's declared level"; EXPECT_TRUE(store.IsCovered(key, kTex2DTarget, 0)); - EXPECT_NE(store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, kwinBytes.data(), kwinBytes.size()), - nullptr); + EXPECT_NE(store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, kwinBytes.data(), kwinBytes.size()), nullptr); EnterBucket(1, 11); EXPECT_FALSE(store.HasShadow(key)) << "a non-shared group of the same session is its own name space"; @@ -411,7 +406,8 @@ namespace { Vector FollowPattern(Uint8 base) { Vector bytes(64); - for (SizeT i = 0; i < bytes.size(); ++i) bytes[i] = static_cast(base + i); + for (SizeT i = 0; i < bytes.size(); ++i) + bytes[i] = static_cast(base + i); return bytes; } @@ -421,7 +417,9 @@ namespace { } // The byte (x, y, channel) of a 4x4 RGBA8 level image. - SizeT At(Int x, Int y, Int c) { return static_cast((y * 4 + x) * 4 + c); } + SizeT At(Int x, Int y, Int c) { + return static_cast((y * 4 + x) * 4 + c); + } } // namespace // THE MOVE: the 2x3 window at (1, 1) of the source lands at (2, 0) of the destination, every other @@ -526,8 +524,8 @@ TEST(StagedTextureStoreTest, ACopyTheStoreCannotVouchForIsNotFollowed) { store.NoteLevelDefined(bare, kTex2DTarget, 0, kFollowExtent); // defined, no bytes crossed const IntVec3 origin{0, 0, 0}; - EXPECT_FALSE(store.FollowCopy(src, kTex2DTarget, 0, IntVec3{3, 0, 0}, dst, kTex2DTarget, 0, origin, - IntVec3{2, 1, 1})) + EXPECT_FALSE( + store.FollowCopy(src, kTex2DTarget, 0, IntVec3{3, 0, 0}, dst, kTex2DTarget, 0, origin, IntVec3{2, 1, 1})) << "the box runs off the source's right edge"; EXPECT_FALSE(store.FollowCopy(narrow, kTex2DTarget, 0, origin, dst, kTex2DTarget, 0, origin, IntVec3{1, 1, 1})) << "a one-byte texel moved into a four-byte one"; @@ -540,11 +538,12 @@ TEST(StagedTextureStoreTest, ACopyTheStoreCannotVouchForIsNotFollowed) { << "the driver wrote the source behind the store's back, so its bytes are not the texels copied"; EXPECT_FALSE(store.LevelHoldsFollowedCopy(dst, kTex2DTarget, 0)); const Uint8* got = store.RequireLevelBytes(dst, kTex2DTarget, 0, "unit"); - for (SizeT i = 0; i < dstBytes.size(); ++i) ASSERT_EQ(got[i], dstBytes[i]) << "a refused copy moved byte " << i; + for (SizeT i = 0; i < dstBytes.size(); ++i) + ASSERT_EQ(got[i], dstBytes[i]) << "a refused copy moved byte " << i; Server::StagedTextureStore monolithStore(/*copies=*/false); - EXPECT_FALSE(monolithStore.FollowCopy(src, kTex2DTarget, 0, origin, dst, kTex2DTarget, 0, origin, - IntVec3{1, 1, 1})); + EXPECT_FALSE( + monolithStore.FollowCopy(src, kTex2DTarget, 0, origin, dst, kTex2DTarget, 0, origin, IntVec3{1, 1, 1})); } // A copy that covers ALL of a destination level whose bytes never crossed (glTexStorage, no upload) @@ -561,7 +560,8 @@ TEST(StagedTextureStoreTest, AWholeLevelCopyCompletesADestinationThatHadNoBytes) kFollowExtent)); ASSERT_TRUE(store.IsCovered(dst, kTex2DTarget, 0)); const Uint8* got = store.RequireLevelBytes(dst, kTex2DTarget, 0, "unit"); - for (SizeT i = 0; i < srcBytes.size(); ++i) ASSERT_EQ(got[i], srcBytes[i]) << "byte " << i; + for (SizeT i = 0; i < srcBytes.size(); ++i) + ASSERT_EQ(got[i], srcBytes[i]) << "byte " << i; } // The client's next run for a level holding a followed copy is the CLIENT's shadow, which never saw @@ -581,7 +581,8 @@ TEST(StagedTextureStoreTest, AClientRunOverAFollowedCopyLandsOnlyItsBoxes) { // The client's whole-level shadow: its own old bytes everywhere, 0xEE in the one row it wrote. Vector run = dstBytes; for (Int x = 0; x < 4; ++x) { - for (Int c = 0; c < 4; ++c) run[At(x, 3, c)] = 0xEE; + for (Int c = 0; c < 4; ++c) + run[At(x, 3, c)] = 0xEE; } const Server::StagedTextureStore::ClientTexelBox lastRow{0, 3, 0, 4, 1, 1}; ASSERT_TRUE(store.AdoptClientBoxes(dst, kTex2DTarget, 0, kFollowExtent, 0, run.data(), run.size(), &lastRow, 1)); @@ -589,7 +590,9 @@ TEST(StagedTextureStoreTest, AClientRunOverAFollowedCopyLandsOnlyItsBoxes) { for (Int y = 0; y < 4; ++y) { for (Int x = 0; x < 4; ++x) { for (Int c = 0; c < 4; ++c) { - const Uint8 want = y == 3 ? Uint8{0xEE} : (x < 2 && y < 2) ? srcBytes[At(x, y, c)] : dstBytes[At(x, y, c)]; + const Uint8 want = y == 3 ? Uint8{0xEE} + : (x < 2 && y < 2) ? srcBytes[At(x, y, c)] + : dstBytes[At(x, y, c)]; ASSERT_EQ(got[At(x, y, c)], want) << "texel (" << x << ", " << y << ")"; } } @@ -600,7 +603,8 @@ TEST(StagedTextureStoreTest, AClientRunOverAFollowedCopyLandsOnlyItsBoxes) { // A slab run that holds rows 2..3 only (imageOffset 32): row 0 is not in it. const Server::StagedTextureStore::ClientTexelBox firstRow{0, 0, 0, 4, 1, 1}; Vector slab(32, 0x33); - EXPECT_FALSE(store.AdoptClientBoxes(dst, kTex2DTarget, 0, kFollowExtent, 32, slab.data(), slab.size(), &firstRow, 1)); + EXPECT_FALSE( + store.AdoptClientBoxes(dst, kTex2DTarget, 0, kFollowExtent, 32, slab.data(), slab.size(), &firstRow, 1)); EXPECT_EQ(got[At(0, 0, 0)], srcBytes[At(0, 0, 0)]) << "a refused run wrote into the level"; } @@ -665,14 +669,16 @@ TEST(StagedTextureStoreTest, TheMipExtentDerivationKeepsArrayLayersFixed) { IntVec3(2, 1, 1)); EXPECT_EQ(Server::StagedTextureMipExtent(static_cast(MG_Pipe::MGPipeResourceTarget::Tex3D), 8, 8, 8, 3), IntVec3(1, 1, 1)); - EXPECT_EQ( - Server::StagedTextureMipExtent(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DArray), 8, 8, 6, 2), - IntVec3(2, 2, 6)) << "the layer count is not a dimension of the image"; + EXPECT_EQ(Server::StagedTextureMipExtent(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2DArray), 8, 8, 6, 2), + IntVec3(2, 2, 6)) + << "the layer count is not a dimension of the image"; EXPECT_EQ( Server::StagedTextureMipExtent(static_cast(MG_Pipe::MGPipeResourceTarget::Tex1DArray), 16, 4, 1, 3), - IntVec3(2, 4, 1)) << "a 1D array's HEIGHT is the layer count"; + IntVec3(2, 4, 1)) + << "a 1D array's HEIGHT is the layer count"; EXPECT_EQ(Server::StagedTextureMipExtent(static_cast(MG_Pipe::MGPipeResourceTarget::TexCube), 7, 7, 1, 3), - IntVec3(1, 1, 1)) << "shrinking clamps at 1, never 0"; + IntVec3(1, 1, 1)) + << "shrinking clamps at 1, never 0"; } // ==================== fix A2: one level, several staged runs ==================== @@ -695,8 +701,7 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove store.NoteLevelDefined(key, kTex2DTarget, 0, extent); // THE SECOND RUN FIRST, deliberately: coverage is a SET and the placement comes from the // record, so the order the pieces land in cannot matter. - EXPECT_EQ(monolithStore.AdoptRun(key, kTex2DTarget, 0, extent, 32, upper.data(), upper.size()), - nullptr); + EXPECT_EQ(monolithStore.AdoptRun(key, kTex2DTarget, 0, extent, 32, upper.data(), upper.size()), nullptr); EXPECT_EQ(monolithStore.TrackedResources(), 0u); store.AdoptRun(key, kTex2DTarget, 0, extent, 32, upper.data(), upper.size()); EXPECT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 0)); @@ -711,8 +716,10 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove << "two adjacent runs must merge into one covered range"; const Uint8* base = store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_stage_chunk_runs"); ASSERT_NE(base, nullptr); - for (SizeT i = 0; i < 32; ++i) EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; - for (SizeT i = 32; i < 64; ++i) EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; + for (SizeT i = 0; i < 32; ++i) + EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; + for (SizeT i = 32; i < 64; ++i) + EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; // w1's retired-stage poison, at the store's own scope: the COPY is what answers the readers. std::fill(lower.begin(), lower.end(), Uint8{0xDD}); @@ -727,8 +734,7 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove Vector part(16, 0x11); store.AdoptRun(holed, kTex2DTarget, 0, extent, 0, part.data(), part.size()); store.AdoptRun(holed, kTex2DTarget, 0, extent, 32, upper.data(), upper.size()); - EXPECT_EQ(store.LevelCoveredRunCount(holed, kTex2DTarget, 0), 2u) - << "two runs with a 16-byte gap merged into one"; + EXPECT_EQ(store.LevelCoveredRunCount(holed, kTex2DTarget, 0), 2u) << "two runs with a 16-byte gap merged into one"; EXPECT_FALSE(store.IsCovered(holed, kTex2DTarget, 0)) << "a span across the gap reads covered"; // A short leading run is not a complete level even if its current high-water mark ends at // that run. The store checks coverage against the server-derived byte bound, not its prefix. @@ -760,8 +766,8 @@ TEST(StagedTextureStoreTest, StageChunkRunsAssembleTheLevelImageAndAGapIsNotCove TEST(StagedTextureStoreTest, AdoptRunExtentMoveDoesNotInheritOldCoverage) { Server::StagedTextureStore store(/*copies=*/true); const Uint64 key = Server::StagedTextureStore::KeyForHandle(TestHandle(15, 1)); - Vector oldRun(64, 0x44); // the whole 4x4 RGBA8 level - Vector newRun(64, 0x99); // the upper half of the 8x4 RGBA8 level + Vector oldRun(64, 0x44); // the whole 4x4 RGBA8 level + Vector newRun(64, 0x99); // the upper half of the 8x4 RGBA8 level store.NoteLevelDefined(key, kTex2DTarget, 0, IntVec3{4, 4, 1}); store.AdoptRun(key, kTex2DTarget, 0, IntVec3{4, 4, 1}, 0, oldRun.data(), oldRun.size()); ASSERT_TRUE(store.IsCovered(key, kTex2DTarget, 0)); @@ -870,8 +876,8 @@ TEST(StagedTextureStoreTest, AGpuGeneratedLevelHasNoBytesAndItsTexelReadIsFatalB ASSERT_TRUE(store.IsLevelDefined(key, kTex2DTarget, 1)); ASSERT_FALSE(store.IsCovered(key, kTex2DTarget, 1)); - EXPECT_EXIT(store.RequireLevelBytes(key, kTex2DTarget, 1, "unit_gpu_level"), - ::testing::KilledBySignal(SIGABRT), ".*"); + EXPECT_EXIT(store.RequireLevelBytes(key, kTex2DTarget, 1, "unit_gpu_level"), ::testing::KilledBySignal(SIGABRT), + ".*"); const std::string log = ReadLog(); EXPECT_NE(log.find("Fatal{StageSnapshotTooNarrow, \"unit_gpu_level\"}"), std::string::npos) << "the abort happened but not for this rule's reason; the log says: " << log; @@ -902,8 +908,8 @@ TEST(StagedTextureStoreTest, ALevelWithAPieceMissingIsRefusedAsAWholeLevelReadBy store.AdoptRun(key, kTex2DTarget, 1, IntVec3{4, 4, 1}, 0, whole.data(), whole.size()); ASSERT_NE(store.RequireLevelBytes(key, kTex2DTarget, 1, "unit"), nullptr); - EXPECT_EXIT(store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_missing_piece"), - ::testing::KilledBySignal(SIGABRT), ".*"); + EXPECT_EXIT(store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_missing_piece"), ::testing::KilledBySignal(SIGABRT), + ".*"); const std::string log = ReadLog(); EXPECT_NE(log.find("Fatal{StageSnapshotTooNarrow, \"unit_missing_piece\"}"), std::string::npos) << "the abort happened but not for this rule's reason; the log says: " << log; @@ -997,9 +1003,8 @@ TEST(StagedTextureProductionTest, TextureSubDataThroughTheRealOpsTableCopiesAndS Vector src(64, 0xAB); // 4x4 texels, 4 bytes each MG_Pipe::MGPSubData rec{}; rec.Res = res; - rec.Target = MG_Pipe::MGPipePackSubDataTarget( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), - static_cast(TextureUploadTarget::Texture2D)); + rec.Target = MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), + static_cast(TextureUploadTarget::Texture2D)); rec.Level = 0; rec.UnionBox = {0, 0, 0, 4, 4, 1}; rec.RegionCount = 0; @@ -1062,8 +1067,8 @@ TEST(StagedTextureProductionTest, HooklessTextureConsumerOwnsBytesAndScopedStora desc.Height = 4; desc.Depth = 1; desc.Levels = 3; - MG_Pipe::MGPRespecifiedLevel level = MG_Pipe::MGPipeMakeRespecifiedLevel( - MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget), 0, 4, 4, 1); + MG_Pipe::MGPRespecifiedLevel level = + MG_Pipe::MGPipeMakeRespecifiedLevel(MG_Pipe::MGPipePackSubDataTarget(desc.Target, kTex2DTarget), 0, 4, 4, 1); ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceRespecify(desc, nullptr, &level)); EXPECT_EQ(store.LevelExtentOrUndefined(key, kTex2DTarget, 0), IntVec3(4, 4, 1)) << "null-data glTexImage defines the level even though no upload follows"; @@ -1149,9 +1154,8 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheHooklessApplierLandWhe Vector lower(32, 0x33); MG_Pipe::MGPSubData upperRecord{}; upperRecord.Res = res; - upperRecord.Target = MG_Pipe::MGPipePackSubDataTarget( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), - static_cast(TextureUploadTarget::Texture2D)); + upperRecord.Target = MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), + static_cast(TextureUploadTarget::Texture2D)); upperRecord.LevelWidth = 4; upperRecord.LevelHeight = 4; upperRecord.LevelDepth = 1; @@ -1167,8 +1171,7 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheHooklessApplierLandWhe upperRegion.SrcRowStride = 16; upperRegion.SrcSliceStride = 64; ASSERT_TRUE(MG_Pipe::MGPipeApplyResourceSubData(upperRecord, upper.data(), &upperRegion)); - EXPECT_EQ(store.LevelByteSize(key, kTex2DTarget, 0), 64u) - << "the image did not grow to the second half's end"; + EXPECT_EQ(store.LevelByteSize(key, kTex2DTarget, 0), 64u) << "the image did not grow to the second half's end"; EXPECT_EQ(store.LevelCoveredRunCount(key, kTex2DTarget, 0), 1u); EXPECT_FALSE(store.IsCovered(key, kTex2DTarget, 0)) << "only half the level has arrived"; @@ -1183,8 +1186,10 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheHooklessApplierLandWhe << "the two halves did not merge into one covered range"; const Uint8* base = store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_production_runs"); ASSERT_NE(base, nullptr); - for (SizeT i = 0; i < 32; ++i) EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; - for (SizeT i = 32; i < 64; ++i) EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; + for (SizeT i = 0; i < 32; ++i) + EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; + for (SizeT i = 32; i < 64; ++i) + EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; MG_Pipe::MGPHandleOnly death{}; death.Handle = res; @@ -1221,9 +1226,8 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheRealTextureHookLandWhe Vector lower(32, 0x33); MG_Pipe::MGPSubData upperRecord{}; upperRecord.Res = res; - upperRecord.Target = MG_Pipe::MGPipePackSubDataTarget( - static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), - static_cast(TextureUploadTarget::Texture2D)); + upperRecord.Target = MG_Pipe::MGPipePackSubDataTarget(static_cast(MG_Pipe::MGPipeResourceTarget::Tex2D), + static_cast(TextureUploadTarget::Texture2D)); upperRecord.LevelWidth = 4; upperRecord.LevelHeight = 4; upperRecord.LevelDepth = 1; @@ -1252,8 +1256,10 @@ TEST(StagedTextureProductionTest, StageChunkRunsThroughTheRealTextureHookLandWhe EXPECT_TRUE(store.IsCovered(key, kTex2DTarget, 0)); const Uint8* base = store.RequireLevelBytes(key, kTex2DTarget, 0, "unit_hook_runs"); ASSERT_NE(base, nullptr); - for (SizeT i = 0; i < 32; ++i) EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; - for (SizeT i = 32; i < 64; ++i) EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; + for (SizeT i = 0; i < 32; ++i) + EXPECT_EQ(base[i], 0x33) << "byte " << i << " of the lower run"; + for (SizeT i = 32; i < 64; ++i) + EXPECT_EQ(base[i], 0x77) << "byte " << i << " of the upper run"; MG_Pipe::MGPHandleOnly death{}; death.Handle = res; diff --git a/MobileGL/MG_Test/Wire/StreamClientPublicationTest.cpp b/MobileGL/MG_Test/Wire/StreamClientPublicationTest.cpp index c590a67a..f63329a5 100644 --- a/MobileGL/MG_Test/Wire/StreamClientPublicationTest.cpp +++ b/MobileGL/MG_Test/Wire/StreamClientPublicationTest.cpp @@ -256,8 +256,8 @@ namespace { const auto flushed = peer->FlushProgress(); if (posted != MOBILEGL_OK || flushed != MOBILEGL_OK) { std::printf("servo stopped: record=%llu post=%d flush=%d\n", - static_cast(applied), - static_cast(posted), static_cast(flushed)); + static_cast(applied), static_cast(posted), + static_cast(flushed)); std::fflush(nullptr); return; } @@ -303,8 +303,8 @@ namespace { MG_Pipe::MGPTextureParams params{}; params.Res = MG_Pipe::MGPipeHandle{static_cast(100 + i), 1}; Int32 status = 0; - (void)client.EmitAndWaitTails(MG_Pipe::MGPWireOp::SetTextureParams, ¶ms, - sizeof params, nullptr, 0, nullptr, 0, &status, nullptr, + (void)client.EmitAndWaitTails(MG_Pipe::MGPWireOp::SetTextureParams, ¶ms, sizeof params, nullptr, 0, + nullptr, 0, &status, nullptr, /*wantReply=*/false, /*willReadReply=*/true); } // DELIVER THEM. A published record reaches the peer on a flush, not on PublishAndNotify - @@ -316,12 +316,12 @@ namespace { // be behind. The servo posts them synchronously, and the reader is a thread on a socketpair, // so the settlement is a wait on the servo's own count plus one scheduling slice. const auto settled = std::chrono::steady_clock::now() + std::chrono::seconds(2); - while (answered.load(std::memory_order_acquire) < 13 && - std::chrono::steady_clock::now() < settled) + while (answered.load(std::memory_order_acquire) < 13 && std::chrono::steady_clock::now() < settled) std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); ASSERT_GE(answered.load(), 13u) << "the peer did not answer the crowding records"; - for (Int32 i = 1; i < kCreates; ++i) create(i); + for (Int32 i = 1; i < kCreates; ++i) + create(i); const std::uint64_t taken = Client::ClientCreateWindowTaken() - takenBefore; const Uint32 pending = Client::ClientCreateWindowPending(); // THE PROPERTY THE DEVICE MEASURED AS BROKEN: `Taken` counts the answers the drain has diff --git a/MobileGL/MG_Test/Wire/StreamLinkTest.cpp b/MobileGL/MG_Test/Wire/StreamLinkTest.cpp index 95aac244..25617e7b 100644 --- a/MobileGL/MG_Test/Wire/StreamLinkTest.cpp +++ b/MobileGL/MG_Test/Wire/StreamLinkTest.cpp @@ -164,7 +164,8 @@ namespace { Queue(3, 3); ASSERT_EQ(client.Flush(), MOBILEGL_OK); ASSERT_EQ(apply.WaitForWork(2000), SessionWait::Reached); - for (std::uint64_t seq = 1; seq <= 3; ++seq) client.DeclareReplyRead(seq); + for (std::uint64_t seq = 1; seq <= 3; ++seq) + client.DeclareReplyRead(seq); for (std::uint64_t seq = 1; seq <= 3; ++seq) { ASSERT_TRUE(apply.ApplyOne([&](const RingRecordView&) { const std::uint64_t value = seq * 11; diff --git a/MobileGL/MG_Test/Wire/SurfaceControlFrameTest.cpp b/MobileGL/MG_Test/Wire/SurfaceControlFrameTest.cpp index 6585e81e..7ace38d3 100644 --- a/MobileGL/MG_Test/Wire/SurfaceControlFrameTest.cpp +++ b/MobileGL/MG_Test/Wire/SurfaceControlFrameTest.cpp @@ -16,8 +16,8 @@ // agreement with the schema is pinned, not assumed; // * every framed op round-trips through a REAL CtrlEnvelope{SurfaceOp} buffer byte-for-byte; // * the wire entry refuses by NAME: an ANativeWindow* arriving over the wire dies -// Fatal{UnmigratedSurface, "AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server"} (a client window never crosses), and a kind the -// schema does not define dies Fatal{ProtocolCorruption, "SurfaceOp"}. +// Fatal{UnmigratedSurface, "AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server"} (a client window never crosses), +// and a kind the schema does not define dies Fatal{ProtocolCorruption, "SurfaceOp"}. // // The inproc blocking path itself - posting, parking, the reply half coming back - is gated by // ServerLoopTest's cases (AVoidForwarderCrossesAsOneDispatchedFrame and the whole @@ -88,7 +88,7 @@ namespace { // Encode one frame and hand back the parsed wire op. The builder must outlive the parse. const ::MobileGL::Wire::SurfaceOp* EncodeAndParse(const SurfaceControlFrame& frame, - flatbuffers::FlatBufferBuilder* builder) { + flatbuffers::FlatBufferBuilder* builder) { if (EncodeSurfaceOpFrame(frame, builder) != SurfaceWireError::None) return nullptr; const auto* envelope = ::MobileGL::Wire::GetCtrlEnvelope(builder->GetBufferPointer()); if (envelope == nullptr || envelope->msg_type() != ::MobileGL::Wire::CtrlMsg::SurfaceOp) return nullptr; @@ -115,7 +115,9 @@ TEST(SurfaceControlFrameTest, EveryMemberIsMechanicallyCheckedAsAnIntegerOrEnum) static_assert(!Server::Detail::IsSurfaceControlValue); EXPECT_TRUE(Server::Detail::SurfaceControlFrameHasOnlyValues()); // Why trivially-copyable alone did not enforce this invariant. - struct PointerBag { void* pointer; }; + struct PointerBag { + void* pointer; + }; static_assert(std::is_trivially_copyable_v && std::is_standard_layout_v); EXPECT_FALSE(Server::Detail::IsSurfaceControlValue); } @@ -125,11 +127,11 @@ TEST(SurfaceControlFrameTest, ExactlyTheTenFramedOpsHaveWireKindsAndTheValuesAre // asserted per op rather than trusted from the static_asserts, because THIS is the table a // reader consults when the schema next changes. const SurfaceControlOp wired[] = { - SurfaceControlOp::InitializeDisplay, SurfaceControlOp::CreateWindowSurface, + SurfaceControlOp::InitializeDisplay, SurfaceControlOp::CreateWindowSurface, SurfaceControlOp::CreatePbufferSurface, SurfaceControlOp::ResizeWindowSurface, - SurfaceControlOp::ReleaseSurface, SurfaceControlOp::MakeCurrent, - SurfaceControlOp::ReleaseCurrent, SurfaceControlOp::SetSwapInterval, - SurfaceControlOp::ReleaseResources, SurfaceControlOp::SetWindowHandle, + SurfaceControlOp::ReleaseSurface, SurfaceControlOp::MakeCurrent, + SurfaceControlOp::ReleaseCurrent, SurfaceControlOp::SetSwapInterval, + SurfaceControlOp::ReleaseResources, SurfaceControlOp::SetWindowHandle, }; for (const SurfaceControlOp op : wired) { ::MobileGL::Wire::SurfaceOpKind kind; @@ -152,7 +154,8 @@ TEST(SurfaceControlFrameTest, ExactlyTheTenFramedOpsHaveWireKindsAndTheValuesAre // a statement about ten and G14 does not let a name be traded for another. const SurfaceControlOp inprocOnly[] = { SurfaceControlOp::None, - SurfaceControlOp::SwapBuffersInprocOnly, SurfaceControlOp::InitWindowSurfaceInprocOnly, + SurfaceControlOp::SwapBuffersInprocOnly, + SurfaceControlOp::InitWindowSurfaceInprocOnly, SurfaceControlOp::ProbeForTesting, }; for (const SurfaceControlOp op : inprocOnly) { @@ -211,14 +214,13 @@ TEST(SurfaceControlFrameTest, TheContextLifecycleFramedOpsAreCreateContextAndDes "protocol break no other case would see"; EXPECT_EQ(static_cast(::MobileGL::Wire::SurfaceOpKind::DestroyContext), 13u); - const SurfaceControlOp wired[] = {SurfaceControlOp::CreateContext, - SurfaceControlOp::DestroyContext}; + const SurfaceControlOp wired[] = {SurfaceControlOp::CreateContext, SurfaceControlOp::DestroyContext}; for (const SurfaceControlOp op : wired) { ::MobileGL::Wire::SurfaceOpKind kind; - ASSERT_TRUE(WireKindForSurfaceControlOp(op, &kind)) - << Server::SurfaceControlOpName(op) << " may never leave the process, so a spawned " - "client's eglCreateContext/eglDestroyContext " - "cannot reach the server"; + ASSERT_TRUE(WireKindForSurfaceControlOp(op, &kind)) << Server::SurfaceControlOpName(op) + << " may never leave the process, so a spawned " + "client's eglCreateContext/eglDestroyContext " + "cannot reach the server"; EXPECT_EQ(static_cast(kind), static_cast(op)) << Server::SurfaceControlOpName(op) << ": the frame and wire enums drifted apart"; SurfaceControlOp back; @@ -234,7 +236,7 @@ TEST(SurfaceControlFrameTest, TheContextLifecycleFramedOpsAreCreateContextAndDes SurfaceControlFrame frame; frame.kind = SurfaceControlOp::CreateContext; frame.seq = 0x99; - frame.context = 7; // the client context token + frame.context = 7; // the client context token frame.shareGroupToken = 3; frame.contextFlags = 0x5; flatbuffers::FlatBufferBuilder builder(256); @@ -303,16 +305,22 @@ TEST(SurfaceControlFrameTest, WindowBackendAndWireWindowKindMapExplicitlyBothWay TEST(SurfaceControlFrameTest, EveryFramedOpRoundTripsThroughTheWireEnvelope) { const SurfaceControlOp wired[] = { - SurfaceControlOp::InitializeDisplay, SurfaceControlOp::CreateWindowSurface, - SurfaceControlOp::CreatePbufferSurface, SurfaceControlOp::ResizeWindowSurface, - SurfaceControlOp::ReleaseSurface, SurfaceControlOp::MakeCurrent, - SurfaceControlOp::ReleaseCurrent, SurfaceControlOp::SetSwapInterval, - SurfaceControlOp::ReleaseResources, SurfaceControlOp::SetWindowHandle, + SurfaceControlOp::InitializeDisplay, + SurfaceControlOp::CreateWindowSurface, + SurfaceControlOp::CreatePbufferSurface, + SurfaceControlOp::ResizeWindowSurface, + SurfaceControlOp::ReleaseSurface, + SurfaceControlOp::MakeCurrent, + SurfaceControlOp::ReleaseCurrent, + SurfaceControlOp::SetSwapInterval, + SurfaceControlOp::ReleaseResources, + SurfaceControlOp::SetWindowHandle, // P5f cp's append and P14 S1's two. This case's name is a claim about EVERY framed op, // which is what makes it the one place an append cannot be forgotten: a new kind that // encodes but does not decode fails here even if the ten-op case above was left alone. SurfaceControlOp::InitCapabilities, - SurfaceControlOp::CreateContext, SurfaceControlOp::DestroyContext, + SurfaceControlOp::CreateContext, + SurfaceControlOp::DestroyContext, }; for (const SurfaceControlOp op : wired) { SurfaceControlFrame frame; @@ -334,8 +342,7 @@ TEST(SurfaceControlFrameTest, EveryFramedOpRoundTripsThroughTheWireEnvelope) { ASSERT_NE(wireOp, nullptr) << Server::SurfaceControlOpName(op) << " failed to encode"; SurfaceControlFrame back; - ASSERT_EQ(DecodeWireSurfaceOp(*wireOp, &back), SurfaceWireError::None) - << Server::SurfaceControlOpName(op); + ASSERT_EQ(DecodeWireSurfaceOp(*wireOp, &back), SurfaceWireError::None) << Server::SurfaceControlOpName(op); EXPECT_EQ(back.kind, frame.kind); EXPECT_EQ(back.seq, frame.seq); EXPECT_EQ(back.display, frame.display); @@ -346,8 +353,7 @@ TEST(SurfaceControlFrameTest, EveryFramedOpRoundTripsThroughTheWireEnvelope) { EXPECT_EQ(back.width, frame.width); EXPECT_EQ(back.height, frame.height); EXPECT_EQ(back.swapInterval, frame.swapInterval); - EXPECT_EQ(back.windowBackend, frame.windowBackend) - << Server::SurfaceControlOpName(op); + EXPECT_EQ(back.windowBackend, frame.windowBackend) << Server::SurfaceControlOpName(op); EXPECT_EQ(back.nativeToken, frame.nativeToken); // P14 S1: the two appended fields travel with every op, 0 included - a field that only // survives on the op that uses it is a field the codec has quietly made op-dependent. @@ -402,10 +408,10 @@ TEST(SurfaceControlFrameTest, MalformedAndInprocOnlyFramesRefuseToEncode) { TEST(SurfaceControlFrameTest, AWireWindowKindThatNamesNoBackendFailsDecodeByName) { flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp( - builder, 1, ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle, 0, 0, ::MobileGL::Wire::WindowKind::Surfaceless, 0, 0, - 0, 0, 0, 0); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, 1, ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle, 0, + 0, ::MobileGL::Wire::WindowKind::Surfaceless, 0, 0, 0, 0, 0, 0); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); const ::MobileGL::Wire::SurfaceOp* wireOp = ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); @@ -416,17 +422,16 @@ TEST(SurfaceControlFrameTest, AWireWindowKindThatNamesNoBackendFailsDecodeByName // And the Android refusal is a DECODE answer before it is a Fatal: the pure half. flatbuffers::FlatBufferBuilder androidBuilder(256); - const auto androidOp = ::MobileGL::Wire::CreateSurfaceOp( - androidBuilder, 1, ::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, 0, 0, - ::MobileGL::Wire::WindowKind::AndroidNativeWindow, 0x1234, 800, 600, 0, 0, 0); + const auto androidOp = + ::MobileGL::Wire::CreateSurfaceOp(androidBuilder, 1, ::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, 0, 0, + ::MobileGL::Wire::WindowKind::AndroidNativeWindow, 0x1234, 800, 600, 0, 0, 0); const auto androidEnvelope = ::MobileGL::Wire::CreateCtrlEnvelope(androidBuilder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, androidOp.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(androidBuilder, androidEnvelope); const ::MobileGL::Wire::SurfaceOp* androidWireOp = ::MobileGL::Wire::GetCtrlEnvelope(androidBuilder.GetBufferPointer())->msg_as_SurfaceOp(); ASSERT_NE(androidWireOp, nullptr); - EXPECT_EQ(DecodeWireSurfaceOp(*androidWireOp, &frame), - SurfaceWireError::AndroidNativeWindowArrived); + EXPECT_EQ(DecodeWireSurfaceOp(*androidWireOp, &frame), SurfaceWireError::AndroidNativeWindowArrived); } #if !defined(_WIN32) @@ -436,17 +441,20 @@ TEST(SurfaceControlFrameTest, AWireWindowKindThatNamesNoBackendFailsDecodeByName // test that only checks for a crash goes green on any other abort in the same body. TEST(SurfaceControlFrameTest, AnAndroidNativeWindowOnTheWireIsRefusedByName) { flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp( - builder, 9, ::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, 1, 2, - ::MobileGL::Wire::WindowKind::AndroidNativeWindow, 0x1234, 800, 600, 0, 0, 0); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = + ::MobileGL::Wire::CreateSurfaceOp(builder, 9, ::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, 1, 2, + ::MobileGL::Wire::WindowKind::AndroidNativeWindow, 0x1234, 800, 600, 0, 0, 0); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); - const ::MobileGL::Wire::SurfaceOp* wireOp = ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); + const ::MobileGL::Wire::SurfaceOp* wireOp = + ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); ASSERT_NE(wireOp, nullptr); EXPECT_EXIT(ServerApplyWireSurfaceOp(*wireOp, nullptr), ::testing::KilledBySignal(SIGABRT), ".*"); const std::string log = ReadLog(); - EXPECT_NE(log.find("Fatal{UnmigratedSurface, \"AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server\"}"), std::string::npos) + EXPECT_NE(log.find("Fatal{UnmigratedSurface, \"AndroidNativeWindow:use-MOBILEGL_IPC_SURFACE=server\"}"), + std::string::npos) << "the abort happened but not for this rule's reason; the log says: " << log; } @@ -455,30 +463,33 @@ TEST(SurfaceControlFrameTest, AnAndroidNativeWindowOnTheWireIsRefusedByName) { // waiting on a reply that never comes, which is a hang wearing a green lane. TEST(SurfaceControlFrameTest, AMetalLayerOnTheWireIsRefusedByName) { // Both operations that consume a native window must refuse the process-local object. - for (const auto kind : {::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, - ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle}) { + for (const auto kind : + {::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface, ::MobileGL::Wire::SurfaceOpKind::SetWindowHandle}) { flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp( - builder, 19, kind, 1, 2, ::MobileGL::Wire::WindowKind::MetalLayer, 0x1234, 800, 600); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope( - builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, 19, kind, 1, 2, + ::MobileGL::Wire::WindowKind::MetalLayer, 0x1234, 800, 600); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); const auto* wireOp = ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); ASSERT_NE(wireOp, nullptr); SurfaceControlFrame frame; EXPECT_EQ(DecodeWireSurfaceOp(*wireOp, &frame), SurfaceWireError::MetalLayerArrived); EXPECT_EXIT(ServerApplyWireSurfaceOp(*wireOp, nullptr), ::testing::KilledBySignal(SIGABRT), ".*"); - EXPECT_NE(ReadLog().find("Fatal{UnmigratedSurface, \"MetalLayer:use-MOBILEGL_IPC_SURFACE=server\"}"), std::string::npos); + EXPECT_NE(ReadLog().find("Fatal{UnmigratedSurface, \"MetalLayer:use-MOBILEGL_IPC_SURFACE=server\"}"), + std::string::npos); } } TEST(SurfaceControlFrameTest, AnUnknownWireOpKindIsProtocolCorruptionByName) { flatbuffers::FlatBufferBuilder builder(256); - const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, 9, static_cast<::MobileGL::Wire::SurfaceOpKind>(200), 1, 2, - ::MobileGL::Wire::WindowKind::None, 0, 0, 0, 0, 0, 0); - const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); + const auto op = ::MobileGL::Wire::CreateSurfaceOp(builder, 9, static_cast<::MobileGL::Wire::SurfaceOpKind>(200), 1, + 2, ::MobileGL::Wire::WindowKind::None, 0, 0, 0, 0, 0, 0); + const auto envelope = + ::MobileGL::Wire::CreateCtrlEnvelope(builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union()); ::MobileGL::Wire::FinishCtrlEnvelopeBuffer(builder, envelope); - const ::MobileGL::Wire::SurfaceOp* wireOp = ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); + const ::MobileGL::Wire::SurfaceOp* wireOp = + ::MobileGL::Wire::GetCtrlEnvelope(builder.GetBufferPointer())->msg_as_SurfaceOp(); ASSERT_NE(wireOp, nullptr); EXPECT_EXIT(ServerApplyWireSurfaceOp(*wireOp, nullptr), ::testing::KilledBySignal(SIGABRT), ".*"); @@ -740,7 +751,9 @@ namespace { } }; - NativeWindowType FakeClientWindow() { return (NativeWindowType)(std::uintptr_t)0x1234; } + NativeWindowType FakeClientWindow() { + return (NativeWindowType)(std::uintptr_t)0x1234; + } } // namespace // THE HEADLINE OF D1: a headless client (NULL window) with MOBILEGL_IPC_SURFACE=server gets a window diff --git a/MobileGL/MG_Test/Wire/TwinOwnershipTest.cpp b/MobileGL/MG_Test/Wire/TwinOwnershipTest.cpp index 907417ef..5a264e18 100644 --- a/MobileGL/MG_Test/Wire/TwinOwnershipTest.cpp +++ b/MobileGL/MG_Test/Wire/TwinOwnershipTest.cpp @@ -85,8 +85,8 @@ namespace { // The six-registry shape, on the same trivial twin and on a kind no shipping path twins // (Query), so a case can instantiate it without touching a real slot space. - using RegistryShapedTable = GL::TwinRegistry; + using RegistryShapedTable = + GL::TwinRegistry; class TwinOwnershipTest : public ::testing::Test { protected: @@ -210,8 +210,7 @@ namespace { // may still be rendering in this process (P14 S2). EXPECT_EQ(table.DropBucketsOfSession(kSessionA), 1u) << "the ended session's group was not dropped"; BindKey(kSessionA, kGroupOne); - EXPECT_EQ(table.FindByHandle(handle), nullptr) - << "the ended session still resolves a twin it built"; + EXPECT_EQ(table.FindByHandle(handle), nullptr) << "the ended session still resolves a twin it built"; BindKey(kSessionB, kGroupOne); const MobileGL::SharedPtr* const survivor = table.FindByHandle(handle); ASSERT_NE(survivor, nullptr) @@ -236,8 +235,7 @@ namespace { Twin* const processWide = Mint(table, handle, 66u); BindKey(kSessionA, kGroupOne); - EXPECT_EQ(table.FindByHandle(handle), nullptr) - << "a served context resolved the process-wide twin"; + EXPECT_EQ(table.FindByHandle(handle), nullptr) << "a served context resolved the process-wide twin"; EXPECT_NE(Mint(table, handle, 77u), processWide); t_sessionKey = 0; @@ -263,8 +261,7 @@ namespace { ASSERT_EQ(table.LiveGenAt(handle.Slot), handle.Gen); BindKey(kSessionA, kGroupTwo); - EXPECT_EQ(table.FindByHandle(handle), nullptr) - << "the six-registry shape is still one process-wide table"; + EXPECT_EQ(table.FindByHandle(handle), nullptr) << "the six-registry shape is still one process-wide table"; EXPECT_EQ(table.LiveGenAt(handle.Slot), 0u) << "the other group's slot reads as live"; MobileGL::SharedPtr* const other = table.GetOrCreateByHandle(handle); ASSERT_NE(other, nullptr); diff --git a/MobileGL/MG_Util/Async/JobNode.cpp b/MobileGL/MG_Util/Async/JobNode.cpp index 9c466f82..2ebab1a6 100644 --- a/MobileGL/MG_Util/Async/JobNode.cpp +++ b/MobileGL/MG_Util/Async/JobNode.cpp @@ -31,26 +31,38 @@ namespace MobileGL::MG_Util::Async { if (!continuation) return; try { continuation(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { MGLOG_E_ONCE("JobNode: a terminal continuation threw (%s); it has been contained, but whatever it " - "was going to do did not happen", - e.what()); - } catch (...) { + "was going to do did not happen", + e.what()); + } + catch (...) { MGLOG_E_ONCE("JobNode: a terminal continuation threw a non-std exception; it has been contained, " - "but whatever it was going to do did not happen"); + "but whatever it was going to do did not happen"); } } } // namespace - Bool JobNode::IsTerminal() const { return IsTerminalState(m_state.load(std::memory_order_acquire)); } + Bool JobNode::IsTerminal() const { + return IsTerminalState(m_state.load(std::memory_order_acquire)); + } - Bool JobNode::IsComplete() const { return m_state.load(std::memory_order_acquire) == JobState::Complete; } + Bool JobNode::IsComplete() const { + return m_state.load(std::memory_order_acquire) == JobState::Complete; + } - Bool JobNode::IsCancelled() const { return m_state.load(std::memory_order_acquire) == JobState::Cancelled; } + Bool JobNode::IsCancelled() const { + return m_state.load(std::memory_order_acquire) == JobState::Cancelled; + } - Bool JobNode::IsCancellationRequested() const { return m_cancelled.load(std::memory_order_acquire); } + Bool JobNode::IsCancellationRequested() const { + return m_cancelled.load(std::memory_order_acquire); + } - JobState JobNode::State() const { return m_state.load(std::memory_order_acquire); } + JobState JobNode::State() const { + return m_state.load(std::memory_order_acquire); + } // The single place a node changes state. Keeping every transition here is what makes the // continuation list exactly-once: the same critical section that publishes the terminal @@ -90,18 +102,18 @@ namespace MobileGL::MG_Util::Async { try { RunBody(); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { // Asio propagates an exception escaping a handler out of thread_pool::run(), // which means std::terminate for the whole process. Every job boundary contains // it and reports the job as Cancelled; the joining GL thread then sees a node // that produced no result, which is the same shape as an abandoned node. - diagnostics.logLines.push_back( - {MOBILEGL_LOG_LEVEL_DEBUG, std::format("Job body threw: {}", e.what())}); + diagnostics.logLines.push_back({MOBILEGL_LOG_LEVEL_DEBUG, std::format("Job body threw: {}", e.what())}); TryTransition(JobState::Running, JobState::Cancelled); return; - } catch (...) { - diagnostics.logLines.push_back( - {MOBILEGL_LOG_LEVEL_DEBUG, String("Job body threw a non-std exception")}); + } + catch (...) { + diagnostics.logLines.push_back({MOBILEGL_LOG_LEVEL_DEBUG, String("Job body threw a non-std exception")}); TryTransition(JobState::Running, JobState::Cancelled); return; } @@ -119,7 +131,9 @@ namespace MobileGL::MG_Util::Async { m_cancelled.load(std::memory_order_acquire) ? JobState::Cancelled : JobState::Complete); } - void JobNode::RunInline() { Run(); } + void JobNode::RunInline() { + Run(); + } void JobNode::Wait() { // Invariant I4, mechanically enforced: no job body ever blocks on another job, so the diff --git a/MobileGL/MG_Util/Async/JobNode.h b/MobileGL/MG_Util/Async/JobNode.h index 82aa9816..54d70f1f 100644 --- a/MobileGL/MG_Util/Async/JobNode.h +++ b/MobileGL/MG_Util/Async/JobNode.h @@ -53,8 +53,8 @@ namespace MobileGL::MG_Util::Async { }; struct JobDiagnostics { - Vector errors; // replayed, in ascending `sequence`, by the join - Vector logLines; // worker-side MGLOG text, flushed in order by the join + Vector errors; // replayed, in ascending `sequence`, by the join + Vector logLines; // worker-side MGLOG text, flushed in order by the join }; // The scheduling primitive every asynchronous compile and link is built on. A node owns @@ -83,7 +83,7 @@ namespace MobileGL::MG_Util::Async { // Lock-free and non-blocking - safe from any thread, including a pool thread. Bool IsTerminal() const; - Bool IsComplete() const; // Complete only; this is what backs GL_COMPLETION_STATUS_KHR + Bool IsComplete() const; // Complete only; this is what backs GL_COMPLETION_STATUS_KHR Bool IsCancelled() const; // settled AS cancelled - the outcome, not the request JobState State() const; diff --git a/MobileGL/MG_Util/Async/ShaderCompilePool.cpp b/MobileGL/MG_Util/Async/ShaderCompilePool.cpp index e142a7a0..96ee6dac 100644 --- a/MobileGL/MG_Util/Async/ShaderCompilePool.cpp +++ b/MobileGL/MG_Util/Async/ShaderCompilePool.cpp @@ -40,7 +40,9 @@ namespace MobileGL::MG_Util::Async { // stack-allocated pools a test builds - those join themselves in their destructor. std::atomic g_processPool{nullptr}; - Bool InProcessTeardown() { return g_processTeardown; } + Bool InProcessTeardown() { + return g_processTeardown; + } void EnsureProcessTeardownSentinel() { std::call_once(g_teardownSentinelOnce, [] { @@ -63,8 +65,7 @@ namespace MobileGL::MG_Util::Async { } Uint64 ReadCpuMaxFrequencyKHz(const Uint cpu) { - const String path = - std::format("/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_max_freq", cpu); + const String path = std::format("/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_max_freq", cpu); std::FILE* file = std::fopen(path.c_str(), "r"); if (file == nullptr) return 0; unsigned long long value = 0; @@ -101,9 +102,12 @@ namespace MobileGL::MG_Util::Async { Bool AsyncShaderCompileEnabled() { switch (MG_Config::Features.AsyncShaderCompile) { - case MG_Config::QuirkOverride::ForceOn: return true; - case MG_Config::QuirkOverride::ForceOff: return false; - case MG_Config::QuirkOverride::Auto: break; + case MG_Config::QuirkOverride::ForceOn: + return true; + case MG_Config::QuirkOverride::ForceOff: + return false; + case MG_Config::QuirkOverride::Auto: + break; } return kAsyncShaderCompileDefault; } @@ -129,9 +133,12 @@ namespace MobileGL::MG_Util::Async { Bool OptimisticShaderStatusActive() { switch (MG_Config::Features.AsyncOptimisticShaderStatus) { - case MG_Config::QuirkOverride::ForceOn: return AsyncShaderCompileActive(); - case MG_Config::QuirkOverride::ForceOff: return false; - case MG_Config::QuirkOverride::Auto: break; + case MG_Config::QuirkOverride::ForceOn: + return AsyncShaderCompileActive(); + case MG_Config::QuirkOverride::ForceOff: + return false; + case MG_Config::QuirkOverride::Auto: + break; } return kOptimisticShaderStatusDefault && AsyncShaderCompileActive(); } @@ -185,7 +192,8 @@ namespace MobileGL::MG_Util::Async { ++inFlight; try { asio::post(*pool, [this, node]() mutable { RunOnWorker(Move(node)); }); - } catch (...) { + } + catch (...) { --inFlight; toCancel.push_back(Move(node)); } @@ -218,7 +226,9 @@ namespace MobileGL::MG_Util::Async { ShaderCompilePool::ShaderCompilePool(const Uint threadCount) : m_impl(MakeUnique(threadCount)) {} - ShaderCompilePool::~ShaderCompilePool() { StopAndDrain(); } + ShaderCompilePool::~ShaderCompilePool() { + StopAndDrain(); + } ShaderCompilePool& ShaderCompilePool::Get() { // Leak-at-exit, like the other MobileGL singletons: the object itself is never @@ -235,9 +245,13 @@ namespace MobileGL::MG_Util::Async { return *pool; } - Bool ShaderCompilePool::IsPoolThread() { return tl_isPoolThread; } + Bool ShaderCompilePool::IsPoolThread() { + return tl_isPoolThread; + } - Uint ShaderCompilePool::GetThreadCount() const { return m_impl->threadCount; } + Uint ShaderCompilePool::GetThreadCount() const { + return m_impl->threadCount; + } Uint ShaderCompilePool::GetMaxConcurrency() const { const std::lock_guard lock(m_impl->mutex); @@ -283,9 +297,10 @@ namespace MobileGL::MG_Util::Async { m_impl->DispatchLocked(toCancel); enqueued = true; } - } catch (...) { + } + catch (...) { MGLOG_E_ONCE("ShaderCompilePool::Post: enqueue failed; cancelling the job so its joiner " - "cannot block forever"); + "cannot block forever"); if (node) node->Cancel(); return; } diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 9d97011c..fe0fcfff 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -90,8 +90,12 @@ namespace MobileGL::MG_Util::BackendLoader { #if defined(MOBILEGL_IOS) "@rpath/", "@executable_path/Frameworks/", "@loader_path/Frameworks/", #else - "/opt/vc/lib/", "/usr/local/lib/", "/usr/lib/", "/usr/lib/x86_64-linux-gnu/", - "/usr/lib64/", "/lib64/", + "/opt/vc/lib/", + "/usr/local/lib/", + "/usr/lib/", + "/usr/lib/x86_64-linux-gnu/", + "/usr/lib64/", + "/lib64/", #endif "" // Keep this last so the dynamic loader can use LD_LIBRARY_PATH. }; @@ -109,8 +113,7 @@ namespace MobileGL::MG_Util::BackendLoader { } lib = dlopen(signedPath.c_str(), flags); if (lib == nullptr) { - MGLOG_F("Failed to open signed trace ANGLE library %s: %s", - signedPath.c_str(), dlerror()); + MGLOG_F("Failed to open signed trace ANGLE library %s: %s", signedPath.c_str(), dlerror()); return nullptr; } MGLOG_I("Loaded signed trace ANGLE library: %s", signedPath.c_str()); @@ -180,8 +183,8 @@ namespace MobileGL::MG_Util::BackendLoader { #endif } - MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR - ResolveSwapBuffersWithDamage(const MG_External::EGLFunctionsTable& funcs) { + MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ResolveSwapBuffersWithDamage( + const MG_External::EGLFunctionsTable& funcs) { if (!funcs.eglGetCurrentDisplay || !funcs.eglQueryString || !funcs.eglGetProcAddress) { return nullptr; } @@ -793,54 +796,26 @@ namespace MobileGL::MG_Util::BackendLoader { } \ } while (0); - { - INIT_EGL_FUNC(eglBindAPI) - INIT_EGL_FUNC(eglBindTexImage) - INIT_EGL_FUNC(eglChooseConfig) - INIT_EGL_FUNC(eglCopyBuffers) - INIT_EGL_FUNC(eglCreateContext) - INIT_EGL_FUNC(eglCreatePbufferFromClientBuffer) - INIT_EGL_FUNC(eglCreatePbufferSurface) - INIT_EGL_FUNC(eglCreatePixmapSurface) - INIT_EGL_FUNC(eglCreatePlatformPixmapSurface) - INIT_EGL_FUNC(eglCreatePlatformWindowSurface) - INIT_EGL_FUNC(eglCreateWindowSurface) - INIT_EGL_FUNC(eglDestroyContext) - INIT_EGL_FUNC(eglDestroySurface) - INIT_EGL_FUNC(eglGetConfigAttrib) - INIT_EGL_FUNC(eglGetConfigs) - INIT_EGL_FUNC(eglGetCurrentContext) - INIT_EGL_FUNC(eglGetCurrentDisplay) - INIT_EGL_FUNC(eglGetCurrentSurface) - INIT_EGL_FUNC(eglGetDisplay) - INIT_EGL_FUNC(eglGetPlatformDisplay) - INIT_EGL_FUNC(eglGetError) - INIT_EGL_FUNC(eglGetProcAddress) - INIT_EGL_FUNC(eglInitialize) - INIT_EGL_FUNC(eglMakeCurrent) - INIT_EGL_FUNC(eglQueryAPI) - INIT_EGL_FUNC(eglQueryContext) - INIT_EGL_FUNC(eglQueryString) - INIT_EGL_FUNC(eglQuerySurface) - INIT_EGL_FUNC(eglReleaseTexImage) - INIT_EGL_FUNC(eglReleaseThread) - INIT_EGL_FUNC(eglSurfaceAttrib) - INIT_EGL_FUNC(eglSwapBuffers) - INIT_EGL_FUNC(eglSwapInterval) - INIT_EGL_FUNC(eglTerminate) - INIT_EGL_FUNC_OPTIONAL(eglUnlockSurfaceKHR) - INIT_EGL_FUNC(eglWaitClient) - INIT_EGL_FUNC(eglWaitGL) - INIT_EGL_FUNC(eglWaitNative) - INIT_EGL_FUNC(eglCreateSync) - INIT_EGL_FUNC(eglDestroySync) - INIT_EGL_FUNC(eglClientWaitSync) - INIT_EGL_FUNC(eglGetSyncAttrib) - INIT_EGL_FUNC(eglCreateImage) - INIT_EGL_FUNC(eglDestroyImage) - INIT_EGL_FUNC(eglGetPlatformDisplay) - INIT_EGL_FUNC(eglWaitSync) - } + {INIT_EGL_FUNC(eglBindAPI) INIT_EGL_FUNC(eglBindTexImage) INIT_EGL_FUNC(eglChooseConfig) INIT_EGL_FUNC( + eglCopyBuffers) INIT_EGL_FUNC(eglCreateContext) INIT_EGL_FUNC(eglCreatePbufferFromClientBuffer) + INIT_EGL_FUNC(eglCreatePbufferSurface) INIT_EGL_FUNC(eglCreatePixmapSurface) INIT_EGL_FUNC( + eglCreatePlatformPixmapSurface) INIT_EGL_FUNC(eglCreatePlatformWindowSurface) + INIT_EGL_FUNC(eglCreateWindowSurface) INIT_EGL_FUNC(eglDestroyContext) INIT_EGL_FUNC(eglDestroySurface) + INIT_EGL_FUNC(eglGetConfigAttrib) INIT_EGL_FUNC(eglGetConfigs) INIT_EGL_FUNC(eglGetCurrentContext) + INIT_EGL_FUNC(eglGetCurrentDisplay) INIT_EGL_FUNC(eglGetCurrentSurface) INIT_EGL_FUNC( + eglGetDisplay) INIT_EGL_FUNC(eglGetPlatformDisplay) INIT_EGL_FUNC(eglGetError) + INIT_EGL_FUNC(eglGetProcAddress) INIT_EGL_FUNC(eglInitialize) INIT_EGL_FUNC(eglMakeCurrent) + INIT_EGL_FUNC(eglQueryAPI) INIT_EGL_FUNC(eglQueryContext) INIT_EGL_FUNC(eglQueryString) + INIT_EGL_FUNC(eglQuerySurface) INIT_EGL_FUNC(eglReleaseTexImage) INIT_EGL_FUNC( + eglReleaseThread) INIT_EGL_FUNC(eglSurfaceAttrib) INIT_EGL_FUNC(eglSwapBuffers) + INIT_EGL_FUNC(eglSwapInterval) INIT_EGL_FUNC(eglTerminate) + INIT_EGL_FUNC_OPTIONAL(eglUnlockSurfaceKHR) INIT_EGL_FUNC(eglWaitClient) + INIT_EGL_FUNC(eglWaitGL) INIT_EGL_FUNC(eglWaitNative) INIT_EGL_FUNC( + eglCreateSync) INIT_EGL_FUNC(eglDestroySync) + INIT_EGL_FUNC(eglClientWaitSync) INIT_EGL_FUNC(eglGetSyncAttrib) + INIT_EGL_FUNC(eglCreateImage) INIT_EGL_FUNC(eglDestroyImage) + INIT_EGL_FUNC(eglGetPlatformDisplay) + INIT_EGL_FUNC(eglWaitSync)} // Display extensions can only be checked after the display is initialized. funcs.eglSwapBuffersWithDamageEXT = nullptr; @@ -856,8 +831,8 @@ namespace MobileGL::MG_Util::BackendLoader { const MG_External::GLESFunctionsTable& f) { const Bool esVersionOk = caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); - if (!esVersionOk || !f.glDrawArraysIndirect || !f.glBindBufferBase || !f.glMapBufferRange || - !f.glUnmapBuffer || !f.glMemoryBarrier || !f.glCreateShader || !f.glCreateProgram) { + if (!esVersionOk || !f.glDrawArraysIndirect || !f.glBindBufferBase || !f.glMapBufferRange || !f.glUnmapBuffer || + !f.glMemoryBarrier || !f.glCreateShader || !f.glCreateProgram) { return false; } // Read from caps, not re-queried: the per-stage limits are resolved (and their query @@ -872,8 +847,7 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I("baseInstance probe skipped: GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = %d", maxVertexSsboBlocks); return false; } - while (f.glGetError() != GL_NO_ERROR) { - } + while (f.glGetError() != GL_NO_ERROR) {} const char* vsSource = "#version 310 es\n" "layout(std430, binding = 0) buffer MgProbeResult { highp int mg_probeValue; };\n" @@ -920,8 +894,7 @@ namespace MobileGL::MG_Util::BackendLoader { if (fs != 0) f.glDeleteShader(fs); if (program == 0) { MGLOG_I("baseInstance probe skipped: probe program failed to build (vs=%u fs=%u)", vs, fs); - while (f.glGetError() != GL_NO_ERROR) { - } + while (f.glGetError() != GL_NO_ERROR) {} return false; } @@ -994,8 +967,7 @@ namespace MobileGL::MG_Util::BackendLoader { f.glDeleteVertexArrays(1, &vao); f.glDeleteBuffers(2, buffers); f.glDeleteProgram(program); - while (f.glGetError() != GL_NO_ERROR) { - } + while (f.glGetError() != GL_NO_ERROR) {} return includesBase; } @@ -1077,8 +1049,7 @@ namespace MobileGL::MG_Util::BackendLoader { caps.FragmentShadingRateMasks[i] = mask; } // The queries above are best-effort: a pname a driver does not know only leaves its default. - while (glesFuncs.glGetError() != GL_NO_ERROR) { - } + while (glesFuncs.glGetError() != GL_NO_ERROR) {} MGLOG_I("GL_EXT_fragment_shading_rate: primitive=%d attachment=%d nonTrivialCombiners=%d rates(1x)=0x%04x " "rates(4x)=0x%04x attachment texel %dx%d..%dx%d", caps.SupportsFragmentShadingRatePrimitive, caps.SupportsFragmentShadingRateAttachment, @@ -1258,8 +1229,7 @@ namespace MobileGL::MG_Util::BackendLoader { caps.SupportsMultiDrawIndirect = hasMultiDrawIndirectExtension && glesFuncs.glMultiDrawArraysIndirectEXT != nullptr && glesFuncs.glMultiDrawElementsIndirectEXT != nullptr; - caps.SupportsMultiDrawElementsBaseVertex = hasDrawElementsBaseVertexExtension && - hasMultiDrawArraysExtension && + caps.SupportsMultiDrawElementsBaseVertex = hasDrawElementsBaseVertexExtension && hasMultiDrawArraysExtension && glesFuncs.glMultiDrawElementsBaseVertexEXT != nullptr; // All three, not any: DirectGLES picks native-vs-emulated once per draw entry point off // this single flag, so a driver that resolved only some of them must count as absent. @@ -1283,8 +1253,7 @@ namespace MobileGL::MG_Util::BackendLoader { } // Each companion builds on the base extension, and each needs the entry points it brings; // the strings alone never count (see AcquireGLESFunctions on stub pointers). - caps.SupportsFragmentShadingRate = caps.SupportsFragmentShadingRate && - glesFuncs.glShadingRateEXT != nullptr && + caps.SupportsFragmentShadingRate = caps.SupportsFragmentShadingRate && glesFuncs.glShadingRateEXT != nullptr && glesFuncs.glShadingRateCombinerOpsEXT != nullptr && glesFuncs.glGetFragmentShadingRatesEXT != nullptr; caps.SupportsFragmentShadingRatePrimitive = @@ -1318,42 +1287,37 @@ namespace MobileGL::MG_Util::BackendLoader { caps.SubgroupSupportedFeatures); // Core from ES 3.2 on, so an extension string is not required there; below 3.2 the // extension is, and the pointer still has to have resolved either way. - const Bool esAtLeast32 = caps.GLESVersion.Major > 3 || - (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); - const Bool esAtLeast31 = caps.GLESVersion.Major > 3 || - (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); + const Bool esAtLeast32 = + caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); + const Bool esAtLeast31 = + caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 1); caps.SupportsFramebufferDefaultLayers = esAtLeast32 || hasGeometryShaderExtension; - caps.SupportsDrawIndirect = esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr && - glesFuncs.glDrawElementsIndirect != nullptr; - caps.SupportsDrawElementsBaseVertex = (esAtLeast32 || hasDrawElementsBaseVertexExtension) && - glesFuncs.glDrawElementsBaseVertex != nullptr; - caps.SupportsTessellationShader = (esAtLeast32 || (esAtLeast31 && hasTessellationShaderExtension)) && - glesFuncs.glPatchParameteri != nullptr; + caps.SupportsDrawIndirect = + esAtLeast31 && glesFuncs.glDrawArraysIndirect != nullptr && glesFuncs.glDrawElementsIndirect != nullptr; + caps.SupportsDrawElementsBaseVertex = + (esAtLeast32 || hasDrawElementsBaseVertexExtension) && glesFuncs.glDrawElementsBaseVertex != nullptr; + caps.SupportsTessellationShader = + (esAtLeast32 || (esAtLeast31 && hasTessellationShaderExtension)) && glesFuncs.glPatchParameteri != nullptr; // The string AND the pointer: eglGetProcAddress may hand back a stub for a name the // driver does not implement. caps.SupportsClipControl = hasClipControlExtension && glesFuncs.glClipControlEXT != nullptr; caps.SupportsComputeShader = esAtLeast31 && glesFuncs.glDispatchCompute != nullptr && - glesFuncs.glMemoryBarrier != nullptr && - glesFuncs.glCreateShader != nullptr && + glesFuncs.glMemoryBarrier != nullptr && glesFuncs.glCreateShader != nullptr && glesFuncs.glCreateProgram != nullptr; - caps.SupportsShaderMultisampleInterpolation = - caps.SupportsShaderMultisampleInterpolation || caps.GLESVersion.Major > 3 || - (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); + caps.SupportsShaderMultisampleInterpolation = caps.SupportsShaderMultisampleInterpolation || + caps.GLESVersion.Major > 3 || + (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2); // Detect optional raster/color-mask entry points by whether they loaded. glColorMaski is GLES // 3.2 core (no extension string), so pointer presence is the reliable signal for all of these. - caps.SupportsPolygonMode = - glesFuncs.glPolygonModeNV != nullptr || glesFuncs.glPolygonModeANGLE != nullptr; - caps.SupportsIndexedColorMask = glesFuncs.glColorMaski != nullptr || - glesFuncs.glColorMaskiEXT != nullptr || + caps.SupportsPolygonMode = glesFuncs.glPolygonModeNV != nullptr || glesFuncs.glPolygonModeANGLE != nullptr; + caps.SupportsIndexedColorMask = glesFuncs.glColorMaski != nullptr || glesFuncs.glColorMaskiEXT != nullptr || glesFuncs.glColorMaskiOES != nullptr; MGLOG_I(" glPolygonMode (NV/ANGLE): %s", caps.SupportsPolygonMode ? "yes" : "no"); MGLOG_I(" indexed glColorMaski: %s", caps.SupportsIndexedColorMask ? "yes" : "no"); - MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", - caps.SupportsDualSourceBlend ? "yes" : "no"); + MGLOG_I(" dual-source blend (EXT_blend_func_extended): %s", caps.SupportsDualSourceBlend ? "yes" : "no"); MGLOG_I(" draw indirect (ES 3.1 core): %s", caps.SupportsDrawIndirect ? "yes" : "no"); - MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s", - caps.SupportsMultiDrawIndirect ? "yes" : "no"); + MGLOG_I(" multi-draw indirect (EXT_multi_draw_indirect): %s", caps.SupportsMultiDrawIndirect ? "yes" : "no"); MGLOG_I(" multi-draw base vertex (EXT/OES_draw_elements_base_vertex + EXT_multi_draw_arrays): %s", caps.SupportsMultiDrawElementsBaseVertex ? "yes" : "no"); MGLOG_I(" draw elements base vertex (ES 3.2 core or EXT/OES_draw_elements_base_vertex): %s", @@ -1370,9 +1334,12 @@ namespace MobileGL::MG_Util::BackendLoader { { const auto pointSizeTierName = [](MG_External::GLESCapabilities::PointSizeTier tier) { switch (tier) { - case MG_External::GLESCapabilities::PointSizeTier::ExtensionEXT: return "EXT"; - case MG_External::GLESCapabilities::PointSizeTier::ExtensionOES: return "OES"; - default: return "no"; + case MG_External::GLESCapabilities::PointSizeTier::ExtensionEXT: + return "EXT"; + case MG_External::GLESCapabilities::PointSizeTier::ExtensionOES: + return "OES"; + default: + return "no"; } }; MGLOG_I(" tessellation gl_PointSize (EXT/OES_tessellation_point_size): %s", @@ -1394,8 +1361,7 @@ namespace MobileGL::MG_Util::BackendLoader { // make every storage-range offset legal, so an unusable answer keeps the 256 default. GLint shaderStorageOffsetAlignment = 0; glesFuncs.glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &shaderStorageOffsetAlignment); - while (glesFuncs.glGetError() != GL_NO_ERROR) { - } + while (glesFuncs.glGetError() != GL_NO_ERROR) {} if (shaderStorageOffsetAlignment > 0) { caps.ShaderStorageBufferOffsetAlignment = shaderStorageOffsetAlignment; } @@ -1494,7 +1460,8 @@ namespace MobileGL::MG_Util::BackendLoader { const auto drainErrors = [&glesFuncs]() { Bool hadError = false; if (glesFuncs.glGetError) { - while (glesFuncs.glGetError() != GL_NO_ERROR) hadError = true; + while (glesFuncs.glGetError() != GL_NO_ERROR) + hadError = true; } return hadError; }; @@ -1606,8 +1573,7 @@ namespace MobileGL::MG_Util::BackendLoader { // Geometry and tessellation shaders and their image-uniform queries are core only in // ES 3.2. DirectGLES emits ESSL 3.10 on an ES 3.1 context, so reporting zero there is // both legal and an accurate description of what the backend compiler can consume. - if (caps.GLESVersion.Major > 3 || - (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) { + if (caps.GLESVersion.Major > 3 || (caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) { glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS, &maxTessControlImageUniforms); glesFuncs.glGetIntegerv(GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, &maxTessEvaluationImageUniforms); @@ -1643,8 +1609,7 @@ namespace MobileGL::MG_Util::BackendLoader { maxFragmentShaderStorageBlocks = 4; } if (esAtLeast32) { - glesFuncs.glGetIntegerv(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, - &maxTessControlShaderStorageBlocks); + glesFuncs.glGetIntegerv(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, &maxTessControlShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, &maxTessEvaluationShaderStorageBlocks); glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, &maxGeometryShaderStorageBlocks); @@ -1739,8 +1704,7 @@ namespace MobileGL::MG_Util::BackendLoader { viewportBoundsRange[0] = -32768.0f; viewportBoundsRange[1] = 32767.0f; } else { - viewportIndexProvokingVertex = - NormalizeProvokingVertexConvention(driverViewportIndexConvention); + viewportIndexProvokingVertex = NormalizeProvokingVertexConvention(driverViewportIndexConvention); } } if (caps.SupportsShaderMultisampleInterpolation && glesFuncs.glGetFloatv) { @@ -1795,8 +1759,7 @@ namespace MobileGL::MG_Util::BackendLoader { // Drain first: an error left by any earlier probe would otherwise read as this // query having failed, and the value would be discarded as a non-answer. if (glesFuncs.glGetError) { - while (glesFuncs.glGetError() != GL_NO_ERROR) { - } + while (glesFuncs.glGetError() != GL_NO_ERROR) {} } glesFuncs.glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, &maxTextureBufferSize); if (glesFuncs.glGetError) { @@ -1827,11 +1790,10 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I(" Buffer textures: UNSUPPORTED (%s). Any shader sampling a " "samplerBuffer will fail to compile, and MobileGL keeps advertising " "GL_MAX_TEXTURE_BUFFER_SIZE = %d because a GL 4.x context may not report 0.", - claimsSupport - ? "this driver advertises buffer textures but no glTexBuffer entry " - "point resolved, so none of them can be called" - : "ES core needs 3.2, and neither GL_EXT_texture_buffer nor " - "GL_OES_texture_buffer is present", + claimsSupport ? "this driver advertises buffer textures but no glTexBuffer entry " + "point resolved, so none of them can be called" + : "ES core needs 3.2, and neither GL_EXT_texture_buffer nor " + "GL_OES_texture_buffer is present", maxTextureBufferSize); } } @@ -1882,9 +1844,8 @@ namespace MobileGL::MG_Util::BackendLoader { SelfTest::ExplicitVertexInputLocationCeiling(glesFuncs); // Guarded on `detected` rather than on the number alone: a probe that reached no verdict // has measured nothing, and the clamp must be driven by evidence or not applied at all. - caps.MaxVertexAttribs = locationCeiling.detected - ? std::min(maxVertexAttribs, locationCeiling.usableLocations) - : maxVertexAttribs; + caps.MaxVertexAttribs = + locationCeiling.detected ? std::min(maxVertexAttribs, locationCeiling.usableLocations) : maxVertexAttribs; if (locationCeiling.detected) { MGLOG_I(" GL_MAX_VERTEX_ATTRIBS reduced from the driver's %d to %d: " "layout(location = N) on a vertex input is refused from N = %d upward", @@ -1909,7 +1870,8 @@ namespace MobileGL::MG_Util::BackendLoader { GLint sharedMemory = 0; while (glesFuncs.glGetError() != GL_NO_ERROR) {} glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, &sharedMemory); - if (glesFuncs.glGetError() == GL_NO_ERROR && sharedMemory > 0) caps.MaxComputeSharedMemorySize = sharedMemory; + if (glesFuncs.glGetError() == GL_NO_ERROR && sharedMemory > 0) + caps.MaxComputeSharedMemorySize = sharedMemory; } // Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves // to MobileGL's own exported entry point, which answers this pname from the very @@ -2045,15 +2007,13 @@ namespace MobileGL::MG_Util::BackendLoader { MGLOG_I(" GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES: %d", caps.MaxCombinedClipAndCullDistances); MGLOG_I(" GL_MAX_VIEWPORTS: %d", caps.MaxViewports); MGLOG_I(" GL_MAX_VIEWPORT_DIMS: [%d, %d]", caps.MaxViewportWidth, caps.MaxViewportHeight); - MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin, - caps.ViewportBoundsRangeMax); + MGLOG_I(" GL_VIEWPORT_BOUNDS_RANGE: [%.3f, %.3f]", caps.ViewportBoundsRangeMin, caps.ViewportBoundsRangeMax); MGLOG_I(" GL_VIEWPORT_SUBPIXEL_BITS: %d", caps.ViewportSubpixelBits); MGLOG_I(" GL_LAYER_PROVOKING_VERTEX: %s", ProvokingVertexConventionName(caps.LayerProvokingVertex)); MGLOG_I(" GL_VIEWPORT_INDEX_PROVOKING_VERTEX: %s", ProvokingVertexConventionName(caps.ViewportIndexProvokingVertex)); - caps.IndirectDrawInstanceIdIncludesBaseInstance = - ProbeIndirectInstanceIdIncludesBaseInstance(caps, glesFuncs); + caps.IndirectDrawInstanceIdIncludesBaseInstance = ProbeIndirectInstanceIdIncludesBaseInstance(caps, glesFuncs); MGLOG_I(" Indirect draw gl_InstanceID includes baseInstance: %s", caps.IndirectDrawInstanceIdIncludesBaseInstance ? "true" : "false"); @@ -2063,27 +2023,26 @@ namespace MobileGL::MG_Util::BackendLoader { // emulation can be exercised on a healthy driver (the integration lane) and turned // off again as a negative control. switch (MG_Config::Features.EsprytUnlocatedIoBlocks) { - case MG_Config::QuirkOverride::ForceOn: - caps.SupportsLocatedInterStageIoBlocks = false; - MGLOG_I(" Located inter-stage interface blocks: forced OFF by " - "MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS; the driver was not probed"); - break; - case MG_Config::QuirkOverride::ForceOff: - caps.SupportsLocatedInterStageIoBlocks = true; - MGLOG_I(" Located inter-stage interface blocks: forced ON by " - "MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS; the driver was not probed"); - break; - case MG_Config::QuirkOverride::Auto: - default: - // SelfTest::ProbeLocatedIoBlocksLosePayload - the Mali-G1-Ultra ES driver - // delivers nothing through an interface block that carries an explicit - // layout(location=) once a tessellation or geometry stage is in the pipeline. - // Probed with its own controls rather than matched on a renderer string; see - // DriverBugProbes.h for the shape and for why the two controls decide what the - // finding is allowed to claim. - caps.SupportsLocatedInterStageIoBlocks = - !SelfTest::LocatedIoBlocksLosePayload(glesFuncs).detected; - break; + case MG_Config::QuirkOverride::ForceOn: + caps.SupportsLocatedInterStageIoBlocks = false; + MGLOG_I(" Located inter-stage interface blocks: forced OFF by " + "MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS; the driver was not probed"); + break; + case MG_Config::QuirkOverride::ForceOff: + caps.SupportsLocatedInterStageIoBlocks = true; + MGLOG_I(" Located inter-stage interface blocks: forced ON by " + "MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS; the driver was not probed"); + break; + case MG_Config::QuirkOverride::Auto: + default: + // SelfTest::ProbeLocatedIoBlocksLosePayload - the Mali-G1-Ultra ES driver + // delivers nothing through an interface block that carries an explicit + // layout(location=) once a tessellation or geometry stage is in the pipeline. + // Probed with its own controls rather than matched on a renderer string; see + // DriverBugProbes.h for the shape and for why the two controls decide what the + // finding is allowed to claim. + caps.SupportsLocatedInterStageIoBlocks = !SelfTest::LocatedIoBlocksLosePayload(glesFuncs).detected; + break; } MGLOG_I(" Located inter-stage interface blocks transport their payload: %s", caps.SupportsLocatedInterStageIoBlocks @@ -2092,16 +2051,16 @@ namespace MobileGL::MG_Util::BackendLoader { "blocks without a location qualifier)"); switch (MG_Config::Features.EsprytDeferGeometryEmits) { - case MG_Config::QuirkOverride::ForceOn: - caps.DropsGeometryWritesAfterEmit = true; - break; - case MG_Config::QuirkOverride::ForceOff: - caps.DropsGeometryWritesAfterEmit = false; - break; - case MG_Config::QuirkOverride::Auto: - default: - caps.DropsGeometryWritesAfterEmit = SelfTest::GeometryStageSsboWriteAfterEmitDropped(glesFuncs); - break; + case MG_Config::QuirkOverride::ForceOn: + caps.DropsGeometryWritesAfterEmit = true; + break; + case MG_Config::QuirkOverride::ForceOff: + caps.DropsGeometryWritesAfterEmit = false; + break; + case MG_Config::QuirkOverride::Auto: + default: + caps.DropsGeometryWritesAfterEmit = SelfTest::GeometryStageSsboWriteAfterEmitDropped(glesFuncs); + break; } MGLOG_I(" Geometry-stage storage writes after EmitVertex are dropped: %s", caps.DropsGeometryWritesAfterEmit @@ -2109,60 +2068,54 @@ namespace MobileGL::MG_Util::BackendLoader { : "false"); switch (MG_Config::Features.EsprytAtomicSubgroupScratch) { - case MG_Config::QuirkOverride::ForceOn: - caps.SharedMemoryLoadsGoStale = true; - break; - case MG_Config::QuirkOverride::ForceOff: - caps.SharedMemoryLoadsGoStale = false; - break; - case MG_Config::QuirkOverride::Auto: - default: - caps.SharedMemoryLoadsGoStale = SelfTest::SharedMemoryLoadsGoStale(glesFuncs); - break; + case MG_Config::QuirkOverride::ForceOn: + caps.SharedMemoryLoadsGoStale = true; + break; + case MG_Config::QuirkOverride::ForceOff: + caps.SharedMemoryLoadsGoStale = false; + break; + case MG_Config::QuirkOverride::Auto: + default: + caps.SharedMemoryLoadsGoStale = SelfTest::SharedMemoryLoadsGoStale(glesFuncs); + break; } MGLOG_I(" Shared-memory loads go stale across barrier(): %s", - caps.SharedMemoryLoadsGoStale - ? "true (the subgroup emulation reads its scratch through atomic loads)" - : "false"); + caps.SharedMemoryLoadsGoStale ? "true (the subgroup emulation reads its scratch through atomic loads)" + : "false"); caps.IsAngleRenderer = caps.GLESRendererString.find("ANGLE") != String::npos; switch (MG_Config::Features.EsprytInvalidImagePlaceholder) { - case MG_Config::QuirkOverride::ForceOn: - caps.UseInvalidImagePlaceholder = true; - break; - case MG_Config::QuirkOverride::ForceOff: - caps.UseInvalidImagePlaceholder = false; - break; - case MG_Config::QuirkOverride::Auto: - default: - // ANGLE dereferences a null texture when a dispatch or draw reaches an EMPTY image - // unit the program uses, so the probe cannot be run there: its subject variant is - // exactly that dispatch, and a crash is no verdict. ANGLE keeps the placeholder - // without being asked. - caps.UseInvalidImagePlaceholder = - caps.IsAngleRenderer || SelfTest::EmptyImageUnitsReadNonzero(glesFuncs); - break; + case MG_Config::QuirkOverride::ForceOn: + caps.UseInvalidImagePlaceholder = true; + break; + case MG_Config::QuirkOverride::ForceOff: + caps.UseInvalidImagePlaceholder = false; + break; + case MG_Config::QuirkOverride::Auto: + default: + // ANGLE dereferences a null texture when a dispatch or draw reaches an EMPTY image + // unit the program uses, so the probe cannot be run there: its subject variant is + // exactly that dispatch, and a crash is no verdict. ANGLE keeps the placeholder + // without being asked. + caps.UseInvalidImagePlaceholder = caps.IsAngleRenderer || SelfTest::EmptyImageUnitsReadNonzero(glesFuncs); + break; } MGLOG_I(" Invalid image units bound to a placeholder: %s", !caps.UseInvalidImagePlaceholder ? "false (an invalid image unit stays empty)" : caps.IsAngleRenderer ? "true (ANGLE faults on a use of an empty image unit)" : "true (empty image units read nonzero)"); - caps.IsAngleLlvmpipeRenderer = - caps.IsAngleRenderer && caps.GLESRendererString.find("llvmpipe") != String::npos; + caps.IsAngleLlvmpipeRenderer = caps.IsAngleRenderer && caps.GLESRendererString.find("llvmpipe") != String::npos; caps.AvoidSamplerMipmapMinFilter = caps.IsAngleLlvmpipeRenderer && MG_Config::Features.EsprytAvoidSamplerMipmapMinFilter; - caps.AvoidExplicitLodBias = - caps.IsAngleLlvmpipeRenderer && MG_Config::Features.EsprytAvoidExplicitLodBias; - MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s", - caps.SupportsDisjointTimerQuery ? "true" : "false"); + caps.AvoidExplicitLodBias = caps.IsAngleLlvmpipeRenderer && MG_Config::Features.EsprytAvoidExplicitLodBias; + MGLOG_I(" GL_EXT_disjoint_timer_query supported: %s", caps.SupportsDisjointTimerQuery ? "true" : "false"); MGLOG_I(" GL_KHR_parallel_shader_compile supported: %s", caps.SupportsParallelShaderCompile ? "true" : "false"); MGLOG_I(" ANGLE renderer: %s", caps.IsAngleRenderer ? "true" : "false"); MGLOG_I(" ANGLE llvmpipe renderer: %s", caps.IsAngleLlvmpipeRenderer ? "true" : "false"); - MGLOG_I(" Avoid sampler mipmap min filter: %s", - caps.AvoidSamplerMipmapMinFilter ? "true" : "false"); + MGLOG_I(" Avoid sampler mipmap min filter: %s", caps.AvoidSamplerMipmapMinFilter ? "true" : "false"); MGLOG_I(" Avoid explicit LOD bias: %s", caps.AvoidExplicitLodBias ? "true" : "false"); // Last line of defence. Capability init is the very first thing that touches the driver, diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index 6c2d5255..758a635f 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -614,11 +614,9 @@ namespace MobileGL { // suffixed spellings instead, and a strict eglGetProcAddress returns NULL for the core // one there - so resolving only the core name makes both extension tiers look absent. GL_FUNC_TYPEDEF(void, glTextureViewEXT, GLuint texture, GLenum target, GLuint origtexture, - GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, - GLuint numlayers) + GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) GL_FUNC_TYPEDEF(void, glTextureViewOES, GLuint texture, GLenum target, GLuint origtexture, - GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, - GLuint numlayers) + GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) // GL_EXT_fragment_shading_rate (+ _attachment). Extension-only in every ES version. GL_FUNC_TYPEDEF(void, glShadingRateEXT, GLenum rate) GL_FUNC_TYPEDEF(void, glShadingRateCombinerOpsEXT, GLenum combinerOp0, GLenum combinerOp1) @@ -1165,8 +1163,8 @@ namespace MobileGL { // neither cannot compile such a shader at all. Gate on this, never on the entry point: // eglGetProcAddress hands back live-looking stubs (see AcquireGLESFunctions). enum class TextureBufferTier : Uint8 { - None = 0, // no core support and neither extension; glTexBuffer is unusable - CoreEs32, // ES >= 3.2, buffer textures are core and ESSL 320 needs no directive + None = 0, // no core support and neither extension; glTexBuffer is unusable + CoreEs32, // ES >= 3.2, buffer textures are core and ESSL 320 needs no directive ExtensionEXT, // GL_EXT_texture_buffer; ESSL below 320 must say GL_EXT_texture_buffer ExtensionOES, // GL_OES_texture_buffer; ESSL below 320 must say GL_OES_texture_buffer }; @@ -1457,8 +1455,8 @@ namespace MobileGL { // caller presents with plain eglSwapBuffers, which a zero-rectangle call to either // spelling is defined to be equivalent to anyway. Exposed so MG_Test can pin the // preference order without a driver. - MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR - ResolveSwapBuffersWithDamage(const MG_External::EGLFunctionsTable& funcs); + MG_External::EGL::eglSwapBuffersWithDamageEXT_PTR ResolveSwapBuffersWithDamage( + const MG_External::EGLFunctionsTable& funcs); Bool FillInGLESCapabilities(MG_External::GLESCapabilities& caps, const MG_External::GLESFunctionsTable& glesFuncs); // Detects whether indirect draws leak the command's baseInstance word into diff --git a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp index 840954b4..485ee46b 100644 --- a/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp @@ -105,8 +105,7 @@ namespace MobileGL::MG_Util::BackendLoader { } inline Bool HasUsableShaderSubgroupSupport(const VkPhysicalDeviceSubgroupProperties& subgroupProps) { - return subgroupProps.subgroupSize > 0 && - (subgroupProps.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 && + return subgroupProps.subgroupSize > 0 && (subgroupProps.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0 && (subgroupProps.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0; } @@ -236,15 +235,14 @@ namespace MobileGL::MG_Util::BackendLoader { // for one of them refuses per-slice attachment for that format only, which // VkTextureManager detects and records at image creation; this field just says whether // the capability is worth offering at all. - static constexpr VkFormat k3DSliceProbeFormats[] = {VK_FORMAT_R8G8B8A8_UNORM, - VK_FORMAT_R8G8B8A8_SRGB}; + static constexpr VkFormat k3DSliceProbeFormats[] = {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_R8G8B8A8_SRGB}; Bool all2DArrayCompatible = true; for (const VkFormat probeFormat : k3DSliceProbeFormats) { VkImageFormatProperties probeProperties{}; const VkResult probeResult = vkGetPhysicalDeviceImageFormatProperties( physicalDevice, probeFormat, VK_IMAGE_TYPE_3D, VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, &probeProperties); if (probeResult != VK_SUCCESS) { all2DArrayCompatible = false; @@ -253,16 +251,15 @@ namespace MobileGL::MG_Util::BackendLoader { } caps.Supports2DArrayCompatible3DImages = all2DArrayCompatible; } - caps.SupportsVertexPipelineStoresAndAtomics = - supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE; + caps.SupportsVertexPipelineStoresAndAtomics = supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE; caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE; caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE; caps.SupportsTessellationShader = supportedFeatures.tessellationShader == VK_TRUE; caps.SupportsShaderClipDistance = supportedFeatures.shaderClipDistance == VK_TRUE; caps.SupportsShaderCullDistance = supportedFeatures.shaderCullDistance == VK_TRUE; caps.MaxShaderStorageBlockSize = static_cast(p.limits.maxStorageBufferRange); - const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 && - HasUsableShaderSubgroupSupport(subgroupProps); + const Bool supportsShaderSubgroup = + vk.vkGetPhysicalDeviceProperties2 && HasUsableShaderSubgroupSupport(subgroupProps); caps.SupportsShaderSubgroup = supportsShaderSubgroup; if (caps.SupportsShaderSubgroup) { caps.SubgroupSize = subgroupProps.subgroupSize; @@ -277,8 +274,8 @@ namespace MobileGL::MG_Util::BackendLoader { } MGLOG_I("Vulkan shader subgroup support: native=%s size=%u stages=0x%x operations=0x%x", - supportsShaderSubgroup ? "true" : "false", subgroupProps.subgroupSize, - subgroupProps.supportedStages, subgroupProps.supportedOperations); + supportsShaderSubgroup ? "true" : "false", subgroupProps.subgroupSize, subgroupProps.supportedStages, + subgroupProps.supportedOperations); return true; } @@ -290,8 +287,7 @@ namespace MobileGL::MG_Util::BackendLoader { caps.DriverVersionString = DecodeDriverVersion(properties.driverVersion); caps.VendorId = properties.vendorID; caps.UniformBufferOffsetAlignment = static_cast(properties.limits.minUniformBufferOffsetAlignment); - caps.ShaderStorageBufferOffsetAlignment = - static_cast(properties.limits.minStorageBufferOffsetAlignment); + caps.ShaderStorageBufferOffsetAlignment = static_cast(properties.limits.minStorageBufferOffsetAlignment); caps.AliasedLineWidthRangeMin = properties.limits.lineWidthRange[0]; caps.AliasedLineWidthRangeMax = properties.limits.lineWidthRange[1]; caps.MaxSamplerAnisotropy = properties.limits.maxSamplerAnisotropy; diff --git a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp index 38526ef5..8ce94746 100644 --- a/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp +++ b/MobileGL/MG_Util/Converters/MGToMG/TextureEnumConverter.cpp @@ -139,10 +139,10 @@ namespace MobileGL { return TextureInternalFormat::RGBA16; default: MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " - "returning original.", - __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), - MG_Util::ConvertTextureInputFormatToString(format).c_str(), - MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); + "returning original.", + __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), + MG_Util::ConvertTextureInputFormatToString(format).c_str(), + MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); return internalformat; } } @@ -152,10 +152,10 @@ namespace MobileGL { return TextureInternalFormat::RGB8; default: MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " - "returning original.", - __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), - MG_Util::ConvertTextureInputFormatToString(format).c_str(), - MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); + "returning original.", + __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), + MG_Util::ConvertTextureInputFormatToString(format).c_str(), + MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); return internalformat; } } @@ -167,10 +167,10 @@ namespace MobileGL { return TextureInternalFormat::RG16; default: MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " - "returning original.", - __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), - MG_Util::ConvertTextureInputFormatToString(format).c_str(), - MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); + "returning original.", + __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), + MG_Util::ConvertTextureInputFormatToString(format).c_str(), + MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); return internalformat; } } @@ -182,10 +182,10 @@ namespace MobileGL { return TextureInternalFormat::R16; default: MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, " - "returning original.", - __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), - MG_Util::ConvertTextureInputFormatToString(format).c_str(), - MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); + "returning original.", + __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), + MG_Util::ConvertTextureInputFormatToString(format).c_str(), + MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); return internalformat; } } @@ -217,11 +217,12 @@ namespace MobileGL { } } default: { - MGLOG_W_ONCE("%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, returning " - "original.", - __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), - MG_Util::ConvertTextureInputFormatToString(format).c_str(), - MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); + MGLOG_W_ONCE( + "%s: Can't infer sized internal format from internalformat=%s, format=%s, type=%s, returning " + "original.", + __func__, MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str(), + MG_Util::ConvertTextureInputFormatToString(format).c_str(), + MG_Util::ConvertTexturePixelDataTypeToString(type).c_str()); return internalformat; } } @@ -311,7 +312,7 @@ namespace MobileGL { return TextureInternalFormat::DepthStencil; default: MGLOG_W_ONCE("%s: Unknown or unhandled internal format %s, returning original.", __func__, - MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str()); + MG_Util::ConvertTextureInternalFormatToString(internalformat).c_str()); return internalformat; } } diff --git a/MobileGL/MG_Util/Damage/Damage.h b/MobileGL/MG_Util/Damage/Damage.h index 085051cc..98d6f643 100644 --- a/MobileGL/MG_Util/Damage/Damage.h +++ b/MobileGL/MG_Util/Damage/Damage.h @@ -105,7 +105,8 @@ namespace MobileGL::MG_Util::Damage { for (const Rect& taken : out) { Vector next; for (const Rect& piece : pieces) { - for (const Rect& left : Subtract(piece, taken)) next.push_back(left); + for (const Rect& left : Subtract(piece, taken)) + next.push_back(left); } pieces = std::move(next); if (pieces.empty()) break; @@ -126,7 +127,8 @@ namespace MobileGL::MG_Util::Damage { } static Region FromRects(const Rect* rects, SizeT count) { Region region; - for (SizeT i = 0; i < count; ++i) region.Add(rects[i]); + for (SizeT i = 0; i < count; ++i) + region.Add(rects[i]); return region; } // EGL_KHR/EXT_swap_buffers_with_damage's argument: `count` groups of x, y, width, height, @@ -157,7 +159,8 @@ namespace MobileGL::MG_Util::Damage { SetFull(); return; } - for (const Rect& rect : other.m_rects) Add(rect); + for (const Rect& rect : other.m_rects) + Add(rect); } void SetFull() { m_full = true; @@ -199,7 +202,8 @@ namespace MobileGL::MG_Util::Damage { // Far too many to pair up every frame: their bounding box. if (distinct.size() > 4 * kMaxRects && distinct.size() > limit) { Rect box; - for (const Rect& rect : distinct) box = Bound(box, rect); + for (const Rect& rect : distinct) + box = Bound(box, rect); distinct.assign(1, box); } while (distinct.size() > limit) { @@ -229,7 +233,8 @@ namespace MobileGL::MG_Util::Damage { // The rectangles flipped between bottom-left and top-left origin (a Full region stays Full). Region FlippedY(Int32 height) const { Region region = *this; - for (Rect& rect : region.m_rects) rect = FlipY(rect, height); + for (Rect& rect : region.m_rects) + rect = FlipY(rect, height); return region; } @@ -253,7 +258,8 @@ namespace MobileGL::MG_Util::Damage { const Vector& rects = region.Rects(); if (rects.size() > maxRects) { Rect box; - for (const Rect& rect : rects) box = Bound(box, rect); + for (const Rect& rect : rects) + box = Bound(box, rect); out[0] = box.X, out[1] = box.Y, out[2] = box.Width, out[3] = box.Height; return 1; } @@ -303,7 +309,8 @@ namespace MobileGL::MG_Util::Damage { if (clipped.IsFull()) return {FlippedCopy{whole, whole}}; Vector copies; copies.reserve(clipped.Rects().size()); - for (const Rect& rect : clipped.Rects()) copies.push_back({rect, FlipY(rect, height)}); + for (const Rect& rect : clipped.Rects()) + copies.push_back({rect, FlipY(rect, height)}); return copies; } @@ -312,9 +319,7 @@ namespace MobileGL::MG_Util::Damage { public: explicit BufferDamageTracker(SizeT buffers = 0) { Resize(buffers); } - void Resize(SizeT buffers) { - m_missed.assign(buffers, Region::Full()); - } + void Resize(SizeT buffers) { m_missed.assign(buffers, Region::Full()); } SizeT Size() const { return m_missed.size(); } // The buffer's contents are unknown (new, reallocated, resized): its next write copies all. @@ -322,7 +327,8 @@ namespace MobileGL::MG_Util::Damage { if (index < m_missed.size()) m_missed[index].SetFull(); } void InvalidateAll() { - for (Region& region : m_missed) region.SetFull(); + for (Region& region : m_missed) + region.SetFull(); } // A frame whose damage is `frame` is written into buffer `index` of a `width` x `height` diff --git a/MobileGL/MG_Util/Debug/Log.cpp b/MobileGL/MG_Util/Debug/Log.cpp index 66d2d8bb..09451b06 100644 --- a/MobileGL/MG_Util/Debug/Log.cpp +++ b/MobileGL/MG_Util/Debug/Log.cpp @@ -50,7 +50,9 @@ namespace MobileGL { t_forwardsToPeer = forwarder != nullptr; } - void SetThreadForwardsLogToPeer(bool forwards) { t_forwardsToPeer = forwards; } + void SetThreadForwardsLogToPeer(bool forwards) { + t_forwardsToPeer = forwards; + } void WithLogBarrier(void (*action)(void*), void* user) { std::lock_guard lock(LogMutex()); @@ -63,12 +65,18 @@ namespace MobileGL { int LogLevelFromAndroidPriority(int priority) { switch (priority) { case ANDROID_LOG_VERBOSE: - case ANDROID_LOG_DEBUG: return MOBILEGL_LOG_LEVEL_DEBUG; - case ANDROID_LOG_INFO: return MOBILEGL_LOG_LEVEL_INFO; - case ANDROID_LOG_WARN: return MOBILEGL_LOG_LEVEL_WARN; - case ANDROID_LOG_ERROR: return MOBILEGL_LOG_LEVEL_ERROR; - case ANDROID_LOG_FATAL: return MOBILEGL_LOG_LEVEL_FATAL; - default: return MOBILEGL_LOG_LEVEL_INFO; + case ANDROID_LOG_DEBUG: + return MOBILEGL_LOG_LEVEL_DEBUG; + case ANDROID_LOG_INFO: + return MOBILEGL_LOG_LEVEL_INFO; + case ANDROID_LOG_WARN: + return MOBILEGL_LOG_LEVEL_WARN; + case ANDROID_LOG_ERROR: + return MOBILEGL_LOG_LEVEL_ERROR; + case ANDROID_LOG_FATAL: + return MOBILEGL_LOG_LEVEL_FATAL; + default: + return MOBILEGL_LOG_LEVEL_INFO; } } @@ -125,7 +133,9 @@ namespace MobileGL { return t_logRole == 1; } - void SetThreadLogRole(LogRole role) { t_logRole = role == LogRole::Server ? 1 : 0; } + void SetThreadLogRole(LogRole role) { + t_logRole = role == LogRole::Server ? 1 : 0; + } std::string ReadRoleLogs(const char* basePath) { std::string all; @@ -186,7 +196,9 @@ namespace MobileGL { return t_logRole == 1 ? LogRole::Server : LogRole::Client; } - bool CurrentProcessIsSpawnedServer() { return ProcessIsSpawnedServer(); } + bool CurrentProcessIsSpawnedServer() { + return ProcessIsSpawnedServer(); + } #endif std::mutex& LogMutex() { @@ -338,8 +350,7 @@ namespace MobileGL { // every MobileGL log occupy TWO logcat records, the second one empty. That // halved the useful depth of every `adb logcat -t N` window the CI // diagnostics read (android-plugin/trace-replay-ci.sh). - __android_log_print(androidLogLevel, "MobileGL", "%.*s", static_cast(out.size() - 1), - out.c_str()); + __android_log_print(androidLogLevel, "MobileGL", "%.*s", static_cast(out.size() - 1), out.c_str()); #endif WriteToFile(out.c_str()); @@ -354,8 +365,10 @@ namespace MobileGL { // A FATAL line goes out after the log mutex is released, like a session-scoped // one: the forwarder may wait (bounded) for it to reach the peer before the // process aborts, and must not hold every other thread's line while it does. - if (level >= MOBILEGL_LOG_LEVEL_FATAL) forwardOutsideLock = true; - else s_forwarder(s_forwarderUser, level, out.c_str()); + if (level >= MOBILEGL_LOG_LEVEL_FATAL) + forwardOutsideLock = true; + else + s_forwarder(s_forwarderUser, level, out.c_str()); } else { forwardOutsideLock = t_forwardsToPeer; } diff --git a/MobileGL/MG_Util/Debug/Log.h b/MobileGL/MG_Util/Debug/Log.h index ffdc4e86..0954f052 100644 --- a/MobileGL/MG_Util/Debug/Log.h +++ b/MobileGL/MG_Util/Debug/Log.h @@ -198,7 +198,10 @@ namespace MobileGL { // THREAD-SCOPED, NOT PROCESS-SCOPED, for the case that is easy to miss: under inproc // the server role is the mgl-srv-apply THREAD, so a process-wide flag would file its // lines under whichever role set it last. - enum class LogRole { Client, Server }; + enum class LogRole { + Client, + Server + }; // Called by the apply thread when it starts (inproc), and once by ServerMain for a // whole spawned server process. Without it a thread defaults to the process role, @@ -252,7 +255,10 @@ namespace MobileGL { // byte-identical to the baseline, so these may not become library symbols. Nothing // inside the library calls them - only tests and harnesses do - so an inline // definition emits code into those readers and nothing at all into libMobileGL.so. - enum class LogRole { Client, Server }; + enum class LogRole { + Client, + Server + }; // No roles to separate, so nothing to record. Present so that a harness may say which // role a thread is without asking which build it is in. @@ -267,8 +273,12 @@ namespace MobileGL { // one role in this build, so "which role is this thread" has exactly one true answer // and no state to read - but a caller must still compile and mean the right thing in // both flavours rather than wrapping these in an `#if` it might get wrong once. - inline LogRole CurrentThreadRole() { return LogRole::Client; } - inline bool CurrentProcessIsSpawnedServer() { return false; } + inline LogRole CurrentThreadRole() { + return LogRole::Client; + } + inline bool CurrentProcessIsSpawnedServer() { + return false; + } inline std::string ReadRoleLogs(const char* basePath) { std::string all; diff --git a/MobileGL/MG_Util/Math/SmallFloat.h b/MobileGL/MG_Util/Math/SmallFloat.h index e63c2f20..3340f9fb 100644 --- a/MobileGL/MG_Util/Math/SmallFloat.h +++ b/MobileGL/MG_Util/Math/SmallFloat.h @@ -46,8 +46,12 @@ namespace MobileGL::MG_Util { (mantissa >> (23u - static_cast(mantissaBits))); } - inline Uint32 EncodeFloatToUnsignedF11(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 6); } - inline Uint32 EncodeFloatToUnsignedF10(Float value) { return EncodeFloatToUnsignedSmallFloat(value, 5); } + inline Uint32 EncodeFloatToUnsignedF11(Float value) { + return EncodeFloatToUnsignedSmallFloat(value, 6); + } + inline Uint32 EncodeFloatToUnsignedF10(Float value) { + return EncodeFloatToUnsignedSmallFloat(value, 5); + } // Decodes an unsigned small float (5-bit exponent, bias 15, mantissaBits mantissa bits). inline Float DecodeUnsignedSmallFloatToFloat(Uint32 field, Int mantissaBits) { @@ -58,15 +62,17 @@ namespace MobileGL::MG_Util { return std::exp2(-14.0f) * static_cast(mantissa) * mantissaScale; } if (exponent == 31) { - return mantissa == 0 ? std::numeric_limits::infinity() - : std::numeric_limits::quiet_NaN(); + return mantissa == 0 ? std::numeric_limits::infinity() : std::numeric_limits::quiet_NaN(); } - return std::exp2(static_cast(exponent) - 15.0f) * - (1.0f + static_cast(mantissa) * mantissaScale); + return std::exp2(static_cast(exponent) - 15.0f) * (1.0f + static_cast(mantissa) * mantissaScale); } - inline Float DecodeUnsignedF11ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 6); } - inline Float DecodeUnsignedF10ToFloat(Uint32 field) { return DecodeUnsignedSmallFloatToFloat(field, 5); } + inline Float DecodeUnsignedF11ToFloat(Uint32 field) { + return DecodeUnsignedSmallFloatToFloat(field, 6); + } + inline Float DecodeUnsignedF10ToFloat(Uint32 field) { + return DecodeUnsignedSmallFloatToFloat(field, 5); + } // RGB9E5 shared-exponent encode, following the EXT_texture_shared_exponent spec algorithm // (N = 9 mantissa bits, B = 15 exponent bias, Emax = 31). @@ -84,11 +90,10 @@ namespace MobileGL::MG_Util { Int sharedExponent = 0; // all-zero input keeps the all-zero word if (maxComponent > 0.0f) { - sharedExponent = std::max(-kExponentBias - 1, static_cast(std::floor(std::log2(maxComponent)))) + - 1 + kExponentBias; + sharedExponent = + std::max(-kExponentBias - 1, static_cast(std::floor(std::log2(maxComponent)))) + 1 + kExponentBias; const Float maxScaled = std::floor( - maxComponent / std::exp2(static_cast(sharedExponent - kExponentBias - kMantissaBits)) + - 0.5f); + maxComponent / std::exp2(static_cast(sharedExponent - kExponentBias - kMantissaBits)) + 0.5f); if (maxScaled >= 512.0f) { // rounded up to 2^N: bump the shared exponent instead ++sharedExponent; } diff --git a/MobileGL/MG_Util/Metrics/BufferMetrics.cpp b/MobileGL/MG_Util/Metrics/BufferMetrics.cpp index 6d58dd6b..daa8c1e2 100644 --- a/MobileGL/MG_Util/Metrics/BufferMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/BufferMetrics.cpp @@ -12,10 +12,14 @@ namespace MobileGL { namespace MG_Util { Uint32 FixedRestartIndexForGLType(GLenum indexType) { switch (indexType) { - case GL_UNSIGNED_BYTE: return 0xFFu; - case GL_UNSIGNED_SHORT: return 0xFFFFu; - case GL_UNSIGNED_INT: return 0xFFFFFFFFu; - default: return 0; + case GL_UNSIGNED_BYTE: + return 0xFFu; + case GL_UNSIGNED_SHORT: + return 0xFFFFu; + case GL_UNSIGNED_INT: + return 0xFFFFFFFFu; + default: + return 0; } } diff --git a/MobileGL/MG_Util/Metrics/PipeStats.cpp b/MobileGL/MG_Util/Metrics/PipeStats.cpp index 16dd1314..e624a4d3 100644 --- a/MobileGL/MG_Util/Metrics/PipeStats.cpp +++ b/MobileGL/MG_Util/Metrics/PipeStats.cpp @@ -188,7 +188,9 @@ namespace MobileGL::MG_Util::PipeStats { counter.fetch_add(amount, std::memory_order_relaxed); } - inline Uint64 Read(const Counter& counter) { return counter.load(std::memory_order_relaxed); } + inline Uint64 Read(const Counter& counter) { + return counter.load(std::memory_order_relaxed); + } // Bucket 0 is "0 bytes", bucket n>0 holds [2^(n-1), 2^n). Saturates at the last // bucket so a pathological record cannot index out of the array. @@ -218,22 +220,38 @@ namespace MobileGL::MG_Util::PipeStats { } const char* const kByteClassNames[kByteClassCount] = { - "stage-buffer", "stage-texture", "stage-ubo-global", - "stage-ubo-named", "stage-vertex-client", "stage-index-client", - "stage-indirect-cmd", "persistent-map-push", "residual-value-block", - "cso-blob-bytes", "stage-segment-bytes", + "stage-buffer", "stage-texture", "stage-ubo-global", "stage-ubo-named", + "stage-vertex-client", "stage-index-client", "stage-indirect-cmd", "persistent-map-push", + "residual-value-block", "cso-blob-bytes", "stage-segment-bytes", }; const char* const kCallClassNames[kCallClassCount] = { - "draws", "accessor-calls", "tex-upload-emissions", "tex-upload-box", "tex-upload-rect", + "draws", + "accessor-calls", + "tex-upload-emissions", + "tex-upload-box", + "tex-upload-rect", "tex-upload-jobs", - "render-state-cso-mints", "render-state-cso-binds", "map-persistent-roundtrips", - "framebuffer-emissions", "sampler-view-emissions", "sampler-state-emissions", - "shader-image-emissions", "client-tex-upload-emissions", "tex-remint-pulls", + "render-state-cso-mints", + "render-state-cso-binds", + "map-persistent-roundtrips", + "framebuffer-emissions", + "sampler-view-emissions", + "sampler-state-emissions", + "shader-image-emissions", + "client-tex-upload-emissions", + "tex-remint-pulls", "resident-subdata-emissions", - "residual-pulls", "server-verb-boundaries", "wire-records", - "wire-indirect-native-draws", "wire-indirect-cpu-expansions", "wire-indirect-barriers", - "wire-host-waits", "wire-host-waits-indirect", "wire-host-wait-us", - "wire-indirect-native-dispatches", "resource-readbacks", + "residual-pulls", + "server-verb-boundaries", + "wire-records", + "wire-indirect-native-draws", + "wire-indirect-cpu-expansions", + "wire-indirect-barriers", + "wire-host-waits", + "wire-host-waits-indirect", + "wire-host-wait-us", + "wire-indirect-native-dispatches", + "resource-readbacks", }; const char* const kGateNames[kGateCount] = { "espryt-render-state", "espryt-texture-sync-list", "espryt-unit-bindings-epoch", @@ -254,9 +272,8 @@ namespace MobileGL::MG_Util::PipeStats { // "csob-blob" and not "csob": the cso[] bracket below already prints csob= for the // render-state CSO BIND count, and two different numbers under one grep is how a // recorded baseline stops meaning anything. - const char* const kByteClassShort[kByteClassCount] = {"buf", "tex", "ubog", "ubon", "vtxc", - "idxc", "icmd", "pmap", "resid", - "csob-blob", "seg", + const char* const kByteClassShort[kByteClassCount] = { + "buf", "tex", "ubog", "ubon", "vtxc", "idxc", "icmd", "pmap", "resid", "csob-blob", "seg", }; const char* const kGateShort[kGateCount] = {"ers", "etl", "eub", "mfp", "mpm", "mdt"}; @@ -353,8 +370,7 @@ namespace MobileGL::MG_Util::PipeStats { #endif std::ofstream out(path, std::ios::out | std::ios::trunc); if (!out) { - MGLOG_W("PipeStats: could not open MOBILEGL_PIPE_STATS_FILE='%s' for writing", - path.c_str()); + MGLOG_W("PipeStats: could not open MOBILEGL_PIPE_STATS_FILE='%s' for writing", path.c_str()); return; } out << FormatJson(); @@ -388,8 +404,8 @@ namespace MobileGL::MG_Util::PipeStats { // the price G1 charges for adding anything to this file at all. const String dumpPath = RoleDerivedJsonDumpPathForTesting(); MGLOG_I("MGPipe stats: counters ON (MOBILEGL_PIPE_STATS), summary every %llu frames%s%s", - static_cast(g_summaryPeriod), - dumpPath.empty() ? "" : ", JSON dump to ", dumpPath.c_str()); + static_cast(g_summaryPeriod), dumpPath.empty() ? "" : ", JSON dump to ", + dumpPath.c_str()); #else MGLOG_I("MGPipe stats: counters ON (MOBILEGL_PIPE_STATS), summary every %llu frames%s%s", static_cast(g_summaryPeriod), @@ -399,7 +415,9 @@ namespace MobileGL::MG_Util::PipeStats { } } - Uint64 SummaryFramePeriod() { return g_summaryPeriod; } + Uint64 SummaryFramePeriod() { + return g_summaryPeriod; + } void Shutdown() { if (!g_pipeStatsEnabled || g_shutdownDone) { @@ -431,7 +449,9 @@ namespace MobileGL::MG_Util::PipeStats { g_gauges[static_cast(gauge)].store(value, std::memory_order_relaxed); } - Uint64 GaugeValue(Gauge gauge) { return Read(g_gauges[static_cast(gauge)]); } + Uint64 GaugeValue(Gauge gauge) { + return Read(g_gauges[static_cast(gauge)]); + } void CountGate(Gate gate, Bool hit) { const Uint32 index = static_cast(gate); @@ -444,7 +464,9 @@ namespace MobileGL::MG_Util::PipeStats { } } - void RecordDrawPayloadBytes(Uint64 bytes) { Bump(g_totalPayloadBuckets[PayloadBucketOf(bytes)], 1); } + void RecordDrawPayloadBytes(Uint64 bytes) { + Bump(g_totalPayloadBuckets[PayloadBucketOf(bytes)], 1); + } // P6 gate 8's staged-blob distribution. A count, not a sum: the bytes already have a home // in stage-segment-bytes, and what this exists to answer is the SHAPE (how many blobs there @@ -499,23 +521,43 @@ namespace MobileGL::MG_Util::PipeStats { } } - Uint64 FrameBytes(ByteClass byteClass) { return Read(g_frameBytes[static_cast(byteClass)]); } - Uint64 TotalBytes(ByteClass byteClass) { return Read(g_totalBytes[static_cast(byteClass)]); } - Uint64 FrameCalls(CallClass callClass) { return Read(g_frameCalls[static_cast(callClass)]); } - Uint64 TotalCalls(CallClass callClass) { return Read(g_totalCalls[static_cast(callClass)]); } - Uint64 TotalGateHits(Gate gate) { return Read(g_totalGateHit[static_cast(gate)]); } - Uint64 TotalGateMisses(Gate gate) { return Read(g_totalGateMiss[static_cast(gate)]); } + Uint64 FrameBytes(ByteClass byteClass) { + return Read(g_frameBytes[static_cast(byteClass)]); + } + Uint64 TotalBytes(ByteClass byteClass) { + return Read(g_totalBytes[static_cast(byteClass)]); + } + Uint64 FrameCalls(CallClass callClass) { + return Read(g_frameCalls[static_cast(callClass)]); + } + Uint64 TotalCalls(CallClass callClass) { + return Read(g_totalCalls[static_cast(callClass)]); + } + Uint64 TotalGateHits(Gate gate) { + return Read(g_totalGateHit[static_cast(gate)]); + } + Uint64 TotalGateMisses(Gate gate) { + return Read(g_totalGateMiss[static_cast(gate)]); + } Uint64 TotalPayloadBucket(Uint32 bucket) { return bucket < kPayloadHistogramBuckets ? Read(g_totalPayloadBuckets[bucket]) : 0; } Uint64 TotalStagedBlobBucket(Uint32 bucket) { return bucket < kStagedBlobHistogramBuckets ? Read(g_totalStagedBlobBuckets[bucket]) : 0; } - Uint64 FrameCount() { return Read(g_frameCount); } + Uint64 FrameCount() { + return Read(g_frameCount); + } - const char* NameOf(ByteClass byteClass) { return kByteClassNames[static_cast(byteClass)]; } - const char* NameOf(CallClass callClass) { return kCallClassNames[static_cast(callClass)]; } - const char* NameOf(Gate gate) { return kGateNames[static_cast(gate)]; } + const char* NameOf(ByteClass byteClass) { + return kByteClassNames[static_cast(byteClass)]; + } + const char* NameOf(CallClass callClass) { + return kCallClassNames[static_cast(callClass)]; + } + const char* NameOf(Gate gate) { + return kGateNames[static_cast(gate)]; + } String FormatWindowLine() { // Window values: everything since the previous summary. A run total over a workload @@ -589,8 +631,7 @@ namespace MobileGL::MG_Util::PipeStats { line += " sve=" + std::to_string(calls[static_cast(CallClass::SamplerViewEmissions)]); line += " sse=" + std::to_string(calls[static_cast(CallClass::SamplerStateEmissions)]); line += " sie=" + std::to_string(calls[static_cast(CallClass::ShaderImageEmissions)]); - line += " ctu=" + - std::to_string(calls[static_cast(CallClass::ClientTextureUploadEmissions)]); + line += " ctu=" + std::to_string(calls[static_cast(CallClass::ClientTextureUploadEmissions)]); // trp is the texture re-mint count (ROADMAP open question 2): every one is a texture // Espryt had already allocated and then had to re-mint image-bindable because // ImageBindableHint reached it too late - replaying the client's shadow in monolith, the @@ -602,8 +643,7 @@ namespace MobileGL::MG_Util::PipeStats { // buffer are identical either way - so it is what the white-box cases read. Zero under // monolith by construction, and zero under split too until the server publishes // kCapResidentSubData off its own wire resource table (MG_Backend/Init.cpp). - line += " rsd=" + - std::to_string(calls[static_cast(CallClass::ResidentSubDataEmissions)]); + line += " rsd=" + std::to_string(calls[static_cast(CallClass::ResidentSubDataEmissions)]); // rsp is P5's residual-pull count: reads, on the server side, of a PipeInputs field no // pushed record supplies, answered out of the client's residual fill under the verb // barrier. It is the SIZE OF THE P6/P7/P8 DEBT and it is published on this line rather @@ -617,8 +657,7 @@ namespace MobileGL::MG_Util::PipeStats { // the monolith arm never stamps, so a green lane with vbs=0 says only that the mechanism // never ran. It is deliberately NOT rsp: rsp reaches zero when the phase SUCCEEDS, so a // positive control built on it would fail on the day the debt is paid. - line += " vbs=" + - std::to_string(calls[static_cast(CallClass::ServerVerbBoundaries)]); + line += " vbs=" + std::to_string(calls[static_cast(CallClass::ServerVerbBoundaries)]); // P6 GATE 8's SECOND NUMBER (CONTRACT-P6.md §9 item 8): records/frame AFTER chunking, i.e. // the count of records the wire encoder actually committed this window rather than the // number of times an emitter was asked for one. The gap between the two is the whole @@ -633,10 +672,8 @@ namespace MobileGL::MG_Util::PipeStats { // its numerator times the frame count is a formatting bug, and a per-frame figure over a // window with no Present in it is the 47x overstatement PipeStatsTest already pins. line += " wrec=" + std::to_string(calls[static_cast(CallClass::WireRecords)]); - line += " wrec/f=" + (perFrame - ? FormatFixed2(calls[static_cast(CallClass::WireRecords)], - windowFrames) - : std::to_string(calls[static_cast(CallClass::WireRecords)])); + line += " wrec/f=" + (perFrame ? FormatFixed2(calls[static_cast(CallClass::WireRecords)], windowFrames) + : std::to_string(calls[static_cast(CallClass::WireRecords)])); // P5's three wire gauges, and THEY ARE RUN TOTALS on a line whose every other field is // a window - see the Gauge enum for the argument. `maxrec` is R-10's proof obligation // (BRIEF 8 item 3): the largest single record this run wrote, in BYTES, beside the cap @@ -674,8 +711,7 @@ namespace MobileGL::MG_Util::PipeStats { line += " wdefsync=" + std::to_string(Read(g_gauges[static_cast(Gauge::WireDeferredSyncs)])); // P8-D: the Magma wire arm's indirect draws and its host-access waits, windowed like the // other CallClass fields (per window, not per frame: the create fixtures hold one frame). - line += "] windr[wind=" + - std::to_string(calls[static_cast(CallClass::WireIndirectNativeDraws)]); + line += "] windr[wind=" + std::to_string(calls[static_cast(CallClass::WireIndirectNativeDraws)]); line += " wixp=" + std::to_string(calls[static_cast(CallClass::WireIndirectCpuExpansions)]); line += " wibar=" + std::to_string(calls[static_cast(CallClass::WireIndirectBarriers)]); line += " whw=" + std::to_string(calls[static_cast(CallClass::WireHostWaits)]); @@ -739,30 +775,30 @@ namespace MobileGL::MG_Util::PipeStats { } // The gauges, under their long names. Run totals here as on the summary line. json += " },\n \"wire\": {\n"; - json += " \"max-record-bytes\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::MaxRecordBytes)])) + ",\n"; + json += + " \"max-record-bytes\": " + std::to_string(Read(g_gauges[static_cast(Gauge::MaxRecordBytes)])) + + ",\n"; json += " \"max-record-bytes-cap\": " + std::to_string(Read(g_gauges[static_cast(Gauge::MaxRecordBytesCap)])) + ",\n"; - json += " \"ring-wraps\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::RingWraps)])) + ",\n"; - json += " \"ring-wrap-pads\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::RingWrapPads)])) + ",\n"; - json += " \"ring-waits\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::RingWaits)])) + ",\n"; + json += " \"ring-wraps\": " + std::to_string(Read(g_gauges[static_cast(Gauge::RingWraps)])) + ",\n"; + json += " \"ring-wrap-pads\": " + std::to_string(Read(g_gauges[static_cast(Gauge::RingWrapPads)])) + + ",\n"; + json += " \"ring-waits\": " + std::to_string(Read(g_gauges[static_cast(Gauge::RingWaits)])) + ",\n"; // The wait ledger under its long names, same run totals as the summary line's wait[]. - json += " \"server-waits\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::ServerWaits)])) + ",\n"; - json += " \"server-parks\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::ServerParks)])) + ",\n"; - json += " \"client-waits\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::ClientWaits)])) + ",\n"; - json += " \"client-parks\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::ClientParks)])) + ",\n"; + json += + " \"server-waits\": " + std::to_string(Read(g_gauges[static_cast(Gauge::ServerWaits)])) + ",\n"; + json += + " \"server-parks\": " + std::to_string(Read(g_gauges[static_cast(Gauge::ServerParks)])) + ",\n"; + json += + " \"client-waits\": " + std::to_string(Read(g_gauges[static_cast(Gauge::ClientWaits)])) + ",\n"; + json += + " \"client-parks\": " + std::to_string(Read(g_gauges[static_cast(Gauge::ClientParks)])) + ",\n"; // P7 wave 4 M2's four, run maxima / a run count as on the summary line's wbuf[]. - json += " \"wire-buffers\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::WireBuffers)])) + ",\n"; - json += " \"wire-stores-peak\": " + - std::to_string(Read(g_gauges[static_cast(Gauge::WireStoresPeak)])) + ",\n"; + json += + " \"wire-buffers\": " + std::to_string(Read(g_gauges[static_cast(Gauge::WireBuffers)])) + ",\n"; + json += + " \"wire-stores-peak\": " + std::to_string(Read(g_gauges[static_cast(Gauge::WireStoresPeak)])) + + ",\n"; json += " \"wire-deferred-bytes-peak\": " + std::to_string(Read(g_gauges[static_cast(Gauge::WireDeferredBytesPeak)])) + ",\n"; json += " \"wire-deferred-syncs\": " + @@ -793,9 +829,13 @@ namespace MobileGL::MG_Util::PipeStats { return json; } - void SetEnabledForTesting(Bool enabled) { g_pipeStatsEnabled = enabled; } + void SetEnabledForTesting(Bool enabled) { + g_pipeStatsEnabled = enabled; + } - void ResetForTesting() { ResetCounters(); } + void ResetForTesting() { + ResetCounters(); + } #if MOBILEGL_BUILD_DISAGGREGATED String RoleDerivedJsonDumpPathForTesting() { diff --git a/MobileGL/MG_Util/Metrics/PipeStats.h b/MobileGL/MG_Util/Metrics/PipeStats.h index eca060c2..6b666875 100644 --- a/MobileGL/MG_Util/Metrics/PipeStats.h +++ b/MobileGL/MG_Util/Metrics/PipeStats.h @@ -448,7 +448,9 @@ namespace MobileGL::MG_Util::PipeStats { // predicted branch - do not turn this into a function call. extern Bool g_pipeStatsEnabled; - inline Bool Enabled() { return g_pipeStatsEnabled; } + inline Bool Enabled() { + return g_pipeStatsEnabled; + } // Latches g_pipeStatsEnabled from MG_Config::Features.PipeStats and clears every // counter. Called from MobileGL::Initialize() right after the config load. diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index 3ca3a3fa..8c6e28e2 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -518,7 +518,7 @@ namespace MobileGL { break; default: MGLOG_W_ONCE("Unimplemented internal format in GetComponentSizesForInternalFormat: %d", - static_cast(internal)); + static_cast(internal)); break; } diff --git a/MobileGL/MG_Util/SelfTest/DriverBenchJni.cpp b/MobileGL/MG_Util/SelfTest/DriverBenchJni.cpp index 1637548c..951f06c1 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBenchJni.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverBenchJni.cpp @@ -135,8 +135,8 @@ namespace { const uint64_t y = *static_cast(b); return x < y ? -1 : x > y; } - #define now_ns NowNs - #define cmp_u64 CompareU64 +#define now_ns NowNs +#define cmp_u64 CompareU64 // Failure funnel for the shared scene builder: remember the first failure, // let the run finish reporting it instead of aborting the service process. @@ -158,7 +158,7 @@ namespace { glMultiDrawElementsBaseVertex(mode, counts, type, offsets, drawCount, baseVertices); } - #include "../../MG_Benchmark/Driver/DriverBenchCases.inc" +#include "../../MG_Benchmark/Driver/DriverBenchCases.inc" // Frame boundary: a real fence wait. MobileGL's glFinish/glFlush are // deliberate no-ops, so a glFinish-paced loop would time only CPU submit. @@ -177,11 +177,21 @@ namespace { out.reserve(value.size() + 8); for (const char c : value) { switch (c) { - case '"': out += "\\\""; break; - case '\\': out += "\\\\"; break; - case '\n': out += "\\n"; break; - case '\r': out += "\\r"; break; - case '\t': out += "\\t"; break; + case '"': + out += "\\\""; + break; + case '\\': + out += "\\\\"; + break; + case '\n': + out += "\\n"; + break; + case '\r': + out += "\\r"; + break; + case '\t': + out += "\\t"; + break; default: if (static_cast(c) < 0x20 || static_cast(c) >= 0x7F) { char buffer[8]; @@ -206,7 +216,10 @@ namespace { // Espryt's display teardown is precisely the hazard being avoided. bool BootEgl(EglBench& out, std::string& error) { out.display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (out.display == EGL_NO_DISPLAY) { error = "eglGetDisplay failed"; return false; } + if (out.display == EGL_NO_DISPLAY) { + error = "eglGetDisplay failed"; + return false; + } EGLint major = 0, minor = 0; if (eglInitialize(out.display, &major, &minor) != EGL_TRUE) { char buffer[48]; @@ -214,13 +227,12 @@ namespace { error = buffer; return false; } - const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24, - EGL_NONE}; + const EGLint configAttribs[] = { + EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_DEPTH_SIZE, 24, + EGL_NONE}; EGLConfig config = nullptr; EGLint numConfigs = 0; - if (eglChooseConfig(out.display, configAttribs, &config, 1, &numConfigs) != EGL_TRUE || - numConfigs < 1) { + if (eglChooseConfig(out.display, configAttribs, &config, 1, &numConfigs) != EGL_TRUE || numConfigs < 1) { error = "eglChooseConfig found no pbuffer config"; return false; } @@ -296,16 +308,17 @@ namespace { qsort(samples, static_cast(g_frames), sizeof(uint64_t), CompareU64); const uint64_t median = samples[g_frames / 2]; const double frameMs = static_cast(median) / 1e6; - const double nsPerOp = - benchCase.opsPerFrame > 0 ? static_cast(median) / static_cast(benchCase.opsPerFrame) : 0.0; + const double nsPerOp = benchCase.opsPerFrame > 0 + ? static_cast(median) / static_cast(benchCase.opsPerFrame) + : 0.0; const GLenum caseError = glGetError(); char row[256]; snprintf(row, sizeof row, "%s{\"case\":\"%s\",\"frames\":%d,\"opsPerFrame\":%ld,\"medianFrameMs\":%.3f," "\"nsPerOp\":%.1f,\"fps\":%.1f,\"glError\":%u}", - rows.empty() ? "" : ",", benchCase.name, g_frames, benchCase.opsPerFrame, frameMs, - nsPerOp, 1e9 / static_cast(median), caseError); + rows.empty() ? "" : ",", benchCase.name, g_frames, benchCase.opsPerFrame, frameMs, nsPerOp, + 1e9 / static_cast(median), caseError); rows += row; } @@ -324,8 +337,10 @@ namespace { } // namespace -extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_BenchService_nativeRunDriverBench( - JNIEnv* env, jclass, jstring backendType, jint frames, jint warmupFrames) { +extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_BenchService_nativeRunDriverBench(JNIEnv* env, jclass, + jstring backendType, + jint frames, + jint warmupFrames) { const char* backendChars = backendType ? env->GetStringUTFChars(backendType, nullptr) : nullptr; const std::string backend = backendChars ? backendChars : "DirectGLES"; if (backendChars) env->ReleaseStringUTFChars(backendType, backendChars); @@ -333,9 +348,11 @@ extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_BenchService_nativ std::string result; try { result = RunBench(backend, frames, warmupFrames); - } catch (const std::exception& e) { + } + catch (const std::exception& e) { result = std::string("{\"error\":\"") + EscapeJson(e.what()) + "\"}"; - } catch (...) { + } + catch (...) { result = "{\"error\":\"unknown native exception\"}"; } return env->NewStringUTF(result.c_str()); diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp index 54b49a71..2021b945 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.cpp @@ -35,9 +35,8 @@ namespace MobileGL::MG_Util::SelfTest { constexpr GLuint kBeforeEmitBinding = 0; constexpr GLuint kAfterEmitBinding = 1; - const char* const kProbeVertexSource = - "#version 320 es\n" - "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n"; + const char* const kProbeVertexSource = "#version 320 es\n" + "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 1.0); }\n"; // No gl_PointSize anywhere: writing it from a geometry shader needs // EXT/OES_geometry_point_size, which not every ES 3.2 driver exposes (some desktop drivers do not), @@ -56,34 +55,31 @@ namespace MobileGL::MG_Util::SelfTest { " g_after.data[0] = 7u;\n" "}\n"; - const char* const kProbeFragmentSource = - "#version 320 es\n" - "precision highp float;\n" - "layout(location = 0) out vec4 o_color;\n" - "void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); }\n"; + const char* const kProbeFragmentSource = "#version 320 es\n" + "precision highp float;\n" + "layout(location = 0) out vec4 o_color;\n" + "void main() { o_color = vec4(0.0, 1.0, 0.0, 1.0); }\n"; Bool HasEveryEntryPoint(const GLESFunctionsTable& gl) { return gl.glCreateShader && gl.glShaderSource && gl.glCompileShader && gl.glGetShaderiv && gl.glGetShaderInfoLog && gl.glCreateProgram && gl.glAttachShader && gl.glLinkProgram && - gl.glGetProgramiv && gl.glDeleteShader && gl.glDeleteProgram && gl.glUseProgram && - gl.glGenBuffers && gl.glBindBuffer && gl.glBufferData && gl.glBindBufferBase && - gl.glDeleteBuffers && gl.glGenVertexArrays && gl.glBindVertexArray && - gl.glDeleteVertexArrays && gl.glGenFramebuffers && gl.glBindFramebuffer && - gl.glFramebufferRenderbuffer && gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && - gl.glGenRenderbuffers && gl.glBindRenderbuffer && gl.glRenderbufferStorage && - gl.glDeleteRenderbuffers && gl.glViewport && gl.glDrawArrays && gl.glMemoryBarrier && - gl.glMapBufferRange && gl.glUnmapBuffer && gl.glGetIntegerv && gl.glGetIntegeri_v && - gl.glGetError && gl.glFinish && gl.glEnable && gl.glDisable && gl.glIsEnabled; + gl.glGetProgramiv && gl.glDeleteShader && gl.glDeleteProgram && gl.glUseProgram && gl.glGenBuffers && + gl.glBindBuffer && gl.glBufferData && gl.glBindBufferBase && gl.glDeleteBuffers && + gl.glGenVertexArrays && gl.glBindVertexArray && gl.glDeleteVertexArrays && gl.glGenFramebuffers && + gl.glBindFramebuffer && gl.glFramebufferRenderbuffer && gl.glCheckFramebufferStatus && + gl.glDeleteFramebuffers && gl.glGenRenderbuffers && gl.glBindRenderbuffer && + gl.glRenderbufferStorage && gl.glDeleteRenderbuffers && gl.glViewport && gl.glDrawArrays && + gl.glMemoryBarrier && gl.glMapBufferRange && gl.glUnmapBuffer && gl.glGetIntegerv && + gl.glGetIntegeri_v && gl.glGetError && gl.glFinish && gl.glEnable && gl.glDisable && gl.glIsEnabled; } void Drain(const GLESFunctionsTable& gl) { // Bounded: a driver that returns an error forever must not hang the probe. - for (Int i = 0; i < 32 && gl.glGetError() != GL_NO_ERROR; ++i) { - } + for (Int i = 0; i < 32 && gl.glGetError() != GL_NO_ERROR; ++i) {} } - GLuint CompileStage(const GLESFunctionsTable& gl, GLenum stage, const char* source, - const char* stageName, const char* probeName) { + GLuint CompileStage(const GLESFunctionsTable& gl, GLenum stage, const char* source, const char* stageName, + const char* probeName) { const GLuint shader = gl.glCreateShader(stage); if (shader == 0) return 0; gl.glShaderSource(shader, 1, &source, nullptr); @@ -234,17 +230,15 @@ namespace MobileGL::MG_Util::SelfTest { gl.glUseProgram(static_cast(state.program)); gl.glViewport(state.viewport[0], state.viewport[1], state.viewport[2], state.viewport[3]); if (gl.glBindImageTexture != nullptr) { - gl.glBindImageTexture(kProbeImageUnit, static_cast(state.imageName), - state.imageLevel, state.imageLayered != 0 ? GL_TRUE : GL_FALSE, - state.imageLayer, static_cast(state.imageAccess), - static_cast(state.imageFormat)); + gl.glBindImageTexture(kProbeImageUnit, static_cast(state.imageName), state.imageLevel, + state.imageLayered != 0 ? GL_TRUE : GL_FALSE, state.imageLayer, + static_cast(state.imageAccess), static_cast(state.imageFormat)); } if (gl.glActiveTexture != nullptr) { gl.glActiveTexture(GL_TEXTURE0); if (gl.glBindTexture != nullptr) { gl.glBindTexture(GL_TEXTURE_2D, static_cast(state.texture2D)); - gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, - static_cast(state.texture2DMultisample)); + gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, static_cast(state.texture2DMultisample)); gl.glBindTexture(GL_TEXTURE_2D_ARRAY, static_cast(state.texture2DArray)); } gl.glActiveTexture(static_cast(state.activeTexture)); @@ -266,8 +260,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, static_cast(state.pixelUnpackBuffer)); } if (gl.glClearColor != nullptr) { - gl.glClearColor(state.clearColor[0], state.clearColor[1], state.clearColor[2], - state.clearColor[3]); + gl.glClearColor(state.clearColor[0], state.clearColor[1], state.clearColor[2], state.clearColor[3]); } if (state.rasterizerDiscard) gl.glEnable(GL_RASTERIZER_DISCARD); if (state.scissorTest) gl.glEnable(GL_SCISSOR_TEST); @@ -279,8 +272,7 @@ namespace MobileGL::MG_Util::SelfTest { GLuint ReadFirstWord(const GLESFunctionsTable& gl, GLuint buffer) { gl.glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer); - const void* mapped = - gl.glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kProbeSize, GL_MAP_READ_BIT); + const void* mapped = gl.glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kProbeSize, GL_MAP_READ_BIT); if (mapped == nullptr) return 0u; GLuint value = 0; std::memcpy(&value, mapped, sizeof(value)); @@ -312,12 +304,9 @@ namespace MobileGL::MG_Util::SelfTest { GLuint vertexArray = 0, framebuffer = 0, renderbuffer = 0; do { - vertexShader = CompileStage(gl, GL_VERTEX_SHADER, kProbeVertexSource, "vertex", - kGeometryProbeName); - geometryShader = CompileStage(gl, GL_GEOMETRY_SHADER, kProbeGeometrySource, "geometry", - kGeometryProbeName); - fragmentShader = CompileStage(gl, GL_FRAGMENT_SHADER, kProbeFragmentSource, "fragment", - kGeometryProbeName); + vertexShader = CompileStage(gl, GL_VERTEX_SHADER, kProbeVertexSource, "vertex", kGeometryProbeName); + geometryShader = CompileStage(gl, GL_GEOMETRY_SHADER, kProbeGeometrySource, "geometry", kGeometryProbeName); + fragmentShader = CompileStage(gl, GL_FRAGMENT_SHADER, kProbeFragmentSource, "fragment", kGeometryProbeName); if (vertexShader == 0 || geometryShader == 0 || fragmentShader == 0) { inconclusive = "one of the probe stages did not compile"; break; @@ -355,8 +344,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 4, 4); gl.glGenFramebuffers(1, &framebuffer); gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer); - gl.glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - renderbuffer); + gl.glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); if (gl.glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { inconclusive = "the probe's own 4x4 RGBA8 framebuffer came back incomplete"; break; @@ -401,8 +389,7 @@ namespace MobileGL::MG_Util::SelfTest { } while (false); if (inconclusive != nullptr) { - MGLOG_I("[driver-bug] geometry write-after-emit probe reached no verdict (%s)", - inconclusive); + MGLOG_I("[driver-bug] geometry write-after-emit probe reached no verdict (%s)", inconclusive); } if (vertexArray != 0) gl.glDeleteVertexArrays(1, &vertexArray); @@ -464,8 +451,7 @@ namespace MobileGL::MG_Util::SelfTest { Optional RunStaleSharedVariant(const GLESFunctionsTable& gl, const char* readExpression, GLuint buffer) { const String source = StaleSharedComputeSource(readExpression); - const GLuint shader = - CompileStage(gl, GL_COMPUTE_SHADER, source.c_str(), "compute", kStaleSharedProbeName); + const GLuint shader = CompileStage(gl, GL_COMPUTE_SHADER, source.c_str(), "compute", kStaleSharedProbeName); if (shader == 0) return std::nullopt; const GLuint program = gl.glCreateProgram(); Optional result; @@ -510,8 +496,7 @@ namespace MobileGL::MG_Util::SelfTest { Restore(gl, saved); if (!plain || !atomic) { - MGLOG_I("[driver-bug] %s probe reached no verdict (a variant did not build or run)", - kStaleSharedProbeName); + MGLOG_I("[driver-bug] %s probe reached no verdict (a variant did not build or run)", kStaleSharedProbeName); return false; } if (*atomic != kStaleSharedExpected) { @@ -538,7 +523,8 @@ namespace MobileGL::MG_Util::SelfTest { String EmptyImageComputeSource() { return "#version 310 es\n" "layout(local_size_x = 1) in;\n" - "layout(rgba8, binding = " + std::to_string(kProbeImageUnit) + + "layout(rgba8, binding = " + + std::to_string(kProbeImageUnit) + ") readonly uniform highp image2D u_image;\n" "layout(std430, binding = 0) buffer Result { highp uint data[4]; } g_result;\n" "void main() { g_result.data[0] = packUnorm4x8(imageLoad(u_image, ivec2(0))); }\n"; @@ -658,8 +644,7 @@ namespace MobileGL::MG_Util::SelfTest { } }; for (const StageSource& stage : stages) { - const GLuint shader = - CompileStage(gl, stage.stage, stage.source.c_str(), stage.name, probeName); + const GLuint shader = CompileStage(gl, stage.stage, stage.source.c_str(), stage.name, probeName); if (shader == 0) { releaseShaders(); return build; @@ -701,17 +686,15 @@ namespace MobileGL::MG_Util::SelfTest { // Every probe below rasterizes the same full-viewport quad from a vertex ID, so none of // them needs a vertex buffer or an attribute. - const char* const kFullscreenQuadBody = - " vec2 mg_p = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0,\n" - " (gl_VertexID & 2) == 0 ? -1.0 : 1.0);\n" - " gl_Position = vec4(mg_p, 0.0, 1.0);\n"; + const char* const kFullscreenQuadBody = " vec2 mg_p = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0,\n" + " (gl_VertexID & 2) == 0 ? -1.0 : 1.0);\n" + " gl_Position = vec4(mg_p, 0.0, 1.0);\n"; - const char* const kProbeQuadVertexSource = - "#version 320 es\n" - "void main()\n{\n" - " vec2 mg_p = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0,\n" - " (gl_VertexID & 2) == 0 ? -1.0 : 1.0);\n" - " gl_Position = vec4(mg_p, 0.0, 1.0);\n}\n"; + const char* const kProbeQuadVertexSource = "#version 320 es\n" + "void main()\n{\n" + " vec2 mg_p = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0,\n" + " (gl_VertexID & 2) == 0 ? -1.0 : 1.0);\n" + " gl_Position = vec4(mg_p, 0.0, 1.0);\n}\n"; // Puts the context into the shape every probe draw wants. The caller has already saved // the state this disturbs. @@ -741,30 +724,26 @@ namespace MobileGL::MG_Util::SelfTest { Bool HasMsaaEntryPoints(const GLESFunctionsTable& gl) { return gl.glCreateShader && gl.glShaderSource && gl.glCompileShader && gl.glGetShaderiv && gl.glGetShaderInfoLog && gl.glCreateProgram && gl.glAttachShader && gl.glLinkProgram && - gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteShader && - gl.glDeleteProgram && gl.glUseProgram && gl.glGenTextures && gl.glBindTexture && - gl.glDeleteTextures && gl.glTexParameteri && gl.glTexImage2D && - gl.glTexStorage2DMultisample && gl.glGenFramebuffers && gl.glBindFramebuffer && - gl.glFramebufferTexture2D && gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && - gl.glGenVertexArrays && gl.glBindVertexArray && gl.glDeleteVertexArrays && - gl.glActiveTexture && gl.glGetUniformLocation && gl.glUniform1i && gl.glViewport && - gl.glClear && gl.glClearColor && gl.glReadPixels && gl.glDrawArrays && - gl.glPixelStorei && gl.glGetIntegerv && gl.glGetIntegeri_v && gl.glGetError && - gl.glFinish && gl.glEnable && gl.glDisable && gl.glIsEnabled; + gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteShader && gl.glDeleteProgram && + gl.glUseProgram && gl.glGenTextures && gl.glBindTexture && gl.glDeleteTextures && + gl.glTexParameteri && gl.glTexImage2D && gl.glTexStorage2DMultisample && gl.glGenFramebuffers && + gl.glBindFramebuffer && gl.glFramebufferTexture2D && gl.glCheckFramebufferStatus && + gl.glDeleteFramebuffers && gl.glGenVertexArrays && gl.glBindVertexArray && gl.glDeleteVertexArrays && + gl.glActiveTexture && gl.glGetUniformLocation && gl.glUniform1i && gl.glViewport && gl.glClear && + gl.glClearColor && gl.glReadPixels && gl.glDrawArrays && gl.glPixelStorei && gl.glGetIntegerv && + gl.glGetIntegeri_v && gl.glGetError && gl.glFinish && gl.glEnable && gl.glDisable && gl.glIsEnabled; } - const char* const kMsaaFillFragmentSource = - "#version 320 es\n" - "precision highp float;\n" - "layout(location = 0) out highp vec4 o_color;\n" - "void main() { o_color = vec4(1.0, 0.0, 0.0, 0.0); }\n"; + const char* const kMsaaFillFragmentSource = "#version 320 es\n" + "precision highp float;\n" + "layout(location = 0) out highp vec4 o_color;\n" + "void main() { o_color = vec4(1.0, 0.0, 0.0, 0.0); }\n"; - const char* const kMsaaSampleFragmentSource = - "#version 320 es\n" - "precision highp float;\n" - "layout(location = 0) out highp float o_color;\n" - "layout(location = 0) flat in highp float v_result;\n" - "void main() { o_color = v_result; }\n"; + const char* const kMsaaSampleFragmentSource = "#version 320 es\n" + "precision highp float;\n" + "layout(location = 0) out highp float o_color;\n" + "layout(location = 0) flat in highp float v_result;\n" + "void main() { o_color = v_result; }\n"; // One vertex shader per round. The `round` term is multiplied by zero, so it changes // nothing about the result and everything about the source text - which is the point: @@ -786,8 +765,8 @@ namespace MobileGL::MG_Util::SelfTest { // Samples one texel of `source` `rounds` times, each round through its own freshly // linked program and its own freshly created R32F output texture, and stores what came // back. Returns false when any round could not be set up at all. - Bool RunMsaaSampledRead(const GLESFunctionsTable& gl, GLuint source, Int sampleIndex, - char component, Int rounds, Vector& values) { + Bool RunMsaaSampledRead(const GLESFunctionsTable& gl, GLuint source, Int sampleIndex, char component, + Int rounds, Vector& values) { values.clear(); for (Int round = 0; round < rounds; ++round) { GLuint output = 0; @@ -797,20 +776,18 @@ namespace MobileGL::MG_Util::SelfTest { do { gl.glGenTextures(1, &output); gl.glBindTexture(GL_TEXTURE_2D, output); - gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, kMsaaOutputSize, kMsaaOutputSize, 0, - GL_RED, GL_FLOAT, nullptr); + gl.glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, kMsaaOutputSize, kMsaaOutputSize, 0, GL_RED, GL_FLOAT, + nullptr); gl.glGenFramebuffers(1, &framebuffer); gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - output, 0); + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, output, 0); if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) break; - build = BuildProgram(gl, - {{GL_VERTEX_SHADER, - BuildMsaaSampleVertexSource(sampleIndex, component, round), - "vertex"}, - {GL_FRAGMENT_SHADER, kMsaaSampleFragmentSource, "fragment"}}, - kMsaaProbeName); + build = BuildProgram( + gl, + {{GL_VERTEX_SHADER, BuildMsaaSampleVertexSource(sampleIndex, component, round), "vertex"}, + {GL_FRAGMENT_SHADER, kMsaaSampleFragmentSource, "fragment"}}, + kMsaaProbeName); if (!build.linked) break; gl.glUseProgram(build.program); @@ -818,8 +795,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, source); gl.glUniform1i(gl.glGetUniformLocation(build.program, "mg_probeSampler"), 0); gl.glViewport(0, 0, kMsaaOutputSize, kMsaaOutputSize); - gl.glClearColor(kMsaaClearSentinel, kMsaaClearSentinel, kMsaaClearSentinel, - kMsaaClearSentinel); + gl.glClearColor(kMsaaClearSentinel, kMsaaClearSentinel, kMsaaClearSentinel, kMsaaClearSentinel); gl.glClear(GL_COLOR_BUFFER_BIT); Drain(gl); gl.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); @@ -827,8 +803,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glFinish(); Vector pixels(static_cast(kMsaaOutputSize) * kMsaaOutputSize, 0.0f); - gl.glReadPixels(0, 0, kMsaaOutputSize, kMsaaOutputSize, GL_RED, GL_FLOAT, - pixels.data()); + gl.glReadPixels(0, 0, kMsaaOutputSize, kMsaaOutputSize, GL_RED, GL_FLOAT, pixels.data()); if (gl.glGetError() != GL_NO_ERROR) break; values.push_back(pixels[0]); ok = true; @@ -845,8 +820,7 @@ namespace MobileGL::MG_Util::SelfTest { // Creates a `samples`-sample R32F multisample texture, fills it with (1, 0, 0, 0), and // points GL_TEXTURE_SWIZZLE_R/G/B at ALPHA/BLUE/GREEN with GL_TEXTURE_SWIZZLE_A at // `alphaSwizzle`. Every source the probe builds is identical except for that one enum. - GLuint MakeSwizzledMultisampleSource(const GLESFunctionsTable& gl, GLsizei samples, - GLenum alphaSwizzle) { + GLuint MakeSwizzledMultisampleSource(const GLESFunctionsTable& gl, GLsizei samples, GLenum alphaSwizzle) { GLuint texture = 0; gl.glGenTextures(1, &texture); gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture); @@ -859,8 +833,7 @@ namespace MobileGL::MG_Util::SelfTest { GLuint framebuffer = 0; gl.glGenFramebuffers(1, &framebuffer); gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, - texture, 0); + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, texture, 0); Bool filled = false; ProgramBuild fill; if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { @@ -920,9 +893,8 @@ namespace MobileGL::MG_Util::SelfTest { Bool HasImageProgramEntryPoints(const GLESFunctionsTable& gl) { return gl.glCreateShader && gl.glShaderSource && gl.glCompileShader && gl.glGetShaderiv && gl.glGetShaderInfoLog && gl.glCreateProgram && gl.glAttachShader && gl.glLinkProgram && - gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteShader && - gl.glDeleteProgram && gl.glGetIntegerv && gl.glGetIntegeri_v && gl.glGetError && - gl.glIsEnabled; + gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteShader && gl.glDeleteProgram && + gl.glGetIntegerv && gl.glGetIntegeri_v && gl.glGetError && gl.glIsEnabled; } // One stage declaring `count` image uniforms named `namePrefix`0..count-1, each stored to @@ -936,8 +908,8 @@ namespace MobileGL::MG_Util::SelfTest { } source += "precision highp float;\nprecision highp int;\n"; for (Int i = 0; i < count; ++i) { - source += format("layout(binding = {}, rgba32f) uniform writeonly highp image2D {}{};\n", - i % units, namePrefix, i); + source += format("layout(binding = {}, rgba32f) uniform writeonly highp image2D {}{};\n", i % units, + namePrefix, i); } if (stage == GL_FRAGMENT_SHADER) { source += "layout(location = 0) out highp vec4 o_color;\n"; @@ -966,12 +938,11 @@ namespace MobileGL::MG_Util::SelfTest { const char* fragmentPrefix) { return BuildProgram( gl, - {{GL_VERTEX_SHADER, BuildImageNameStageSource(GL_VERTEX_SHADER, count, units, vertexPrefix), - "vertex"}, - {GL_GEOMETRY_SHADER, - BuildImageNameStageSource(GL_GEOMETRY_SHADER, count, units, geometryPrefix), "geometry"}, - {GL_FRAGMENT_SHADER, - BuildImageNameStageSource(GL_FRAGMENT_SHADER, count, units, fragmentPrefix), "fragment"}}, + {{GL_VERTEX_SHADER, BuildImageNameStageSource(GL_VERTEX_SHADER, count, units, vertexPrefix), "vertex"}, + {GL_GEOMETRY_SHADER, BuildImageNameStageSource(GL_GEOMETRY_SHADER, count, units, geometryPrefix), + "geometry"}, + {GL_FRAGMENT_SHADER, BuildImageNameStageSource(GL_FRAGMENT_SHADER, count, units, fragmentPrefix), + "fragment"}}, kImageLocationProbeName); } @@ -991,13 +962,13 @@ namespace MobileGL::MG_Util::SelfTest { }; Bool HasImageDrawEntryPoints(const GLESFunctionsTable& gl) { - return HasImageProgramEntryPoints(gl) && gl.glUseProgram && gl.glGenTextures && - gl.glBindTexture && gl.glDeleteTextures && gl.glTexStorage2D && gl.glTexSubImage2D && - gl.glGenFramebuffers && gl.glBindFramebuffer && gl.glFramebufferTexture2D && - gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && gl.glGenVertexArrays && - gl.glBindVertexArray && gl.glDeleteVertexArrays && gl.glBindImageTexture && - gl.glViewport && gl.glClear && gl.glClearColor && gl.glReadPixels && gl.glDrawArrays && - gl.glMemoryBarrier && gl.glPixelStorei && gl.glFinish && gl.glEnable && gl.glDisable; + return HasImageProgramEntryPoints(gl) && gl.glUseProgram && gl.glGenTextures && gl.glBindTexture && + gl.glDeleteTextures && gl.glTexStorage2D && gl.glTexSubImage2D && gl.glGenFramebuffers && + gl.glBindFramebuffer && gl.glFramebufferTexture2D && gl.glCheckFramebufferStatus && + gl.glDeleteFramebuffers && gl.glGenVertexArrays && gl.glBindVertexArray && gl.glDeleteVertexArrays && + gl.glBindImageTexture && gl.glViewport && gl.glClear && gl.glClearColor && gl.glReadPixels && + gl.glDrawArrays && gl.glMemoryBarrier && gl.glPixelStorei && gl.glFinish && gl.glEnable && + gl.glDisable; } ImageDrawTargets MakeImageDrawTargets(const GLESFunctionsTable& gl, GLsizei size) { @@ -1011,8 +982,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, size, size); gl.glGenFramebuffers(1, &targets.framebuffer); gl.glBindFramebuffer(GL_FRAMEBUFFER, targets.framebuffer); - gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targets.color, - 0); + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targets.color, 0); targets.valid = gl.glGetError() == GL_NO_ERROR && gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; Drain(gl); @@ -1032,8 +1002,7 @@ namespace MobileGL::MG_Util::SelfTest { const SizeT texelCount = static_cast(targets.size) * targets.size * 4; const Vector zeroes(texelCount, 0.0f); gl.glBindTexture(GL_TEXTURE_2D, targets.image); - gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, targets.size, targets.size, GL_RGBA, GL_FLOAT, - zeroes.data()); + gl.glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, targets.size, targets.size, GL_RGBA, GL_FLOAT, zeroes.data()); gl.glMemoryBarrier(GL_ALL_BARRIER_BITS); gl.glFinish(); Drain(gl); @@ -1057,8 +1026,7 @@ namespace MobileGL::MG_Util::SelfTest { } void BeginProbeDraw(const GLESFunctionsTable& gl, const ImageDrawTargets& targets) { - gl.glBindImageTexture(kProbeImageUnit, targets.image, 0, GL_FALSE, 0, GL_READ_WRITE, - GL_RGBA32F); + gl.glBindImageTexture(kProbeImageUnit, targets.image, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F); gl.glBindFramebuffer(GL_FRAMEBUFFER, targets.framebuffer); gl.glViewport(0, 0, targets.size, targets.size); gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); @@ -1081,8 +1049,7 @@ namespace MobileGL::MG_Util::SelfTest { "{}" " imageStore({}, ivec2(0), vec4({}));\n" " memoryBarrier();\n}}\n", - kProbeImageUnit, writeName, kFullscreenQuadBody, writeName, - kQualifierMergeValue); + kProbeImageUnit, writeName, kFullscreenQuadBody, writeName, kQualifierMergeValue); } String BuildQualifierMergeFragmentSource(const char* readName) { @@ -1100,13 +1067,12 @@ namespace MobileGL::MG_Util::SelfTest { // Draws the pair once and reports how many fragments failed to see the vertex stage's // store. -1 means the case could not be built or drawn at all. - Int RunQualifierMergeCase(const GLESFunctionsTable& gl, const ImageDrawTargets& targets, - const char* writeName, const char* readName) { + Int RunQualifierMergeCase(const GLESFunctionsTable& gl, const ImageDrawTargets& targets, const char* writeName, + const char* readName) { ProgramBuild build = BuildProgram(gl, {{GL_VERTEX_SHADER, BuildQualifierMergeVertexSource(writeName), "vertex"}, - {GL_FRAGMENT_SHADER, BuildQualifierMergeFragmentSource(readName), - "fragment"}}, + {GL_FRAGMENT_SHADER, BuildQualifierMergeFragmentSource(readName), "fragment"}}, kQualifierMergeProbeName); Int failed = -1; if (build.linked) { @@ -1135,24 +1101,23 @@ namespace MobileGL::MG_Util::SelfTest { // (the strongest shape ESSL offers) from the shape MobileGL emits today; everything else // about the two is identical, so the two numbers are comparable. String BuildCoherencyFragmentSource(const char* qualifiers, const char* barriers) { - return format( - "#version 320 es\n" - "precision highp float;\n" - "precision highp int;\n" - "layout(binding = {}, rgba32f) uniform {} readonly highp image2D mg_probeImageRead;\n" - "layout(binding = {}, rgba32f) uniform {} writeonly highp image2D mg_probeImageWrite;\n" - "layout(location = 0) out highp vec4 o_color;\n" - "void main()\n{{\n" - " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" - " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n" - " for (int mg_i = 0; mg_i < 3; ++mg_i)\n {{\n" - " imageStore(mg_probeImageWrite, mg_c, vec4(float(mg_i)));\n" - " {}\n" - " highp vec4 mg_v = imageLoad(mg_probeImageRead, mg_c);\n" - " if (any(notEqual(mg_v, vec4(float(mg_i)))))\n {{\n" - " o_color = vec4(1.0, 0.0, 0.0, 1.0);\n break;\n }}\n" - " }}\n}}\n", - kProbeImageUnit, qualifiers, kProbeImageUnit, qualifiers, barriers); + return format("#version 320 es\n" + "precision highp float;\n" + "precision highp int;\n" + "layout(binding = {}, rgba32f) uniform {} readonly highp image2D mg_probeImageRead;\n" + "layout(binding = {}, rgba32f) uniform {} writeonly highp image2D mg_probeImageWrite;\n" + "layout(location = 0) out highp vec4 o_color;\n" + "void main()\n{{\n" + " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" + " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n" + " for (int mg_i = 0; mg_i < 3; ++mg_i)\n {{\n" + " imageStore(mg_probeImageWrite, mg_c, vec4(float(mg_i)));\n" + " {}\n" + " highp vec4 mg_v = imageLoad(mg_probeImageRead, mg_c);\n" + " if (any(notEqual(mg_v, vec4(float(mg_i)))))\n {{\n" + " o_color = vec4(1.0, 0.0, 0.0, 1.0);\n break;\n }}\n" + " }}\n}}\n", + kProbeImageUnit, qualifiers, kProbeImageUnit, qualifiers, barriers); } // Every qualifier and every barrier ESSL has for this. A driver that still misses the @@ -1168,45 +1133,43 @@ namespace MobileGL::MG_Util::SelfTest { // the store and the read are just in different draws, with a glMemoryBarrier and a // glFinish between them. String BuildCoherencyControlStoreSource() { - return format( - "#version 320 es\n" - "precision highp float;\n" - "precision highp int;\n" - "layout(binding = {}, rgba32f) uniform coherent writeonly highp image2D mg_probeImageWrite;\n" - "layout(location = 0) out highp vec4 o_color;\n" - "void main()\n{{\n" - " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" - " imageStore(mg_probeImageWrite, mg_c, vec4(float(mg_c.x * {} + mg_c.y)));\n" - " memoryBarrierImage();\n" - " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n}}\n", - kProbeImageUnit, kCoherencySize); + return format("#version 320 es\n" + "precision highp float;\n" + "precision highp int;\n" + "layout(binding = {}, rgba32f) uniform coherent writeonly highp image2D mg_probeImageWrite;\n" + "layout(location = 0) out highp vec4 o_color;\n" + "void main()\n{{\n" + " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" + " imageStore(mg_probeImageWrite, mg_c, vec4(float(mg_c.x * {} + mg_c.y)));\n" + " memoryBarrierImage();\n" + " o_color = vec4(0.0, 1.0, 0.0, 1.0);\n}}\n", + kProbeImageUnit, kCoherencySize); } String BuildCoherencyControlLoadSource() { - return format( - "#version 320 es\n" - "precision highp float;\n" - "precision highp int;\n" - "layout(binding = {}, rgba32f) uniform coherent readonly highp image2D mg_probeImageRead;\n" - "layout(location = 0) out highp vec4 o_color;\n" - "void main()\n{{\n" - " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" - " highp vec4 mg_v = imageLoad(mg_probeImageRead, mg_c);\n" - " o_color = all(equal(mg_v, vec4(float(mg_c.x * {} + mg_c.y))))\n" - " ? vec4(0.0, 1.0, 0.0, 1.0)\n" - " : vec4(1.0, 0.0, 0.0, 1.0);\n}}\n", - kProbeImageUnit, kCoherencySize); + return format("#version 320 es\n" + "precision highp float;\n" + "precision highp int;\n" + "layout(binding = {}, rgba32f) uniform coherent readonly highp image2D mg_probeImageRead;\n" + "layout(location = 0) out highp vec4 o_color;\n" + "void main()\n{{\n" + " ivec2 mg_c = ivec2(gl_FragCoord.xy);\n" + " highp vec4 mg_v = imageLoad(mg_probeImageRead, mg_c);\n" + " o_color = all(equal(mg_v, vec4(float(mg_c.x * {} + mg_c.y))))\n" + " ? vec4(0.0, 1.0, 0.0, 1.0)\n" + " : vec4(1.0, 0.0, 0.0, 1.0);\n}}\n", + kProbeImageUnit, kCoherencySize); } // One draw, store and dependent read inside the same invocation, in whichever // qualifier/barrier shape the caller asked for. - Int RunCoherencyShape(const GLESFunctionsTable& gl, const ImageDrawTargets& targets, - const char* qualifiers, const char* barriers) { - ProgramBuild build = BuildProgram( - gl, - {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, - {GL_FRAGMENT_SHADER, BuildCoherencyFragmentSource(qualifiers, barriers), "fragment"}}, - kCoherencyProbeName); + Int RunCoherencyShape(const GLESFunctionsTable& gl, const ImageDrawTargets& targets, const char* qualifiers, + const char* barriers) { + ProgramBuild build = + BuildProgram(gl, + {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, + {GL_FRAGMENT_SHADER, BuildCoherencyFragmentSource(qualifiers, barriers), "fragment"}}, + kCoherencyProbeName); Int failed = -1; if (build.linked) { ZeroProbeImage(gl, targets); @@ -1227,16 +1190,14 @@ namespace MobileGL::MG_Util::SelfTest { // The control: the same store and the same dependent read, split across two draws with a // glMemoryBarrier and a glFinish between them. Int RunCoherencyControl(const GLESFunctionsTable& gl, const ImageDrawTargets& targets) { - ProgramBuild store = - BuildProgram(gl, - {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, - {GL_FRAGMENT_SHADER, BuildCoherencyControlStoreSource(), "fragment"}}, - kCoherencyProbeName); - ProgramBuild load = - BuildProgram(gl, - {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, - {GL_FRAGMENT_SHADER, BuildCoherencyControlLoadSource(), "fragment"}}, - kCoherencyProbeName); + ProgramBuild store = BuildProgram(gl, + {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, + {GL_FRAGMENT_SHADER, BuildCoherencyControlStoreSource(), "fragment"}}, + kCoherencyProbeName); + ProgramBuild load = BuildProgram(gl, + {{GL_VERTEX_SHADER, kProbeQuadVertexSource, "vertex"}, + {GL_FRAGMENT_SHADER, BuildCoherencyControlLoadSource(), "fragment"}}, + kCoherencyProbeName); Int failed = -1; if (store.linked && load.linked) { ZeroProbeImage(gl, targets); @@ -1271,8 +1232,7 @@ namespace MobileGL::MG_Util::SelfTest { const Bool sampleQueryAccepted = gl.glGetError() == GL_NO_ERROR; Drain(gl); if (!sampleQueryAccepted || maxColorTextureSamples < 2) return false; - const GLsizei samples = - maxColorTextureSamples >= 4 ? 4 : static_cast(maxColorTextureSamples); + const GLsizei samples = maxColorTextureSamples >= 4 ? 4 : static_cast(maxColorTextureSamples); const Int lastSample = static_cast(samples) - 1; SavedState saved; @@ -1308,8 +1268,7 @@ namespace MobileGL::MG_Util::SelfTest { // sampling program onward, so a single read would call an affected driver clean. constexpr Int kRounds = 2; if (!RunMsaaSampledRead(gl, swizzledSource, lastSample, 'w', kRounds, subject) || - !RunMsaaSampledRead(gl, defaultSwizzleSource, lastSample, 'w', kRounds, - controlDefaultSwizzle) || + !RunMsaaSampledRead(gl, defaultSwizzleSource, lastSample, 'w', kRounds, controlDefaultSwizzle) || !RunMsaaSampledRead(gl, swizzledSource, 0, 'w', kRounds, controlSampleZero) || !RunMsaaSampledRead(gl, swizzledSource, lastSample, 'x', kRounds, controlRedComponent)) { inconclusive = "one of the sampling programs could not be built or drawn"; @@ -1333,10 +1292,9 @@ namespace MobileGL::MG_Util::SelfTest { MGLOG_I("[driver-bug] R32F multisample swizzle probe: %d samples; subject(swizzle_a=RED, " "sample=%d, .w) %s; controls default-swizzle [%s] sample-0 [%s] .x [%s]; expected " "%g everywhere%s", - static_cast(samples), static_cast(lastSample), - DescribeRounds(subject).c_str(), DescribeRounds(controlDefaultSwizzle).c_str(), - DescribeRounds(controlSampleZero).c_str(), DescribeRounds(controlRedComponent).c_str(), - static_cast(kMsaaExpected), + static_cast(samples), static_cast(lastSample), DescribeRounds(subject).c_str(), + DescribeRounds(controlDefaultSwizzle).c_str(), DescribeRounds(controlSampleZero).c_str(), + DescribeRounds(controlRedComponent).c_str(), static_cast(kMsaaExpected), corrupted ? " - THE SWIZZLED ALPHA READ IS CORRUPTED" : ""); } while (false); @@ -1399,13 +1357,11 @@ namespace MobileGL::MG_Util::SelfTest { // Same stage count, same per-stage image count, same bindings, same stores. The names // are the only difference between these two programs. - ProgramBuild subject = - BuildThreeStageImageProgram(gl, perStage, static_cast(maxImageUnits), "mg_probeVsImage", - "mg_probeGsImage", "mg_probeFsImage"); + ProgramBuild subject = BuildThreeStageImageProgram(gl, perStage, static_cast(maxImageUnits), + "mg_probeVsImage", "mg_probeGsImage", "mg_probeFsImage"); ProgramBuild control = - BuildThreeStageImageProgram(gl, perStage, static_cast(maxImageUnits), - "mg_probeSharedImage", "mg_probeSharedImage", - "mg_probeSharedImage"); + BuildThreeStageImageProgram(gl, perStage, static_cast(maxImageUnits), "mg_probeSharedImage", + "mg_probeSharedImage", "mg_probeSharedImage"); if (subject.compiled && control.compiled) { measurement.driverMessage = FirstLine(subject.infoLog); // Both failing is an honest refusal of a shape that is simply too big; both linking @@ -1414,11 +1370,9 @@ namespace MobileGL::MG_Util::SelfTest { MGLOG_I("[driver-bug] image-location-per-name probe: %d image uniform(s) per stage over " "%d image unit(s); %d distinct names %s, %d shared names %s%s%s", static_cast(perStage), static_cast(maxImageUnits), - static_cast(measurement.subjectDistinctNames), - subject.linked ? "link" : "FAIL", static_cast(measurement.controlDistinctNames), - control.linked ? "link" : "FAIL", - measurement.driverMessage.empty() ? "" : "; driver says: ", - measurement.driverMessage.c_str()); + static_cast(measurement.subjectDistinctNames), subject.linked ? "link" : "FAIL", + static_cast(measurement.controlDistinctNames), control.linked ? "link" : "FAIL", + measurement.driverMessage.empty() ? "" : "; driver says: ", measurement.driverMessage.c_str()); } else { MGLOG_I("[driver-bug] image-location-per-name probe reached no verdict (a probe stage " "did not compile)"); @@ -1475,8 +1429,7 @@ namespace MobileGL::MG_Util::SelfTest { subjectFailed = RunQualifierMergeCase(gl, targets, "mg_probeImage", "mg_probeImage"); // The control names the two halves the way MobileGL's image-uniform repair does. - controlFailed = RunQualifierMergeCase(gl, targets, "mg_imageWo_mg_probeImage", - "mg_imageRo_mg_probeImage"); + controlFailed = RunQualifierMergeCase(gl, targets, "mg_imageWo_mg_probeImage", "mg_imageRo_mg_probeImage"); if (subjectFailed < 0 || controlFailed < 0) { inconclusive = "one of the two programs could not be built or drawn"; break; @@ -1493,13 +1446,11 @@ namespace MobileGL::MG_Util::SelfTest { "store for %d/%d texels, renamed control lost %d%s", static_cast(subjectFailed), static_cast(kQualifierMergeSize) * static_cast(kQualifierMergeSize), - static_cast(controlFailed), - dropped ? " - THE MERGED WRITE IS DISCARDED" : ""); + static_cast(controlFailed), dropped ? " - THE MERGED WRITE IS DISCARDED" : ""); } while (false); if (inconclusive != nullptr) { - MGLOG_I("[driver-bug] cross-stage image qualifier merge probe reached no verdict (%s)", - inconclusive); + MGLOG_I("[driver-bug] cross-stage image qualifier merge probe reached no verdict (%s)", inconclusive); } ReleaseImageDrawTargets(gl, targets); @@ -1514,8 +1465,7 @@ namespace MobileGL::MG_Util::SelfTest { return dropped; } - ImageCoherencyResidualMeasurement ProbeImageWriteReadCoherencyResidual( - const GLESFunctionsTable& gl) { + ImageCoherencyResidualMeasurement ProbeImageWriteReadCoherencyResidual(const GLESFunctionsTable& gl) { ImageCoherencyResidualMeasurement measurement; if (!HasImageDrawEntryPoints(gl)) return measurement; @@ -1527,8 +1477,7 @@ namespace MobileGL::MG_Util::SelfTest { const Bool limitsAccepted = gl.glGetError() == GL_NO_ERROR; Drain(gl); // The subject declares BOTH halves of the split pair in the fragment stage. - if (!limitsAccepted || maxFragmentImages < 2 || - maxImageUnits <= static_cast(kProbeImageUnit)) { + if (!limitsAccepted || maxFragmentImages < 2 || maxImageUnits <= static_cast(kProbeImageUnit)) { return measurement; } @@ -1553,10 +1502,8 @@ namespace MobileGL::MG_Util::SelfTest { break; } - subjectFailed = RunCoherencyShape(gl, targets, kStrongestCoherencyQualifiers, - kStrongestCoherencyBarriers); - emittedFailed = - RunCoherencyShape(gl, targets, kEmittedCoherencyQualifiers, kEmittedCoherencyBarriers); + subjectFailed = RunCoherencyShape(gl, targets, kStrongestCoherencyQualifiers, kStrongestCoherencyBarriers); + emittedFailed = RunCoherencyShape(gl, targets, kEmittedCoherencyQualifiers, kEmittedCoherencyBarriers); controlFailed = RunCoherencyControl(gl, targets); if (subjectFailed < 0 || emittedFailed < 0 || controlFailed < 0) { inconclusive = "one of the three programs could not be built or drawn"; @@ -1586,8 +1533,7 @@ namespace MobileGL::MG_Util::SelfTest { } while (false); if (inconclusive != nullptr) { - MGLOG_I("[driver-bug] image write-read coherency probe reached no verdict (%s)", - inconclusive); + MGLOG_I("[driver-bug] image write-read coherency probe reached no verdict (%s)", inconclusive); } ReleaseImageDrawTargets(gl, targets); @@ -1597,10 +1543,8 @@ namespace MobileGL::MG_Util::SelfTest { return measurement; } - const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual( - const GLESFunctionsTable& gl) { - static const ImageCoherencyResidualMeasurement measurement = - ProbeImageWriteReadCoherencyResidual(gl); + const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual(const GLESFunctionsTable& gl) { + static const ImageCoherencyResidualMeasurement measurement = ProbeImageWriteReadCoherencyResidual(gl); return measurement; } @@ -1619,8 +1563,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glGenTextures(1, &texture); if (texture == 0) return 0; gl.glBindTexture(GL_TEXTURE_2D_ARRAY, texture); - gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kLayeredBlitSize, kLayeredBlitSize, - kLayeredBlitLayers); + gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, kLayeredBlitSize, kLayeredBlitSize, kLayeredBlitLayers); gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); const GLubyte fills[kLayeredBlitLayers] = {layer0, layer1}; @@ -1632,8 +1575,8 @@ namespace MobileGL::MG_Util::SelfTest { texels[i + 2] = fills[layer]; texels[i + 3] = 255; } - gl.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kLayeredBlitSize, kLayeredBlitSize, 1, - GL_RGBA, GL_UNSIGNED_BYTE, texels); + gl.glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, kLayeredBlitSize, kLayeredBlitSize, 1, GL_RGBA, + GL_UNSIGNED_BYTE, texels); } gl.glBindTexture(GL_TEXTURE_2D_ARRAY, 0); return texture; @@ -1682,8 +1625,8 @@ namespace MobileGL::MG_Util::SelfTest { gl.glReadBuffer(GL_COLOR_ATTACHMENT0); gl.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destinationFramebuffer); Drain(gl); - gl.glBlitFramebuffer(0, 0, kLayeredBlitSize, kLayeredBlitSize, 0, 0, kLayeredBlitSize, - kLayeredBlitSize, GL_COLOR_BUFFER_BIT, GL_NEAREST); + gl.glBlitFramebuffer(0, 0, kLayeredBlitSize, kLayeredBlitSize, 0, 0, kLayeredBlitSize, kLayeredBlitSize, + GL_COLOR_BUFFER_BIT, GL_NEAREST); if (gl.glGetError() == GL_NO_ERROR) { landedOn = -2; // issued, but seen on no layer yet for (GLint layer = 0; layer < kLayeredBlitLayers; ++layer) { @@ -1741,8 +1684,7 @@ namespace MobileGL::MG_Util::SelfTest { } else { MGLOG_I("[driver-bug] %s probe: a blit asking for destination layer 1 landed on " "layer %d%s", - kLayeredBlitProbeName, subjectLanded, - detected ? " - THE DESTINATION LAYER IS IGNORED" : ""); + kLayeredBlitProbeName, subjectLanded, detected ? " - THE DESTINATION LAYER IS IGNORED" : ""); } } @@ -1759,20 +1701,18 @@ namespace MobileGL::MG_Util::SelfTest { namespace { Optional ProbeLayeredBlitDestinationBug(const GLESFunctionsTable& gl) { if (!BlitIgnoresDestinationArrayLayer(gl)) return std::nullopt; - return DriverBugFinding{ - "glBlitFramebuffer ignores the destination array layer", - DriverBugVerdict::Fixed, - "a glBlitFramebuffer whose DRAW framebuffer attaches a non-zero array layer with " - "glFramebufferTextureLayer writes to layer 0 instead, and raises no error doing " - "it. Measured here on the colour aspect; the depth aspect behaves the same way on " - "the device this was characterised on. The layer is honoured everywhere else on " - "the same driver - the blit's own SOURCE layer is read correctly, which is this " - "probe's control - so neither layered attachments nor blitting is withdrawn. " - "MobileGL performs such a blit with glCopyImageSubData instead, which takes the " - "destination layer explicitly and honours it here, and applies that substitute to " - "the depth and stencil aspects as well; a blit that scales, flips, changes format, " - "resolves samples or is clipped by the scissor cannot be expressed as a copy and " - "is still handed to the driver"}; + return DriverBugFinding{"glBlitFramebuffer ignores the destination array layer", DriverBugVerdict::Fixed, + "a glBlitFramebuffer whose DRAW framebuffer attaches a non-zero array layer with " + "glFramebufferTextureLayer writes to layer 0 instead, and raises no error doing " + "it. Measured here on the colour aspect; the depth aspect behaves the same way on " + "the device this was characterised on. The layer is honoured everywhere else on " + "the same driver - the blit's own SOURCE layer is read correctly, which is this " + "probe's control - so neither layered attachments nor blitting is withdrawn. " + "MobileGL performs such a blit with glCopyImageSubData instead, which takes the " + "destination layer explicitly and honours it here, and applies that substitute to " + "the depth and stencil aspects as well; a blit that scales, flips, changes format, " + "resolves samples or is clipped by the scissor cannot be expressed as a copy and " + "is still handed to the driver"}; } // ===================== EXPLICIT VERTEX INPUT LOCATION CEILING ===================== @@ -1806,7 +1746,8 @@ namespace MobileGL::MG_Util::SelfTest { if (const SizeT newline = message.find('\n'); newline != String::npos) { message.resize(newline); } - while (!message.empty() && (message.back() == ' ' || message.back() == '\r')) message.pop_back(); + while (!message.empty() && (message.back() == ' ' || message.back() == '\r')) + message.pop_back(); *firstRejectionMessage = Move(message); } gl.glDeleteShader(shader); @@ -1919,18 +1860,16 @@ namespace MobileGL::MG_Util::SelfTest { MGLOG_I("[driver-bug] %s probe: GL_MAX_VERTEX_ATTRIBS is %d but layout(location = N) on a " "vertex input is refused from N = %d upward - only %d location(s) are usable; " "glBindAttribLocation(%d) %s%s%s", - kAttributeLocationProbeName, advertised, measurement.usableLocations, - measurement.usableLocations, advertised - 1, + kAttributeLocationProbeName, advertised, measurement.usableLocations, measurement.usableLocations, + advertised - 1, measurement.bindAttribLocationReachesAdvertisedMax ? "still resolves correctly" : "does not resolve either", - measurement.driverMessage.empty() ? "" : "; the driver says: ", - measurement.driverMessage.c_str()); + measurement.driverMessage.empty() ? "" : "; the driver says: ", measurement.driverMessage.c_str()); return measurement; } const VertexInputLocationCeilingMeasurement& ExplicitVertexInputLocationCeiling(const GLESFunctionsTable& gl) { - static const VertexInputLocationCeilingMeasurement measurement = - ProbeExplicitVertexInputLocationCeiling(gl); + static const VertexInputLocationCeilingMeasurement measurement = ProbeExplicitVertexInputLocationCeiling(gl); return measurement; } @@ -1946,16 +1885,15 @@ namespace MobileGL::MG_Util::SelfTest { // BuildProgram, Save/Restore, PrepareForProbeDraw and the draw below actually call. Bool HasIoBlockProbeEntryPoints(const GLESFunctionsTable& gl) { return gl.glCreateShader && gl.glShaderSource && gl.glCompileShader && gl.glGetShaderiv && - gl.glGetShaderInfoLog && gl.glCreateProgram && gl.glAttachShader && - gl.glLinkProgram && gl.glGetProgramiv && gl.glGetProgramInfoLog && - gl.glDeleteShader && gl.glDeleteProgram && gl.glUseProgram && gl.glGenVertexArrays && - gl.glBindVertexArray && gl.glDeleteVertexArrays && gl.glGenRenderbuffers && - gl.glBindRenderbuffer && gl.glRenderbufferStorage && gl.glDeleteRenderbuffers && - gl.glGenFramebuffers && gl.glBindFramebuffer && gl.glFramebufferRenderbuffer && - gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && gl.glViewport && - gl.glClearColor && gl.glClear && gl.glDrawArrays && gl.glReadPixels && - gl.glPixelStorei && gl.glGetIntegerv && gl.glGetIntegeri_v && gl.glGetError && - gl.glEnable && gl.glDisable && gl.glIsEnabled; + gl.glGetShaderInfoLog && gl.glCreateProgram && gl.glAttachShader && gl.glLinkProgram && + gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteShader && gl.glDeleteProgram && + gl.glUseProgram && gl.glGenVertexArrays && gl.glBindVertexArray && gl.glDeleteVertexArrays && + gl.glGenRenderbuffers && gl.glBindRenderbuffer && gl.glRenderbufferStorage && + gl.glDeleteRenderbuffers && gl.glGenFramebuffers && gl.glBindFramebuffer && + gl.glFramebufferRenderbuffer && gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && + gl.glViewport && gl.glClearColor && gl.glClear && gl.glDrawArrays && gl.glReadPixels && + gl.glPixelStorei && gl.glGetIntegerv && gl.glGetIntegeri_v && gl.glGetError && gl.glEnable && + gl.glDisable && gl.glIsEnabled; } // Two values that survive an 8-bit target exactly, so the read is a comparison and not // a tolerance: 0.25 -> 64, 0.5 -> 128. A stage that received nothing reads 0/0, which is @@ -2012,20 +1950,18 @@ namespace MobileGL::MG_Util::SelfTest { // fragment stage received the payload. `outRan` distinguishes "the payload did not // arrive" from "this program could not be built or drawn at all" - the second is // inconclusive and must never become a finding. - Bool IoBlockPayloadArrives(const GLESFunctionsTable& gl, const char* blockQualifier, - Bool withGeometryStage, Bool& outRan) { + Bool IoBlockPayloadArrives(const GLESFunctionsTable& gl, const char* blockQualifier, Bool withGeometryStage, + Bool& outRan) { outRan = false; Vector stages; stages.push_back({GL_VERTEX_SHADER, BuildIoBlockVertexSource(blockQualifier), "vertex"}); if (withGeometryStage) { - stages.push_back( - {GL_GEOMETRY_SHADER, BuildIoBlockGeometrySource(blockQualifier), "geometry"}); + stages.push_back({GL_GEOMETRY_SHADER, BuildIoBlockGeometrySource(blockQualifier), "geometry"}); } - stages.push_back({GL_FRAGMENT_SHADER, - BuildIoBlockFragmentSource(blockQualifier, - withGeometryStage ? "MgProbeBlock2" - : "MgProbeBlock"), - "fragment"}); + stages.push_back( + {GL_FRAGMENT_SHADER, + BuildIoBlockFragmentSource(blockQualifier, withGeometryStage ? "MgProbeBlock2" : "MgProbeBlock"), + "fragment"}); const ProgramBuild build = BuildProgram(gl, stages, kIoBlockProbeName); if (!build.linked) { @@ -2041,8 +1977,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, 1, 1); gl.glGenFramebuffers(1, &framebuffer); gl.glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); - gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, - renderbuffer); + gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderbuffer); if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { gl.glUseProgram(build.program); gl.glViewport(0, 0, 1, 1); @@ -2098,8 +2033,7 @@ namespace MobileGL::MG_Util::SelfTest { const Bool controlArrives = IoBlockPayloadArrives(gl, "", true, controlRan); if (controlRan && controlArrives) { Bool subjectRan = false; - const Bool subjectArrives = - IoBlockPayloadArrives(gl, "layout(location = 0) ", true, subjectRan); + const Bool subjectArrives = IoBlockPayloadArrives(gl, "layout(location = 0) ", true, subjectRan); if (subjectRan && !subjectArrives) { measurement.detected = true; // The second control, and the one that scopes the repair: the same located @@ -2109,8 +2043,7 @@ namespace MobileGL::MG_Util::SelfTest { // ALSO fails is losing payloads the repair does not reach, and the report says // so rather than implying the fix is complete. Bool vsFsRan = false; - const Bool vsFsArrives = - IoBlockPayloadArrives(gl, "layout(location = 0) ", false, vsFsRan); + const Bool vsFsArrives = IoBlockPayloadArrives(gl, "layout(location = 0) ", false, vsFsRan); measurement.alsoAffectsVertexToFragment = vsFsRan && !vsFsArrives; } } @@ -2200,14 +2133,16 @@ namespace MobileGL::MG_Util::SelfTest { // complete because all three levels exist under MAX_LEVEL = 2. CtsShape, }; - constexpr Packed16Recipe kPacked16Recipes[] = {Packed16Recipe::UploadsFirst, - Packed16Recipe::ParamsFirst, + constexpr Packed16Recipe kPacked16Recipes[] = {Packed16Recipe::UploadsFirst, Packed16Recipe::ParamsFirst, Packed16Recipe::CtsShape}; const char* Packed16RecipeName(Packed16Recipe recipe) { switch (recipe) { - case Packed16Recipe::UploadsFirst: return "uploads-first"; - case Packed16Recipe::ParamsFirst: return "params-first"; - case Packed16Recipe::CtsShape: return "cts-shape"; + case Packed16Recipe::UploadsFirst: + return "uploads-first"; + case Packed16Recipe::ParamsFirst: + return "params-first"; + case Packed16Recipe::CtsShape: + return "cts-shape"; } return "?"; } @@ -2229,10 +2164,10 @@ namespace MobileGL::MG_Util::SelfTest { } for (GLint level = 0; level < kPacked16Levels; ++level) { const GLsizei size = kPacked16BaseSize >> level; - const Vector words( - static_cast(size) * static_cast(size) * kPacked16Layers, kPacked16Word); - gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGB5_A1, size, size, kPacked16Layers, 0, - GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, words.data()); + const Vector words(static_cast(size) * static_cast(size) * kPacked16Layers, + kPacked16Word); + gl.glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGB5_A1, size, size, kPacked16Layers, 0, GL_RGBA, + GL_UNSIGNED_SHORT_5_5_5_1, words.data()); } if (recipe == Packed16Recipe::UploadsFirst) { gl.glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -2261,8 +2196,8 @@ namespace MobileGL::MG_Util::SelfTest { for (GLint level = 0; level < kPacked16Levels; ++level) { const GLsizei size = std::max(kPacked16DstSize >> level, 1); const Vector texels(static_cast(size) * size, fill); - gl.glTexImage2D(GL_TEXTURE_2D, level, GL_RGB5_A1, size, size, 0, GL_RGBA, - GL_UNSIGNED_SHORT_5_5_5_1, texels.data()); + gl.glTexImage2D(GL_TEXTURE_2D, level, GL_RGB5_A1, size, size, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, + texels.data()); } gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); @@ -2301,13 +2236,13 @@ namespace MobileGL::MG_Util::SelfTest { // Copies a kPacked16DstSize-square region out of (source, sourceTarget, sourceLevel) // layer 0 onto a freshly 0xFFFF-filled 2D destination and hands back the destination's // texel (0, 0). False when the copy raised an error or the readback could not run. - Bool Packed16CopyLandsTexel(const GLESFunctionsTable& gl, GLuint source, GLenum sourceTarget, - GLint sourceLevel, GLubyte out[4]) { + Bool Packed16CopyLandsTexel(const GLESFunctionsTable& gl, GLuint source, GLenum sourceTarget, GLint sourceLevel, + GLubyte out[4]) { const GLuint destination = MakePacked16FlatTexture(gl, Uint16{0xFFFF}); if (destination == 0) return false; Drain(gl); - gl.glCopyImageSubData(source, sourceTarget, sourceLevel, 0, 0, 0, destination, - GL_TEXTURE_2D, 0, 0, 0, 0, kPacked16DstSize, kPacked16DstSize, 1); + gl.glCopyImageSubData(source, sourceTarget, sourceLevel, 0, 0, 0, destination, GL_TEXTURE_2D, 0, 0, 0, 0, + kPacked16DstSize, kPacked16DstSize, 1); const Bool copied = gl.glGetError() == GL_NO_ERROR; const Bool read = copied && ReadPacked16Texel(gl, destination, false, 0, out); gl.glDeleteTextures(1, &destination); @@ -2341,8 +2276,8 @@ namespace MobileGL::MG_Util::SelfTest { GLubyte level1[4] = {0, 0, 0, 0}; if (array == 0 || !Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 0, level0) || !Packed16CopyLandsTexel(gl, array, GL_TEXTURE_2D_ARRAY, 1, level1)) { - MGLOG_I("[driver-bug] %s probe [%s]: no verdict (a subject copy could not run)", - kPacked16CopyProbeName, Packed16RecipeName(recipe)); + MGLOG_I("[driver-bug] %s probe [%s]: no verdict (a subject copy could not run)", kPacked16CopyProbeName, + Packed16RecipeName(recipe)); } else if (!ReadPacked16Texel(gl, array, true, 1, direct) || !Packed16TexelNear(direct, kPacked16Expected)) { // The recipe's own round trip: reading the level directly decodes the driver's @@ -2352,26 +2287,23 @@ namespace MobileGL::MG_Util::SelfTest { MGLOG_I("[driver-bug] %s probe [%s]: no verdict (the array's own level-1 readback " "answered (%d, %d, %d, %d) instead of the word - the upload, not the " "copy, is what diverges)", - kPacked16CopyProbeName, Packed16RecipeName(recipe), direct[0], direct[1], - direct[2], direct[3]); - } else if (Packed16TexelNear(level0, kPacked16Mirrored) || - Packed16TexelNear(level1, kPacked16Mirrored)) { + kPacked16CopyProbeName, Packed16RecipeName(recipe), direct[0], direct[1], direct[2], direct[3]); + } else if (Packed16TexelNear(level0, kPacked16Mirrored) || Packed16TexelNear(level1, kPacked16Mirrored)) { mirrored = true; MGLOG_I("[driver-bug] %s probe [%s]: copies delivered level 0 (%d, %d, %d, %d) / " "level 1 (%d, %d, %d, %d) - the 1_5_5_5_REV re-encoding of the word - " "THIS ALLOCATION'S FIELD ORDER IS MIRRORED", - kPacked16CopyProbeName, Packed16RecipeName(recipe), level0[0], level0[1], - level0[2], level0[3], level1[0], level1[1], level1[2], level1[3]); - } else if (Packed16TexelNear(level0, kPacked16Expected) && - Packed16TexelNear(level1, kPacked16Expected)) { - MGLOG_I("[driver-bug] %s probe [%s]: both levels copied the word intact", - kPacked16CopyProbeName, Packed16RecipeName(recipe)); + kPacked16CopyProbeName, Packed16RecipeName(recipe), level0[0], level0[1], level0[2], level0[3], + level1[0], level1[1], level1[2], level1[3]); + } else if (Packed16TexelNear(level0, kPacked16Expected) && Packed16TexelNear(level1, kPacked16Expected)) { + MGLOG_I("[driver-bug] %s probe [%s]: both levels copied the word intact", kPacked16CopyProbeName, + Packed16RecipeName(recipe)); } else { MGLOG_I("[driver-bug] %s probe [%s]: no verdict (copies read back level 0 " "(%d, %d, %d, %d) / level 1 (%d, %d, %d, %d), neither the word nor its " "mirror)", - kPacked16CopyProbeName, Packed16RecipeName(recipe), level0[0], level0[1], - level0[2], level0[3], level1[0], level1[1], level1[2], level1[3]); + kPacked16CopyProbeName, Packed16RecipeName(recipe), level0[0], level0[1], level0[2], level0[3], + level1[0], level1[1], level1[2], level1[3]); } if (array != 0) gl.glDeleteTextures(1, &array); return mirrored; @@ -2379,11 +2311,10 @@ namespace MobileGL::MG_Util::SelfTest { } // namespace Bool ProbeCopyImageMirrorsPacked16FieldOrder(const GLESFunctionsTable& gl) { - if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexParameteri || !gl.glTexImage2D || - !gl.glTexImage3D || !gl.glDeleteTextures || !gl.glCopyImageSubData || !gl.glGenFramebuffers || - !gl.glBindFramebuffer || !gl.glFramebufferTexture2D || !gl.glFramebufferTextureLayer || - !gl.glCheckFramebufferStatus || !gl.glDeleteFramebuffers || !gl.glReadBuffer || - !gl.glReadPixels || !gl.glPixelStorei || !gl.glGetError) { + if (!gl.glGenTextures || !gl.glBindTexture || !gl.glTexParameteri || !gl.glTexImage2D || !gl.glTexImage3D || + !gl.glDeleteTextures || !gl.glCopyImageSubData || !gl.glGenFramebuffers || !gl.glBindFramebuffer || + !gl.glFramebufferTexture2D || !gl.glFramebufferTextureLayer || !gl.glCheckFramebufferStatus || + !gl.glDeleteFramebuffers || !gl.glReadBuffer || !gl.glReadPixels || !gl.glPixelStorei || !gl.glGetError) { return false; } @@ -2451,13 +2382,12 @@ namespace MobileGL::MG_Util::SelfTest { Optional ProbeExplicitVertexInputLocationCeilingBug(const GLESFunctionsTable& gl) { const VertexInputLocationCeilingMeasurement& measurement = ExplicitVertexInputLocationCeiling(gl); if (!measurement.detected) return std::nullopt; - String detail = - format("GL_MAX_VERTEX_ATTRIBS is {} but the ESSL compiler refuses " - "layout(location = N) on a vertex input for every N at or above {} - so {} of " - "the {} attributes advertised cannot be declared at all", - measurement.advertisedMaxVertexAttribs, measurement.usableLocations, - measurement.advertisedMaxVertexAttribs - measurement.usableLocations, - measurement.advertisedMaxVertexAttribs); + String detail = format("GL_MAX_VERTEX_ATTRIBS is {} but the ESSL compiler refuses " + "layout(location = N) on a vertex input for every N at or above {} - so {} of " + "the {} attributes advertised cannot be declared at all", + measurement.advertisedMaxVertexAttribs, measurement.usableLocations, + measurement.advertisedMaxVertexAttribs - measurement.usableLocations, + measurement.advertisedMaxVertexAttribs); if (!measurement.driverMessage.empty()) { detail += format(" - the driver says \"{}\"", measurement.driverMessage); } @@ -2479,16 +2409,15 @@ namespace MobileGL::MG_Util::SelfTest { Optional ProbeGeometryWriteAfterEmitBug(const GLESFunctionsTable& gl) { if (!GeometryStageSsboWriteAfterEmitDropped(gl)) return std::nullopt; - String detail = - "the driver silently discards shader storage buffer writes a geometry shader " - "issues after its last EmitVertex()/EndPrimitive(); the identical write issued " - "BEFORE the emit lands, for both point and triangle geometry shaders. "; + String detail = "the driver silently discards shader storage buffer writes a geometry shader " + "issues after its last EmitVertex()/EndPrimitive(); the identical write issued " + "BEFORE the emit lands, for both point and triangle geometry shaders. "; // The repair follows the knob, so the row describes what this process does. if (MG_Config::Features.EsprytDeferGeometryEmits == MG_Config::QuirkOverride::ForceOff) { detail += "THE REPAIR IS DISABLED in this process (MOBILEGL_ESPRYT_DEFER_GEOMETRY_EMITS), so a " "geometry shader that writes after emitting loses those writes"; - return DriverBugFinding{"Geometry-stage storage writes after EmitVertex", - DriverBugVerdict::Unfixable, Move(detail)}; + return DriverBugFinding{"Geometry-stage storage writes after EmitVertex", DriverBugVerdict::Unfixable, + Move(detail)}; } detail += "DirectGLES defers every EmitVertex()/EndPrimitive() of a geometry stage that writes " "memory: the outputs are snapshotted at each emit and the vertices replayed after the " @@ -2500,11 +2429,10 @@ namespace MobileGL::MG_Util::SelfTest { Optional ProbeStaleSharedMemoryLoadBug(const GLESFunctionsTable& gl) { if (!SharedMemoryLoadsGoStale(gl)) return std::nullopt; - String detail = - "the ES compiler treats ordinary loads from shared variables as pure: two reads of one " - "shared word separated by a store and barrier() (memoryBarrierShared() too) return the " - "first value, and a read can move past a later store; atomicAdd(x, 0u) reads of the same " - "words stay in place. "; + String detail = "the ES compiler treats ordinary loads from shared variables as pure: two reads of one " + "shared word separated by a store and barrier() (memoryBarrierShared() too) return the " + "first value, and a read can move past a later store; atomicAdd(x, 0u) reads of the same " + "words stay in place. "; if (MG_Config::Features.EsprytAtomicSubgroupScratch == MG_Config::QuirkOverride::ForceOff) { detail += "THE REPAIR IS DISABLED in this process (MOBILEGL_ESPRYT_ATOMIC_SUBGROUP_SCRATCH), so " "an emulated subgroup operation can see the lanes of an earlier one"; @@ -2537,54 +2465,47 @@ namespace MobileGL::MG_Util::SelfTest { Optional ProbeR32FMultisampleSwizzleBug(const GLESFunctionsTable& gl) { if (!R32FMultisampleSwizzleCorrupted(gl)) return std::nullopt; - return DriverBugFinding{ - "R32F multisample fetch through a swizzled alpha", - DriverBugVerdict::Unfixable, - "texelFetch() on an R32F GL_TEXTURE_2D_MULTISAMPLE texture whose " - "GL_TEXTURE_SWIZZLE_A is not the default returns uninitialised memory - a " - "different value every run - for any sample index other than 0, from the SECOND " - "such program in the context onward. The identical fetch with the default alpha " - "swizzle, at sample index 0, or through .x instead of .w is correct, so neither " - "R32F multisample targets nor texture swizzles are withdrawn. No substitute was " - "adopted: widening every R32F multisample target to RG32F would sidestep it and " - "doubles multisample memory, which was declined. A shader that fetches a non-zero " - "sample through a swizzled alpha cannot be relied on here"}; + return DriverBugFinding{"R32F multisample fetch through a swizzled alpha", DriverBugVerdict::Unfixable, + "texelFetch() on an R32F GL_TEXTURE_2D_MULTISAMPLE texture whose " + "GL_TEXTURE_SWIZZLE_A is not the default returns uninitialised memory - a " + "different value every run - for any sample index other than 0, from the SECOND " + "such program in the context onward. The identical fetch with the default alpha " + "swizzle, at sample index 0, or through .x instead of .w is correct, so neither " + "R32F multisample targets nor texture swizzles are withdrawn. No substitute was " + "adopted: widening every R32F multisample target to RG32F would sidestep it and " + "doubles multisample memory, which was declined. A shader that fetches a non-zero " + "sample through a swizzled alpha cannot be relied on here"}; } Optional ProbeImageLocationPerNameBug(const GLESFunctionsTable& gl) { const ImageLocationBudgetMeasurement& measurement = ImageLocationPerNameBudget(gl); if (!measurement.detected) return std::nullopt; - String detail = format( - "this driver's image-location budget is charged per distinct uniform NAME, not per " - "image unit: a vertex+geometry+fragment program declaring {} image uniform(s) per " - "stage links when all three stages share one set of names ({} distinct) and is " - "rejected when each stage names its own copies ({} distinct), with the same " - "bindings, the same qualifiers and the same stores either way", - measurement.perStageImageUniforms, measurement.controlDistinctNames, - measurement.subjectDistinctNames); + String detail = format("this driver's image-location budget is charged per distinct uniform NAME, not per " + "image unit: a vertex+geometry+fragment program declaring {} image uniform(s) per " + "stage links when all three stages share one set of names ({} distinct) and is " + "rejected when each stage names its own copies ({} distinct), with the same " + "bindings, the same qualifiers and the same stores either way", + measurement.perStageImageUniforms, measurement.controlDistinctNames, + measurement.subjectDistinctNames); if (!measurement.driverMessage.empty()) { detail += format(" - the driver says \"{}\"", measurement.driverMessage); } - detail += - ". MobileGL names a repaired image uniform after the REPAIR rather than after the " - "stage, so stages that use an image alike keep one shared name and stay merged; " - "applications never see the link failure"; - return DriverBugFinding{"Image locations charged per uniform name", - DriverBugVerdict::Fixed, Move(detail)}; + detail += ". MobileGL names a repaired image uniform after the REPAIR rather than after the " + "stage, so stages that use an image alike keep one shared name and stay merged; " + "applications never see the link failure"; + return DriverBugFinding{"Image locations charged per uniform name", DriverBugVerdict::Fixed, Move(detail)}; } Optional ProbeCrossStageImageQualifierMergeBug(const GLESFunctionsTable& gl) { if (!CrossStageImageQualifierMergeDropsWrites(gl)) return std::nullopt; - return DriverBugFinding{ - "Cross-stage image qualifier merge discards writes", - DriverBugVerdict::Fixed, - "an image declared `coherent writeonly` in one stage and `coherent readonly` in " - "another under the SAME name is merged into one uniform whose writing stage's " - "imageStore()s are then silently discarded - every fragment reads the " - "pre-store value. The identical shader pair with the two halves renamed keeps " - "every store, which is what proves the stage's image writes work and only the " - "merge is at fault. MobileGL renames the two halves (mg_imageWo_ / mg_imageRo_) so " - "the driver cannot merge them, and application behaviour is correct"}; + return DriverBugFinding{"Cross-stage image qualifier merge discards writes", DriverBugVerdict::Fixed, + "an image declared `coherent writeonly` in one stage and `coherent readonly` in " + "another under the SAME name is merged into one uniform whose writing stage's " + "imageStore()s are then silently discarded - every fragment reads the " + "pre-store value. The identical shader pair with the two halves renamed keeps " + "every store, which is what proves the stage's image writes work and only the " + "merge is at fault. MobileGL renames the two halves (mg_imageWo_ / mg_imageRo_) so " + "the driver cannot merge them, and application behaviour is correct"}; } Optional ProbeImageCoherencyResidualBug(const GLESFunctionsTable& gl) { @@ -2594,37 +2515,33 @@ namespace MobileGL::MG_Util::SelfTest { return measurement.totalTexels > 0 ? 100.0 * texels / measurement.totalTexels : 0.0; }; return DriverBugFinding{ - "Image write-to-read ordering within one invocation", - DriverBugVerdict::Unfixable, - format( - "an imageLoad() does not observe the imageStore() that precedes it in the same " - "fragment invocation for {} of {} texels ({:.2f}%) under the STRONGEST shape " - "ESSL offers - a `coherent volatile` readonly/writeonly pair on one binding " - "with both memoryBarrierImage() and memoryBarrier() between the store and the " - "read - which is what leaves nothing to substitute. The shape MobileGL emits " - "today (`coherent` plus memoryBarrierImage()) misses {} ({:.2f}%) on this " - "driver. The same dependency split across two draws with a glMemoryBarrier and " - "a glFinish between them is clean, so writes do become visible; it is the " - "ordering inside one invocation that is not honoured. MobileGL keeps the " - "coherent pair and the barrier - without them every texel is wrong - and a " - "shader that reads back its own image write within one invocation cannot be " - "relied on here", - measurement.mismatchedTexels, measurement.totalTexels, - percentOf(measurement.mismatchedTexels), measurement.emittedShapeMismatchedTexels, - percentOf(measurement.emittedShapeMismatchedTexels))}; + "Image write-to-read ordering within one invocation", DriverBugVerdict::Unfixable, + format("an imageLoad() does not observe the imageStore() that precedes it in the same " + "fragment invocation for {} of {} texels ({:.2f}%) under the STRONGEST shape " + "ESSL offers - a `coherent volatile` readonly/writeonly pair on one binding " + "with both memoryBarrierImage() and memoryBarrier() between the store and the " + "read - which is what leaves nothing to substitute. The shape MobileGL emits " + "today (`coherent` plus memoryBarrierImage()) misses {} ({:.2f}%) on this " + "driver. The same dependency split across two draws with a glMemoryBarrier and " + "a glFinish between them is clean, so writes do become visible; it is the " + "ordering inside one invocation that is not honoured. MobileGL keeps the " + "coherent pair and the barrier - without them every texel is wrong - and a " + "shader that reads back its own image write within one invocation cannot be " + "relied on here", + measurement.mismatchedTexels, measurement.totalTexels, percentOf(measurement.mismatchedTexels), + measurement.emittedShapeMismatchedTexels, percentOf(measurement.emittedShapeMismatchedTexels))}; } Optional ProbeLocatedIoBlockPayloadBug(const GLESFunctionsTable& gl) { const LocatedIoBlockMeasurement& measurement = LocatedIoBlocksLosePayload(gl); if (!measurement.detected) return std::nullopt; - String detail = - "an inter-stage interface block that carries an explicit layout(location = N) " - "delivers NOTHING once a geometry (or tessellation) stage is in the pipeline: the " - "stages compile, the program links with an empty info log, the draw raises no " - "error, and the consuming stage reads zeroes. The byte-identical program with the " - "qualifier removed from the blocks carries its payload correctly, which is what " - "makes this a LOCATION defect rather than an interface-block one - blocks " - "themselves work here"; + String detail = "an inter-stage interface block that carries an explicit layout(location = N) " + "delivers NOTHING once a geometry (or tessellation) stage is in the pipeline: the " + "stages compile, the program links with an empty info log, the draw raises no " + "error, and the consuming stage reads zeroes. The byte-identical program with the " + "qualifier removed from the blocks carries its payload correctly, which is what " + "makes this a LOCATION defect rather than an interface-block one - blocks " + "themselves work here"; detail += measurement.alsoAffectsVertexToFragment ? ". A located block between a VERTEX and a FRAGMENT stage is lost on " "this driver too, so the defect is wider than the repair below " @@ -2640,17 +2557,15 @@ namespace MobileGL::MG_Util::SelfTest { const Bool repairDisabled = MG_Config::Features.EsprytUnlocatedIoBlocks == MG_Config::QuirkOverride::ForceOff; if (repairDisabled) { - detail += - ". THE REPAIR IS DISABLED in this process: MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS " - "is set to force located blocks ON, so DirectGLES emits the location " - "qualifier the driver cannot honour and the payload is lost. Unset the " - "variable to get the repair back"; + detail += ". THE REPAIR IS DISABLED in this process: MOBILEGL_ESPRYT_UNLOCATED_IO_BLOCKS " + "is set to force located blocks ON, so DirectGLES emits the location " + "qualifier the driver cannot honour and the payload is lost. Unset the " + "variable to get the repair back"; } else { - detail += - ". MobileGL emits a tessellation/geometry program's interface blocks with no " - "location qualifier at all (StripIoBlockLocationsPass) and lets ES match them " - "by block name and member sequence, which it does; the locations were invented " - "by the cross-stage IO resolver rather than written by the application"; + detail += ". MobileGL emits a tessellation/geometry program's interface blocks with no " + "location qualifier at all (StripIoBlockLocationsPass) and lets ES match them " + "by block name and member sequence, which it does; the locations were invented " + "by the cross-stage IO resolver rather than written by the application"; } return DriverBugFinding{"Located inter-stage interface blocks carry no payload", (repairDisabled || measurement.alsoAffectsVertexToFragment) @@ -2668,26 +2583,23 @@ namespace MobileGL::MG_Util::SelfTest { // probe has fired. Should POST ever run before env parsing, the field still holds // its Auto default - which is also what the widening itself consults, so the row // and the behaviour cannot disagree. - const Bool widened = MG_Config::Features.EsprytWidenPacked16Storage != - MG_Config::QuirkOverride::ForceOff; - String detail = - "the driver stores SOME 16-bit packed images (RGB565 / RGB5_A1 / RGBA4) with the " - "R/G/B/A fields packed from the other end of the word - which allocations get the " - "*_REV layout depends on shape and context history (measured here on a 30x30x12 " - "three-level 2D array, every level of it) - so a glCopyImageSubData, a raw " - "texel-block move, between a mirrored allocation and a plain one lands the fields " - "reversed (a 5551 word 0x0047 arrives as 0x8C20). Uploads and readbacks of the " - "same image are clean - the driver decodes its own layout consistently, which is " - "this probe's second control - so only the raw-copy path ever crosses the two " - "layouts. "; + const Bool widened = MG_Config::Features.EsprytWidenPacked16Storage != MG_Config::QuirkOverride::ForceOff; + String detail = "the driver stores SOME 16-bit packed images (RGB565 / RGB5_A1 / RGBA4) with the " + "R/G/B/A fields packed from the other end of the word - which allocations get the " + "*_REV layout depends on shape and context history (measured here on a 30x30x12 " + "three-level 2D array, every level of it) - so a glCopyImageSubData, a raw " + "texel-block move, between a mirrored allocation and a plain one lands the fields " + "reversed (a 5551 word 0x0047 arrives as 0x8C20). Uploads and readbacks of the " + "same image are clean - the driver decodes its own layout consistently, which is " + "this probe's second control - so only the raw-copy path ever crosses the two " + "layouts. "; if (widened) { - detail += - "MobileGL stores these three formats as 8-bit-per-channel ES storage on this " - "driver instead (GL_RGB8 / GL_RGBA8, the storage their canonical shadow " - "already holds and the client word round-trips through exactly), so no " - "16-bit packed image is left for a copy to disagree about, at twice the " - "memory for images of those formats; override with " - "MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE"; + detail += "MobileGL stores these three formats as 8-bit-per-channel ES storage on this " + "driver instead (GL_RGB8 / GL_RGBA8, the storage their canonical shadow " + "already holds and the client word round-trips through exactly), so no " + "16-bit packed image is left for a copy to disagree about, at twice the " + "memory for images of those formats; override with " + "MOBILEGL_ESPRYT_WIDEN_PACKED16_STORAGE"; return DriverBugFinding{ "glCopyImageSubData mirrors 16-bit packed texels between differently-laid-out images", DriverBugVerdict::Fixed, detail}; @@ -2743,17 +2655,15 @@ namespace MobileGL::MG_Util::SelfTest { "4-sample images cleared to depth 0.25 / stencil 0x5A over a depth 0.75 / stencil 0xA5 " "sentinel: "; detail += DescribeWireDepthResolveProbe(measurement); - detail += - ". On the split, inproc and tcp transports a multisample depth/stencil resolve " - "(glBlitFramebuffer out of a multisample framebuffer) therefore takes the shader pass " - "first - sample 0 fetched and written through gl_FragDepth / gl_FragStencilRefARB - and " - "keeps the render pass only as the fallback for what the shader cannot write; the " - "monolith transport never records that render pass"; + detail += ". On the split, inproc and tcp transports a multisample depth/stencil resolve " + "(glBlitFramebuffer out of a multisample framebuffer) therefore takes the shader pass " + "first - sample 0 fetched and written through gl_FragDepth / gl_FragStencilRefARB - and " + "keeps the render pass only as the fallback for what the shader cannot write; the " + "monolith transport never records that render pass"; if (!shaderStencilExport) { - detail += - ". THIS DEVICE HAS NO VK_EXT_shader_stencil_export, so a fragment shader cannot write " - "the stencil aspect: a STENCIL resolve still falls back to the render pass and reads " - "back unwritten. Depth is repaired, stencil is not"; + detail += ". THIS DEVICE HAS NO VK_EXT_shader_stencil_export, so a fragment shader cannot write " + "the stencil aspect: a STENCIL resolve still falls back to the render pass and reads " + "back unwritten. Depth is repaired, stencil is not"; } return DriverBugFinding{"Depth/stencil render-pass resolve writes nothing", shaderStencilExport ? DriverBugVerdict::Fixed : DriverBugVerdict::Unfixable, @@ -2768,9 +2678,8 @@ namespace MobileGL::MG_Util::SelfTest { if (context.getInstanceProcAddr == nullptr || context.deviceExtensions == nullptr) return std::nullopt; Bool renderPassArmAvailable = false, shaderStencilExport = false; const WireDepthResolveProbeMeasurement measurement = RunWireDepthResolveProbeOnThrowawayDevice( - context.getInstanceProcAddr, context.instance, context.physicalDevice, - context.graphicsQueueFamilyIndex, *context.deviceExtensions, renderPassArmAvailable, - shaderStencilExport); + context.getInstanceProcAddr, context.instance, context.physicalDevice, context.graphicsQueueFamilyIndex, + *context.deviceExtensions, renderPassArmAvailable, shaderStencilExport); // Without the extension there is no render-pass arm and nothing to have a bug in. if (!renderPassArmAvailable) return std::nullopt; const WireDepthResolveProbeVerdict verdict = EvaluateWireDepthResolveProbe(measurement); @@ -2782,40 +2691,37 @@ namespace MobileGL::MG_Util::SelfTest { return DescribeDepthStencilResolvePassBug(measurement, shaderStencilExport); } - // The same probe the Magma renderer runs at bring-up (VulkanRenderer::ArmWireE5RawReads), // on a throwaway device with one graphics queue. Optional ProbeE5TransferReadNormalizeBug(const VulkanDriverBugProbeContext& context) { using namespace MG_Backend::DirectVulkan; if (context.getInstanceProcAddr == nullptr) return std::nullopt; - const WireE5TransferProbeMeasurement measurement = RunWireE5TransferProbeOnThrowawayDevice( - context.getInstanceProcAddr, context.instance, context.physicalDevice, - context.graphicsQueueFamilyIndex); + const WireE5TransferProbeMeasurement measurement = + RunWireE5TransferProbeOnThrowawayDevice(context.getInstanceProcAddr, context.instance, + context.physicalDevice, context.graphicsQueueFamilyIndex); const WireE5TransferProbeVerdict verdict = EvaluateWireE5TransferProbe(measurement); MGLOG_I("Driver POST: E5B9G9R9 transfer probe verdict=%s: %s", WireE5TransferProbeVerdictName(verdict), DescribeWireE5TransferProbe(measurement).c_str()); if (verdict != WireE5TransferProbeVerdict::TransferReadNormalizes) return std::nullopt; - String detail = - "vkCmdCopyImageToBuffer out of a 30x30 VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 image returns " - "non-canonical shared-exponent words re-encoded (same value, different bits), while a " - "compute shader reading the same image through an R32_UINT view returns every stored " - "word: "; + String detail = "vkCmdCopyImageToBuffer out of a 30x30 VK_FORMAT_E5B9G9R9_UFLOAT_PACK32 image returns " + "non-canonical shared-exponent words re-encoded (same value, different bits), while a " + "compute shader reading the same image through an R32_UINT view returns every stored " + "word: "; detail += DescribeWireE5TransferProbe(measurement); - detail += - ". GL moves RGB9_E5 blocks bit-exactly (glGetTexImage with UNSIGNED_INT_5_9_9_9_REV, " - "glCopyImageSubData), so Magma reads such textures through that shader into an R32_UINT " - "twin and copies the twin instead (MOBILEGL_MAGMA_E5_RAW_READS=0 turns it off)"; - return DriverBugFinding{"E5B9G9R9 transfer reads canonicalize shared exponents", - DriverBugVerdict::Fixed, Move(detail)}; + detail += ". GL moves RGB9_E5 blocks bit-exactly (glGetTexImage with UNSIGNED_INT_5_9_9_9_REV, " + "glCopyImageSubData), so Magma reads such textures through that shader into an R32_UINT " + "twin and copies the twin instead (MOBILEGL_MAGMA_E5_RAW_READS=0 turns it off)"; + return DriverBugFinding{"E5B9G9R9 transfer reads canonicalize shared exponents", DriverBugVerdict::Fixed, + Move(detail)}; } // The same probe the Magma renderer runs at bring-up (VulkanRenderer::ArmWireCopySliceSplit). Optional ProbeCrossFormatSliceCopyBug(const VulkanDriverBugProbeContext& context) { using namespace MG_Backend::DirectVulkan; if (context.getInstanceProcAddr == nullptr) return std::nullopt; - const WireCopySliceProbeMeasurement measurement = RunWireCopySliceProbeOnThrowawayDevice( - context.getInstanceProcAddr, context.instance, context.physicalDevice, - context.graphicsQueueFamilyIndex); + const WireCopySliceProbeMeasurement measurement = + RunWireCopySliceProbeOnThrowawayDevice(context.getInstanceProcAddr, context.instance, + context.physicalDevice, context.graphicsQueueFamilyIndex); const WireCopySliceProbeVerdict verdict = EvaluateWireCopySliceProbe(measurement); MGLOG_I("Driver POST: slice copy probe verdict=%s: %s", WireCopySliceProbeVerdictName(verdict), DescribeWireCopySliceProbe(measurement).c_str()); @@ -2824,20 +2730,19 @@ namespace MobileGL::MG_Util::SelfTest { "a vkCmdCopyImage region between a 2D array and a 3D image of DIFFERENT (size-compatible) " "formats moves only its first slice, while the same copy as one region per slice is exact: "; detail += DescribeWireCopySliceProbe(measurement); - detail += - ". Magma splits such glCopyImageSubData calls one slice per region " - "(MOBILEGL_MAGMA_SPLIT_SLICE_COPIES=0 turns it off)"; - return DriverBugFinding{"Cross-format 2D-array <-> 3D image copies drop slices", - DriverBugVerdict::Fixed, Move(detail)}; + detail += ". Magma splits such glCopyImageSubData calls one slice per region " + "(MOBILEGL_MAGMA_SPLIT_SLICE_COPIES=0 turns it off)"; + return DriverBugFinding{"Cross-format 2D-array <-> 3D image copies drop slices", DriverBugVerdict::Fixed, + Move(detail)}; } // The same probe the Magma renderer runs at bring-up (VulkanRenderer::ArmWireDiscardStateReemit). Optional ProbeDiscardStateBug(const VulkanDriverBugProbeContext& context) { using namespace MG_Backend::DirectVulkan; if (context.getInstanceProcAddr == nullptr) return std::nullopt; - const WireDiscardStateProbeMeasurement measurement = RunWireDiscardStateProbeOnThrowawayDevice( - context.getInstanceProcAddr, context.instance, context.physicalDevice, - context.graphicsQueueFamilyIndex); + const WireDiscardStateProbeMeasurement measurement = + RunWireDiscardStateProbeOnThrowawayDevice(context.getInstanceProcAddr, context.instance, + context.physicalDevice, context.graphicsQueueFamilyIndex); const WireDiscardStateProbeVerdict verdict = EvaluateWireDiscardStateProbe(measurement); MGLOG_I("Driver POST: discard state probe verdict=%s: %s", WireDiscardStateProbeVerdictName(verdict), DescribeWireDiscardStateProbe(measurement).c_str()); @@ -2846,11 +2751,10 @@ namespace MobileGL::MG_Util::SelfTest { "a draw with a rasterizerDiscardEnable pipeline drops the dynamic viewport/scissor, so a later " "draw that inherits them draws nothing, while after a rasterizing draw it does: "; detail += DescribeWireDiscardStateProbe(measurement); - detail += - ". Magma sets the dynamic state again after every discard draw " - "(MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD=0 turns it off)"; - return DriverBugFinding{"Rasterizer-discard draws drop dynamic viewport/scissor", - DriverBugVerdict::Fixed, Move(detail)}; + detail += ". Magma sets the dynamic state again after every discard draw " + "(MOBILEGL_MAGMA_REEMIT_STATE_AFTER_DISCARD=0 turns it off)"; + return DriverBugFinding{"Rasterizer-discard draws drop dynamic viewport/scissor", DriverBugVerdict::Fixed, + Move(detail)}; } using VulkanDriverBugProbeFn = Optional (*)(const VulkanDriverBugProbeContext&); constexpr VulkanDriverBugProbeFn kVulkanDriverBugProbes[] = { diff --git a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h index 0fd1c592..2921113a 100644 --- a/MobileGL/MG_Util/SelfTest/DriverBugProbes.h +++ b/MobileGL/MG_Util/SelfTest/DriverBugProbes.h @@ -186,13 +186,12 @@ namespace MobileGL::MG_Util::SelfTest { // Every invocation of a 32-wide compute workgroup stores 1 into its shared slot, barrier(), // reads slot 0, barrier(), stores 7, barrier(), reads slot 0 again, and writes both reads - // to its own result word: the second read must see 7. The Adreno ES compiler treats ordinary shared loads as pure - it hands back the - // first read (and, with other addresses, moves a read past a later store) - while an - // atomicAdd(x, 0u) read of the same words is kept in place. That atomic twin is the - // control: the verdict is "stale" only when the plain program goes wrong while the atomic - // one stays right. Deterministic (a compiler transform), so latched; false when compute - // shaders or an entry point are missing or anything fails to set up. Restores the GL state - // it touches. + // to its own result word: the second read must see 7. The Adreno ES compiler treats ordinary shared loads as pure - + // it hands back the first read (and, with other addresses, moves a read past a later store) - while an atomicAdd(x, + // 0u) read of the same words is kept in place. That atomic twin is the control: the verdict is "stale" only when + // the plain program goes wrong while the atomic one stays right. Deterministic (a compiler transform), so latched; + // false when compute shaders or an entry point are missing or anything fails to set up. Restores the GL state it + // touches. Bool ProbeSharedMemoryLoadsGoStale(const MG_External::GLESFunctionsTable& gl); // ProbeSharedMemoryLoadsGoStale(), evaluated at most once per process. @@ -319,12 +318,10 @@ namespace MobileGL::MG_Util::SelfTest { // // `detected` is false unless the strongest shape is dirty AND the control is clean. The // shape MobileGL emits is measured either way, so the report can say what applications get. - ImageCoherencyResidualMeasurement ProbeImageWriteReadCoherencyResidual( - const MG_External::GLESFunctionsTable& gl); + ImageCoherencyResidualMeasurement ProbeImageWriteReadCoherencyResidual(const MG_External::GLESFunctionsTable& gl); // ProbeImageWriteReadCoherencyResidual(), evaluated at most once per process. - const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual( - const MG_External::GLESFunctionsTable& gl); + const ImageCoherencyResidualMeasurement& ImageWriteReadCoherencyResidual(const MG_External::GLESFunctionsTable& gl); // Copies one known GL_UNSIGNED_SHORT_5_5_5_1 word out of both mip levels of a GL_RGB5_A1 // 2D array into plain 2D images with glCopyImageSubData and reads the landed texels back diff --git a/MobileGL/MG_Util/SelfTest/DriverPost.cpp b/MobileGL/MG_Util/SelfTest/DriverPost.cpp index 6896b94c..84febd52 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPost.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPost.cpp @@ -200,21 +200,19 @@ namespace MobileGL::MG_Util::SelfTest { void AppendAsyncShaderCompileRow(ReportBuilder& builder) { constexpr const char* rowName = "Asynchronous shader compilation"; if (!MG_Util::Async::AsyncShaderCompileEnabled()) { - builder.Warn(rowName, - "off; GL_KHR_parallel_shader_compile is not advertised and " - "glCompileShader/glLinkProgram run on the calling thread instead. The " - "programs are identical - only the overlap is lost, so a shaderpack load " - "takes as long as its compiles do (set environment variable " - "MOBILEGL_ASYNC_SHADER_COMPILE=1 to enable it)"); + builder.Warn(rowName, "off; GL_KHR_parallel_shader_compile is not advertised and " + "glCompileShader/glLinkProgram run on the calling thread instead. The " + "programs are identical - only the overlap is lost, so a shaderpack load " + "takes as long as its compiles do (set environment variable " + "MOBILEGL_ASYNC_SHADER_COMPILE=1 to enable it)"); return; } const Uint threads = MG_Util::Async::DetectShaderCompileThreadCount(); - builder.Pass(rowName, - format("on with {} compiler thread{}; GL_KHR_parallel_shader_compile is advertised " - "and GL_MAX_SHADER_COMPILER_THREADS_KHR = {} (set environment variable " - "MOBILEGL_ASYNC_SHADER_COMPILE=0 to disable it, or " - "MOBILEGL_ASYNC_SHADER_COMPILE_THREADS=n to change the count)", - threads, threads == 1 ? "" : "s", threads)); + builder.Pass(rowName, format("on with {} compiler thread{}; GL_KHR_parallel_shader_compile is advertised " + "and GL_MAX_SHADER_COMPILER_THREADS_KHR = {} (set environment variable " + "MOBILEGL_ASYNC_SHADER_COMPILE=0 to disable it, or " + "MOBILEGL_ASYNC_SHADER_COMPILE_THREADS=n to change the count)", + threads, threads == 1 ? "" : "s", threads)); } // Appends the four "MobileGL reported ..." rows for one backend section. @@ -300,16 +298,15 @@ namespace MobileGL::MG_Util::SelfTest { return; } if (deviceLimit > kMobileGLMaxVertexAttribs) { - builder.Warn(rowName, - format("{} = {} (> {}); MobileGL clamps GL_MAX_VERTEX_ATTRIBS to {} because its " - "current-vertex-attribute storage and its Uint32 attribute masks hold {} " - "locations, so the driver's extra attributes stay unusable", - driverLimitName, deviceLimit, kMobileGLMaxVertexAttribs, - kMobileGLMaxVertexAttribs, kMobileGLMaxVertexAttribs)); + builder.Warn(rowName, format("{} = {} (> {}); MobileGL clamps GL_MAX_VERTEX_ATTRIBS to {} because its " + "current-vertex-attribute storage and its Uint32 attribute masks hold {} " + "locations, so the driver's extra attributes stay unusable", + driverLimitName, deviceLimit, kMobileGLMaxVertexAttribs, + kMobileGLMaxVertexAttribs, kMobileGLMaxVertexAttribs)); return; } - builder.Pass(rowName, format("{} = {}; MobileGL advertises GL_MAX_VERTEX_ATTRIBS = {}", - driverLimitName, deviceLimit, deviceLimit)); + builder.Pass(rowName, format("{} = {}; MobileGL advertises GL_MAX_VERTEX_ATTRIBS = {}", driverLimitName, + deviceLimit, deviceLimit)); } // "Shader subgroups": how this backend implements GL_KHR_shader_subgroup on this driver @@ -361,12 +358,10 @@ namespace MobileGL::MG_Util::SelfTest { versionLabel + " (< 3.1: no compute shaders or native indirect draws)"); } - EvaluateVertexAttribLimit(builder, caps.MaxVertexAttribs, "Vertex attributes", - "GL_MAX_VERTEX_ATTRIBS"); + EvaluateVertexAttribLimit(builder, caps.MaxVertexAttribs, "Vertex attributes", "GL_MAX_VERTEX_ATTRIBS"); if (caps.SupportsPolygonMode) { - builder.Pass("Polygon mode", - "glPolygonMode GL_LINE/GL_POINT available via GL_NV/ANGLE_polygon_mode"); + builder.Pass("Polygon mode", "glPolygonMode GL_LINE/GL_POINT available via GL_NV/ANGLE_polygon_mode"); } else { builder.FailOptional("Polygon mode", "no GL_NV/ANGLE_polygon_mode; glPolygonMode GL_LINE/GL_POINT silently " @@ -397,8 +392,7 @@ namespace MobileGL::MG_Util::SelfTest { if (es31) { GLint maxVertexSsboBlocks = 0; glesFuncs.glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexSsboBlocks); - while (glesFuncs.glGetError && glesFuncs.glGetError() != GL_NO_ERROR) { - } + while (glesFuncs.glGetError && glesFuncs.glGetError() != GL_NO_ERROR) {} if (maxVertexSsboBlocks >= 1) { builder.Pass("Vertex shader storage blocks", format("GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS = {}", maxVertexSsboBlocks)); @@ -520,28 +514,25 @@ namespace MobileGL::MG_Util::SelfTest { } } if (caps.SupportsTextureBorderClamp) { - builder.Pass("Texture border clamp", - "supported (GL_TEXTURE_BORDER_COLOR reaches the driver, so " - "GL_CLAMP_TO_BORDER samples the colour the application set)"); + builder.Pass("Texture border clamp", "supported (GL_TEXTURE_BORDER_COLOR reaches the driver, so " + "GL_CLAMP_TO_BORDER samples the colour the application set)"); } else { - builder.FailOptional( - "Texture border clamp", - "not supported (pre-ES 3.2 without GL_EXT/OES_texture_border_clamp); " - "GL_TEXTURE_BORDER_COLOR is not synced to the driver at all, so anything " - "sampling outside a GL_CLAMP_TO_BORDER texture reads the driver's default " - "border instead of the requested colour, and no wrap mode substitutes for it"); + builder.FailOptional("Texture border clamp", + "not supported (pre-ES 3.2 without GL_EXT/OES_texture_border_clamp); " + "GL_TEXTURE_BORDER_COLOR is not synced to the driver at all, so anything " + "sampling outside a GL_CLAMP_TO_BORDER texture reads the driver's default " + "border instead of the requested colour, and no wrap mode substitutes for it"); } if (caps.SupportsTextureCubeMapArray) { builder.Pass("Texture cube map array", "supported (GL_TEXTURE_CUBE_MAP_ARRAY textures get real storage and can be " "attached to a framebuffer)"); } else { - builder.FailOptional( - "Texture cube map array", - "not supported (pre-ES 3.2 without GL_EXT/OES_texture_cube_map_array); a cube " - "map array texture gets no driver storage at all, so sampling one reads nothing " - "and rendering to one does not reach the screen. Nothing substitutes: the " - "shaders that declare a samplerCubeArray do not compile either"); + builder.FailOptional("Texture cube map array", + "not supported (pre-ES 3.2 without GL_EXT/OES_texture_cube_map_array); a cube " + "map array texture gets no driver storage at all, so sampling one reads nothing " + "and rendering to one does not reach the screen. Nothing substitutes: the " + "shaders that declare a samplerCubeArray do not compile either"); } // FAIL, and specifically FailOptional. The consequence is severe - buffer textures // are CORE in OpenGL 3.1 and MobileGL advertises a 4.x context, so an application @@ -569,11 +560,10 @@ namespace MobileGL::MG_Util::SelfTest { : "MobileGL's floor - this driver claims buffer textures but rejected the query"; switch (caps.TextureBufferSupport) { case Tier::CoreEs32: - builder.Pass("Buffer textures", - format("core in ES 3.2; GL_MAX_TEXTURE_BUFFER_SIZE = {} is {}, and " - "ESSL 320 needs no #extension directive to declare a " - "samplerBuffer", - advertisedLimit, limitProvenance)); + builder.Pass("Buffer textures", format("core in ES 3.2; GL_MAX_TEXTURE_BUFFER_SIZE = {} is {}, and " + "ESSL 320 needs no #extension directive to declare a " + "samplerBuffer", + advertisedLimit, limitProvenance)); break; case Tier::ExtensionEXT: builder.Pass("Buffer textures", @@ -592,17 +582,16 @@ namespace MobileGL::MG_Util::SelfTest { break; case Tier::None: default: - builder.FailOptional( - "Buffer textures", - format("not supported (pre-ES 3.2 without GL_EXT/OES_texture_buffer); " - "glTexBuffer does not exist, so a buffer texture gets no storage, " - "and any shader declaring a samplerBuffer fails to compile and " - "leaves its program unlinked - every draw using it is a silent " - "no-op. MobileGL still reports GL_MAX_TEXTURE_BUFFER_SIZE = {}: " - "the value is a floor it cannot honour, kept because an OpenGL " - "4.x context may not answer 0 and GL has no way to say that a " - "core feature is missing", - advertisedLimit)); + builder.FailOptional("Buffer textures", + format("not supported (pre-ES 3.2 without GL_EXT/OES_texture_buffer); " + "glTexBuffer does not exist, so a buffer texture gets no storage, " + "and any shader declaring a samplerBuffer fails to compile and " + "leaves its program unlinked - every draw using it is a silent " + "no-op. MobileGL still reports GL_MAX_TEXTURE_BUFFER_SIZE = {}: " + "the value is a floor it cannot honour, kept because an OpenGL " + "4.x context may not answer 0 and GL has no way to say that a " + "core feature is missing", + advertisedLimit)); break; } } @@ -752,8 +741,7 @@ namespace MobileGL::MG_Util::SelfTest { } // Drain stale errors so probe failures are attributable to the probe itself. - while (glesFuncs.glGetError() != GL_NO_ERROR) { - } + while (glesFuncs.glGetError() != GL_NO_ERROR) {} GLuint queryId = 0; glesFuncs.glGenQueries(1, &queryId); @@ -802,9 +790,9 @@ namespace MobileGL::MG_Util::SelfTest { fail(format("GL error 0x{:x} while reading GL_QUERY_RESULT", resultError)); return; } - builder.Pass("Timer queries", - extensionPresent + format("; timer query functional (probe observed {} ns)", elapsedNs) + - disabledNote); + builder.Pass("Timer queries", extensionPresent + + format("; timer query functional (probe observed {} ns)", elapsedNs) + + disabledNote); } // Compiles + links a two-stage program on the probe context. Returns 0 on failure and writes a @@ -825,16 +813,19 @@ namespace MobileGL::MG_Util::SelfTest { GLchar log[512] = {}; GLsizei len = 0; g.glGetShaderInfoLog(out, static_cast(sizeof(log) - 1), &len, log); - detail = format("{} shader compile failed: {}", - stage == GL_VERTEX_SHADER ? "vertex" : "fragment", + detail = format("{} shader compile failed: {}", stage == GL_VERTEX_SHADER ? "vertex" : "fragment", len > 0 ? log : "(no info log)"); return false; } return true; }; GLuint v = 0, f = 0; - const ScopeGuard delV([&]() { if (v) g.glDeleteShader(v); }); - const ScopeGuard delF([&]() { if (f) g.glDeleteShader(f); }); + const ScopeGuard delV([&]() { + if (v) g.glDeleteShader(v); + }); + const ScopeGuard delF([&]() { + if (f) g.glDeleteShader(f); + }); if (!compile(GL_VERTEX_SHADER, vs, v) || !compile(GL_FRAGMENT_SHADER, fs, f)) { return 0; } @@ -884,13 +875,12 @@ namespace MobileGL::MG_Util::SelfTest { if (!g.glCreateShader || !g.glShaderSource || !g.glCompileShader || !g.glGetShaderiv || !g.glGetShaderInfoLog || !g.glDeleteShader || !g.glCreateProgram || !g.glAttachShader || !g.glLinkProgram || !g.glGetProgramiv || !g.glUseProgram || !g.glDeleteProgram || - !g.glGenFramebuffers || !g.glBindFramebuffer || !g.glDeleteFramebuffers || - !g.glGenRenderbuffers || !g.glBindRenderbuffer || !g.glRenderbufferStorage || - !g.glFramebufferRenderbuffer || !g.glDeleteRenderbuffers || !g.glCheckFramebufferStatus || - !g.glGenBuffers || !g.glBindBuffer || !g.glBufferData || !g.glDeleteBuffers || - !g.glGetAttribLocation || !g.glVertexAttribPointer || !g.glEnableVertexAttribArray || - !g.glViewport || !g.glClearColor || !g.glClear || !g.glDrawArrays || !g.glReadPixels || - !g.glFinish || !g.glGetError) { + !g.glGenFramebuffers || !g.glBindFramebuffer || !g.glDeleteFramebuffers || !g.glGenRenderbuffers || + !g.glBindRenderbuffer || !g.glRenderbufferStorage || !g.glFramebufferRenderbuffer || + !g.glDeleteRenderbuffers || !g.glCheckFramebufferStatus || !g.glGenBuffers || !g.glBindBuffer || + !g.glBufferData || !g.glDeleteBuffers || !g.glGetAttribLocation || !g.glVertexAttribPointer || + !g.glEnableVertexAttribArray || !g.glViewport || !g.glClearColor || !g.glClear || !g.glDrawArrays || + !g.glReadPixels || !g.glFinish || !g.glGetError) { fail("the render entry points did not resolve through eglGetProcAddress"); return; } @@ -901,43 +891,39 @@ namespace MobileGL::MG_Util::SelfTest { // same header for consistency. const Int esslVer = caps.GLESVersion.Major * 100 + caps.GLESVersion.Minor * 10; const String header = format("#version {} es\n", esslVer >= 300 ? esslVer : 300); - static const char* const kVsNativeBody = - "#extension GL_NV_shader_noperspective_interpolation : require\n" - "in vec4 a_pos;\n" - "in float a_v;\n" - "noperspective out highp float v_out;\n" - "void main() { gl_Position = a_pos; v_out = a_v; }\n"; - static const char* const kFsNativeBody = - "#extension GL_NV_shader_noperspective_interpolation : require\n" - "precision highp float;\n" - "noperspective in highp float v_out;\n" - "out vec4 fragColor;\n" - "void main() { fragColor = vec4(v_out, 0.0, 0.0, 1.0); }\n"; + static const char* const kVsNativeBody = "#extension GL_NV_shader_noperspective_interpolation : require\n" + "in vec4 a_pos;\n" + "in float a_v;\n" + "noperspective out highp float v_out;\n" + "void main() { gl_Position = a_pos; v_out = a_v; }\n"; + static const char* const kFsNativeBody = "#extension GL_NV_shader_noperspective_interpolation : require\n" + "precision highp float;\n" + "noperspective in highp float v_out;\n" + "out vec4 fragColor;\n" + "void main() { fragColor = vec4(v_out, 0.0, 0.0, 1.0); }\n"; // Exactly MobileGL's emulation (verified against EmulateNoPerspectivePass output): pre-multiply // the varying by clip-w in the vertex stage, recover with gl_FragCoord.w in the fragment stage, // no noperspective qualifier (so the driver interpolates it perspective-correct). - static const char* const kVsEmuBody = - "in vec4 a_pos;\n" - "in float a_v;\n" - "out highp float v_out;\n" - "void main() { gl_Position = a_pos; v_out = a_v * gl_Position.w; }\n"; + static const char* const kVsEmuBody = "in vec4 a_pos;\n" + "in float a_v;\n" + "out highp float v_out;\n" + "void main() { gl_Position = a_pos; v_out = a_v * gl_Position.w; }\n"; static const char* const kFsEmuBody = "precision highp float;\n" "in highp float v_out;\n" "out vec4 fragColor;\n" "void main() { fragColor = vec4(v_out * gl_FragCoord.w, 0.0, 0.0, 1.0); }\n"; - while (g.glGetError() != GL_NO_ERROR) { - } + while (g.glGetError() != GL_NO_ERROR) {} const String vsSrc = header + (native ? kVsNativeBody : kVsEmuBody); const String fsSrc = header + (native ? kFsNativeBody : kFsEmuBody); String linkDetail; const GLuint prog = CompileLinkProgram(g, vsSrc.c_str(), fsSrc.c_str(), linkDetail); if (prog == 0) { - fail(native ? "a noperspective program failed to build though the extension is advertised: " + - linkDetail - : "the emulation program failed to build: " + linkDetail); + fail(native + ? "a noperspective program failed to build though the extension is advertised: " + linkDetail + : "the emulation program failed to build: " + linkDetail); return; } const ScopeGuard delProg([&]() { g.glDeleteProgram(prog); }); @@ -946,7 +932,9 @@ namespace MobileGL::MG_Util::SelfTest { constexpr GLsizei kDim = 9; GLuint rbo = 0, fbo = 0, vbo = 0; g.glGenRenderbuffers(1, &rbo); - const ScopeGuard delRbo([&]() { if (rbo) g.glDeleteRenderbuffers(1, &rbo); }); + const ScopeGuard delRbo([&]() { + if (rbo) g.glDeleteRenderbuffers(1, &rbo); + }); g.glBindRenderbuffer(GL_RENDERBUFFER, rbo); g.glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kDim, kDim); g.glGenFramebuffers(1, &fbo); @@ -972,7 +960,9 @@ namespace MobileGL::MG_Util::SelfTest { 8.f, 8.f, 0.f, 8.f, 1.f, // }; g.glGenBuffers(1, &vbo); - const ScopeGuard delVbo([&]() { if (vbo) g.glDeleteBuffers(1, &vbo); }); + const ScopeGuard delVbo([&]() { + if (vbo) g.glDeleteBuffers(1, &vbo); + }); g.glBindBuffer(GL_ARRAY_BUFFER, vbo); g.glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW); @@ -1022,15 +1012,14 @@ namespace MobileGL::MG_Util::SelfTest { return; } if (native) { - builder.Pass("noperspective interpolation", - pathNote + format("; native noperspective renders screen-linear (centre {} ~= 0.5)", - observedByte)); + builder.Pass( + "noperspective interpolation", + pathNote + format("; native noperspective renders screen-linear (centre {} ~= 0.5)", observedByte)); } else { builder.Warn("noperspective interpolation", - pathNote + - format("; the emulation renders screen-linear correctly (centre {} ~= 0.5), " - "but this is the fallback path with less driver coverage", - observedByte)); + pathNote + format("; the emulation renders screen-linear correctly (centre {} ~= 0.5), " + "but this is the fallback path with less driver coverage", + observedByte)); } } @@ -1221,9 +1210,18 @@ namespace MobileGL::MG_Util::SelfTest { } stageDone("eglBindAPI(EGL_OPENGL_ES_API) succeeded"); - const EGLint configAttribs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + const EGLint configAttribs[] = {EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, EGL_NONE}; EGLConfig config = nullptr; EGLint numConfigs = 0; @@ -1281,8 +1279,8 @@ namespace MobileGL::MG_Util::SelfTest { caps.SubgroupSupportedFeatures & MG_Backend::DirectGLES::kEsslPrintableSubgroupFeatures) : String()); builder.report.formatCapabilities.emplace(); - MG_Backend::DirectGLES::PopulateFormatCapabilities( - glesFuncs, caps, builder.report.formatCapabilities.value()); + MG_Backend::DirectGLES::PopulateFormatCapabilities(glesFuncs, caps, + builder.report.formatCapabilities.value()); ReportThreeChannelColorAttachments(builder, caps, builder.report.formatCapabilities.value()); // The "Known Driver Bugs" section. Deliberately last, and deliberately not a // builder.Pass/Warn/Fail row: these are not capability checks and they must not move @@ -1372,8 +1370,7 @@ namespace MobileGL::MG_Util::SelfTest { } String VkApiVersionToString(Uint32 version) { - return format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), - VK_VERSION_PATCH(version)); + return format("{}.{}.{}", VK_VERSION_MAJOR(version), VK_VERSION_MINOR(version), VK_VERSION_PATCH(version)); } // Real timestamp-query probe, emitting the backend's single "Timer queries" row: @@ -1384,8 +1381,7 @@ namespace MobileGL::MG_Util::SelfTest { // down from a scope guard before the caller's instance guard runs. void ProbeVulkanTimerQuery(ReportBuilder& builder, PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice, - Uint32 graphicsQueueFamilyIndex, Uint32 timestampValidBits, - Float timestampPeriod) { + Uint32 graphicsQueueFamilyIndex, Uint32 timestampValidBits, Float timestampPeriod) { const String disabledNote = TimerQueryDisabledNote(); const String timestampFacts = format("timestampValidBits = {} on the graphics queue family; timestampPeriod = {} ns per tick", @@ -1427,19 +1423,18 @@ namespace MobileGL::MG_Util::SelfTest { reinterpret_cast(getInstanceProcAddr(instance, "vkWaitForFences")); const auto vkQueueSubmitFn = reinterpret_cast(getInstanceProcAddr(instance, "vkQueueSubmit")); - const auto vkGetQueryPoolResultsFn = reinterpret_cast( - getInstanceProcAddr(instance, "vkGetQueryPoolResults")); + const auto vkGetQueryPoolResultsFn = + reinterpret_cast(getInstanceProcAddr(instance, "vkGetQueryPoolResults")); const auto vkDeviceWaitIdleFn = reinterpret_cast(getInstanceProcAddr(instance, "vkDeviceWaitIdle")); if (vkCreateDeviceFn == nullptr || vkDestroyDeviceFn == nullptr || vkGetDeviceQueueFn == nullptr || vkCreateCommandPoolFn == nullptr || vkDestroyCommandPoolFn == nullptr || vkAllocateCommandBuffersFn == nullptr || vkBeginCommandBufferFn == nullptr || - vkEndCommandBufferFn == nullptr || vkCreateQueryPoolFn == nullptr || - vkDestroyQueryPoolFn == nullptr || vkCmdResetQueryPoolFn == nullptr || - vkCmdWriteTimestampFn == nullptr || vkCreateFenceFn == nullptr || vkDestroyFenceFn == nullptr || - vkWaitForFencesFn == nullptr || vkQueueSubmitFn == nullptr || vkGetQueryPoolResultsFn == nullptr || - vkDeviceWaitIdleFn == nullptr) { + vkEndCommandBufferFn == nullptr || vkCreateQueryPoolFn == nullptr || vkDestroyQueryPoolFn == nullptr || + vkCmdResetQueryPoolFn == nullptr || vkCmdWriteTimestampFn == nullptr || vkCreateFenceFn == nullptr || + vkDestroyFenceFn == nullptr || vkWaitForFencesFn == nullptr || vkQueueSubmitFn == nullptr || + vkGetQueryPoolResultsFn == nullptr || vkDeviceWaitIdleFn == nullptr) { fail("vkGetInstanceProcAddr could not resolve the entry points required for the " "timestamp probe"); return; @@ -1573,21 +1568,19 @@ namespace MobileGL::MG_Util::SelfTest { // Skip the teardown idle wait too (see the scope guard): the // submission is still pending on a possibly-hung GPU. fenceWaitTimedOut = true; - fail(format("vkWaitForFences did not signal within 5 s (VkResult = {})", - static_cast(result))); + fail(format("vkWaitForFences did not signal within 5 s (VkResult = {})", static_cast(result))); return; } Uint64 timestamps[2] = {0, 0}; - result = vkGetQueryPoolResultsFn(device, queryPool, 0, 2, sizeof(timestamps), timestamps, - sizeof(Uint64), VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); + result = vkGetQueryPoolResultsFn(device, queryPool, 0, 2, sizeof(timestamps), timestamps, sizeof(Uint64), + VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT); if (result != VK_SUCCESS) { fail(format("vkGetQueryPoolResults failed (VkResult = {})", static_cast(result))); return; } - const Uint64 validMask = - timestampValidBits >= 64 ? ~0ull : ((1ull << timestampValidBits) - 1ull); + const Uint64 validMask = timestampValidBits >= 64 ? ~0ull : ((1ull << timestampValidBits) - 1ull); const Uint64 t0 = timestamps[0] & validMask; const Uint64 t1 = timestamps[1] & validMask; if (t1 < t0) { @@ -1618,8 +1611,7 @@ namespace MobileGL::MG_Util::SelfTest { // Throwaway device on purpose, like every probe here: the row reports the // driver, not the renderer's configuration - MOBILEGL_MAGMA_PRIMGEN_QUERY_REROUTE // steers the renderer, never this row. - void ProbeVulkanPrimitivesGeneratedNoXfb(ReportBuilder& builder, - PFN_vkGetInstanceProcAddr getInstanceProcAddr, + void ProbeVulkanPrimitivesGeneratedNoXfb(ReportBuilder& builder, PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice, Uint32 graphicsQueueFamilyIndex, const Vector& deviceExtensions, @@ -1690,8 +1682,7 @@ namespace MobileGL::MG_Util::SelfTest { Bool primitivesGeneratedQueryUsable = false; VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT enabledPgqFeatures{}; - enabledPgqFeatures.sType = - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT; + enabledPgqFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT; if (HasVkExtension(deviceExtensions, VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME)) { VkPhysicalDevicePrimitivesGeneratedQueryFeaturesEXT pgqQuery{}; pgqQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRIMITIVES_GENERATED_QUERY_FEATURES_EXT; @@ -1705,16 +1696,14 @@ namespace MobileGL::MG_Util::SelfTest { enabledPgqFeatures.primitivesGeneratedQuery = VK_TRUE; enabledPgqFeatures.primitivesGeneratedQueryWithRasterizerDiscard = VK_TRUE; enabledPgqFeatures.pNext = &enabledXfbFeatures; - enabledExtensions[enabledExtensionCount++] = - VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME; + enabledExtensions[enabledExtensionCount++] = VK_EXT_PRIMITIVES_GENERATED_QUERY_EXTENSION_NAME; } } VkDeviceCreateInfo deviceInfo{}; deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; - deviceInfo.pNext = primitivesGeneratedQueryUsable - ? static_cast(&enabledPgqFeatures) - : static_cast(&enabledXfbFeatures); + deviceInfo.pNext = primitivesGeneratedQueryUsable ? static_cast(&enabledPgqFeatures) + : static_cast(&enabledXfbFeatures); deviceInfo.queueCreateInfoCount = 1; deviceInfo.pQueueCreateInfos = &queueInfo; deviceInfo.enabledExtensionCount = enabledExtensionCount; @@ -1761,12 +1750,10 @@ namespace MobileGL::MG_Util::SelfTest { auto& fns = probeContext.fns; const auto resolve = [&](const char* name) { return getInstanceProcAddr(instance, name); }; fns.vkCreateCommandPool = reinterpret_cast(resolve("vkCreateCommandPool")); - fns.vkDestroyCommandPool = - reinterpret_cast(resolve("vkDestroyCommandPool")); + fns.vkDestroyCommandPool = reinterpret_cast(resolve("vkDestroyCommandPool")); fns.vkAllocateCommandBuffers = reinterpret_cast(resolve("vkAllocateCommandBuffers")); - fns.vkBeginCommandBuffer = - reinterpret_cast(resolve("vkBeginCommandBuffer")); + fns.vkBeginCommandBuffer = reinterpret_cast(resolve("vkBeginCommandBuffer")); fns.vkEndCommandBuffer = reinterpret_cast(resolve("vkEndCommandBuffer")); fns.vkCreateQueryPool = reinterpret_cast(resolve("vkCreateQueryPool")); fns.vkDestroyQueryPool = reinterpret_cast(resolve("vkDestroyQueryPool")); @@ -1778,19 +1765,13 @@ namespace MobileGL::MG_Util::SelfTest { fns.vkCmdEndQueryIndexedEXT = reinterpret_cast(resolve("vkCmdEndQueryIndexedEXT")); fns.vkCreateRenderPass = reinterpret_cast(resolve("vkCreateRenderPass")); - fns.vkDestroyRenderPass = - reinterpret_cast(resolve("vkDestroyRenderPass")); - fns.vkCreateFramebuffer = - reinterpret_cast(resolve("vkCreateFramebuffer")); - fns.vkDestroyFramebuffer = - reinterpret_cast(resolve("vkDestroyFramebuffer")); - fns.vkCmdBeginRenderPass = - reinterpret_cast(resolve("vkCmdBeginRenderPass")); + fns.vkDestroyRenderPass = reinterpret_cast(resolve("vkDestroyRenderPass")); + fns.vkCreateFramebuffer = reinterpret_cast(resolve("vkCreateFramebuffer")); + fns.vkDestroyFramebuffer = reinterpret_cast(resolve("vkDestroyFramebuffer")); + fns.vkCmdBeginRenderPass = reinterpret_cast(resolve("vkCmdBeginRenderPass")); fns.vkCmdEndRenderPass = reinterpret_cast(resolve("vkCmdEndRenderPass")); - fns.vkCreateShaderModule = - reinterpret_cast(resolve("vkCreateShaderModule")); - fns.vkDestroyShaderModule = - reinterpret_cast(resolve("vkDestroyShaderModule")); + fns.vkCreateShaderModule = reinterpret_cast(resolve("vkCreateShaderModule")); + fns.vkDestroyShaderModule = reinterpret_cast(resolve("vkDestroyShaderModule")); fns.vkCreatePipelineLayout = reinterpret_cast(resolve("vkCreatePipelineLayout")); fns.vkDestroyPipelineLayout = @@ -1804,12 +1785,10 @@ namespace MobileGL::MG_Util::SelfTest { fns.vkDestroyFence = reinterpret_cast(resolve("vkDestroyFence")); fns.vkQueueSubmit = reinterpret_cast(resolve("vkQueueSubmit")); fns.vkWaitForFences = reinterpret_cast(resolve("vkWaitForFences")); - fns.vkGetQueryPoolResults = - reinterpret_cast(resolve("vkGetQueryPoolResults")); + fns.vkGetQueryPoolResults = reinterpret_cast(resolve("vkGetQueryPoolResults")); fns.vkDeviceWaitIdle = reinterpret_cast(resolve("vkDeviceWaitIdle")); - const PrimitivesGeneratedNoXfbMeasurement measurement = - RunPrimitivesGeneratedNoXfbProbe(probeContext); + const PrimitivesGeneratedNoXfbMeasurement measurement = RunPrimitivesGeneratedNoXfbProbe(probeContext); // Before any return below: the guard above owns the device and must know. probeFenceWaitTimedOut = measurement.fenceWaitTimedOut; if (!measurement.ran) { @@ -1819,8 +1798,7 @@ namespace MobileGL::MG_Util::SelfTest { return; } - const auto shapeFacts = [](const char* name, - const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { + const auto shapeFacts = [](const char* name, const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { if (!shape.drawn) { return format("{} not drawn (no tessellationShader)", name); } @@ -1839,8 +1817,8 @@ namespace MobileGL::MG_Util::SelfTest { return facts; }; const String facts = shapeFacts("triangles", measurement.trianglesPlain) + "; " + - shapeFacts("triangles under discard", measurement.trianglesDiscard) + - "; " + shapeFacts("patches under discard", measurement.patchesDiscard); + shapeFacts("triangles under discard", measurement.trianglesDiscard) + "; " + + shapeFacts("patches under discard", measurement.patchesDiscard); const auto statisticsExactOn = [](const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { return shape.statisticsMeasured && shape.statisticsClippingInput == shape.expectedPrimitives; @@ -1853,8 +1831,7 @@ namespace MobileGL::MG_Util::SelfTest { // string printed right after it. const auto describeMissedStatisticsShapes = [&]() { String missed; - const auto note = [&](const char* name, - const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { + const auto note = [&](const char* name, const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { if (!shape.drawn || statisticsExactOn(shape)) { return; } @@ -1862,10 +1839,9 @@ namespace MobileGL::MG_Util::SelfTest { missed += " and "; } missed += name; - missed += shape.statisticsMeasured - ? format(" (read {} of {} expected)", shape.statisticsClippingInput, - shape.expectedPrimitives) - : String(" (its statistics slot did not read back)"); + missed += shape.statisticsMeasured ? format(" (read {} of {} expected)", + shape.statisticsClippingInput, shape.expectedPrimitives) + : String(" (its statistics slot did not read back)"); }; note("the plain draw", measurement.trianglesPlain); note("triangles under rasterizer discard", measurement.trianglesDiscard); @@ -1890,21 +1866,19 @@ namespace MobileGL::MG_Util::SelfTest { switch (EvaluatePrimitivesGeneratedNoXfbVerdict(measurement)) { case PrimitivesGeneratedNoXfbVerdict::StreamCounts: - builder.Pass(RowName, - "the stream query counts a draw made with no capture span open, as " - "VK_EXT_transform_feedback defines (" + - facts + ")"); + builder.Pass(RowName, "the stream query counts a draw made with no capture span open, as " + "VK_EXT_transform_feedback defines (" + + facts + ")"); return; case PrimitivesGeneratedNoXfbVerdict::PrimitivesGeneratedExtSubstitute: - builder.Warn(RowName, - "the stream query answers 0 for a draw made with no capture span open - " - "the shape the CTS measures the tessellator with - while a " - "VK_QUERY_TYPE_PRIMITIVES_GENERATED_EXT query around an identical replay answers " - "exactly right, rasterizer discard included, so the renderer " - "accumulates GL_PRIMITIVES_GENERATED for such draws through the " - "dedicated query instead (one extra query slot per XFB-inactive draw " - "inside a GENERATED span; " + - facts + ")"); + builder.Warn(RowName, "the stream query answers 0 for a draw made with no capture span open - " + "the shape the CTS measures the tessellator with - while a " + "VK_QUERY_TYPE_PRIMITIVES_GENERATED_EXT query around an identical replay answers " + "exactly right, rasterizer discard included, so the renderer " + "accumulates GL_PRIMITIVES_GENERATED for such draws through the " + "dedicated query instead (one extra query slot per XFB-inactive draw " + "inside a GENERATED span; " + + facts + ")"); return; case PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute: builder.Warn(RowName, @@ -1936,8 +1910,7 @@ namespace MobileGL::MG_Util::SelfTest { String downgradeShapes; const auto noteDowngrade = [&](const char* name, const PrimitivesGeneratedNoXfbShapeMeasurement& shape) { - if (!shape.drawn || statisticsExactOn(shape) || - shape.streamGenerated != shape.expectedPrimitives) { + if (!shape.drawn || statisticsExactOn(shape) || shape.streamGenerated != shape.expectedPrimitives) { return; } if (!downgradeShapes.empty()) { @@ -1979,8 +1952,7 @@ namespace MobileGL::MG_Util::SelfTest { // driver does, not what MobileGL elects to advertise to applications. void ProbeVulkanIterationRPWitness(ReportBuilder& builder, PFN_vkGetInstanceProcAddr getInstanceProcAddr, VkInstance instance, VkPhysicalDevice physicalDevice, - Uint32 computeQueueFamilyIndex, - const VkPhysicalDeviceProperties& properties, + Uint32 computeQueueFamilyIndex, const VkPhysicalDeviceProperties& properties, Bool subgroupPropertiesAvailable, const VkPhysicalDeviceSubgroupProperties& subgroupProperties) { constexpr const char* RowName = "Subgroup first-reduction witness"; @@ -1998,8 +1970,7 @@ namespace MobileGL::MG_Util::SelfTest { } IterationRPWitnessLimits limits{}; - limits.computeStageSupported = - (subgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0; + limits.computeStageSupported = (subgroupProperties.supportedStages & VK_SHADER_STAGE_COMPUTE_BIT) != 0; limits.basicSubgroupSupported = (subgroupProperties.supportedOperations & VK_SUBGROUP_FEATURE_BASIC_BIT) != 0; limits.arithmeticSubgroupSupported = @@ -2007,8 +1978,8 @@ namespace MobileGL::MG_Util::SelfTest { limits.subgroupSize = subgroupProperties.subgroupSize; limits.maxComputeWorkGroupInvocations = properties.limits.maxComputeWorkGroupInvocations; limits.maxComputeWorkGroupSize = {properties.limits.maxComputeWorkGroupSize[0], - properties.limits.maxComputeWorkGroupSize[1], - properties.limits.maxComputeWorkGroupSize[2]}; + properties.limits.maxComputeWorkGroupSize[1], + properties.limits.maxComputeWorkGroupSize[2]}; limits.maxComputeSharedMemorySize = properties.limits.maxComputeSharedMemorySize; limits.maxPerStageDescriptorStorageBuffers = properties.limits.maxPerStageDescriptorStorageBuffers; limits.maxDescriptorSetStorageBuffers = properties.limits.maxDescriptorSetStorageBuffers; @@ -2020,10 +1991,9 @@ namespace MobileGL::MG_Util::SelfTest { // WARN, not FAIL: there is nothing to witness on a device with no native // subgroup contract, and the renderer takes its non-subgroup iteration path, // which produces the same image. - builder.Warn(RowName, - eligibility.detail + - "; the renderer takes its non-subgroup iteration path instead, which " - "renders the same thing without the first-reduction shortcut"); + builder.Warn(RowName, eligibility.detail + + "; the renderer takes its non-subgroup iteration path instead, which " + "renders the same thing without the first-reduction shortcut"); return; } if (eligibility.eligibility == IterationRPWitnessEligibility::FailInadequateLimits) { @@ -2056,8 +2026,7 @@ namespace MobileGL::MG_Util::SelfTest { reinterpret_cast(getInstanceProcAddr(instance, "vkFreeMemory")); const auto vkBindBufferMemoryFn = reinterpret_cast(getInstanceProcAddr(instance, "vkBindBufferMemory")); - const auto vkMapMemoryFn = - reinterpret_cast(getInstanceProcAddr(instance, "vkMapMemory")); + const auto vkMapMemoryFn = reinterpret_cast(getInstanceProcAddr(instance, "vkMapMemory")); const auto vkUnmapMemoryFn = reinterpret_cast(getInstanceProcAddr(instance, "vkUnmapMemory")); const auto vkCreateDescriptorSetLayoutFn = reinterpret_cast( @@ -2066,8 +2035,8 @@ namespace MobileGL::MG_Util::SelfTest { getInstanceProcAddr(instance, "vkDestroyDescriptorSetLayout")); const auto vkCreateDescriptorPoolFn = reinterpret_cast(getInstanceProcAddr(instance, "vkCreateDescriptorPool")); - const auto vkDestroyDescriptorPoolFn = reinterpret_cast( - getInstanceProcAddr(instance, "vkDestroyDescriptorPool")); + const auto vkDestroyDescriptorPoolFn = + reinterpret_cast(getInstanceProcAddr(instance, "vkDestroyDescriptorPool")); const auto vkAllocateDescriptorSetsFn = reinterpret_cast( getInstanceProcAddr(instance, "vkAllocateDescriptorSets")); const auto vkUpdateDescriptorSetsFn = @@ -2078,8 +2047,8 @@ namespace MobileGL::MG_Util::SelfTest { reinterpret_cast(getInstanceProcAddr(instance, "vkDestroyShaderModule")); const auto vkCreatePipelineLayoutFn = reinterpret_cast(getInstanceProcAddr(instance, "vkCreatePipelineLayout")); - const auto vkDestroyPipelineLayoutFn = reinterpret_cast( - getInstanceProcAddr(instance, "vkDestroyPipelineLayout")); + const auto vkDestroyPipelineLayoutFn = + reinterpret_cast(getInstanceProcAddr(instance, "vkDestroyPipelineLayout")); const auto vkCreateComputePipelinesFn = reinterpret_cast( getInstanceProcAddr(instance, "vkCreateComputePipelines")); const auto vkDestroyPipelineFn = @@ -2096,8 +2065,8 @@ namespace MobileGL::MG_Util::SelfTest { reinterpret_cast(getInstanceProcAddr(instance, "vkEndCommandBuffer")); const auto vkCmdBindPipelineFn = reinterpret_cast(getInstanceProcAddr(instance, "vkCmdBindPipeline")); - const auto vkCmdBindDescriptorSetsFn = reinterpret_cast( - getInstanceProcAddr(instance, "vkCmdBindDescriptorSets")); + const auto vkCmdBindDescriptorSetsFn = + reinterpret_cast(getInstanceProcAddr(instance, "vkCmdBindDescriptorSets")); const auto vkCmdDispatchFn = reinterpret_cast(getInstanceProcAddr(instance, "vkCmdDispatch")); const auto vkCmdPipelineBarrierFn = @@ -2123,10 +2092,10 @@ namespace MobileGL::MG_Util::SelfTest { vkUpdateDescriptorSetsFn == nullptr || vkCreateShaderModuleFn == nullptr || vkDestroyShaderModuleFn == nullptr || vkCreatePipelineLayoutFn == nullptr || vkDestroyPipelineLayoutFn == nullptr || vkCreateComputePipelinesFn == nullptr || - vkDestroyPipelineFn == nullptr || vkCreateCommandPoolFn == nullptr || vkDestroyCommandPoolFn == nullptr || - vkAllocateCommandBuffersFn == nullptr || vkBeginCommandBufferFn == nullptr || - vkEndCommandBufferFn == nullptr || vkCmdBindPipelineFn == nullptr || - vkCmdBindDescriptorSetsFn == nullptr || vkCmdDispatchFn == nullptr || + vkDestroyPipelineFn == nullptr || vkCreateCommandPoolFn == nullptr || + vkDestroyCommandPoolFn == nullptr || vkAllocateCommandBuffersFn == nullptr || + vkBeginCommandBufferFn == nullptr || vkEndCommandBufferFn == nullptr || + vkCmdBindPipelineFn == nullptr || vkCmdBindDescriptorSetsFn == nullptr || vkCmdDispatchFn == nullptr || vkCmdPipelineBarrierFn == nullptr || vkCreateFenceFn == nullptr || vkDestroyFenceFn == nullptr || vkQueueSubmitFn == nullptr || vkWaitForFencesFn == nullptr || vkDeviceWaitIdleFn == nullptr) { fail("vkGetInstanceProcAddr could not resolve the Vulkan entry points required for the witness"); @@ -2211,8 +2180,8 @@ namespace MobileGL::MG_Util::SelfTest { Uint32 memoryTypeIndex = std::numeric_limits::max(); for (Uint32 index = 0; index < memoryProperties.memoryTypeCount; ++index) { const Bool compatible = (memoryRequirements.memoryTypeBits & (1u << index)) != 0u; - const VkMemoryPropertyFlags required = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | - VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + const VkMemoryPropertyFlags required = + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; if (compatible && (memoryProperties.memoryTypes[index].propertyFlags & required) == required) { memoryTypeIndex = index; break; @@ -2537,9 +2506,8 @@ namespace MobileGL::MG_Util::SelfTest { // fallback costs a buffer queue the headless path does not need, and because // this used to abort the process instead. if (HasVkExtension(instanceExtensions, VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME)) { - builder.Pass("Headless surface", - format("{} present; windowless contexts get a real headless surface", - VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME)); + builder.Pass("Headless surface", format("{} present; windowless contexts get a real headless surface", + VK_EXT_HEADLESS_SURFACE_EXTENSION_NAME)); } else { builder.Warn("Headless surface", format("{} absent; a windowless (pbuffer) context falls back to an AImageReader " @@ -2579,16 +2547,14 @@ namespace MobileGL::MG_Util::SelfTest { getInstanceProcAddr(instance, "vkGetPhysicalDeviceQueueFamilyProperties")); const auto vkGetPhysicalDeviceFeaturesFn = reinterpret_cast( getInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures")); - const auto vkEnumerateDeviceExtensionPropertiesFn = - reinterpret_cast( - getInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties")); + const auto vkEnumerateDeviceExtensionPropertiesFn = reinterpret_cast( + getInstanceProcAddr(instance, "vkEnumerateDeviceExtensionProperties")); const auto vkGetPhysicalDeviceFeatures2Fn = reinterpret_cast( getInstanceProcAddr(instance, "vkGetPhysicalDeviceFeatures2")); const auto vkGetPhysicalDeviceProperties2Fn = reinterpret_cast( getInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2")); - const auto vkGetPhysicalDeviceFormatPropertiesFn = - reinterpret_cast( - getInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties")); + const auto vkGetPhysicalDeviceFormatPropertiesFn = reinterpret_cast( + getInstanceProcAddr(instance, "vkGetPhysicalDeviceFormatProperties")); // The instance is destroyed from a scope guard so it is released on every early-return // path and even if a String/format allocation throws while report rows are being built. @@ -2611,8 +2577,8 @@ namespace MobileGL::MG_Util::SelfTest { Uint32 deviceCount = 0; const VkResult countResult = vkEnumeratePhysicalDevicesFn(instance, &deviceCount, nullptr); if (countResult != VK_SUCCESS) { - builder.Fail("Graphics device", format("vkEnumeratePhysicalDevices failed (VkResult = {})", - static_cast(countResult))); + builder.Fail("Graphics device", + format("vkEnumeratePhysicalDevices failed (VkResult = {})", static_cast(countResult))); return; } if (deviceCount == 0) { @@ -2679,14 +2645,12 @@ namespace MobileGL::MG_Util::SelfTest { // The chosen-device facts (name, enumeration count, graphics queue) ride along // on both outcomes so the device API verdict never hides them. - const String deviceFacts = - format("{} ({} device(s) enumerated, picked the first with a graphics queue); " - "graphics queue family present", - String(properties.deviceName), deviceCount); + const String deviceFacts = format("{} ({} device(s) enumerated, picked the first with a graphics queue); " + "graphics queue family present", + String(properties.deviceName), deviceCount); if (properties.apiVersion >= VK_API_VERSION_1_1) { builder.Pass("Graphics device", - deviceFacts + - format("; device API Vulkan {}", VkApiVersionToString(properties.apiVersion))); + deviceFacts + format("; device API Vulkan {}", VkApiVersionToString(properties.apiVersion))); } else { builder.Fail("Graphics device", deviceFacts + format("; but Device API version: Vulkan {} (< 1.1); the DirectVulkan " @@ -2761,7 +2725,7 @@ namespace MobileGL::MG_Util::SelfTest { "unsupported; glMultiDraw* batches take the indirect or unrolled tier " "instead, with identical output"); } - const char* resolvedTier = multiDrawExtUsable ? "ext" + const char* resolvedTier = multiDrawExtUsable ? "ext" : features.multiDrawIndirect == VK_TRUE ? "indirect" : "unroll"; String tierDetail = format("default tier \"{}\" (chain: ext -> indirect -> unroll)", resolvedTier); @@ -2809,9 +2773,8 @@ namespace MobileGL::MG_Util::SelfTest { if (features.dualSrcBlend == VK_TRUE) { builder.Pass("dualSrcBlend", "GL_SRC1_* dual-source blend factors supported"); } else { - builder.FailOptional("dualSrcBlend", - "unsupported; a draw using a GL_SRC1_* blend factor hard-fails, and a " - "second fragment output cannot be produced any other way"); + builder.FailOptional("dualSrcBlend", "unsupported; a draw using a GL_SRC1_* blend factor hard-fails, and a " + "second fragment output cannot be produced any other way"); } // The Magma counterpart of the GLES "Buffer textures" row, so the two sections can be // read side by side. Vulkan has no optional-feature bit here: a uniform texel buffer is @@ -2841,8 +2804,8 @@ namespace MobileGL::MG_Util::SelfTest { const Bool sliceCapable = vkGetPhysicalDeviceImageFormatProperties( physicalDevice, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TYPE_3D, VK_IMAGE_TILING_OPTIMAL, - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT, &sliceProbe) == VK_SUCCESS; if (sliceCapable) { builder.Pass("2D-array-compatible 3D images", @@ -2946,8 +2909,7 @@ namespace MobileGL::MG_Util::SelfTest { properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; properties2.pNext = &provokingVertexProperties; vkGetPhysicalDeviceProperties2Fn(physicalDevice, &properties2); - provokingVertexModePerPipeline = - provokingVertexProperties.provokingVertexModePerPipeline == VK_TRUE; + provokingVertexModePerPipeline = provokingVertexProperties.provokingVertexModePerPipeline == VK_TRUE; transformFeedbackPreservesTriangleFanProvokingVertex = provokingVertexProperties.transformFeedbackPreservesTriangleFanProvokingVertex == VK_TRUE; } @@ -3047,13 +3009,13 @@ namespace MobileGL::MG_Util::SelfTest { // Same usability rule as the Vulkan capability loader's // HasUsableShaderSubgroupSupport, which decides native vs emulated in the real backend. ReportShaderSubgroups(builder, subgroupUsable, - subgroupUsable ? format("subgroup size {}, stages 0x{:x}, operations 0x{:x}", - subgroupProperties.subgroupSize, - subgroupProperties.supportedStages, - subgroupProperties.supportedOperations) - : String()); - summary.shaderSubgroupUsable = MG_Backend::ResolveSubgroupImplementation(subgroupUsable) != - MG_Backend::SubgroupImplementation::None; + subgroupUsable + ? format("subgroup size {}, stages 0x{:x}, operations 0x{:x}", + subgroupProperties.subgroupSize, subgroupProperties.supportedStages, + subgroupProperties.supportedOperations) + : String()); + summary.shaderSubgroupUsable = + MG_Backend::ResolveSubgroupImplementation(subgroupUsable) != MG_Backend::SubgroupImplementation::None; } else { ReportShaderSubgroups(builder, false, String()); summary.shaderSubgroupUsable = @@ -3061,7 +3023,7 @@ namespace MobileGL::MG_Util::SelfTest { } ProbeVulkanIterationRPWitness(builder, getInstanceProcAddr, instance, physicalDevice, computeQueueFamilyIndex, - properties, subgroupPropertiesAvailable, subgroupProperties); + properties, subgroupPropertiesAvailable, subgroupProperties); ProbeVulkanPrimitivesGeneratedNoXfb(builder, getInstanceProcAddr, instance, physicalDevice, graphicsQueueFamilyIndex, deviceExtensions, features, @@ -3080,9 +3042,8 @@ namespace MobileGL::MG_Util::SelfTest { } if (HasVkExtension(deviceExtensions, VK_KHR_DRAW_INDIRECT_COUNT_EXTENSION_NAME)) { - builder.Pass("VK_KHR_draw_indirect_count", - "supported (count-buffer indirect draws run as single native " - "vkCmdDraw*IndirectCount commands)"); + builder.Pass("VK_KHR_draw_indirect_count", "supported (count-buffer indirect draws run as single native " + "vkCmdDraw*IndirectCount commands)"); } else { builder.Warn("VK_KHR_draw_indirect_count", "not supported; count-buffer indirect draws (glMultiDraw*IndirectCount) fall " @@ -3110,25 +3071,24 @@ namespace MobileGL::MG_Util::SelfTest { // the graphics queue family and a non-zero tick period. summary.timerQueriesSupported = graphicsQueueTimestampValidBits > 0 && timestampPeriod > 0.0f; if (graphicsQueueTimestampValidBits > 0) { - ProbeVulkanTimerQuery(builder, getInstanceProcAddr, instance, physicalDevice, - graphicsQueueFamilyIndex, graphicsQueueTimestampValidBits, timestampPeriod); + ProbeVulkanTimerQuery(builder, getInstanceProcAddr, instance, physicalDevice, graphicsQueueFamilyIndex, + graphicsQueueTimestampValidBits, timestampPeriod); } else { - builder.FailOptional( - "Timer queries", - format("timestampValidBits = 0 on the graphics queue family; timestampPeriod = {} ns " - "per tick; the graphics queue cannot write a timestamp at all, so there is " - "nothing to time GPU work with and glBeginQuery(GL_TIME_ELAPSED) has no " - "substitute", - timestampPeriod) + - TimerQueryDisabledNote()); + builder.FailOptional("Timer queries", + format("timestampValidBits = 0 on the graphics queue family; timestampPeriod = {} ns " + "per tick; the graphics queue cannot write a timestamp at all, so there is " + "nothing to time GPU work with and glBeginQuery(GL_TIME_ELAPSED) has no " + "substitute", + timestampPeriod) + + TimerQueryDisabledNote()); } if (vkGetPhysicalDeviceFormatPropertiesFn != nullptr) { MG_External::VulkanCapabilities formatProbeCapabilities{}; BackendLoader::FillInVulkanCapabilities(formatProbeCapabilities, properties); builder.report.formatCapabilities.emplace(); - MG_Backend::DirectVulkan::PopulateFormatCapabilities( - physicalDevice, vkGetPhysicalDeviceFormatPropertiesFn, formatProbeCapabilities, - builder.report.formatCapabilities.value()); + MG_Backend::DirectVulkan::PopulateFormatCapabilities(physicalDevice, vkGetPhysicalDeviceFormatPropertiesFn, + formatProbeCapabilities, + builder.report.formatCapabilities.value()); } } diff --git a/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp b/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp index 141d3b01..ddfb6af9 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.cpp @@ -16,8 +16,7 @@ namespace MobileGL::MG_Util::SelfTest { namespace { [[nodiscard]] IterationRPWitnessValidationResult Failure(IterationRPWitnessValidationFailure failure, - std::string detail, - std::uint32_t scanStage = 0u, + std::string detail, std::uint32_t scanStage = 0u, std::uint32_t subgroup = 0u) { IterationRPWitnessValidationResult result; result.ok = false; @@ -60,8 +59,7 @@ namespace MobileGL::MG_Util::SelfTest { } } // namespace - IterationRPWitnessEligibilityResult - EvaluateIterationRPWitnessEligibility(const IterationRPWitnessLimits& limits) { + IterationRPWitnessEligibilityResult EvaluateIterationRPWitnessEligibility(const IterationRPWitnessLimits& limits) { // This classification deliberately precedes numeric limits. An absent native // compute/basic/arithmetic subgroup contract means there is nothing to witness, // whereas every resource/entry-point failure on a capable device is a POST FAIL. @@ -70,9 +68,10 @@ namespace MobileGL::MG_Util::SelfTest { if (!limits.computeStageSupported) missing.emplace_back("VK_SHADER_STAGE_COMPUTE_BIT"); if (!limits.basicSubgroupSupported) missing.emplace_back("VK_SUBGROUP_FEATURE_BASIC_BIT"); if (!limits.arithmeticSubgroupSupported) missing.emplace_back("VK_SUBGROUP_FEATURE_ARITHMETIC_BIT"); - return {IterationRPWitnessEligibility::SkipUnsupportedNativeFeatureSet, - "skipped because the native compute/basic/arithmetic subgroup feature set is unsupported (missing " + - JoinRequirements(missing) + ')'}; + return { + IterationRPWitnessEligibility::SkipUnsupportedNativeFeatureSet, + "skipped because the native compute/basic/arithmetic subgroup feature set is unsupported (missing " + + JoinRequirements(missing) + ')'}; } std::vector inadequate; @@ -100,14 +99,13 @@ namespace MobileGL::MG_Util::SelfTest { inadequate.emplace_back("maxBoundDescriptorSets < 1"); } if (limits.maxStorageBufferRange < sizeof(IterationRPWitnessOutput)) { - inadequate.emplace_back("maxStorageBufferRange < " + - std::to_string(sizeof(IterationRPWitnessOutput))); + inadequate.emplace_back("maxStorageBufferRange < " + std::to_string(sizeof(IterationRPWitnessOutput))); } if (!inadequate.empty()) { return {IterationRPWitnessEligibility::FailInadequateLimits, "insufficient Vulkan limits for a 32x16x1 workgroup, one output SSBO, and " + - std::to_string(kIterationRPWitnessSharedMemoryBytes) + " bytes of shared memory: " + - JoinRequirements(inadequate)}; + std::to_string(kIterationRPWitnessSharedMemoryBytes) + + " bytes of shared memory: " + JoinRequirements(inadequate)}; } return {IterationRPWitnessEligibility::Execute, {}}; } @@ -159,10 +157,9 @@ namespace MobileGL::MG_Util::SelfTest { return Failure(IterationRPWitnessValidationFailure::Topology, "topology: an invocation reported an invalid subgroup lane"); } - if ((output.topologyFlags & ~(IterationRPWitnessNonuniformNumSubgroups | - IterationRPWitnessInvalidNumSubgroups | - IterationRPWitnessInvalidSubgroupId | - IterationRPWitnessInvalidSubgroupLane)) != 0u) { + if ((output.topologyFlags & ~(IterationRPWitnessNonuniformNumSubgroups | IterationRPWitnessInvalidNumSubgroups | + IterationRPWitnessInvalidSubgroupId | IterationRPWitnessInvalidSubgroupLane)) != + 0u) { std::ostringstream detail; detail << "topology: unknown topology flags 0x" << std::hex << output.topologyFlags; return Failure(IterationRPWitnessValidationFailure::Topology, detail.str()); @@ -170,8 +167,8 @@ namespace MobileGL::MG_Util::SelfTest { const std::uint32_t expectedMask = ExpectedSeenSubgroupMask(numSubgroups); if (output.seenSubgroupMask != expectedMask) { std::ostringstream detail; - detail << "topology: seen subgroup-ID mask was 0x" << std::hex << output.seenSubgroupMask - << ", expected 0x" << expectedMask; + detail << "topology: seen subgroup-ID mask was 0x" << std::hex << output.seenSubgroupMask << ", expected 0x" + << expectedMask; return Failure(IterationRPWitnessValidationFailure::Topology, detail.str()); } const std::uint32_t expectedLoopLength = ComputeIterationRPWitnessLoopLength(numSubgroups); @@ -184,8 +181,8 @@ namespace MobileGL::MG_Util::SelfTest { for (std::uint32_t subgroup = 0u; subgroup < numSubgroups; ++subgroup) { if (output.lastLaneWriterCount[subgroup] != 1u) { std::ostringstream detail; - detail << "topology: subgroup " << subgroup << " has " - << output.lastLaneWriterCount[subgroup] << " source last-lane writers, expected exactly 1"; + detail << "topology: subgroup " << subgroup << " has " << output.lastLaneWriterCount[subgroup] + << " source last-lane writers, expected exactly 1"; return Failure(IterationRPWitnessValidationFailure::Topology, detail.str(), 0u, subgroup); } } @@ -203,8 +200,8 @@ namespace MobileGL::MG_Util::SelfTest { } if (output.owner511.x == 0u || output.owner511.w != output.owner511.x - 1u) { std::ostringstream detail; - detail << "final owner: invocation 511 is not the last lane of highest subgroup (size " - << output.owner511.x << ", lane " << output.owner511.w << ')'; + detail << "final owner: invocation 511 is not the last lane of highest subgroup (size " << output.owner511.x + << ", lane " << output.owner511.w << ')'; return Failure(IterationRPWitnessValidationFailure::FinalOwner, detail.str()); } @@ -226,8 +223,7 @@ namespace MobileGL::MG_Util::SelfTest { std::ostringstream detail; detail << "initial subgroup handoff: subgroup " << subgroup << " rawPrefix observed " << Vec2String(output.rawPrefix[subgroup]) << ", expected " << Vec2String(expected); - return Failure(IterationRPWitnessValidationFailure::InitialSubgroupHandoff, detail.str(), 0u, - subgroup); + return Failure(IterationRPWitnessValidationFailure::InitialSubgroupHandoff, detail.str(), 0u, subgroup); } } @@ -266,8 +262,8 @@ namespace MobileGL::MG_Util::SelfTest { } std::ostringstream detail; - detail << "N=" << numSubgroups << ", owner511=id" << output.owner511.z << "/lane" << output.owner511.w - << ", " << expectedLoopLength << " scan stages, average=" << Vec2String(output.finalAverage); + detail << "N=" << numSubgroups << ", owner511=id" << output.owner511.z << "/lane" << output.owner511.w << ", " + << expectedLoopLength << " scan stages, average=" << Vec2String(output.finalAverage); IterationRPWitnessValidationResult result; result.ok = true; result.failure = IterationRPWitnessValidationFailure::None; diff --git a/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.h b/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.h index 1999ed4f..7be1b3b4 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.h +++ b/MobileGL/MG_Util/SelfTest/DriverPostIterationRPWitness.h @@ -92,8 +92,7 @@ namespace MobileGL::MG_Util::SelfTest { // two 32-entry scalar diagnostic arrays in the GLSL source. Keep this // independent of the output SSBO size. constexpr std::uint32_t kIterationRPWitnessSharedMemoryBytes = - kIterationRPWitnessMaxSubgroups * sizeof(IterationRPWitnessVec2) + - 3u * sizeof(std::uint32_t) + + kIterationRPWitnessMaxSubgroups * sizeof(IterationRPWitnessVec2) + 3u * sizeof(std::uint32_t) + 2u * kIterationRPWitnessMaxSubgroups * sizeof(std::uint32_t); enum class IterationRPWitnessEligibility { @@ -142,12 +141,11 @@ namespace MobileGL::MG_Util::SelfTest { std::string detail; }; - [[nodiscard]] IterationRPWitnessEligibilityResult - EvaluateIterationRPWitnessEligibility(const IterationRPWitnessLimits& limits); + [[nodiscard]] IterationRPWitnessEligibilityResult EvaluateIterationRPWitnessEligibility( + const IterationRPWitnessLimits& limits); // Mirrors the source's findMSB expression for valid N in [2, 32]. [[nodiscard]] std::uint32_t ComputeIterationRPWitnessLoopLength(std::uint32_t numSubgroups); - [[nodiscard]] IterationRPWitnessValidationResult - ValidateIterationRPWitness(const IterationRPWitnessOutput& output); + [[nodiscard]] IterationRPWitnessValidationResult ValidateIterationRPWitness(const IterationRPWitnessOutput& output); } // namespace MobileGL::MG_Util::SelfTest diff --git a/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp b/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp index 79a1b439..a7d03031 100644 --- a/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp +++ b/MobileGL/MG_Util/SelfTest/DriverPostJni.cpp @@ -76,8 +76,8 @@ namespace { Uint64 BuildFormatCapabilityMask(FormatCapabilityFlags capabilities) { Uint64 mask = 0; - for (SizeT capabilityIndex = 0; - capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); ++capabilityIndex) { + for (SizeT capabilityIndex = 0; capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); + ++capabilityIndex) { if (MobileGL::MG_Backend::HasFormatCapability( capabilities, MobileGL::MG_Backend::kReportedFormatCapabilities[capabilityIndex])) { mask |= 1ull << capabilityIndex; @@ -88,18 +88,16 @@ namespace { void AppendFormatCapabilitiesJson(StringStream& out, const FormatCapabilityCache& cache) { out << ",\"formatCapabilities\":{\"capabilities\":["; - for (SizeT capabilityIndex = 0; - capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); ++capabilityIndex) { + for (SizeT capabilityIndex = 0; capabilityIndex < MobileGL::MG_Backend::kReportedFormatCapabilities.size(); + ++capabilityIndex) { if (capabilityIndex != 0) { out << ','; } - AppendJsonString( - out, MobileGL::MG_Backend::GetFormatCapabilityName( - MobileGL::MG_Backend::kReportedFormatCapabilities[capabilityIndex])); + AppendJsonString(out, MobileGL::MG_Backend::GetFormatCapabilityName( + MobileGL::MG_Backend::kReportedFormatCapabilities[capabilityIndex])); } out << "],\"targets\":["; - for (SizeT targetIndex = 0; targetIndex < MobileGL::MG_Backend::kFormatCapabilityTargetCount; - ++targetIndex) { + for (SizeT targetIndex = 0; targetIndex < MobileGL::MG_Backend::kFormatCapabilityTargetCount; ++targetIndex) { if (targetIndex != 0) { out << ','; } @@ -112,9 +110,8 @@ namespace { out << ','; } out << '['; - AppendJsonString( - out, MobileGL::MG_Util::ConvertTextureInternalFormatToString( - static_cast(formatIndex))); + AppendJsonString(out, MobileGL::MG_Util::ConvertTextureInternalFormatToString( + static_cast(formatIndex))); out << ',' << BuildFormatCapabilityMask(cache.FullCaps[targetIndex][formatIndex]); out << ',' << BuildFormatCapabilityMask(cache.CaveatCaps[targetIndex][formatIndex]) << ']'; } @@ -157,9 +154,8 @@ namespace { out << "{\"name\":"; AppendJsonString(out, bug.name); out << ",\"verdict\":"; - AppendJsonString(out, bug.verdict == MobileGL::MG_Util::SelfTest::DriverBugVerdict::Fixed - ? "FIXED" - : "UNFIXABLE"); + AppendJsonString(out, bug.verdict == MobileGL::MG_Util::SelfTest::DriverBugVerdict::Fixed ? "FIXED" + : "UNFIXABLE"); out << ",\"detail\":"; AppendJsonString(out, bug.detail); out << '}'; @@ -184,9 +180,11 @@ extern "C" JNIEXPORT jstring JNICALL Java_top_mobilegl_plugin_PostActivity_nativ AppendBackendReportJson(out, vulkanReport); out << '}'; json = out.str(); - } catch (const std::exception& exception) { + } + catch (const std::exception& exception) { json = "{\"error\":\"" + EscapeJsonString(exception.what()) + "\"}"; - } catch (...) { + } + catch (...) { json = "{\"error\":\"driver POST failed with an unknown native exception\"}"; } return env->NewStringUTF(json.c_str()); diff --git a/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp b/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp index 0b62fc57..887c83a0 100644 --- a/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp +++ b/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.cpp @@ -30,9 +30,18 @@ namespace MobileGL::MG_Util::SelfTest { constexpr GLsizei kQuads = 64 * 32; constexpr Int kAttempts = 3; constexpr std::array kUploadNames = {"SubData", "Copy/persistent staging", - "Copy/SubData staging"}; - enum class Shape { Unmapped, Mapped, FinishBefore, FinishBoth, MapThenUnmap, BarrierBefore }; - struct Vertex { GLfloat x, y, r, g, b; }; + "Copy/SubData staging"}; + enum class Shape { + Unmapped, + Mapped, + FinishBefore, + FinishBoth, + MapThenUnmap, + BarrierBefore + }; + struct Vertex { + GLfloat x, y, r, g, b; + }; constexpr GLsizeiptr kPayloadSize = kQuads * 6 * sizeof(Vertex); static_assert(kOffset + kPayloadSize <= kArenaSize); @@ -41,20 +50,20 @@ namespace MobileGL::MG_Util::SelfTest { } Bool CanProbe(const GLESFunctionsTable& gl) { - if (!(gl.glGetIntegerv && gl.glGetBooleanv && gl.glGetFloatv && gl.glGetError && - gl.glGetStringi && gl.glIsEnabled && gl.glEnable && gl.glDisable && - gl.glCreateShader && gl.glShaderSource && gl.glCompileShader && gl.glGetShaderiv && - gl.glGetShaderInfoLog && gl.glDeleteShader && gl.glCreateProgram && gl.glAttachShader && - gl.glLinkProgram && gl.glGetProgramiv && gl.glGetProgramInfoLog && gl.glDeleteProgram && - gl.glUseProgram && gl.glGenBuffers && gl.glBindBuffer && gl.glBufferStorageEXT && - gl.glMapBufferRange && gl.glUnmapBuffer && gl.glBufferData && gl.glBufferSubData && - gl.glCopyBufferSubData && gl.glDeleteBuffers && gl.glGenVertexArrays && - gl.glBindVertexArray && gl.glVertexAttribPointer && gl.glEnableVertexAttribArray && - gl.glDeleteVertexArrays && gl.glGenTextures && gl.glBindTexture && gl.glTexStorage2D && - gl.glDeleteTextures && gl.glGenFramebuffers && gl.glBindFramebuffer && + if (!(gl.glGetIntegerv && gl.glGetBooleanv && gl.glGetFloatv && gl.glGetError && gl.glGetStringi && + gl.glIsEnabled && gl.glEnable && gl.glDisable && gl.glCreateShader && gl.glShaderSource && + gl.glCompileShader && gl.glGetShaderiv && gl.glGetShaderInfoLog && gl.glDeleteShader && + gl.glCreateProgram && gl.glAttachShader && gl.glLinkProgram && gl.glGetProgramiv && + gl.glGetProgramInfoLog && gl.glDeleteProgram && gl.glUseProgram && gl.glGenBuffers && + gl.glBindBuffer && gl.glBufferStorageEXT && gl.glMapBufferRange && gl.glUnmapBuffer && + gl.glBufferData && gl.glBufferSubData && gl.glCopyBufferSubData && gl.glDeleteBuffers && + gl.glGenVertexArrays && gl.glBindVertexArray && gl.glVertexAttribPointer && + gl.glEnableVertexAttribArray && gl.glDeleteVertexArrays && gl.glGenTextures && gl.glBindTexture && + gl.glTexStorage2D && gl.glDeleteTextures && gl.glGenFramebuffers && gl.glBindFramebuffer && gl.glFramebufferTexture2D && gl.glCheckFramebufferStatus && gl.glDeleteFramebuffers && - gl.glViewport && gl.glColorMask && gl.glClearColor && gl.glClear && gl.glDrawArrays && - gl.glFinish && gl.glMemoryBarrier && gl.glPixelStorei && gl.glReadPixels)) return false; + gl.glViewport && gl.glColorMask && gl.glClearColor && gl.glClear && gl.glDrawArrays && gl.glFinish && + gl.glMemoryBarrier && gl.glPixelStorei && gl.glReadPixels)) + return false; DrainErrors(gl); GLint major = 0, minor = 0, count = 0; gl.glGetIntegerv(GL_MAJOR_VERSION, &major); @@ -63,8 +72,8 @@ namespace MobileGL::MG_Util::SelfTest { if (gl.glGetError() != GL_NO_ERROR || major < 3 || (major == 3 && minor < 1)) return false; for (GLint i = 0; i < count; ++i) { const auto* extension = gl.glGetStringi(GL_EXTENSIONS, i); - if (extension && std::strcmp(reinterpret_cast(extension), - "GL_EXT_buffer_storage") == 0) return true; + if (extension && std::strcmp(reinterpret_cast(extension), "GL_EXT_buffer_storage") == 0) + return true; } return false; } @@ -79,11 +88,11 @@ namespace MobileGL::MG_Util::SelfTest { GLfloat clear[4]{}; GLboolean colorMask[4]{}; static constexpr std::array enables = { - GL_BLEND, GL_DEPTH_TEST, GL_STENCIL_TEST, GL_CULL_FACE, GL_SCISSOR_TEST, - GL_RASTERIZER_DISCARD, GL_DITHER, GL_SAMPLE_ALPHA_TO_COVERAGE, + GL_BLEND, GL_DEPTH_TEST, GL_STENCIL_TEST, GL_CULL_FACE, + GL_SCISSOR_TEST, GL_RASTERIZER_DISCARD, GL_DITHER, GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE, GL_SAMPLE_MASK}; - static constexpr std::array packNames = { - GL_PACK_ALIGNMENT, GL_PACK_ROW_LENGTH, GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS}; + static constexpr std::array packNames = {GL_PACK_ALIGNMENT, GL_PACK_ROW_LENGTH, + GL_PACK_SKIP_PIXELS, GL_PACK_SKIP_ROWS}; std::array enabled{}; std::array pack{}; @@ -100,14 +109,18 @@ namespace MobileGL::MG_Util::SelfTest { gl.glGetIntegerv(GL_VIEWPORT, viewport); gl.glGetFloatv(GL_COLOR_CLEAR_VALUE, clear); gl.glGetBooleanv(GL_COLOR_WRITEMASK, colorMask); - for (SizeT i = 0; i < enables.size(); ++i) enabled[i] = gl.glIsEnabled(enables[i]); - for (SizeT i = 0; i < packNames.size(); ++i) gl.glGetIntegerv(packNames[i], &pack[i]); + for (SizeT i = 0; i < enables.size(); ++i) + enabled[i] = gl.glIsEnabled(enables[i]); + for (SizeT i = 0; i < packNames.size(); ++i) + gl.glGetIntegerv(packNames[i], &pack[i]); } void Prepare() { - for (auto cap : enables) gl.glDisable(cap); + for (auto cap : enables) + gl.glDisable(cap); gl.glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); gl.glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - for (auto name : packNames) gl.glPixelStorei(name, name == GL_PACK_ALIGNMENT ? 1 : 0); + for (auto name : packNames) + gl.glPixelStorei(name, name == GL_PACK_ALIGNMENT ? 1 : 0); gl.glViewport(0, 0, kSide, kSide); gl.glClearColor(0, 0, 0, 1); } @@ -125,9 +138,13 @@ namespace MobileGL::MG_Util::SelfTest { gl.glClearColor(clear[0], clear[1], clear[2], clear[3]); gl.glColorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]); for (SizeT i = 0; i < enables.size(); ++i) { - if (enabled[i]) gl.glEnable(enables[i]); else gl.glDisable(enables[i]); + if (enabled[i]) + gl.glEnable(enables[i]); + else + gl.glDisable(enables[i]); } - for (SizeT i = 0; i < packNames.size(); ++i) gl.glPixelStorei(packNames[i], pack[i]); + for (SizeT i = 0; i < packNames.size(); ++i) + gl.glPixelStorei(packNames[i], pack[i]); } }; @@ -159,12 +176,14 @@ namespace MobileGL::MG_Util::SelfTest { return shader; } Bool Setup() { - const GLuint vs = Compile(GL_VERTEX_SHADER, - "#version 310 es\nlayout(location=0) in vec2 pos; layout(location=1) in vec3 color;\n" - "out highp vec3 vColor; void main(){gl_Position=vec4(pos,0,1);vColor=color;}\n"); - const GLuint fs = Compile(GL_FRAGMENT_SHADER, - "#version 310 es\nprecision highp float; in highp vec3 vColor;\n" - "layout(location=0) out vec4 outColor; void main(){outColor=vec4(vColor,1);}\n"); + const GLuint vs = + Compile(GL_VERTEX_SHADER, + "#version 310 es\nlayout(location=0) in vec2 pos; layout(location=1) in vec3 color;\n" + "out highp vec3 vColor; void main(){gl_Position=vec4(pos,0,1);vColor=color;}\n"); + const GLuint fs = + Compile(GL_FRAGMENT_SHADER, + "#version 310 es\nprecision highp float; in highp vec3 vColor;\n" + "layout(location=0) out vec4 outColor; void main(){outColor=vec4(vColor,1);}\n"); if (vs && fs) { program = gl.glCreateProgram(); if (program) { @@ -194,8 +213,7 @@ namespace MobileGL::MG_Util::SelfTest { gl.glBindTexture(GL_TEXTURE_2D, textures[i]); gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSide, kSide); gl.glBindFramebuffer(GL_FRAMEBUFFER, fbos[i]); - gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, - textures[i], 0); + gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[i], 0); if (gl.glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) return false; } return gl.glGetError() == GL_NO_ERROR; @@ -216,8 +234,12 @@ namespace MobileGL::MG_Util::SelfTest { }; void FillVertices(Vector& vertices, Int channel) { - constexpr std::array quad = {{{-1,-1,0,0,0}, {1,-1,0,0,0}, {1,1,0,0,0}, - {-1,-1,0,0,0}, {1,1,0,0,0}, {-1,1,0,0,0}}}; + constexpr std::array quad = {{{-1, -1, 0, 0, 0}, + {1, -1, 0, 0, 0}, + {1, 1, 0, 0, 0}, + {-1, -1, 0, 0, 0}, + {1, 1, 0, 0, 0}, + {-1, 1, 0, 0, 0}}}; for (SizeT k = 0; k < vertices.size(); ++k) { auto& v = vertices[k]; v = quad[k % 6]; @@ -230,8 +252,8 @@ namespace MobileGL::MG_Util::SelfTest { } } - BufferOrderingSample Run(const GLESFunctionsTable& gl, const Resources& resources, - const Vector& seed, Int upload, Shape shape) { + BufferOrderingSample Run(const GLESFunctionsTable& gl, const Resources& resources, const Vector& seed, + Int upload, Shape shape) { BufferOrderingSample sample; sample.status = BufferOrderingProbeStatus::Failed; Buffers buffers(gl); @@ -252,9 +274,9 @@ namespace MobileGL::MG_Util::SelfTest { if (shape == Shape::MapThenUnmap && !gl.glUnmapBuffer(GL_ARRAY_BUFFER)) break; } gl.glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), - reinterpret_cast(kOffset)); + reinterpret_cast(kOffset)); gl.glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), - reinterpret_cast(kOffset + 2 * sizeof(GLfloat))); + reinterpret_cast(kOffset + 2 * sizeof(GLfloat))); gl.glEnableVertexAttribArray(0); gl.glEnableVertexAttribArray(1); void* sourceMap = nullptr; @@ -283,19 +305,20 @@ namespace MobileGL::MG_Util::SelfTest { } else { // No slot is reused until the entire batch has finished on the GPU. if (upload == 1) { - std::memcpy(static_cast(sourceMap) + slot * kPayloadSize, - payload.data(), kPayloadSize); + std::memcpy(static_cast(sourceMap) + slot * kPayloadSize, payload.data(), + kPayloadSize); } else { - gl.glBufferSubData(GL_COPY_READ_BUFFER, slot * kPayloadSize, - kPayloadSize, payload.data()); + gl.glBufferSubData(GL_COPY_READ_BUFFER, slot * kPayloadSize, kPayloadSize, + payload.data()); } - gl.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, - slot * kPayloadSize, kOffset, kPayloadSize); + gl.glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, slot * kPayloadSize, + kOffset, kPayloadSize); } if (shape == Shape::FinishBoth) gl.glFinish(); gl.glBindFramebuffer(GL_FRAMEBUFFER, resources.fbos[slot]); gl.glClear(GL_COLOR_BUFFER_BIT); - for (Int draw = 0; draw < kDraws; ++draw) gl.glDrawArrays(GL_TRIANGLES, 0, kQuads * 6); + for (Int draw = 0; draw < kDraws; ++draw) + gl.glDrawArrays(GL_TRIANGLES, 0, kQuads * 6); } // No readback/Finish between subject update/draw pairs. Early readback // would hide precisely the old-reader/new-writer overlap being tested. @@ -337,9 +360,10 @@ namespace MobileGL::MG_Util::SelfTest { String DescribeUpload(const BufferOrderingUploadMeasurement& row, Int upload) { return format("{}: mapped {}, never-mapped {}, Finish-before {}, Finish-both {}, " - "map-then-unmap {}, barrier-before {}", kUploadNames[upload], Describe(row.mapped), - Describe(row.unmapped), Describe(row.finishBefore), Describe(row.finishBoth), - Describe(row.mapThenUnmap), Describe(row.barrierBefore)); + "map-then-unmap {}, barrier-before {}", + kUploadNames[upload], Describe(row.mapped), Describe(row.unmapped), + Describe(row.finishBefore), Describe(row.finishBoth), Describe(row.mapThenUnmap), + Describe(row.barrierBefore)); } } // namespace @@ -351,7 +375,8 @@ namespace MobileGL::MG_Util::SelfTest { state.Prepare(); Resources resources(gl); if (gl.glGetError() != GL_NO_ERROR || !resources.Setup()) { - for (auto& row : measurement.uploads) row.unmapped.status = BufferOrderingProbeStatus::Failed; + for (auto& row : measurement.uploads) + row.unmapped.status = BufferOrderingProbeStatus::Failed; MGLOG_I("[driver-bug] persistent buffer ordering: setup failed; inconclusive"); return measurement; } @@ -382,13 +407,15 @@ namespace MobileGL::MG_Util::SelfTest { row.Detected() ? "detected" : "not detected or inconclusive"); } return measurement; - } catch (const std::bad_alloc&) { + } + catch (const std::bad_alloc&) { // The CPU initializer is arena-sized too. An allocation failure must not discard // the rest of the POST report or turn a partially sampled case into a finding. MGLOG_I("[driver-bug] persistent buffer ordering: host allocation failed; inconclusive"); PersistentBufferOrderingMeasurement measurement; measurement.supported = true; - for (auto& row : measurement.uploads) row.unmapped.status = BufferOrderingProbeStatus::Failed; + for (auto& row : measurement.uploads) + row.unmapped.status = BufferOrderingProbeStatus::Failed; return measurement; } diff --git a/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.h b/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.h index b4d457e6..505a62ae 100644 --- a/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.h +++ b/MobileGL/MG_Util/SelfTest/PersistentBufferOrderingProbe.h @@ -12,7 +12,11 @@ #include namespace MobileGL::MG_Util::SelfTest { - enum class BufferOrderingProbeStatus : Uint8 { NotRun, Complete, Failed }; + enum class BufferOrderingProbeStatus : Uint8 { + NotRun, + Complete, + Failed + }; struct BufferOrderingSample { BufferOrderingProbeStatus status = BufferOrderingProbeStatus::NotRun; @@ -33,8 +37,8 @@ namespace MobileGL::MG_Util::SelfTest { BufferOrderingSample barrierBefore; Bool Detected() const { - return unmapped.Passed() && finishBoth.Passed() && - mapped.status == BufferOrderingProbeStatus::Complete && mapped.badFrames != 0; + return unmapped.Passed() && finishBoth.Passed() && mapped.status == BufferOrderingProbeStatus::Complete && + mapped.badFrames != 0; } }; @@ -57,8 +61,7 @@ namespace MobileGL::MG_Util::SelfTest { // Finish-before-and-after control permits a finding. Setup/GL failures are inconclusive. // Explicit allocations are one 128 MiB arena, its initializer, and small staging/FBOs; // allocations, batches and draws are bounded. Every touched GL state is restored. - PersistentBufferOrderingMeasurement ProbePersistentBufferUpdateOrdering( - const MG_External::GLESFunctionsTable& gl); + PersistentBufferOrderingMeasurement ProbePersistentBufferUpdateOrdering(const MG_External::GLESFunctionsTable& gl); // Used by the POST collector. A report never labels an inconclusive sample as a bug. Optional DescribePersistentBufferOrderingBug( diff --git a/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp b/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp index 84f40dc7..846a8b93 100644 --- a/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp +++ b/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbe.cpp @@ -40,9 +40,8 @@ namespace MobileGL::MG_Util::SelfTest { fns.vkCreateShaderModule != nullptr && fns.vkDestroyShaderModule != nullptr && fns.vkCreatePipelineLayout != nullptr && fns.vkDestroyPipelineLayout != nullptr && fns.vkCreateGraphicsPipelines != nullptr && fns.vkDestroyPipeline != nullptr && - fns.vkCmdBindPipeline != nullptr && fns.vkCmdDraw != nullptr && - fns.vkCreateFence != nullptr && fns.vkDestroyFence != nullptr && - fns.vkQueueSubmit != nullptr && fns.vkWaitForFences != nullptr && + fns.vkCmdBindPipeline != nullptr && fns.vkCmdDraw != nullptr && fns.vkCreateFence != nullptr && + fns.vkDestroyFence != nullptr && fns.vkQueueSubmit != nullptr && fns.vkWaitForFences != nullptr && fns.vkGetQueryPoolResults != nullptr && fns.vkDeviceWaitIdle != nullptr; } } // namespace @@ -111,12 +110,10 @@ namespace MobileGL::MG_Util::SelfTest { } fns.vkDeviceWaitIdle(device); if (fence != VK_NULL_HANDLE) fns.vkDestroyFence(device, fence, nullptr); - if (trianglePlainPipeline != VK_NULL_HANDLE) - fns.vkDestroyPipeline(device, trianglePlainPipeline, nullptr); + if (trianglePlainPipeline != VK_NULL_HANDLE) fns.vkDestroyPipeline(device, trianglePlainPipeline, nullptr); if (triangleDiscardPipeline != VK_NULL_HANDLE) fns.vkDestroyPipeline(device, triangleDiscardPipeline, nullptr); - if (patchDiscardPipeline != VK_NULL_HANDLE) - fns.vkDestroyPipeline(device, patchDiscardPipeline, nullptr); + if (patchDiscardPipeline != VK_NULL_HANDLE) fns.vkDestroyPipeline(device, patchDiscardPipeline, nullptr); if (pipelineLayout != VK_NULL_HANDLE) fns.vkDestroyPipelineLayout(device, pipelineLayout, nullptr); if (vertModule != VK_NULL_HANDLE) fns.vkDestroyShaderModule(device, vertModule, nullptr); if (tescModule != VK_NULL_HANDLE) fns.vkDestroyShaderModule(device, tescModule, nullptr); @@ -159,8 +156,7 @@ namespace MobileGL::MG_Util::SelfTest { pgqPoolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO; pgqPoolInfo.queryType = VK_QUERY_TYPE_PRIMITIVES_GENERATED_EXT; pgqPoolInfo.queryCount = kShapeSlots; - if (fns.vkCreateQueryPool(device, &pgqPoolInfo, nullptr, &primitivesGeneratedQueryPool) != - VK_SUCCESS) { + if (fns.vkCreateQueryPool(device, &pgqPoolInfo, nullptr, &primitivesGeneratedQueryPool) != VK_SUCCESS) { return fail("vkCreateQueryPool(PRIMITIVES_GENERATED_EXT) failed"); } } @@ -305,8 +301,7 @@ namespace MobileGL::MG_Util::SelfTest { pipelineInfo.layout = pipelineLayout; pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; - return fns.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &out) == - VK_SUCCESS; + return fns.vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &out) == VK_SUCCESS; }; if (!makePipeline(false, false, trianglePlainPipeline)) { return fail("vkCreateGraphicsPipelines(triangles) failed"); @@ -451,9 +446,8 @@ namespace MobileGL::MG_Util::SelfTest { if (!measurement.ran || !measurement.trianglesPlain.drawn || !measurement.trianglesDiscard.drawn) { return PrimitivesGeneratedNoXfbVerdict::Inconclusive; } - const PrimitivesGeneratedNoXfbShapeMeasurement* shapes[3] = {&measurement.trianglesPlain, - &measurement.trianglesDiscard, - &measurement.patchesDiscard}; + const PrimitivesGeneratedNoXfbShapeMeasurement* shapes[3] = { + &measurement.trianglesPlain, &measurement.trianglesDiscard, &measurement.patchesDiscard}; Bool anyStreamSilent = false; Bool allStreamExact = true; Bool allPrimitivesGeneratedExtExact = true; @@ -483,12 +477,10 @@ namespace MobileGL::MG_Util::SelfTest { return PrimitivesGeneratedNoXfbVerdict::Inconclusive; } } - if (!shape->primitivesGeneratedExtMeasured || - shape->primitivesGeneratedExt != shape->expectedPrimitives) { + if (!shape->primitivesGeneratedExtMeasured || shape->primitivesGeneratedExt != shape->expectedPrimitives) { allPrimitivesGeneratedExtExact = false; } - if (!shape->statisticsMeasured || - shape->statisticsClippingInput != shape->expectedPrimitives) { + if (!shape->statisticsMeasured || shape->statisticsClippingInput != shape->expectedPrimitives) { allStatisticsExact = false; // Only a shape the stream query was silent on may be left behind by // the substitute; a shape it answered exactly must not be traded away. @@ -533,8 +525,7 @@ namespace MobileGL::MG_Util::SelfTest { if (primitivesGeneratedQueryUsable) { return PrimGenRerouteKind::PrimitivesGeneratedExt; } - return pipelineStatisticsEnabled ? PrimGenRerouteKind::ClippingStatistics - : PrimGenRerouteKind::None; + return pipelineStatisticsEnabled ? PrimGenRerouteKind::ClippingStatistics : PrimGenRerouteKind::None; case MG_Config::QuirkOverride::Auto: break; } @@ -544,8 +535,7 @@ namespace MobileGL::MG_Util::SelfTest { : PrimGenRerouteKind::None; case PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitute: case PrimitivesGeneratedNoXfbVerdict::StatisticsSubstitutePlainOnly: - return pipelineStatisticsEnabled ? PrimGenRerouteKind::ClippingStatistics - : PrimGenRerouteKind::None; + return pipelineStatisticsEnabled ? PrimGenRerouteKind::ClippingStatistics : PrimGenRerouteKind::None; case PrimitivesGeneratedNoXfbVerdict::Inconclusive: case PrimitivesGeneratedNoXfbVerdict::StreamCounts: case PrimitivesGeneratedNoXfbVerdict::Unfixable: diff --git a/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h b/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h index a5cb1500..663b9eda 100644 --- a/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h +++ b/MobileGL/MG_Util/SelfTest/PrimitivesGeneratedNoXfbProbeSpv.h @@ -22,134 +22,99 @@ namespace MobileGL::MG_Util::SelfTest { inline constexpr std::uint32_t kPrimitivesGeneratedNoXfbProbeVertSpv[] = { - 0x07230203, 0x00010300, 0x0008000b, 0x0000002a, 0x00000000, 0x00020011, - 0x00000001, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, - 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000000, - 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x0000001a, 0x00030003, - 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, - 0x00060005, 0x0000000b, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, - 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, - 0x00070006, 0x0000000b, 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, - 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, - 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, - 0x446c6c75, 0x61747369, 0x0065636e, 0x00030005, 0x0000000d, 0x00000000, - 0x00060005, 0x0000001a, 0x565f6c67, 0x65747265, 0x646e4978, 0x00007865, - 0x00050005, 0x0000001f, 0x65646e69, 0x6c626178, 0x00000065, 0x00030047, - 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, - 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, - 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, - 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00040047, 0x0000001a, - 0x0000000b, 0x0000002a, 0x00020013, 0x00000002, 0x00030021, 0x00000003, - 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, - 0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000, - 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, - 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, - 0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, - 0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, - 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, - 0x00040017, 0x00000010, 0x00000006, 0x00000002, 0x0004002b, 0x00000008, - 0x00000011, 0x00000003, 0x0004001c, 0x00000012, 0x00000010, 0x00000011, - 0x0004002b, 0x00000006, 0x00000013, 0xbf800000, 0x0005002c, 0x00000010, - 0x00000014, 0x00000013, 0x00000013, 0x0004002b, 0x00000006, 0x00000015, - 0x40400000, 0x0005002c, 0x00000010, 0x00000016, 0x00000015, 0x00000013, - 0x0005002c, 0x00000010, 0x00000017, 0x00000013, 0x00000015, 0x0006002c, - 0x00000012, 0x00000018, 0x00000014, 0x00000016, 0x00000017, 0x00040020, - 0x00000019, 0x00000001, 0x0000000e, 0x0004003b, 0x00000019, 0x0000001a, - 0x00000001, 0x0004002b, 0x0000000e, 0x0000001c, 0x00000003, 0x00040020, - 0x0000001e, 0x00000007, 0x00000012, 0x00040020, 0x00000020, 0x00000007, - 0x00000010, 0x0004002b, 0x00000006, 0x00000023, 0x00000000, 0x0004002b, - 0x00000006, 0x00000024, 0x3f800000, 0x00040020, 0x00000028, 0x00000003, - 0x00000007, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, - 0x000200f8, 0x00000005, 0x0004003b, 0x0000001e, 0x0000001f, 0x00000007, - 0x0004003d, 0x0000000e, 0x0000001b, 0x0000001a, 0x0005008b, 0x0000000e, - 0x0000001d, 0x0000001b, 0x0000001c, 0x0003003e, 0x0000001f, 0x00000018, - 0x00050041, 0x00000020, 0x00000021, 0x0000001f, 0x0000001d, 0x0004003d, - 0x00000010, 0x00000022, 0x00000021, 0x00050051, 0x00000006, 0x00000025, - 0x00000022, 0x00000000, 0x00050051, 0x00000006, 0x00000026, 0x00000022, - 0x00000001, 0x00070050, 0x00000007, 0x00000027, 0x00000025, 0x00000026, - 0x00000023, 0x00000024, 0x00050041, 0x00000028, 0x00000029, 0x0000000d, - 0x0000000f, 0x0003003e, 0x00000029, 0x00000027, 0x000100fd, 0x00010038, + 0x07230203, 0x00010300, 0x0008000b, 0x0000002a, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, 0x00000001, + 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000000, + 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x0000001a, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, + 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, 0x0000000b, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, + 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, + 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, + 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, + 0x00030005, 0x0000000d, 0x00000000, 0x00060005, 0x0000001a, 0x565f6c67, 0x65747265, 0x646e4978, 0x00007865, + 0x00050005, 0x0000001f, 0x65646e69, 0x6c626178, 0x00000065, 0x00030047, 0x0000000b, 0x00000002, 0x00050048, + 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, + 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003, 0x0000000b, + 0x00000004, 0x00040047, 0x0000001a, 0x0000000b, 0x0000002a, 0x00020013, 0x00000002, 0x00030021, 0x00000003, + 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, 0x00040015, + 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, + 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a, 0x00040020, + 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, + 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040017, 0x00000010, 0x00000006, + 0x00000002, 0x0004002b, 0x00000008, 0x00000011, 0x00000003, 0x0004001c, 0x00000012, 0x00000010, 0x00000011, + 0x0004002b, 0x00000006, 0x00000013, 0xbf800000, 0x0005002c, 0x00000010, 0x00000014, 0x00000013, 0x00000013, + 0x0004002b, 0x00000006, 0x00000015, 0x40400000, 0x0005002c, 0x00000010, 0x00000016, 0x00000015, 0x00000013, + 0x0005002c, 0x00000010, 0x00000017, 0x00000013, 0x00000015, 0x0006002c, 0x00000012, 0x00000018, 0x00000014, + 0x00000016, 0x00000017, 0x00040020, 0x00000019, 0x00000001, 0x0000000e, 0x0004003b, 0x00000019, 0x0000001a, + 0x00000001, 0x0004002b, 0x0000000e, 0x0000001c, 0x00000003, 0x00040020, 0x0000001e, 0x00000007, 0x00000012, + 0x00040020, 0x00000020, 0x00000007, 0x00000010, 0x0004002b, 0x00000006, 0x00000023, 0x00000000, 0x0004002b, + 0x00000006, 0x00000024, 0x3f800000, 0x00040020, 0x00000028, 0x00000003, 0x00000007, 0x00050036, 0x00000002, + 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003b, 0x0000001e, 0x0000001f, 0x00000007, + 0x0004003d, 0x0000000e, 0x0000001b, 0x0000001a, 0x0005008b, 0x0000000e, 0x0000001d, 0x0000001b, 0x0000001c, + 0x0003003e, 0x0000001f, 0x00000018, 0x00050041, 0x00000020, 0x00000021, 0x0000001f, 0x0000001d, 0x0004003d, + 0x00000010, 0x00000022, 0x00000021, 0x00050051, 0x00000006, 0x00000025, 0x00000022, 0x00000000, 0x00050051, + 0x00000006, 0x00000026, 0x00000022, 0x00000001, 0x00070050, 0x00000007, 0x00000027, 0x00000025, 0x00000026, + 0x00000023, 0x00000024, 0x00050041, 0x00000028, 0x00000029, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000029, + 0x00000027, 0x000100fd, 0x00010038, }; - inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeVertSpvWordCount = sizeof(kPrimitivesGeneratedNoXfbProbeVertSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeVertSpv[0]); + inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeVertSpvWordCount = + sizeof(kPrimitivesGeneratedNoXfbProbeVertSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeVertSpv[0]); inline constexpr std::uint32_t kPrimitivesGeneratedNoXfbProbeTescSpv[] = { - 0x07230203, 0x00010300, 0x0008000b, 0x0000001d, 0x00000000, 0x00020011, - 0x00000003, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, - 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000001, - 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000001a, 0x00040010, - 0x00000004, 0x0000001a, 0x00000001, 0x00030003, 0x00000002, 0x000001c2, - 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00070005, 0x0000000b, - 0x545f6c67, 0x4c737365, 0x6c657665, 0x6574754f, 0x00000072, 0x00070005, - 0x0000001a, 0x545f6c67, 0x4c737365, 0x6c657665, 0x656e6e49, 0x00000072, - 0x00040047, 0x0000000b, 0x0000000b, 0x0000000b, 0x00030047, 0x0000000b, - 0x0000000f, 0x00040047, 0x0000001a, 0x0000000b, 0x0000000c, 0x00030047, - 0x0000001a, 0x0000000f, 0x00020013, 0x00000002, 0x00030021, 0x00000003, - 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040015, 0x00000007, - 0x00000020, 0x00000000, 0x0004002b, 0x00000007, 0x00000008, 0x00000004, - 0x0004001c, 0x00000009, 0x00000006, 0x00000008, 0x00040020, 0x0000000a, - 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, - 0x00040015, 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, - 0x0000000d, 0x00000000, 0x0004002b, 0x00000006, 0x0000000e, 0x3f800000, - 0x00040020, 0x0000000f, 0x00000003, 0x00000006, 0x0004002b, 0x0000000c, - 0x00000011, 0x00000001, 0x0004002b, 0x0000000c, 0x00000013, 0x00000002, - 0x0004002b, 0x0000000c, 0x00000015, 0x00000003, 0x0004002b, 0x00000007, - 0x00000017, 0x00000002, 0x0004001c, 0x00000018, 0x00000006, 0x00000017, - 0x00040020, 0x00000019, 0x00000003, 0x00000018, 0x0004003b, 0x00000019, - 0x0000001a, 0x00000003, 0x00050036, 0x00000002, 0x00000004, 0x00000000, - 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x0000000f, 0x00000010, - 0x0000000b, 0x0000000d, 0x0003003e, 0x00000010, 0x0000000e, 0x00050041, - 0x0000000f, 0x00000012, 0x0000000b, 0x00000011, 0x0003003e, 0x00000012, - 0x0000000e, 0x00050041, 0x0000000f, 0x00000014, 0x0000000b, 0x00000013, - 0x0003003e, 0x00000014, 0x0000000e, 0x00050041, 0x0000000f, 0x00000016, - 0x0000000b, 0x00000015, 0x0003003e, 0x00000016, 0x0000000e, 0x00050041, - 0x0000000f, 0x0000001b, 0x0000001a, 0x0000000d, 0x0003003e, 0x0000001b, - 0x0000000e, 0x00050041, 0x0000000f, 0x0000001c, 0x0000001a, 0x00000011, - 0x0003003e, 0x0000001c, 0x0000000e, 0x000100fd, 0x00010038, + 0x07230203, 0x00010300, 0x0008000b, 0x0000001d, 0x00000000, 0x00020011, 0x00000003, 0x0006000b, 0x00000001, + 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000001, + 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000001a, 0x00040010, 0x00000004, 0x0000001a, 0x00000001, + 0x00030003, 0x00000002, 0x000001c2, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00070005, 0x0000000b, + 0x545f6c67, 0x4c737365, 0x6c657665, 0x6574754f, 0x00000072, 0x00070005, 0x0000001a, 0x545f6c67, 0x4c737365, + 0x6c657665, 0x656e6e49, 0x00000072, 0x00040047, 0x0000000b, 0x0000000b, 0x0000000b, 0x00030047, 0x0000000b, + 0x0000000f, 0x00040047, 0x0000001a, 0x0000000b, 0x0000000c, 0x00030047, 0x0000001a, 0x0000000f, 0x00020013, + 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040015, 0x00000007, + 0x00000020, 0x00000000, 0x0004002b, 0x00000007, 0x00000008, 0x00000004, 0x0004001c, 0x00000009, 0x00000006, + 0x00000008, 0x00040020, 0x0000000a, 0x00000003, 0x00000009, 0x0004003b, 0x0000000a, 0x0000000b, 0x00000003, + 0x00040015, 0x0000000c, 0x00000020, 0x00000001, 0x0004002b, 0x0000000c, 0x0000000d, 0x00000000, 0x0004002b, + 0x00000006, 0x0000000e, 0x3f800000, 0x00040020, 0x0000000f, 0x00000003, 0x00000006, 0x0004002b, 0x0000000c, + 0x00000011, 0x00000001, 0x0004002b, 0x0000000c, 0x00000013, 0x00000002, 0x0004002b, 0x0000000c, 0x00000015, + 0x00000003, 0x0004002b, 0x00000007, 0x00000017, 0x00000002, 0x0004001c, 0x00000018, 0x00000006, 0x00000017, + 0x00040020, 0x00000019, 0x00000003, 0x00000018, 0x0004003b, 0x00000019, 0x0000001a, 0x00000003, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x00050041, 0x0000000f, 0x00000010, + 0x0000000b, 0x0000000d, 0x0003003e, 0x00000010, 0x0000000e, 0x00050041, 0x0000000f, 0x00000012, 0x0000000b, + 0x00000011, 0x0003003e, 0x00000012, 0x0000000e, 0x00050041, 0x0000000f, 0x00000014, 0x0000000b, 0x00000013, + 0x0003003e, 0x00000014, 0x0000000e, 0x00050041, 0x0000000f, 0x00000016, 0x0000000b, 0x00000015, 0x0003003e, + 0x00000016, 0x0000000e, 0x00050041, 0x0000000f, 0x0000001b, 0x0000001a, 0x0000000d, 0x0003003e, 0x0000001b, + 0x0000000e, 0x00050041, 0x0000000f, 0x0000001c, 0x0000001a, 0x00000011, 0x0003003e, 0x0000001c, 0x0000000e, + 0x000100fd, 0x00010038, }; - inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeTescSpvWordCount = sizeof(kPrimitivesGeneratedNoXfbProbeTescSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeTescSpv[0]); + inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeTescSpvWordCount = + sizeof(kPrimitivesGeneratedNoXfbProbeTescSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeTescSpv[0]); inline constexpr std::uint32_t kPrimitivesGeneratedNoXfbProbeTeseSpv[] = { - 0x07230203, 0x00010300, 0x0008000b, 0x00000021, 0x00000000, 0x00020011, - 0x00000003, 0x0006000b, 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, - 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000002, - 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000012, 0x00030010, - 0x00000004, 0x00000016, 0x00030010, 0x00000004, 0x00000001, 0x00030010, - 0x00000004, 0x00000004, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, - 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, 0x0000000b, 0x505f6c67, - 0x65567265, 0x78657472, 0x00000000, 0x00060006, 0x0000000b, 0x00000000, - 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, - 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, - 0x00000002, 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00070006, - 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, - 0x00030005, 0x0000000d, 0x00000000, 0x00060005, 0x00000012, 0x545f6c67, - 0x43737365, 0x64726f6f, 0x00000000, 0x00030047, 0x0000000b, 0x00000002, - 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, - 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, - 0x00000002, 0x0000000b, 0x00000003, 0x00050048, 0x0000000b, 0x00000003, - 0x0000000b, 0x00000004, 0x00040047, 0x00000012, 0x0000000b, 0x0000000d, - 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, - 0x00000006, 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000004, - 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, - 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, 0x00000006, 0x00000009, - 0x0006001e, 0x0000000b, 0x00000007, 0x00000006, 0x0000000a, 0x0000000a, - 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, - 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, - 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040017, 0x00000010, - 0x00000006, 0x00000003, 0x00040020, 0x00000011, 0x00000001, 0x00000010, - 0x0004003b, 0x00000011, 0x00000012, 0x00000001, 0x00040017, 0x00000013, - 0x00000006, 0x00000002, 0x0004002b, 0x00000006, 0x00000016, 0x40000000, - 0x0004002b, 0x00000006, 0x00000018, 0x3f800000, 0x0004002b, 0x00000006, - 0x0000001b, 0x00000000, 0x00040020, 0x0000001f, 0x00000003, 0x00000007, - 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, - 0x00000005, 0x0004003d, 0x00000010, 0x00000014, 0x00000012, 0x0007004f, - 0x00000013, 0x00000015, 0x00000014, 0x00000014, 0x00000000, 0x00000001, - 0x0005008e, 0x00000013, 0x00000017, 0x00000015, 0x00000016, 0x00050050, - 0x00000013, 0x00000019, 0x00000018, 0x00000018, 0x00050083, 0x00000013, - 0x0000001a, 0x00000017, 0x00000019, 0x00050051, 0x00000006, 0x0000001c, - 0x0000001a, 0x00000000, 0x00050051, 0x00000006, 0x0000001d, 0x0000001a, - 0x00000001, 0x00070050, 0x00000007, 0x0000001e, 0x0000001c, 0x0000001d, - 0x0000001b, 0x00000018, 0x00050041, 0x0000001f, 0x00000020, 0x0000000d, - 0x0000000f, 0x0003003e, 0x00000020, 0x0000001e, 0x000100fd, 0x00010038, + 0x07230203, 0x00010300, 0x0008000b, 0x00000021, 0x00000000, 0x00020011, 0x00000003, 0x0006000b, 0x00000001, + 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, 0x0007000f, 0x00000002, + 0x00000004, 0x6e69616d, 0x00000000, 0x0000000d, 0x00000012, 0x00030010, 0x00000004, 0x00000016, 0x00030010, + 0x00000004, 0x00000001, 0x00030010, 0x00000004, 0x00000004, 0x00030003, 0x00000002, 0x000001c2, 0x00040005, + 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, 0x0000000b, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, + 0x00060006, 0x0000000b, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x0000000b, 0x00000001, + 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x0000000b, 0x00000002, 0x435f6c67, 0x4470696c, + 0x61747369, 0x0065636e, 0x00070006, 0x0000000b, 0x00000003, 0x435f6c67, 0x446c6c75, 0x61747369, 0x0065636e, + 0x00030005, 0x0000000d, 0x00000000, 0x00060005, 0x00000012, 0x545f6c67, 0x43737365, 0x64726f6f, 0x00000000, + 0x00030047, 0x0000000b, 0x00000002, 0x00050048, 0x0000000b, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, + 0x0000000b, 0x00000001, 0x0000000b, 0x00000001, 0x00050048, 0x0000000b, 0x00000002, 0x0000000b, 0x00000003, + 0x00050048, 0x0000000b, 0x00000003, 0x0000000b, 0x00000004, 0x00040047, 0x00000012, 0x0000000b, 0x0000000d, + 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, + 0x00000007, 0x00000006, 0x00000004, 0x00040015, 0x00000008, 0x00000020, 0x00000000, 0x0004002b, 0x00000008, + 0x00000009, 0x00000001, 0x0004001c, 0x0000000a, 0x00000006, 0x00000009, 0x0006001e, 0x0000000b, 0x00000007, + 0x00000006, 0x0000000a, 0x0000000a, 0x00040020, 0x0000000c, 0x00000003, 0x0000000b, 0x0004003b, 0x0000000c, + 0x0000000d, 0x00000003, 0x00040015, 0x0000000e, 0x00000020, 0x00000001, 0x0004002b, 0x0000000e, 0x0000000f, + 0x00000000, 0x00040017, 0x00000010, 0x00000006, 0x00000003, 0x00040020, 0x00000011, 0x00000001, 0x00000010, + 0x0004003b, 0x00000011, 0x00000012, 0x00000001, 0x00040017, 0x00000013, 0x00000006, 0x00000002, 0x0004002b, + 0x00000006, 0x00000016, 0x40000000, 0x0004002b, 0x00000006, 0x00000018, 0x3f800000, 0x0004002b, 0x00000006, + 0x0000001b, 0x00000000, 0x00040020, 0x0000001f, 0x00000003, 0x00000007, 0x00050036, 0x00000002, 0x00000004, + 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x00000010, 0x00000014, 0x00000012, 0x0007004f, + 0x00000013, 0x00000015, 0x00000014, 0x00000014, 0x00000000, 0x00000001, 0x0005008e, 0x00000013, 0x00000017, + 0x00000015, 0x00000016, 0x00050050, 0x00000013, 0x00000019, 0x00000018, 0x00000018, 0x00050083, 0x00000013, + 0x0000001a, 0x00000017, 0x00000019, 0x00050051, 0x00000006, 0x0000001c, 0x0000001a, 0x00000000, 0x00050051, + 0x00000006, 0x0000001d, 0x0000001a, 0x00000001, 0x00070050, 0x00000007, 0x0000001e, 0x0000001c, 0x0000001d, + 0x0000001b, 0x00000018, 0x00050041, 0x0000001f, 0x00000020, 0x0000000d, 0x0000000f, 0x0003003e, 0x00000020, + 0x0000001e, 0x000100fd, 0x00010038, }; - inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeTeseSpvWordCount = sizeof(kPrimitivesGeneratedNoXfbProbeTeseSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeTeseSpv[0]); + inline constexpr std::size_t kPrimitivesGeneratedNoXfbProbeTeseSpvWordCount = + sizeof(kPrimitivesGeneratedNoXfbProbeTeseSpv) / sizeof(kPrimitivesGeneratedNoXfbProbeTeseSpv[0]); } // namespace MobileGL::MG_Util::SelfTest diff --git a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp index bcd97b15..fd2caaeb 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.cpp @@ -45,8 +45,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // changing the fingerprint, which is exactly the memo-hazard property wanted here. HashBytes(state, &env.params, sizeof(env.params)); if (!env.advertisedExtensions.empty()) { - HashBytes(state, env.advertisedExtensions.data(), - env.advertisedExtensions.size() * sizeof(GLExtension)); + HashBytes(state, env.advertisedExtensions.data(), env.advertisedExtensions.size() * sizeof(GLExtension)); } return state; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h index 6bb70d5d..030240f5 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h +++ b/MobileGL/MG_Util/ShaderTranspiler/CompileEnv.h @@ -61,10 +61,10 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // exact no-backend fallback it had before: extensions read as advertised, limits // read as the frontend defaults. BackendType backend = BackendType::Unknown; - MG_Backend::DynamicBackendParameters params{}; // by value, never by reference + MG_Backend::DynamicBackendParameters params{}; // by value, never by reference Vector advertisedExtensions; - Uint64 fingerprint = 0; // set by CaptureCompileEnv() + Uint64 fingerprint = 0; // set by CaptureCompileEnv() // The FRONT-END half of the environment: the subset of the fields above that can // change what glslang PRODUCES - the SPIR-V or the reflection - as opposed to what a @@ -142,7 +142,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // only such argument left in this classification. The moment anything hands this // value to glslang - a TBuiltInResource field, a built-in constant - it MUST move // into the fingerprint, exactly as its companions just did. - Uint64 frontendFingerprint = 0; // set by CaptureCompileEnv() + Uint64 frontendFingerprint = 0; // set by CaptureCompileEnv() Bool HasBackend() const { return backend != BackendType::Unknown; } // Whether the backend this env was captured against can CONSUME a module that still @@ -163,12 +163,8 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // compiles, unit tests) is the untouched module. Like nativeFloat64, each bit is L1 // key material of its own (SpirvTranslationKeyInputs), never part of the frontend // fingerprint: glslang produces the same thing either way. - Bool DemotesTessellationPointSize() const { - return HasBackend() && !params.SupportsTessellationPointSize; - } - Bool DemotesGeometryPointSize() const { - return HasBackend() && !params.SupportsGeometryPointSize; - } + Bool DemotesTessellationPointSize() const { return HasBackend() && !params.SupportsTessellationPointSize; } + Bool DemotesGeometryPointSize() const { return HasBackend() && !params.SupportsGeometryPointSize; } // Matches the historical rule exactly: with no active backend every extension counts // as advertised, because the frontend then has nothing to gate against. Bool IsExtensionAdvertised(GLExtension extension) const { diff --git a/MobileGL/MG_Util/ShaderTranspiler/EsslBuiltinFunctionNames.h b/MobileGL/MG_Util/ShaderTranspiler/EsslBuiltinFunctionNames.h index 8f1aea53..e9cf6a8f 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/EsslBuiltinFunctionNames.h +++ b/MobileGL/MG_Util/ShaderTranspiler/EsslBuiltinFunctionNames.h @@ -44,47 +44,158 @@ namespace MobileGL { // // "main" is deliberately absent from both. inline constexpr std::string_view kEsslBuiltinFunctionNames[] = { - "EmitVertex", "EndPrimitive", - "abs", "acos", "acosh", "all", "any", "asin", "asinh", "atan", "atanh", - "atomicAdd", "atomicAnd", "atomicCompSwap", "atomicCounter", - "atomicCounterDecrement", "atomicCounterIncrement", "atomicExchange", - "atomicMax", "atomicMin", "atomicOr", "atomicXor", - "barrier", "bitCount", "bitfieldExtract", "bitfieldInsert", "bitfieldReverse", - "ceil", "clamp", "cos", "cosh", "cross", - "dFdx", "dFdy", "degrees", "determinant", "distance", "dot", - "equal", "exp", "exp2", - "faceforward", "findLSB", "findMSB", "floatBitsToInt", "floatBitsToUint", - "floor", "fma", "fract", "frexp", "fwidth", - "greaterThan", "greaterThanEqual", "groupMemoryBarrier", - "imageAtomicAdd", "imageAtomicAnd", "imageAtomicCompSwap", - "imageAtomicExchange", "imageAtomicMax", "imageAtomicMin", "imageAtomicOr", - "imageAtomicXor", "imageLoad", "imageSize", "imageStore", "imulExtended", - "intBitsToFloat", "interpolateAtCentroid", "interpolateAtOffset", - "interpolateAtSample", "inverse", "inversesqrt", "isinf", "isnan", - "ldexp", "length", "length_squared", "lessThan", "lessThanEqual", "log", "log2", - "matrixCompMult", "max", "max3", "memoryBarrier", - "memoryBarrierAtomicCounter", "memoryBarrierBuffer", "memoryBarrierImage", - "memoryBarrierShared", "mid3", "min", "min3", "mix", "mod", "modf", - "normalize", "not", "notEqual", + "EmitVertex", + "EndPrimitive", + "abs", + "acos", + "acosh", + "all", + "any", + "asin", + "asinh", + "atan", + "atanh", + "atomicAdd", + "atomicAnd", + "atomicCompSwap", + "atomicCounter", + "atomicCounterDecrement", + "atomicCounterIncrement", + "atomicExchange", + "atomicMax", + "atomicMin", + "atomicOr", + "atomicXor", + "barrier", + "bitCount", + "bitfieldExtract", + "bitfieldInsert", + "bitfieldReverse", + "ceil", + "clamp", + "cos", + "cosh", + "cross", + "dFdx", + "dFdy", + "degrees", + "determinant", + "distance", + "dot", + "equal", + "exp", + "exp2", + "faceforward", + "findLSB", + "findMSB", + "floatBitsToInt", + "floatBitsToUint", + "floor", + "fma", + "fract", + "frexp", + "fwidth", + "greaterThan", + "greaterThanEqual", + "groupMemoryBarrier", + "imageAtomicAdd", + "imageAtomicAnd", + "imageAtomicCompSwap", + "imageAtomicExchange", + "imageAtomicMax", + "imageAtomicMin", + "imageAtomicOr", + "imageAtomicXor", + "imageLoad", + "imageSize", + "imageStore", + "imulExtended", + "intBitsToFloat", + "interpolateAtCentroid", + "interpolateAtOffset", + "interpolateAtSample", + "inverse", + "inversesqrt", + "isinf", + "isnan", + "ldexp", + "length", + "length_squared", + "lessThan", + "lessThanEqual", + "log", + "log2", + "matrixCompMult", + "max", + "max3", + "memoryBarrier", + "memoryBarrierAtomicCounter", + "memoryBarrierBuffer", + "memoryBarrierImage", + "memoryBarrierShared", + "mid3", + "min", + "min3", + "mix", + "mod", + "modf", + "normalize", + "not", + "notEqual", "outerProduct", - "packHalf2x16", "packSnorm2x16", "packSnorm4x8", "packUnorm2x16", - "packUnorm4x8", "pow", - "radians", "reflect", "refract", "round", "roundEven", - "sign", "sin", "sinh", "smoothstep", "sqrt", "step", - "tan", "tanh", "texelFetch", "texelFetchOffset", "texture", - "textureGather", "textureGatherOffset", "textureGatherOffsets", - "textureGrad", "textureGradOffset", "textureLod", "textureLodOffset", - "textureOffset", "textureProj", "textureProjGrad", "textureProjGradOffset", - "textureProjLod", "textureProjLodOffset", "textureProjOffset", "textureSize", - "transpose", "trunc", - "uaddCarry", "uintBitsToFloat", "umulExtended", "unpackHalf2x16", - "unpackSnorm2x16", "unpackSnorm4x8", "unpackUnorm2x16", "unpackUnorm4x8", + "packHalf2x16", + "packSnorm2x16", + "packSnorm4x8", + "packUnorm2x16", + "packUnorm4x8", + "pow", + "radians", + "reflect", + "refract", + "round", + "roundEven", + "sign", + "sin", + "sinh", + "smoothstep", + "sqrt", + "step", + "tan", + "tanh", + "texelFetch", + "texelFetchOffset", + "texture", + "textureGather", + "textureGatherOffset", + "textureGatherOffsets", + "textureGrad", + "textureGradOffset", + "textureLod", + "textureLodOffset", + "textureOffset", + "textureProj", + "textureProjGrad", + "textureProjGradOffset", + "textureProjLod", + "textureProjLodOffset", + "textureProjOffset", + "textureSize", + "transpose", + "trunc", + "uaddCarry", + "uintBitsToFloat", + "umulExtended", + "unpackHalf2x16", + "unpackSnorm2x16", + "unpackSnorm4x8", + "unpackUnorm2x16", + "unpackUnorm4x8", "usubBorrow", }; inline bool IsEsslBuiltinFunctionName(std::string_view name) { - return std::binary_search(std::begin(kEsslBuiltinFunctionNames), - std::end(kEsslBuiltinFunctionNames), name); + return std::binary_search(std::begin(kEsslBuiltinFunctionNames), std::end(kEsslBuiltinFunctionNames), + name); } // The parse-level subset, sorted for std::binary_search. @@ -115,8 +226,8 @@ namespace MobileGL { }; inline bool IsLexicalPreemptRenameName(std::string_view name) { - return std::binary_search(std::begin(kLexicalPreemptRenameNames), - std::end(kLexicalPreemptRenameNames), name); + return std::binary_search(std::begin(kLexicalPreemptRenameNames), std::end(kLexicalPreemptRenameNames), + name); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp index 8513bfff..d0006113 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.cpp @@ -190,8 +190,7 @@ namespace MobileGL { const MG_Backend::DynamicBackendParameters fallbackParameters{}; const auto& activeBackend = MG_Backend::pActiveBackendObject; const auto& dynamicParameters = - env ? env->params - : (activeBackend ? activeBackend->GetDynamicParameters() : fallbackParameters); + env ? env->params : (activeBackend ? activeBackend->GetDynamicParameters() : fallbackParameters); Resources.maxImageUnits = dynamicParameters.MaxImageUnits; Resources.maxCombinedImageUnitsAndFragmentOutputs = dynamicParameters.MaxImageUnits + dynamicParameters.MaxDrawBuffers; @@ -200,8 +199,7 @@ namespace MobileGL { // the two glslang fields have to carry the same value: glGetIntegerv answers this // expression while gl_MaxCombinedShaderOutputResources expanded from a stale // literal 8, and the CTS compares the two directly. - Resources.maxCombinedShaderOutputResources = - Resources.maxCombinedImageUnitsAndFragmentOutputs; + Resources.maxCombinedShaderOutputResources = Resources.maxCombinedImageUnitsAndFragmentOutputs; Resources.maxVertexImageUniforms = dynamicParameters.MaxVertexImageUniforms; Resources.maxTessControlImageUniforms = dynamicParameters.MaxTessControlImageUniforms; Resources.maxTessEvaluationImageUniforms = dynamicParameters.MaxTessEvaluationImageUniforms; @@ -413,8 +411,8 @@ namespace MobileGL { if (String violation = FindEsBlockArrayIndexViolation(*result.value(), source); !violation.empty()) { ResultInfo r; - r.log = "Error: [glslang] Cannot compile " + ConvertGLEnumToString(shaderType) + ":\n" + - violation; + r.log = + "Error: [glslang] Cannot compile " + ConvertGLEnumToString(shaderType) + ":\n" + violation; r.errc = -2; return std::unexpected(r); } @@ -454,7 +452,9 @@ namespace MobileGL { Bool g_builtinsPrewarmed = false; } // namespace - void ShaderCompiler::ResetPrewarmLatch() { g_builtinsPrewarmed = false; } + void ShaderCompiler::ResetPrewarmLatch() { + g_builtinsPrewarmed = false; + } void ShaderCompiler::PrewarmBuiltins() { if (g_builtinsPrewarmed) return; @@ -474,14 +474,11 @@ namespace MobileGL { "#version 330 core\nvoid main() { gl_Position = vec4(0.0); }\n"; const CompileEnv& env = *GetDefaultCompileEnv(); - for (const auto& [type, source] : - {std::pair{GL_VERTEX_SHADER, kPrewarmVertexSource}, - std::pair{GL_FRAGMENT_SHADER, kPrewarmFragmentSource}, - std::pair{GL_VERTEX_SHADER, kPrewarmLegacyVertexSource}}) { - ShaderAttrib attrib{.shaderType = static_cast(type), - .sourceStr = source, - .flags = 0, - .env = &env}; + for (const auto& [type, source] : {std::pair{GL_VERTEX_SHADER, kPrewarmVertexSource}, + std::pair{GL_FRAGMENT_SHADER, kPrewarmFragmentSource}, + std::pair{GL_VERTEX_SHADER, kPrewarmLegacyVertexSource}}) { + ShaderAttrib attrib{ + .shaderType = static_cast(type), .sourceStr = source, .flags = 0, .env = &env}; // The result is deliberately discarded: the value is the symbol table // glslang cached as a side effect. A failure here is not fatal - it just // means the first real compile pays for the table, exactly as before. @@ -524,7 +521,8 @@ namespace MobileGL { long long remainder = element; for (SizeT d = 0; d < outerDimensions; ++d) { long long stride = 1; - for (SizeT inner = d + 1; inner < outerDimensions; ++inner) stride *= dimensions[inner]; + for (SizeT inner = d + 1; inner < outerDimensions; ++inner) + stride *= dimensions[inner]; key += "[" + std::to_string(remainder / stride) + "]"; remainder %= stride; } @@ -565,14 +563,12 @@ namespace MobileGL { // Half one: the uniforms the relaxed remap swallowed, out of the snapshot it // takes on the way past. - for (const glslang::TIntermediate::TUniformLocation& record : - intermediate->getUniformLocations()) { + for (const glslang::TIntermediate::TUniformLocation& record : intermediate->getUniformLocations()) { if (record.location < 0) continue; // Keep the first sighting. Two records for one name mean the parser saw the // declaration twice, and the first is the one the symbol table kept. locations.emplace(record.name, record.location); - RecordArrayOfArraysElementLocations(record.name, record.arraySizes, record.location, - locations); + RecordArrayOfArraysElementLocations(record.name, record.arraySizes, record.location, locations); } // Half two: the OPAQUE uniforms, which the remap never touches (the guard in @@ -638,15 +634,12 @@ namespace MobileGL { UniquePtr resolver; for (unsigned stage = 0; stage < EShLangCount; stage++) { if (program->getIntermediate((EShLanguage)stage) == nullptr) continue; - resolver = - MakeUnique(*program, (EShLanguage)stage, attrib.explicitVertexInLocations, - attrib.explicitFragmentOutLocations, - attrib.explicitFragmentOutIndices, - attrib.explicitOpaqueUniformBindings, - attrib.storageBlocksWithoutBinding, - attrib.uniformBlocksWithoutBinding, - &attrib.resourceBindingLimits, - attrib.resourceBindingViolation); + resolver = MakeUnique( + *program, (EShLanguage)stage, attrib.explicitVertexInLocations, + attrib.explicitFragmentOutLocations, attrib.explicitFragmentOutIndices, + attrib.explicitOpaqueUniformBindings, attrib.storageBlocksWithoutBinding, + attrib.uniformBlocksWithoutBinding, &attrib.resourceBindingLimits, + attrib.resourceBindingViolation); break; } auto ioMapper = UniquePtr(glslang::GetGlslIoMapper()); @@ -737,28 +730,26 @@ namespace MobileGL { &warmup)) { tools.Validate(warmup); } - std::atexit(+[] { - Async::ShaderCompilePool::StopAndDrainProcessPoolAtExit(); - }); + std::atexit(+[] { Async::ShaderCompilePool::StopAndDrainProcessPoolAtExit(); }); }); } spvtools::MessageConsumer MakeSpirvMessageConsumer(const char* site) { - return [site](spv_message_level_t level, const char* /*source*/, - const spv_position_t& position, const char* message) { + return [site](spv_message_level_t level, const char* /*source*/, const spv_position_t& position, + const char* message) { const char* text = message ? message : ""; switch (level) { - case SPV_MSG_FATAL: - case SPV_MSG_INTERNAL_ERROR: - case SPV_MSG_ERROR: - // Unlatched: only reachable with the validation switch armed, - // and every VUID names a different defect. (Parked at MGLOG_I - // until the Log.h ordering fix made E live at INFO.) - MGLOG_E("[spirv] %s: %s (word index %zu)", site, text, position.index); - break; - default: - MGLOG_D("[spirv] %s: %s", site, text); - break; + case SPV_MSG_FATAL: + case SPV_MSG_INTERNAL_ERROR: + case SPV_MSG_ERROR: + // Unlatched: only reachable with the validation switch armed, + // and every VUID names a different defect. (Parked at MGLOG_I + // until the Log.h ordering fix made E live at INFO.) + MGLOG_E("[spirv] %s: %s (word index %zu)", site, text, position.index); + break; + default: + MGLOG_D("[spirv] %s: %s", site, text); + break; } }; } @@ -766,17 +757,14 @@ namespace MobileGL { // Validation is decoupled from control flow on purpose: a failure logs and // bumps the latch, and the caller proceeds exactly as the shipping (non- // validating) configuration would. Tests assert on the latch delta. - void ValidateOrLatch(const char* site, const Vector& binary, - const bool enableSpirvValidation) { + void ValidateOrLatch(const char* site, const Vector& binary, const bool enableSpirvValidation) { if (!enableSpirvValidation) return; PinValidatorTablesForProcessExit(); spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); tools.SetMessageConsumer(MakeSpirvMessageConsumer(site)); if (!tools.Validate(binary)) { - MGLOG_E("[spirv] %s: produced a module that fails validation (failure #%llu)", - site, - static_cast( - ShaderCompiler::NoteSpirvValidationFailure())); + MGLOG_E("[spirv] %s: produced a module that fails validation (failure #%llu)", site, + static_cast(ShaderCompiler::NoteSpirvValidationFailure())); } } @@ -789,9 +777,8 @@ namespace MobileGL { // receive. The message consumer is installed unconditionally: without one, // spirv-tools drops pass diagnostics on the floor. bool RunOptimizerChecked(const char* site, spvtools::Optimizer& optimizer, - const Vector& inputBinary, - Vector& outputBinary, const bool validateOutput, - const bool enableSpirvValidation) { + const Vector& inputBinary, Vector& outputBinary, + const bool validateOutput, const bool enableSpirvValidation) { spvtools::OptimizerOptions options; options.set_run_validator(false); optimizer.SetMessageConsumer(MakeSpirvMessageConsumer(site)); @@ -828,8 +815,8 @@ namespace MobileGL { // here only ever happens on a degraded driver that is about to fail the compile // anyway - it is not on the healthy path. std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresBufferTextureSampler"), - spirv.data(), spirv.size()); + SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresBufferTextureSampler"), spirv.data(), + spirv.size()); if (!context) { // Unparseable here means unusable downstream too; let the ordinary transpile // path produce the error rather than inventing a capability verdict from it. @@ -858,8 +845,8 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresTransformFeedback"), - spirv.data(), spirv.size()); + SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresTransformFeedback"), spirv.data(), + spirv.size()); if (!context) { // Unparseable is not a capture verdict; say no, which makes the caller decline // the span rather than issue transform-feedback commands against it. @@ -872,8 +859,7 @@ namespace MobileGL { // no Xfb entry point, and the VUID is about the mode. for (const spvtools::opt::Instruction& mode : context->module()->execution_modes()) { if (mode.NumInOperands() >= 2 && - static_cast(mode.GetSingleWordInOperand(1)) == - spv::ExecutionMode::Xfb) { + static_cast(mode.GetSingleWordInOperand(1)) == spv::ExecutionMode::Xfb) { return true; } } @@ -885,9 +871,8 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - MakeSpirvMessageConsumer("ModuleDeclaresTessellationOrGeometryPointSize"), spirv.data(), - spirv.size()); + SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresTessellationOrGeometryPointSize"), + spirv.data(), spirv.size()); if (!context) { // Unparseable is not a verdict about point size. Say no, so the caller keeps // building the program: the module is already broken for other reasons and @@ -920,8 +905,7 @@ namespace MobileGL { Uint32 ConservativeLocationSpan(const opt_analysis::Type* type) { constexpr Uint32 kUnknownSpan = 4; if (type == nullptr) return kUnknownSpan; - if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || - type->AsBool() != nullptr) { + if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || type->AsBool() != nullptr) { return 1u; } if (const auto* vector = type->AsVector()) { @@ -944,8 +928,7 @@ namespace MobileGL { if (const auto* array = type->AsArray()) { const auto& lengthWords = array->length_info().words; if (lengthWords.size() != 2 || - lengthWords[0] != - static_cast(opt_analysis::Array::LengthInfo::kConstant)) { + lengthWords[0] != static_cast(opt_analysis::Array::LengthInfo::kConstant)) { return kUnknownSpan; } return ConservativeLocationSpan(array->element_type()) * std::max(lengthWords[1], 1u); @@ -977,16 +960,15 @@ namespace MobileGL { probe.parsed = true; // an absent stage constrains nothing return probe; } - std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ProbePointSizeModule"), spirv.data(), - spirv.size()); + std::unique_ptr context = + spvtools::BuildModule(SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ProbePointSizeModule"), + spirv.data(), spirv.size()); if (!context) return probe; probe.parsed = true; for (const spvtools::opt::Instruction& capability : context->capabilities()) { if (capability.NumInOperands() < 1) continue; - const auto declared = - static_cast(capability.GetSingleWordInOperand(0)); + const auto declared = static_cast(capability.GetSingleWordInOperand(0)); if (declared == spv::Capability::TessellationPointSize) { probe.declaresTessellationPointSize = true; } else if (declared == spv::Capability::GeometryPointSize) { @@ -1023,29 +1005,23 @@ namespace MobileGL { spv::Decoration::Location) { const Uint32 location = annotation.GetSingleWordInOperand(2); Uint32 span = 1; - spvtools::opt::Instruction* var = - defUse->GetDef(annotation.GetSingleWordInOperand(0)); + spvtools::opt::Instruction* var = defUse->GetDef(annotation.GetSingleWordInOperand(0)); if (var != nullptr && var->opcode() == spv::Op::OpVariable) { - const auto storage = - static_cast(var->GetSingleWordInOperand(0)); + const auto storage = static_cast(var->GetSingleWordInOperand(0)); // Two location namespaces are NOT varying slots and must not // shrink the carrier budget: vertex-stage inputs (attribute // locations) and fragment-stage outputs (draw buffers). - if ((model == spv::ExecutionModel::Vertex && - storage == spv::StorageClass::Input) || - (model == spv::ExecutionModel::Fragment && - storage == spv::StorageClass::Output)) { + if ((model == spv::ExecutionModel::Vertex && storage == spv::StorageClass::Input) || + (model == spv::ExecutionModel::Fragment && storage == spv::StorageClass::Output)) { continue; } spvtools::opt::Instruction* pointerType = defUse->GetDef(var->type_id()); - if (pointerType != nullptr && - pointerType->opcode() == spv::Op::OpTypePointer) { + if (pointerType != nullptr && pointerType->opcode() == spv::Op::OpTypePointer) { const opt_analysis::Type* pointee = typeMgr->GetType(pointerType->GetSingleWordInOperand(1)); - const Bool peel = - ((storage == spv::StorageClass::Input && peelInputs) || - (storage == spv::StorageClass::Output && peelOutputs)) && - patchDecorated.count(var->result_id()) == 0; + const Bool peel = ((storage == spv::StorageClass::Input && peelInputs) || + (storage == spv::StorageClass::Output && peelOutputs)) && + patchDecorated.count(var->result_id()) == 0; if (peel && pointee != nullptr && pointee->AsArray() != nullptr) { pointee = pointee->AsArray()->element_type(); } @@ -1096,9 +1072,8 @@ namespace MobileGL { } // namespace Bool ShaderCompiler::DemoteTessellationGeometryPointSizeForProgram( - Vector>& modules, const Vector& shaderTypes, - const Bool demoteTessellation, const Bool demoteGeometry, - const Bool captureRequestsPointSize, PointSizeDemotionOutcome& outcome, + Vector>& modules, const Vector& shaderTypes, const Bool demoteTessellation, + const Bool demoteGeometry, const Bool captureRequestsPointSize, PointSizeDemotionOutcome& outcome, const bool validateOutput, const bool enableSpirvValidation) { outcome = {}; if (!demoteTessellation && !demoteGeometry) return true; @@ -1107,11 +1082,20 @@ namespace MobileGL { Int stageIndex[4] = {-1, -1, -1, -1}; // VS, TCS, TES, GS for (SizeT i = 0; i < shaderTypes.size() && i < modules.size(); ++i) { switch (shaderTypes[i]) { - case GL_VERTEX_SHADER: stageIndex[0] = static_cast(i); break; - case GL_TESS_CONTROL_SHADER: stageIndex[1] = static_cast(i); break; - case GL_TESS_EVALUATION_SHADER: stageIndex[2] = static_cast(i); break; - case GL_GEOMETRY_SHADER: stageIndex[3] = static_cast(i); break; - default: break; + case GL_VERTEX_SHADER: + stageIndex[0] = static_cast(i); + break; + case GL_TESS_CONTROL_SHADER: + stageIndex[1] = static_cast(i); + break; + case GL_TESS_EVALUATION_SHADER: + stageIndex[2] = static_cast(i); + break; + case GL_GEOMETRY_SHADER: + stageIndex[3] = static_cast(i); + break; + default: + break; } } if (stageIndex[1] < 0 && stageIndex[2] < 0 && stageIndex[3] < 0) return true; @@ -1133,22 +1117,20 @@ namespace MobileGL { anyGeometryUse |= probe.declaresGeometryPointSize; carrierLocation = std::max(carrierLocation, probe.locationSlotEnd); } - if (!((anyTessellationUse && demoteTessellation) || - (anyGeometryUse && demoteGeometry))) { + if (!((anyTessellationUse && demoteTessellation) || (anyGeometryUse && demoteGeometry))) { return true; } if (carrierLocation > kMaxDemotedPointSizeCarrierLocation) { outcome.declineDetail = std::format( - "the program's varyings already reach location {}, past the carrier budget", - carrierLocation); + "the program's varyings already reach location {}, past the carrier budget", carrierLocation); return true; } // GL 4.6 core 13.3: capture reads the last capture-capable stage - geometry, // else evaluation, else the vertex stage (whose built-in needs no demotion). const Int captureStage = stageIndex[3] >= 0 ? 3 : (stageIndex[2] >= 0 ? 2 : -1); - constexpr GLenum kStageEnum[4] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, - GL_TESS_EVALUATION_SHADER, GL_GEOMETRY_SHADER}; + constexpr GLenum kStageEnum[4] = {GL_VERTEX_SHADER, GL_TESS_CONTROL_SHADER, GL_TESS_EVALUATION_SHADER, + GL_GEOMETRY_SHADER}; // Back to front, so each stage's "I now read the carrier" report can force the // producing stage's output carrier into existence - Vulkan requires every @@ -1164,7 +1146,8 @@ namespace MobileGL { const Int moduleIndex = stageIndex[stage]; if (moduleIndex < 0) continue; Int producer = stage - 1; - while (producer >= 0 && stageIndex[producer] < 0) --producer; + while (producer >= 0 && stageIndex[producer] < 0) + --producer; DemotePointSizeOptions options; options.location = carrierLocation; @@ -1187,11 +1170,10 @@ namespace MobileGL { DemotePointSizeReport report; spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1); - optimizer.RegisterPass( - DemotePointSizePass::CreateDemotePointSizePass(options, &report)); + optimizer.RegisterPass(DemotePointSizePass::CreateDemotePointSizePass(options, &report)); if (!RunOptimizerChecked("DemoteTessellationGeometryPointSizeForProgram", optimizer, - modules[moduleIndex], rewritten[moduleIndex], - validateOutput, enableSpirvValidation)) { + modules[moduleIndex], rewritten[moduleIndex], validateOutput, + enableSpirvValidation)) { return false; // modules untouched: nothing was committed } if (report.declined) { @@ -1210,9 +1192,8 @@ namespace MobileGL { // byte-identical and leaves the honest built-in refusal in charge; only // teaching the synthesized stage to forward the carrier could do better. if (stage == 2 && stageIndex[1] < 0 && report.createdInputCarrier) { - outcome.declineDetail = - "an evaluation stage reads gl_in point size with no control stage to " - "carry it; the synthesized pass-through cannot forward the carrier"; + outcome.declineDetail = "an evaluation stage reads gl_in point size with no control stage to " + "carry it; the synthesized pass-through cannot forward the carrier"; return true; } rewrote[stage] = true; @@ -1238,8 +1219,7 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresFloat64"), spirv.data(), - spirv.size()); + SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresFloat64"), spirv.data(), spirv.size()); if (!context) { return false; } @@ -1271,8 +1251,8 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresFloat64VertexInput"), - spirv.data(), spirv.size()); + SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleDeclaresFloat64VertexInput"), spirv.data(), + spirv.size()); if (!context) { return false; } @@ -1307,9 +1287,9 @@ namespace MobileGL { if (spirv.empty()) { return false; } - std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleReadsLocatedInput"), spirv.data(), - spirv.size()); + std::unique_ptr context = + spvtools::BuildModule(SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ModuleReadsLocatedInput"), + spirv.data(), spirv.size()); if (!context) { return false; } @@ -1347,14 +1327,13 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass()); - return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("DemoteFloat64ToFloat32", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::SanitizeAndOptimizeBinary(const Vector& inputBinary, - Vector& outputBinary, - const bool validateOutput, - const bool enableSpirvValidation, - const bool nativeFloat64) { + Vector& outputBinary, const bool validateOutput, + const bool enableSpirvValidation, const bool nativeFloat64) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); @@ -1424,13 +1403,12 @@ namespace MobileGL { // would have laid out correctly by itself only costs the shader its index // arithmetic. if (!nativeFloat64) { - optimizer.RegisterPass( - FlattenFloat64StorageBlockPass::CreateFlattenFloat64StorageBlockPass()); + optimizer.RegisterPass(FlattenFloat64StorageBlockPass::CreateFlattenFloat64StorageBlockPass()); optimizer.RegisterPass(DemoteFloat64Pass::CreateDemoteFloat64Pass()); } - return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary, - outputBinary, validateOutput, enableSpirvValidation); + return RunOptimizerChecked("SanitizeAndOptimizeBinary", optimizer, inputBinary, outputBinary, + validateOutput, enableSpirvValidation); } bool ShaderCompiler::LowerDrawParametersForEssl(const Vector& inputBinary, @@ -1440,8 +1418,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass()); - return RunOptimizerChecked("LowerDrawParametersForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("LowerDrawParametersForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::LowerViewportIndexForEssl(const Vector& inputBinary, @@ -1451,8 +1429,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(LowerViewportIndexPass::CreateLowerViewportIndexPass()); - return RunOptimizerChecked("LowerViewportIndexForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("LowerViewportIndexForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::DeferGeometryEmitsForEssl(const Vector& inputBinary, @@ -1462,8 +1440,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); Bool deferred = false; optimizer.RegisterPass(DeferGeometryEmitsPass::CreateDeferGeometryEmitsPass(&deferred)); - const bool ok = RunOptimizerChecked("DeferGeometryEmitsForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + const bool ok = RunOptimizerChecked("DeferGeometryEmitsForEssl", optimizer, inputBinary, outputBinary, + true, enableSpirvValidation); outDeferred = ok && deferred; return ok; } @@ -1472,37 +1450,31 @@ namespace MobileGL { return LowerViewportIndexPass::DeclaresViewportIndexBuiltin(binary); } - bool ShaderCompiler::ClampMultisampleFetchesForEssl(const Vector& inputBinary, - Vector& outputBinary, - const Int32 maxColorSamples, - const Int32 maxIntegerSamples, - const Int32 maxDepthSamples, - const Int32 advertisedMaxSamples, - const bool enableSpirvValidation, - const bool deferDirectUnsignedFetches) { + bool ShaderCompiler::ClampMultisampleFetchesForEssl( + const Vector& inputBinary, Vector& outputBinary, const Int32 maxColorSamples, + const Int32 maxIntegerSamples, const Int32 maxDepthSamples, const Int32 advertisedMaxSamples, + const bool enableSpirvValidation, const bool deferDirectUnsignedFetches) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(ClampMultisampleFetchPass::CreateClampMultisampleFetchPass( maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples, deferDirectUnsignedFetches)); - return RunOptimizerChecked("ClampMultisampleFetchesForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("ClampMultisampleFetchesForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::DeclaresMultisampledImage(const Vector& binary) { return ClampMultisampleFetchPass::DeclaresMultisampledImage(binary); } - ShaderCompiler::SpirvGateFeatures ShaderCompiler::ProbeSpirvGateFeatures( - const Vector& binary) { + ShaderCompiler::SpirvGateFeatures ShaderCompiler::ProbeSpirvGateFeatures(const Vector& binary) { SpirvGateFeatures features; if (binary.empty()) { return features; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); if (!context) { // Unparseable here means unusable downstream too; let the ordinary transpile @@ -1523,8 +1495,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(SplitArrayVertexInputsPass::CreateSplitArrayVertexInputsPass()); - return RunOptimizerChecked("SplitArrayVertexInputsForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("SplitArrayVertexInputsForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::BakeImageFormatsForEssl(const Vector& inputBinary, @@ -1536,7 +1508,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(BakeImageFormatsPass::CreateBakeImageFormatsPass(glFormatByName)); - return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("BakeImageFormatsForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::DeclaresFormatlessStorageImage(const Vector& binary) { @@ -1572,14 +1545,13 @@ namespace MobileGL { // itself deliberately does not carry a join of its own. optimizer.RegisterPass(CreateRemoveDuplicatesPass()); - return RunOptimizerChecked("WidenImageFormatsForEssl", optimizer, inputBinary, outputBinary, - true, enableSpirvValidation); + return RunOptimizerChecked("WidenImageFormatsForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::DeclaresWidenableImageFormat(const Vector& binary, const bool onlyFormatsSpirvCrossRefusesToPrint) { - return WidenImageFormatsPass::DeclaresWidenableImageFormat(binary, - onlyFormatsSpirvCrossRefusesToPrint); + return WidenImageFormatsPass::DeclaresWidenableImageFormat(binary, onlyFormatsSpirvCrossRefusesToPrint); } Uint ShaderCompiler::WidenedCoreEsslImageFormat(Uint glInternalFormat) { @@ -1611,14 +1583,14 @@ namespace MobileGL { optimizer.RegisterPass(FlattenXfbInterfaceBlocksPass::CreateFlattenXfbInterfaceBlocksPass( blockNames, &flattenedBlockNames)); - return RunOptimizerChecked("FlattenXfbInterfaceBlocksForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("FlattenXfbInterfaceBlocksForEssl", optimizer, inputBinary, outputBinary, + true, enableSpirvValidation); } - bool ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock( - const String& captureName, const std::set& flattenedBlockNames, String& outName) { - return FlattenXfbInterfaceBlocksPass::RewriteCaptureName(captureName, flattenedBlockNames, - outName); + bool ShaderCompiler::RewriteXfbCaptureNameForFlattenedBlock(const String& captureName, + const std::set& flattenedBlockNames, + String& outName) { + return FlattenXfbInterfaceBlocksPass::RewriteCaptureName(captureName, flattenedBlockNames, outName); } void ShaderCompiler::ProbeIoBlockNamesForEssl(const Vector& binary, @@ -1630,9 +1602,9 @@ namespace MobileGL { // spurious diagnostic through the message consumer. return; } - std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ProbeIoBlockNamesForEssl"), binary.data(), - binary.size()); + std::unique_ptr context = + spvtools::BuildModule(SPV_ENV_VULKAN_1_1, MakeSpirvMessageConsumer("ProbeIoBlockNamesForEssl"), + binary.data(), binary.size()); if (!context) { // Unparseable here means unusable downstream too; let the ordinary transpile // path produce the error rather than inventing a rename plan from it. @@ -1653,15 +1625,13 @@ namespace MobileGL { optimizer.RegisterPass(UniquifyIoBlockNamesPass::CreateUniquifyIoBlockNamesPass( inputBlockRenames, outputBlockRenames, &renamedBlockNames)); - return RunOptimizerChecked("UniquifyIoBlockNamesForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("UniquifyIoBlockNamesForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::StripIoBlockLocationsForEssl(const Vector& inputBinary, - const bool stripInputBlocks, - const bool stripOutputBlocks, - bool& strippedAny, - Vector& outputBinary, + const bool stripInputBlocks, const bool stripOutputBlocks, + bool& strippedAny, Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; strippedAny = false; @@ -1678,8 +1648,8 @@ namespace MobileGL { // and hands the result straight to SPIRV-Cross, which needs no location to // print a block. Validating here would latch a failure on every affected // program and teach the counter to cry wolf. - return RunOptimizerChecked("StripIoBlockLocationsForEssl", optimizer, inputBinary, - outputBinary, false, enableSpirvValidation); + return RunOptimizerChecked("StripIoBlockLocationsForEssl", optimizer, inputBinary, outputBinary, false, + enableSpirvValidation); } bool ShaderCompiler::PackDoubleVertexInputsForVulkan(const Vector& inputBinary, @@ -1689,8 +1659,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass()); - return RunOptimizerChecked("PackDoubleVertexInputsForVulkan", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("PackDoubleVertexInputsForVulkan", optimizer, inputBinary, outputBinary, + true, enableSpirvValidation); } bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector& inputBinary, @@ -1698,11 +1668,10 @@ namespace MobileGL { const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); - optimizer.RegisterPass( - StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); + optimizer.RegisterPass(StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass()); - return RunOptimizerChecked("StripUboMemberRelaxedPrecisionForEssl", optimizer, - inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("StripUboMemberRelaxedPrecisionForEssl", optimizer, inputBinary, + outputBinary, true, enableSpirvValidation); } bool ShaderCompiler::StripNoPerspectiveForEssl(const Vector& inputBinary, @@ -1712,8 +1681,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(StripNoPerspectivePass::CreateStripNoPerspectivePass()); - return RunOptimizerChecked("StripNoPerspectiveForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("StripNoPerspectiveForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::EmulateNoPerspectiveForEssl(const Vector& inputBinary, @@ -1723,8 +1692,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(EmulateNoPerspectivePass::CreateEmulateNoPerspectivePass()); - return RunOptimizerChecked("EmulateNoPerspectiveForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("EmulateNoPerspectiveForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::LegalizeFragmentOutputIndexingForEssl(const Vector& inputBinary, @@ -1761,8 +1730,8 @@ namespace MobileGL { folder.RegisterPass(CreateBlockMergePass()); Vector folded; - if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.fold", folder, inputBinary, - folded, true, enableSpirvValidation) || + if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.fold", folder, inputBinary, folded, + true, enableSpirvValidation) || folded.empty()) { // Fail open onto the fallback rather than onto the illegal module. folded = inputBinary; @@ -1780,8 +1749,8 @@ namespace MobileGL { // false here for the same reason it does in SanitizeAndOptimizeBinary. lowerer.RegisterPass(CreateAggressiveDCEPass(false)); - if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.lower", lowerer, folded, - outputBinary, true, enableSpirvValidation) || + if (!RunOptimizerChecked("LegalizeFragmentOutputIndexingForEssl.lower", lowerer, folded, outputBinary, + true, enableSpirvValidation) || outputBinary.empty()) { outputBinary = folded; return true; @@ -1792,22 +1761,21 @@ namespace MobileGL { // lazily mid-session, so an unlatched line here is unbounded runtime noise. // (Parked at MGLOG_I until the Log.h ordering fix made W live at INFO.) MGLOG_W_ONCE("[spirv] LegalizeFragmentOutputIndexingForEssl: a fragment output is still " - "indexed dynamically; a strict ES driver will reject this shader"); + "indexed dynamically; a strict ES driver will reject this shader"); } return true; } - bool ShaderCompiler::LegalizeResourceArrayIndexingForEssl( - const Vector& inputBinary, Vector& outputBinary, - const bool enableSpirvValidation) { + bool ShaderCompiler::LegalizeResourceArrayIndexingForEssl(const Vector& inputBinary, + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; // Detection gates everything: a module that declares no array of storage blocks // and no array of images, or indexes one only with constants - every shader but // a handful - pays one BuildModule and is handed back byte for byte, so the // folding chain can never perturb a shader that did not need it. - if (!LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing( - inputBinary)) { + if (!LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing(inputBinary)) { outputBinary = inputBinary; return true; } @@ -1832,36 +1800,33 @@ namespace MobileGL { folder.RegisterPass(CreateBlockMergePass()); Vector folded; - if (!RunOptimizerChecked("LegalizeResourceArrayIndexingForEssl.fold", folder, - inputBinary, folded, true, enableSpirvValidation) || + if (!RunOptimizerChecked("LegalizeResourceArrayIndexingForEssl.fold", folder, inputBinary, folded, true, + enableSpirvValidation) || folded.empty()) { // Fail open onto the fallback rather than onto the illegal module. folded = inputBinary; } - if (!LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing( - folded)) { + if (!LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing(folded)) { outputBinary = folded; return true; } // Genuinely dynamic (uniform-derived, non-constant trip count, ...): lower it. Optimizer lowerer(SPV_ENV_VULKAN_1_1); - lowerer.RegisterPass( - LegalizeResourceArrayIndexPass::CreateLowerToConstantSwitchPass()); + lowerer.RegisterPass(LegalizeResourceArrayIndexPass::CreateLowerToConstantSwitchPass()); // The chains the lowering replaced are dead now; remove_outputs must stay // false here for the same reason it does in SanitizeAndOptimizeBinary. lowerer.RegisterPass(CreateAggressiveDCEPass(false)); - if (!RunOptimizerChecked("LegalizeResourceArrayIndexingForEssl.lower", lowerer, folded, - outputBinary, true, enableSpirvValidation) || + if (!RunOptimizerChecked("LegalizeResourceArrayIndexingForEssl.lower", lowerer, folded, outputBinary, + true, enableSpirvValidation) || outputBinary.empty()) { outputBinary = folded; return true; } - if (LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing( - outputBinary)) { + if (LegalizeResourceArrayIndexPass::BinaryHasDynamicResourceArrayIndexing(outputBinary)) { // MGLOG_W, latched, for the same reason the fragment-output one is: this // runs per shader compile and shader packs compile lazily mid-session. MGLOG_W_ONCE("[spirv] LegalizeResourceArrayIndexingForEssl: an array of storage " @@ -1871,9 +1836,9 @@ namespace MobileGL { return true; } - bool ShaderCompiler::FlattenAtomicCounterBlockOffsetsForEssl( - const Vector& inputBinary, Vector& outputBinary, - const bool enableSpirvValidation) { + bool ShaderCompiler::FlattenAtomicCounterBlockOffsetsForEssl(const Vector& inputBinary, + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; // Detection gates everything: a module with no atomic counter, or one whose @@ -1891,8 +1856,7 @@ namespace MobileGL { outputBinary, true, enableSpirvValidation); } - bool ShaderCompiler::LowerRectImages(const Vector& inputBinary, - Vector& outputBinary, + bool ShaderCompiler::LowerRectImages(const Vector& inputBinary, Vector& outputBinary, const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); @@ -1900,11 +1864,13 @@ namespace MobileGL { // Rect and 2D images of the same format become the same type after lowering. optimizer.RegisterPass(DeduplicateImageTypesPass::Create()); - return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("LowerRectImages", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::Lower1DArrayImagesForEssl(const Vector& inputBinary, - Vector& outputBinary, const bool enableSpirvValidation) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; // Declined rather than half-translated: after the rewrite the image is a 2D @@ -1928,8 +1894,8 @@ namespace MobileGL { } if (traits.queriesImageSize) { MGLOG_W_ONCE("[spirv] Lower1DArrayImagesForEssl: the module queries the size of a 1D " - "storage image, which cannot be answered in the 2D shape ES stores it in; " - "leaving the module alone, and a strict ES driver will reject it"); + "storage image, which cannot be answered in the 2D shape ES stores it in; " + "leaving the module alone, and a strict ES driver will reject it"); outputBinary = inputBinary; return true; } @@ -1947,7 +1913,8 @@ namespace MobileGL { // second Shader. Deduplicating afterwards collapses all three at once. optimizer.RegisterPass(CreateRemoveDuplicatesPass()); - return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("Lower1DArrayImagesForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::Lower1DSampledImagesForEssl(const Vector& inputBinary, @@ -1979,27 +1946,30 @@ namespace MobileGL { // them, and to the Sampled1D capability the rewrite turns into a second Shader. optimizer.RegisterPass(CreateRemoveDuplicatesPass()); - return RunOptimizerChecked("Lower1DSampledImagesForEssl", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("Lower1DSampledImagesForEssl", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::RebaseInstanceIndexForVulkan(const Vector& inputBinary, - Vector& outputBinary, const bool enableSpirvValidation) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(RebaseInstanceIndexPass::CreateRebaseInstanceIndexPass()); - return RunOptimizerChecked("RebaseInstanceIndexForVulkan", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("RebaseInstanceIndexForVulkan", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::ZeroBaseVertexForVulkan(const Vector& inputBinary, - Vector& outputBinary, const bool enableSpirvValidation) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(ZeroBaseVertexPass::CreateZeroBaseVertexPass()); - return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("ZeroBaseVertexForVulkan", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::LowerPixelCenterIntegerForVulkan(const Vector& inputBinary, @@ -2014,8 +1984,8 @@ namespace MobileGL { } bool ShaderCompiler::ZeroLayerOutputForVulkan(const Vector& inputBinary, - Vector& outputBinary, - const bool enableSpirvValidation) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(ZeroLayerOutputPass::CreateZeroLayerOutputPass()); @@ -2042,8 +2012,8 @@ namespace MobileGL { Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DeriveNumSubgroupsPass::CreateDeriveNumSubgroupsPass()); - return RunOptimizerChecked("DeriveNumSubgroupsForVulkan", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("DeriveNumSubgroupsForVulkan", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::EmulateSubgroupsForVulkan(const Vector& inputBinary, @@ -2053,51 +2023,52 @@ namespace MobileGL { const bool atomicScratchReads) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); - optimizer.RegisterPass(EmulateSubgroupsPass::CreateEmulateSubgroupsPass(maxWorkgroupScratchBytes, - atomicScratchReads)); + optimizer.RegisterPass( + EmulateSubgroupsPass::CreateEmulateSubgroupsPass(maxWorkgroupScratchBytes, atomicScratchReads)); - return RunOptimizerChecked("EmulateSubgroupsForVulkan", optimizer, inputBinary, + return RunOptimizerChecked("EmulateSubgroupsForVulkan", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); + } + + bool ShaderCompiler::FixIterationRPSubgroupScratchForVulkan(const Vector& inputBinary, + Vector& outputBinary, + const Uint32 nativeSubgroupSize, + const Uint32 maxWorkgroupScratchBytes, + const bool enableSpirvValidation) { + using namespace spvtools; + Optimizer optimizer(SPV_ENV_VULKAN_1_1); + optimizer.RegisterPass(FixIterationRPSubgroupScratchPass::CreateFixIterationRPSubgroupScratchPass( + nativeSubgroupSize, maxWorkgroupScratchBytes)); + + return RunOptimizerChecked("FixIterationRPSubgroupScratchForVulkan", optimizer, inputBinary, outputBinary, true, enableSpirvValidation); } - bool ShaderCompiler::FixIterationRPSubgroupScratchForVulkan( - const Vector& inputBinary, Vector& outputBinary, - const Uint32 nativeSubgroupSize, const Uint32 maxWorkgroupScratchBytes, - const bool enableSpirvValidation) { - using namespace spvtools; - Optimizer optimizer(SPV_ENV_VULKAN_1_1); - optimizer.RegisterPass( - FixIterationRPSubgroupScratchPass::CreateFixIterationRPSubgroupScratchPass( - nativeSubgroupSize, maxWorkgroupScratchBytes)); - - return RunOptimizerChecked("FixIterationRPSubgroupScratchForVulkan", optimizer, - inputBinary, outputBinary, true, enableSpirvValidation); - } - - bool ShaderCompiler::FixIterationRPBarrierForVulkan( - const Vector& inputBinary, Vector& outputBinary, - const bool enableSpirvValidation) { + bool ShaderCompiler::FixIterationRPBarrierForVulkan(const Vector& inputBinary, + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(FixIterationRPBarrierPass::CreateFixIterationRPBarrierPass()); - return RunOptimizerChecked("FixIterationRPBarrierForVulkan", optimizer, - inputBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("FixIterationRPBarrierForVulkan", optimizer, inputBinary, outputBinary, true, + enableSpirvValidation); } bool ShaderCompiler::DecoratePositionInvariantForVulkan(const Vector& inputBinary, - Vector& outputBinary, const bool enableSpirvValidation) { + Vector& outputBinary, + const bool enableSpirvValidation) { using namespace spvtools; Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass()); - return RunOptimizerChecked("DecoratePositionInvariantForVulkan", optimizer, inputBinary, - outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("DecoratePositionInvariantForVulkan", optimizer, inputBinary, outputBinary, + true, enableSpirvValidation); } - bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan( - const Vector& inputBinary, Vector& outputBinary, - const bool enableSpirvValidation) { + bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(const Vector& inputBinary, + Vector& outputBinary, + const bool enableSpirvValidation) { constexpr SizeT kSpirvHeaderWordCount = 5; outputBinary.clear(); if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) { @@ -2122,8 +2093,7 @@ namespace MobileGL { if (opcode == spv::Op::OpCapability && wordCount >= 2) { capabilityInsertOffset = offset + wordCount; const auto capability = static_cast(inputBinary[offset + 1]); - hasReadWithoutFormatCapability |= - capability == spv::Capability::StorageImageReadWithoutFormat; + hasReadWithoutFormatCapability |= capability == spv::Capability::StorageImageReadWithoutFormat; hasWriteWithoutFormatCapability |= capability == spv::Capability::StorageImageWriteWithoutFormat; } else if (opcode == spv::Op::OpTypeFloat && wordCount >= 3) { @@ -2165,13 +2135,11 @@ namespace MobileGL { return false; } Uint32 imageTypeId = resultTypeById[imageId]; - if (imageTypeId < pointerPointeeTypeById.size() && - pointerPointeeTypeById[imageTypeId] != 0) { + if (imageTypeId < pointerPointeeTypeById.size() && pointerPointeeTypeById[imageTypeId] != 0) { imageTypeId = pointerPointeeTypeById[imageTypeId]; } - if (imageTypeId != 0 && - std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) == - atomicImageTypeIds.end()) { + if (imageTypeId != 0 && std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), + imageTypeId) == atomicImageTypeIds.end()) { atomicImageTypeIds.push_back(imageTypeId); } } @@ -2193,9 +2161,8 @@ namespace MobileGL { const Uint32 sampled = outputBinary[offset + 7]; const Bool hasFloatSampledType = std::find(floatTypeIds.begin(), floatTypeIds.end(), sampledTypeId) != floatTypeIds.end(); - const Bool usedByAtomic = - std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) != - atomicImageTypeIds.end(); + const Bool usedByAtomic = std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), + imageTypeId) != atomicImageTypeIds.end(); if (sampled == 2 && hasFloatSampledType && !usedByAtomic) { outputBinary[offset + 8] = static_cast(spv::ImageFormat::Unknown); hasFloatStorageImage = true; @@ -2209,17 +2176,14 @@ namespace MobileGL { } Vector addedCapabilities; - const Uint32 capabilityInstruction = - (2u << 16u) | static_cast(spv::Op::OpCapability); + const Uint32 capabilityInstruction = (2u << 16u) | static_cast(spv::Op::OpCapability); if (!hasReadWithoutFormatCapability) { addedCapabilities.push_back(capabilityInstruction); - addedCapabilities.push_back( - static_cast(spv::Capability::StorageImageReadWithoutFormat)); + addedCapabilities.push_back(static_cast(spv::Capability::StorageImageReadWithoutFormat)); } if (!hasWriteWithoutFormatCapability) { addedCapabilities.push_back(capabilityInstruction); - addedCapabilities.push_back( - static_cast(spv::Capability::StorageImageWriteWithoutFormat)); + addedCapabilities.push_back(static_cast(spv::Capability::StorageImageWriteWithoutFormat)); } outputBinary.insert(outputBinary.begin() + static_cast(capabilityInsertOffset), addedCapabilities.begin(), addedCapabilities.end()); @@ -2228,8 +2192,8 @@ namespace MobileGL { spvtools::Optimizer optimizer(SPV_ENV_VULKAN_1_1); optimizer.RegisterPass(DeduplicateImageTypesPass::Create()); Vector rewrittenBinary = std::move(outputBinary); - return RunOptimizerChecked("UseUnformattedFloatStorageImagesForVulkan", optimizer, - rewrittenBinary, outputBinary, true, enableSpirvValidation); + return RunOptimizerChecked("UseUnformattedFloatStorageImagesForVulkan", optimizer, rewrittenBinary, + outputBinary, true, enableSpirvValidation); } namespace { @@ -2321,11 +2285,11 @@ namespace MobileGL { PrepareSpirvValidation(); spvtools::SpirvTools tools(SPV_ENV_OPENGL_4_5); String diagnostics; - tools.SetMessageConsumer([&diagnostics](spv_message_level_t, const char*, const spv_position_t&, - const char* message) { - if (!diagnostics.empty()) diagnostics += "\n"; - diagnostics += message ? message : ""; - }); + tools.SetMessageConsumer( + [&diagnostics](spv_message_level_t, const char*, const spv_position_t&, const char* message) { + if (!diagnostics.empty()) diagnostics += "\n"; + diagnostics += message ? message : ""; + }); if (!tools.Validate(spirv.data(), spirv.size())) { r.log = "Error: [ARB_gl_spirv] the module failed SPIR-V validation:\n" + diagnostics; return std::unexpected(r); diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h index 569bc01c..6dfdcd5d 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderCompiler.h @@ -46,10 +46,8 @@ namespace MobileGL { // for the WHOLE program (CompileEnv::ConsumesFloat64Natively() minus the // 64-bit-vertex-input exception) before touching any module. Do not re-derive it // per module: the global UBO is one buffer every stage reads. - static bool SanitizeAndOptimizeBinary(const Vector& inputBinary, - Vector& outputBinary, - bool validateOutput = true, - bool enableSpirvValidation = false, + static bool SanitizeAndOptimizeBinary(const Vector& inputBinary, Vector& outputBinary, + bool validateOutput = true, bool enableSpirvValidation = false, bool nativeFloat64 = false); // Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals // (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL. @@ -64,15 +62,13 @@ namespace MobileGL { // runs, instead of every draw made with it becoming a silent no-op. Only for the // DirectGLES transpile path on a driver WITHOUT GL_OES_viewport_array; gl_Layer is // deliberately left alone, being core in ESSL 3.20 geometry shaders. - static bool LowerViewportIndexForEssl(const Vector& inputBinary, - Vector& outputBinary, + static bool LowerViewportIndexForEssl(const Vector& inputBinary, Vector& outputBinary, bool enableSpirvValidation = false); // Moves a geometry stage's emits behind its memory writes (DeferGeometryEmitsPass), for // an ES driver that drops storage writes made after EmitVertex(). `outDeferred` says // whether the module was rewritten; a module the pass declines comes back unchanged. - static bool DeferGeometryEmitsForEssl(const Vector& inputBinary, - Vector& outputBinary, bool& outDeferred, - bool enableSpirvValidation = false); + static bool DeferGeometryEmitsForEssl(const Vector& inputBinary, Vector& outputBinary, + bool& outDeferred, bool enableSpirvValidation = false); // Whether the module declares an output decorated BuiltIn ViewportIndex, i.e. // whether the pass above has anything to do. The gate that keeps every other // stage off an optimizer round trip it does not need. @@ -87,10 +83,8 @@ namespace MobileGL { // only. See ClampMultisampleFetchPass, which also says what // deferDirectUnsignedFetches hands over to the caller's runtime clamp. static bool ClampMultisampleFetchesForEssl(const Vector& inputBinary, - Vector& outputBinary, - Int32 maxColorSamples, - Int32 maxIntegerSamples, - Int32 maxDepthSamples, + Vector& outputBinary, Int32 maxColorSamples, + Int32 maxIntegerSamples, Int32 maxDepthSamples, Int32 advertisedMaxSamples, bool enableSpirvValidation = false, bool deferDirectUnsignedFetches = false); @@ -172,9 +166,9 @@ namespace MobileGL { // located block across any tessellation or geometry boundary; only for the // DirectGLES transpile path, and only when the driver POST says so. See // StripIoBlockLocationsPass. - static bool StripIoBlockLocationsForEssl(const Vector& inputBinary, - bool stripInputBlocks, bool stripOutputBlocks, - bool& strippedAny, Vector& outputBinary, + static bool StripIoBlockLocationsForEssl(const Vector& inputBinary, bool stripInputBlocks, + bool stripOutputBlocks, bool& strippedAny, + Vector& outputBinary, bool enableSpirvValidation = false); // Drops RelaxedPrecision member decorations from uniform-block structs so // SPIRV-Cross prints the same (highp) member precision in every stage; ES @@ -182,19 +176,18 @@ namespace MobileGL { // Only for the DirectGLES transpile path. static bool StripUboMemberRelaxedPrecisionForEssl(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Removes NoPerspective decorations so SPIRV-Cross emits plain (smooth) ESSL varyings. // DirectGLES fallback only, for devices lacking GL_NV_shader_noperspective_interpolation // (SPIRV-Cross would otherwise require that extension and the driver would reject it). - static bool StripNoPerspectiveForEssl(const Vector& inputBinary, - Vector& outputBinary, + static bool StripNoPerspectiveForEssl(const Vector& inputBinary, Vector& outputBinary, bool enableSpirvValidation = false); // Emulates noperspective (screen-linear) interpolation via gl_Position.w / gl_FragCoord.w // so no NV extension is needed; strips what it cannot emulate. DirectGLES fallback for // devices lacking GL_NV_shader_noperspective_interpolation. See EmulateNoPerspectivePass. static bool EmulateNoPerspectiveForEssl(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Makes every index into a fragment-output array a constant integral // expression, which is what GLSL ES requires and SPIR-V does not. Runs the // stock folding chain first (loop unrolling folds the loop-derived indices @@ -206,7 +199,7 @@ namespace MobileGL { // See LegalizeFragmentOutputIndexPass. static bool LegalizeFragmentOutputIndexingForEssl(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Makes every index into an ARRAY OF SHADER STORAGE BLOCKS or an ARRAY OF IMAGE // UNIFORMS a constant integral expression. Desktop GL allows any // dynamically-uniform index in either; ES keeps the ES 3.1 @@ -223,8 +216,8 @@ namespace MobileGL { // when no such array is indexed dynamically, which is every shader but a // handful. See LegalizeResourceArrayIndexPass. static bool LegalizeResourceArrayIndexingForEssl(const Vector& inputBinary, - Vector& outputBinary, - bool enableSpirvValidation = false); + Vector& outputBinary, + bool enableSpirvValidation = false); // Collapses each synthesized gl_AtomicCounterBlock_ into one uint array at // offset 0, re-indexing every counter access to the element that used to sit at // its byte offset. glslang preserves the application's layout(offset = N) as the @@ -256,8 +249,7 @@ namespace MobileGL { // and must see the module unchanged. Copies the input through untouched when the // module declares no such image, which is every shader but a handful. See // Lower1DArrayImagesPass for what it declines and why. - static bool Lower1DArrayImagesForEssl(const Vector& inputBinary, - Vector& outputBinary, + static bool Lower1DArrayImagesForEssl(const Vector& inputBinary, Vector& outputBinary, bool enableSpirvValidation = false); // The SAMPLED-image counterpart. SPIRV-Cross widens a 1D sampler's COORDINATE for // ES and prints the OFFSET and GRADIENT operands with their original 1D arity, so @@ -280,8 +272,7 @@ namespace MobileGL { // natively. See BakeImageFormatsPass for what it declines and why. static bool BakeImageFormatsForEssl(const Vector& inputBinary, const UnorderedMap& glFormatByName, - Vector& outputBinary, - bool enableSpirvValidation = false); + Vector& outputBinary, bool enableSpirvValidation = false); // Whether the module declares a storage image with no format qualifier at all, // i.e. whether BakeImageFormatsForEssl could change anything. One module parse, // so the ~every shader that declares none pays no optimizer run. @@ -312,8 +303,7 @@ namespace MobileGL { // ESSL route even WITH GL_NV_image_formats, because SPIRV-Cross throws for them // rather than printing a token - which is the whole set a driver that advertises // the extension still needs. See WidenImageFormatsPass. - static bool WidenImageFormatsForEssl(const Vector& inputBinary, - Vector& outputBinary, + static bool WidenImageFormatsForEssl(const Vector& inputBinary, Vector& outputBinary, bool onlyFormatsSpirvCrossRefusesToPrint = false, bool enableSpirvValidation = false); // Whether the module declares a storage image WidenImageFormatsForEssl would @@ -345,13 +335,12 @@ namespace MobileGL { static Uint SplitCoreEsslBufferImageFormat(Uint glInternalFormat); static bool RebaseInstanceIndexForVulkan(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Builds the non-indexed-draw variant of a vertex shader: every gl_BaseVertex // read becomes zero, which is what GL defines for a command carrying no // baseVertex parameter while Vulkan's builtin would report firstVertex. // See ZeroBaseVertexPass. - static bool ZeroBaseVertexForVulkan(const Vector& inputBinary, - Vector& outputBinary, + static bool ZeroBaseVertexForVulkan(const Vector& inputBinary, Vector& outputBinary, bool enableSpirvValidation = false); // Drops `layout(pixel_center_integer)` from a fragment module, which the Vulkan // environment forbids, and shifts every gl_FragCoord.xy read by -0.5 to keep its @@ -362,9 +351,8 @@ namespace MobileGL { // Builds the variant of a pre-rasterization module that a non-layered framebuffer // draws with: every gl_Layer store writes 0, since GL ignores gl_Layer there. A // module that writes no Layer comes back unchanged. See ZeroLayerOutputPass. - static bool ZeroLayerOutputForVulkan(const Vector& inputBinary, - Vector& outputBinary, - bool enableSpirvValidation = false); + static bool ZeroLayerOutputForVulkan(const Vector& inputBinary, Vector& outputBinary, + bool enableSpirvValidation = false); // Builds the variant of a fragment module that a draw without multisample fragment // operations (single-sample framebuffer, or MULTISAMPLE disabled) uses: every // gl_SampleMask store writes all-ones, since GL ignores the mask there. A module @@ -393,8 +381,7 @@ namespace MobileGL { // minimum). atomicScratchReads reads the scratch back through atomic loads, // for a driver whose plain shared-memory loads go stale across barrier() // (SelfTest::SharedMemoryLoadsGoStale). See EmulateSubgroupsPass. - static bool EmulateSubgroupsForVulkan(const Vector& inputBinary, - Vector& outputBinary, + static bool EmulateSubgroupsForVulkan(const Vector& inputBinary, Vector& outputBinary, Uint32 maxWorkgroupScratchBytes, bool enableSpirvValidation = false, bool atomicScratchReads = false); @@ -423,7 +410,7 @@ namespace MobileGL { // VertexInputStateFactory::ToVkVertexFormat. static bool PackDoubleVertexInputsForVulkan(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Adds the Invariant decoration to every Position builtin output. GL apps // routinely rely on cross-program position invariance for multi-pass // equality depth tests (e.g. GEQUAL re-draws of the same geometry), and @@ -431,22 +418,21 @@ namespace MobileGL { // decoration. DirectVulkan only. static bool DecoratePositionInvariantForVulkan(const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + bool enableSpirvValidation = false); // Replaces the declared format of float storage images with Unknown and adds the // matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan // shaderStorageImage*WithoutFormat features are enabled, allowing the // glBindImageTexture format to select the descriptor view at runtime. Integer // storage images deliberately keep their declared format for GL-compatible bit // reinterpretation paths (for example, R32F storage accessed as r32ui). - static bool UseUnformattedFloatStorageImagesForVulkan( - const Vector& inputBinary, Vector& outputBinary, - bool enableSpirvValidation = false); + static bool UseUnformattedFloatStorageImagesForVulkan(const Vector& inputBinary, + Vector& outputBinary, + bool enableSpirvValidation = false); // Rewrites every 64-bit float in the module to a 32-bit one, preserving every // block offset and stride exactly (see DemoteFloat64Pass). Already part of // SanitizeAndOptimizeBinary, which is where production reaches it; exposed // separately so a test can drive the demotion on its own. - static bool DemoteFloat64ToFloat32(const Vector& inputBinary, - Vector& outputBinary, + static bool DemoteFloat64ToFloat32(const Vector& inputBinary, Vector& outputBinary, bool enableSpirvValidation = false); static Result DecompileShader(SpvcSession& session); @@ -489,9 +475,9 @@ namespace MobileGL { // groups look like the second. enum class SpecializationFailure { None, - UnknownConstantId, // GL_INVALID_VALUE - UnknownEntryPoint, // GL_INVALID_VALUE - ModuleRejected, // COMPILE_STATUS false + info log + UnknownConstantId, // GL_INVALID_VALUE + UnknownEntryPoint, // GL_INVALID_VALUE + ModuleRejected, // COMPILE_STATUS false + info log }; // What a specialized module turns into: the GLSL the ordinary pipeline compiles, @@ -512,10 +498,12 @@ namespace MobileGL { GLenum xfbBufferMode = GL_INTERLEAVED_ATTRIBS; }; - static Result SpecializeAndDecompileSpirvModule( - const Vector& spirv, GLenum shaderType, const String& entryPoint, - const Vector& constantIds, const Vector& constantValues, - SpecializationFailure& outFailure); + static Result SpecializeAndDecompileSpirvModule(const Vector& spirv, + GLenum shaderType, + const String& entryPoint, + const Vector& constantIds, + const Vector& constantValues, + SpecializationFailure& outFailure); // spirv-val over an application-supplied module, against the environment MobileGL // parses and emits under. glShaderBinary is where a malformed module has to be @@ -643,10 +631,9 @@ namespace MobileGL { // trip the backends' own "reads a located input" refusal against a name the // application never wrote. static Bool DemoteTessellationGeometryPointSizeForProgram( - Vector>& modules, const Vector& shaderTypes, - Bool demoteTessellation, Bool demoteGeometry, Bool captureRequestsPointSize, - PointSizeDemotionOutcome& outcome, bool validateOutput = true, - bool enableSpirvValidation = false); + Vector>& modules, const Vector& shaderTypes, Bool demoteTessellation, + Bool demoteGeometry, Bool captureRequestsPointSize, PointSizeDemotionOutcome& outcome, + bool validateOutput = true, bool enableSpirvValidation = false); // True when the module still declares a 64-bit float type. After // SanitizeAndOptimizeBinary that can only mean DemoteFloat64Pass declined the diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 89358ca5..979bba50 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -53,7 +53,12 @@ namespace { // contains "/*" one byte in, and a naive search for that opener treats the rest of the file as // an unterminated comment. MobileGL::String MaskCommentsAndQuotedText(const MobileGL::String& source) { - enum class Region { Code, SingleLineComment, MultiLineComment, QuotedText }; + enum class Region { + Code, + SingleLineComment, + MultiLineComment, + QuotedText + }; MobileGL::String masked = source; Region region = Region::Code; @@ -216,13 +221,26 @@ namespace { // desktop and ES version) so the normalizer never starts rejecting a form it used to accept. bool IsRecognizedGlslVersion(unsigned version) { switch (version) { - case 100: case 110: case 120: case 130: case 140: case 150: - case 300: case 310: case 320: - case 330: case 400: case 410: case 420: case 430: - case 440: case 450: case 460: - return true; - default: - return false; + case 100: + case 110: + case 120: + case 130: + case 140: + case 150: + case 300: + case 310: + case 320: + case 330: + case 400: + case 410: + case 420: + case 430: + case 440: + case 450: + case 460: + return true; + default: + return false; } } @@ -396,8 +414,7 @@ namespace { if (info.hasValidVersionDirective && probe < lineEnd) { const void* hash = std::memchr(code.data() + probe, '#', lineEnd - probe); if (hash != nullptr) { - recordIfRedundant(static_cast(static_cast(hash) - code.data()), - lineEnd); + recordIfRedundant(static_cast(static_cast(hash) - code.data()), lineEnd); } } } else { @@ -427,10 +444,9 @@ namespace { probe++; SkipDirectiveWhitespace(code, probe, lineEnd); const MobileGL::String behavior = ReadDirectiveIdentifier(code, probe, lineEnd); - const bool isGpuShader5 = extension == "GL_ARB_gpu_shader5" || - extension == "GL_NV_gpu_shader5"; - const bool enablesExtension = behavior == "enable" || behavior == "require" || - behavior == "warn"; + const bool isGpuShader5 = extension == "GL_ARB_gpu_shader5" || extension == "GL_NV_gpu_shader5"; + const bool enablesExtension = + behavior == "enable" || behavior == "require" || behavior == "warn"; if (!extension.empty()) { info.namedExtensions.insert(extension); if (enablesExtension) info.enabledExtensions.insert(extension); @@ -493,8 +509,7 @@ namespace { return "#version " + std::to_string(info.version) + " core\n"; } - const bool useLegacyDesktopVersion = - info.version < 400 && !info.enablesGpuShader5; + const bool useLegacyDesktopVersion = info.version < 400 && !info.enablesGpuShader5; // The trailing marker records that this 330 came from a legacy declaration // (or none at all), so the compile-failure retry may re-raise it to 460. // An application's own "#version 330" never carries it and keeps strict @@ -641,10 +656,9 @@ namespace { bool IsInDirectiveLine(const Vector>& ranges, SizeT offset) { // Ranges are disjoint and sorted, so the only candidate is the last one starting at or // before the offset. - const auto next = std::upper_bound(ranges.begin(), ranges.end(), offset, - [](SizeT value, const std::pair& range) { - return value < range.first; - }); + const auto next = + std::upper_bound(ranges.begin(), ranges.end(), offset, + [](SizeT value, const std::pair& range) { return value < range.first; }); return next != ranges.begin() && offset < std::prev(next)->second; } @@ -657,8 +671,8 @@ namespace { }; bool IsStatementKeywordToken(const CodeToken& token) { - return std::binary_search(std::begin(kStatementKeywordsBeforeCall), - std::end(kStatementKeywordsBeforeCall), std::string_view(token.text)); + return std::binary_search(std::begin(kStatementKeywordsBeforeCall), std::end(kStatementKeywordsBeforeCall), + std::string_view(token.text)); } // A brace counter over raw tokens is preprocessor-blind: it counts the braces of BOTH arms of @@ -856,7 +870,8 @@ namespace { if (lastLine) lineEnd = source.size(); SizeT probe = lineStart; - while (probe < lineEnd && (source[probe] == ' ' || source[probe] == '\t')) probe++; + while (probe < lineEnd && (source[probe] == ' ' || source[probe] == '\t')) + probe++; const bool isLineDirective = masked.compare(probe, 5, "#line") == 0 && (probe + 5 >= lineEnd || !IsIdentifierChar(source[probe + 5])); @@ -890,7 +905,6 @@ namespace { source = std::move(result); } - MobileGL::String TrimDirectiveToken(const MobileGL::String& token) { SizeT start = 0; while (start < token.size() && std::isspace(static_cast(token[start]))) { @@ -907,13 +921,20 @@ namespace { // GL_SHADER_STAGE bit of a stage, as GL_SUBGROUP_SUPPORTED_STAGES_KHR spells it. GLbitfield SubgroupStageBit(MobileGL::ShaderStage stage) { switch (MobileGL::MG_Util::ConvertShaderStageToGLEnum(stage)) { - case GL_VERTEX_SHADER: return GL_VERTEX_SHADER_BIT; - case GL_TESS_CONTROL_SHADER: return GL_TESS_CONTROL_SHADER_BIT; - case GL_TESS_EVALUATION_SHADER: return GL_TESS_EVALUATION_SHADER_BIT; - case GL_GEOMETRY_SHADER: return GL_GEOMETRY_SHADER_BIT; - case GL_FRAGMENT_SHADER: return GL_FRAGMENT_SHADER_BIT; - case GL_COMPUTE_SHADER: return GL_COMPUTE_SHADER_BIT; - default: return 0; + case GL_VERTEX_SHADER: + return GL_VERTEX_SHADER_BIT; + case GL_TESS_CONTROL_SHADER: + return GL_TESS_CONTROL_SHADER_BIT; + case GL_TESS_EVALUATION_SHADER: + return GL_TESS_EVALUATION_SHADER_BIT; + case GL_GEOMETRY_SHADER: + return GL_GEOMETRY_SHADER_BIT; + case GL_FRAGMENT_SHADER: + return GL_FRAGMENT_SHADER_BIT; + case GL_COMPUTE_SHADER: + return GL_COMPUTE_SHADER_BIT; + default: + return 0; } } @@ -986,7 +1007,8 @@ namespace { const SizeT replaceLen = lineEnd - lineStart + (hasLineBreak ? 1 : 0); SizeT probe = 0; const auto skipSpace = [&] { - while (probe < line.size() && std::isspace(static_cast(line[probe]))) probe++; + while (probe < line.size() && std::isspace(static_cast(line[probe]))) + probe++; }; skipSpace(); if (probe < line.size() && line[probe] == '#') { @@ -999,15 +1021,17 @@ namespace { probe += extensionLen; skipSpace(); const SizeT nameStart = probe; - while (probe < line.size() && IsIdentifierChar(line[probe])) probe++; + while (probe < line.size() && IsIdentifierChar(line[probe])) + probe++; const MobileGL::String name = line.substr(nameStart, probe - nameStart); skipSpace(); if (!name.empty() && probe < line.size() && line[probe] == ':' && IsUnavailableShaderExtension(env, stage, name)) { const MobileGL::String behavior = TrimDirectiveToken(line.substr(probe + 1)); if (behavior == "require") { - edits.push_back({lineStart, replaceLen, - "#error " + name + " is not available to this shader stage in MobileGL\n"}); + edits.push_back( + {lineStart, replaceLen, + "#error " + name + " is not available to this shader stage in MobileGL\n"}); } else if (behavior == "enable" || behavior == "warn") { edits.push_back({lineStart, replaceLen, "\n"}); } @@ -1057,9 +1081,8 @@ namespace { } const auto isDecimalInteger = [](const String& text) { - return !text.empty() && std::all_of(text.begin(), text.end(), [](char ch) { - return ch >= '0' && ch <= '9'; - }); + return !text.empty() && + std::all_of(text.begin(), text.end(), [](char ch) { return ch >= '0' && ch <= '9'; }); }; const Vector tokens = TokenizeCode(source); @@ -1185,17 +1208,17 @@ namespace { bool IsParameterQualifierKeyword(const String& text) { static constexpr std::string_view kQualifiers[] = { - "const", "in", "out", "inout", "highp", "mediump", "lowp", - "precise", "coherent", "volatile", "restrict", "readonly", "writeonly", + "const", "in", "out", "inout", "highp", "mediump", "lowp", + "precise", "coherent", "volatile", "restrict", "readonly", "writeonly", }; return std::find(std::begin(kQualifiers), std::end(kQualifiers), std::string_view(text)) != - std::end(kQualifiers); + std::end(kQualifiers); } // The #if/#ifdef/#ifndef nesting in effect at each offset, as (offset, depth) marks. Every // mark takes effect at the END of the directive line that changed the depth. Vector> BuildConditionalDepthMarks(const MobileGL::String& source, - const Vector>& ranges) { + const Vector>& ranges) { Vector> marks; marks.emplace_back(static_cast(0), 0); int depth = 0; @@ -1219,10 +1242,9 @@ namespace { } int ConditionalDepthAt(const Vector>& marks, SizeT offset) { - const auto next = std::upper_bound(marks.begin(), marks.end(), offset, - [](SizeT value, const std::pair& mark) { - return value < mark.first; - }); + const auto next = + std::upper_bound(marks.begin(), marks.end(), offset, + [](SizeT value, const std::pair& mark) { return value < mark.first; }); return next == marks.begin() ? 0 : std::prev(next)->second; } @@ -1346,8 +1368,7 @@ namespace { if (count < 4 || !HasBalancedBraces(tokens)) return; const Vector> directiveRanges = FindDirectiveLineRanges(source); - const Vector> conditionalDepth = - BuildConditionalDepthMarks(source, directiveRanges); + const Vector> conditionalDepth = BuildConditionalDepthMarks(source, directiveRanges); struct SubroutineType { String returnText; // empty until the type declaration itself is seen @@ -1519,7 +1540,7 @@ namespace { const String signature = type.returnText + " " + variable + "(" + parameterList + ")"; text += signature + "; "; appended += signature + " {\n " + (type.returnText == "void" ? "" : "return ") + - type.implementations.front() + "(" + arguments + ");\n}\n"; + type.implementations.front() + "(" + arguments + ");\n}\n"; } prototypes.emplace_back(site.begin, std::move(text)); } @@ -1533,10 +1554,9 @@ namespace { for (const UniformSite& site : uniformSites) { BlankSpan(source, site.begin, site.end); } - std::sort(prototypes.begin(), prototypes.end(), - [](const std::pair& a, const std::pair& b) { - return a.first < b.first; - }); + std::sort( + prototypes.begin(), prototypes.end(), + [](const std::pair& a, const std::pair& b) { return a.first < b.first; }); for (auto it = prototypes.rbegin(); it != prototypes.rend(); ++it) { source.insert(it->first, it->second); } @@ -1593,8 +1613,8 @@ namespace { identifierEnd++; } const SizeT identifierLen = identifierEnd - cursor; - if (identifierLen == 6 && (source.compare(cursor, 6, "packed") == 0 || - source.compare(cursor, 6, "shared") == 0)) { + if (identifierLen == 6 && + (source.compare(cursor, 6, "packed") == 0 || source.compare(cursor, 6, "shared") == 0)) { source.replace(cursor, 6, "std140"); } cursor = identifierEnd; @@ -1610,8 +1630,7 @@ namespace { // tracker rather than a bare offset because this pass rewrites identifiers first, and those // rewrites are raw text: a leading comment banner mentioning `varying` or `texture2D` moves the // anchor, and the tracker notices. - void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source, - AfterVersionAnchor& afterVersion) { + void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source, AfterVersionAnchor& afterVersion) { // Precision qualifiers (highp/mediump/lowp and default-precision statements) are legal and // ignored in the normalized desktop core profiles, so glslang handles them natively. @@ -1781,11 +1800,10 @@ namespace { // and defines the ES macros regardless of behavior, so `all` adds no information - and // emitting all thirty-five for a source that named nothing is exactly the broad rewrite // the named-extensions-only policy exists to avoid. - const bool hasMacroToRestore = - std::any_of(info.namedExtensions.begin(), info.namedExtensions.end(), - [&info](const MobileGL::String& extension) { - return IsEsOnlyPreambleExtensionMacro(extension, info.version); - }); + const bool hasMacroToRestore = std::any_of(info.namedExtensions.begin(), info.namedExtensions.end(), + [&info](const MobileGL::String& extension) { + return IsEsOnlyPreambleExtensionMacro(extension, info.version); + }); // Nothing the desktop preamble is missing: leave the source byte-identical. if (!hasMacroToRestore) return; @@ -1857,9 +1875,8 @@ namespace { return; } - constexpr const char* shim = - "uniform int mg_NumSamples;\n" - "#define gl_NumSamples mg_NumSamples\n"; + constexpr const char* shim = "uniform int mg_NumSamples;\n" + "#define gl_NumSamples mg_NumSamples\n"; source.insert(afterVersion.Get(source), shim); } @@ -1873,8 +1890,8 @@ namespace { if (tokens[i].text != "uniform") continue; // The type is the first identifier after `uniform` that is not a precision qualifier. SizeT type = i + 1; - while (type < tokens.size() && (tokens[type].text == "lowp" || tokens[type].text == "mediump" || - tokens[type].text == "highp")) + while (type < tokens.size() && + (tokens[type].text == "lowp" || tokens[type].text == "mediump" || tokens[type].text == "highp")) ++type; if (type >= tokens.size() || !IsIdentifierToken(tokens[type])) continue; const bool isExternal = tokens[type].text == "samplerExternalOES"; @@ -1887,7 +1904,8 @@ namespace { into->push_back(tokens[j].text); ++j; if (j < tokens.size() && tokens[j].text == "[") { - while (j < tokens.size() && tokens[j].text != "]") ++j; + while (j < tokens.size() && tokens[j].text != "]") + ++j; ++j; } continue; @@ -1910,7 +1928,8 @@ namespace { // refuse the source, a name declared both ways is left out - a 2D binding is the safe reading. constexpr const char* kExternalSamplerMarkerPrefix = "/*mobilegl-external-samplers:"; - void LowerExternalSamplers(MobileGL::ShaderStage stage, MobileGL::String& source, AfterVersionAnchor& afterVersion) { + void LowerExternalSamplers(MobileGL::ShaderStage stage, MobileGL::String& source, + AfterVersionAnchor& afterVersion) { if (source.find("samplerExternalOES") == MobileGL::String::npos) return; Vector names; MobileGL::String preprocessed; @@ -2001,7 +2020,6 @@ namespace MobileGL { ModernizeLegacyGLSL(stage, source, afterVersion); InjectDepthRangeBuiltinShim(stage, source, afterVersion); InjectNumSamplesBuiltinShim(stage, originalLanguage, source, afterVersion); - } Vector ExternalSamplerUniformNames(const String& preprocessedSource) { @@ -2042,8 +2060,8 @@ namespace MobileGL { const ShaderLanguageInfo info = InspectShaderLanguage(preprocessedSource); EsOrigin origin; origin.version = esVersion; - origin.gpuShader5 = ExtensionTurnedOn(info, "GL_EXT_gpu_shader5") || - ExtensionTurnedOn(info, "GL_OES_gpu_shader5"); + origin.gpuShader5 = + ExtensionTurnedOn(info, "GL_EXT_gpu_shader5") || ExtensionTurnedOn(info, "GL_OES_gpu_shader5"); return origin; } @@ -2119,18 +2137,20 @@ namespace MobileGL { const SizeT length = source.size(); SizeT i = 0; - Int layoutParenDepth = 0; // >0 while inside layout(...) + Int layoutParenDepth = 0; // >0 while inside layout(...) Bool pendingLayoutParen = false; // saw "layout", awaiting its '(' while (i < length) { const char c = source[i]; // Comments. if (c == '/' && i + 1 < length && source[i + 1] == '/') { - while (i < length && source[i] != '\n') ++i; + while (i < length && source[i] != '\n') + ++i; continue; } if (c == '/' && i + 1 < length && source[i + 1] == '*') { i += 2; - while (i + 1 < length && !(source[i] == '*' && source[i + 1] == '/')) ++i; + while (i + 1 < length && !(source[i] == '*' && source[i + 1] == '/')) + ++i; i = (i + 1 < length) ? i + 2 : length; continue; } @@ -2165,7 +2185,8 @@ namespace MobileGL { } if (isIdentChar(c) && !(c >= '0' && c <= '9')) { const SizeT start = i; - while (i < length && isIdentChar(source[i])) ++i; + while (i < length && isIdentChar(source[i])) + ++i; const StringView word(source.data() + start, i - start); if (word == "layout") { pendingLayoutParen = true; @@ -2181,14 +2202,15 @@ namespace MobileGL { for (const char* keyword : kLayoutOnlyKeywords) { if (word == keyword) { return String("ERROR: '") + keyword + - "' is a keyword and may not be used as an identifier."; + "' is a keyword and may not be used as an identifier."; } } } continue; } if (isIdentChar(c)) { // digit-led token: skip the whole number/identifier tail - while (i < length && isIdentChar(source[i])) ++i; + while (i < length && isIdentChar(source[i])) + ++i; pendingLayoutParen = false; continue; } @@ -2201,9 +2223,9 @@ namespace MobileGL { namespace { bool IsNonLayoutQualifierKeyword(const String& text) { static const char* kQualifiers[] = { - "highp", "mediump", "lowp", "precise", "const", "flat", - "noperspective", "smooth", "centroid", "sample", "patch", "invariant", - "coherent", "volatile", "restrict", "readonly", "writeonly", "subroutine", + "highp", "mediump", "lowp", "precise", "const", "flat", + "noperspective", "smooth", "centroid", "sample", "patch", "invariant", + "coherent", "volatile", "restrict", "readonly", "writeonly", "subroutine", }; for (const char* qualifier : kQualifiers) { if (text == qualifier) return true; @@ -2239,8 +2261,7 @@ namespace MobileGL { // form, which declares no block at all); the element count for an instance array, // whose elements take base, base+1, ... (GLSL 4.30 4.4.5). -1 means "the grammar // here is outside this scanner's narrow subset", i.e. do not judge this one. - long long StorageBlockBindingPointCount(const Vector& tokens, SizeT bufferPos, - SizeT count) { + long long StorageBlockBindingPointCount(const Vector& tokens, SizeT bufferPos, SizeT count) { SizeT k = bufferPos + 1; if (k < count && IsIdentifierToken(tokens[k])) ++k; // block type name if (k >= count || tokens[k].text != "{") return 1; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp index cd04e94e..b4892c4e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.cpp @@ -56,7 +56,12 @@ namespace MobileGL { // requires the Image Format's class to agree with the OpTypeImage's Sampled // Type, so a bake that disagrees would produce an invalid module rather than a // merely wrong one. - enum class ComponentClass { Float, SignedInt, UnsignedInt, None }; + enum class ComponentClass { + Float, + SignedInt, + UnsignedInt, + None + }; ComponentClass ClassOfImageFormat(spv::ImageFormat format) { switch (format) { @@ -114,7 +119,7 @@ namespace MobileGL { if (sampledType->opcode() == spv::Op::OpTypeInt) { // OpTypeInt in-operands: 0 width, 1 signedness. return sampledType->GetSingleWordInOperand(1) != 0 ? ComponentClass::SignedInt - : ComponentClass::UnsignedInt; + : ComponentClass::UnsignedInt; } return ComponentClass::None; } @@ -136,7 +141,8 @@ namespace MobileGL { return chain; } chain.pointerType = pointerType; - Instruction* pointee = defUseMgr->GetDef(pointerType->GetSingleWordInOperand(kPointerPointeeOperand)); + Instruction* pointee = + defUseMgr->GetDef(pointerType->GetSingleWordInOperand(kPointerPointeeOperand)); if (pointee != nullptr && pointee->opcode() == spv::Op::OpTypeArray) { chain.arrayType = pointee; pointee = defUseMgr->GetDef(pointee->GetSingleWordInOperand(kArrayElementOperand)); @@ -189,45 +195,84 @@ namespace MobileGL { // The GL 4.2 image format table (core spec table 8.26), in its order. Written as // literals rather than through the GL headers because this lives in MG_Util, // which the GL frontend's enums do not reach. - case 0x8814: /*GL_RGBA32F*/ return static_cast(spv::ImageFormat::Rgba32f); - case 0x881A: /*GL_RGBA16F*/ return static_cast(spv::ImageFormat::Rgba16f); - case 0x8230: /*GL_RG32F*/ return static_cast(spv::ImageFormat::Rg32f); - case 0x822F: /*GL_RG16F*/ return static_cast(spv::ImageFormat::Rg16f); - case 0x8C3A: /*GL_R11F_G11F_B10F*/ return static_cast(spv::ImageFormat::R11fG11fB10f); - case 0x822E: /*GL_R32F*/ return static_cast(spv::ImageFormat::R32f); - case 0x822D: /*GL_R16F*/ return static_cast(spv::ImageFormat::R16f); - case 0x8D70: /*GL_RGBA32UI*/ return static_cast(spv::ImageFormat::Rgba32ui); - case 0x8D76: /*GL_RGBA16UI*/ return static_cast(spv::ImageFormat::Rgba16ui); - case 0x8D7C: /*GL_RGBA8UI*/ return static_cast(spv::ImageFormat::Rgba8ui); - case 0x906F: /*GL_RGB10_A2UI*/ return static_cast(spv::ImageFormat::Rgb10a2ui); - case 0x823C: /*GL_RG32UI*/ return static_cast(spv::ImageFormat::Rg32ui); - case 0x823A: /*GL_RG16UI*/ return static_cast(spv::ImageFormat::Rg16ui); - case 0x8238: /*GL_RG8UI*/ return static_cast(spv::ImageFormat::Rg8ui); - case 0x8236: /*GL_R32UI*/ return static_cast(spv::ImageFormat::R32ui); - case 0x8234: /*GL_R16UI*/ return static_cast(spv::ImageFormat::R16ui); - case 0x8232: /*GL_R8UI*/ return static_cast(spv::ImageFormat::R8ui); - case 0x8D82: /*GL_RGBA32I*/ return static_cast(spv::ImageFormat::Rgba32i); - case 0x8D88: /*GL_RGBA16I*/ return static_cast(spv::ImageFormat::Rgba16i); - case 0x8D8E: /*GL_RGBA8I*/ return static_cast(spv::ImageFormat::Rgba8i); - case 0x823B: /*GL_RG32I*/ return static_cast(spv::ImageFormat::Rg32i); - case 0x8239: /*GL_RG16I*/ return static_cast(spv::ImageFormat::Rg16i); - case 0x8237: /*GL_RG8I*/ return static_cast(spv::ImageFormat::Rg8i); - case 0x8235: /*GL_R32I*/ return static_cast(spv::ImageFormat::R32i); - case 0x8233: /*GL_R16I*/ return static_cast(spv::ImageFormat::R16i); - case 0x8231: /*GL_R8I*/ return static_cast(spv::ImageFormat::R8i); - case 0x8058: /*GL_RGBA8*/ return static_cast(spv::ImageFormat::Rgba8); - case 0x805B: /*GL_RGBA16*/ return static_cast(spv::ImageFormat::Rgba16); - case 0x8059: /*GL_RGB10_A2*/ return static_cast(spv::ImageFormat::Rgb10A2); - case 0x822B: /*GL_RG8*/ return static_cast(spv::ImageFormat::Rg8); - case 0x822C: /*GL_RG16*/ return static_cast(spv::ImageFormat::Rg16); - case 0x8229: /*GL_R8*/ return static_cast(spv::ImageFormat::R8); - case 0x822A: /*GL_R16*/ return static_cast(spv::ImageFormat::R16); - case 0x8F97: /*GL_RGBA8_SNORM*/ return static_cast(spv::ImageFormat::Rgba8Snorm); - case 0x8F9B: /*GL_RGBA16_SNORM*/ return static_cast(spv::ImageFormat::Rgba16Snorm); - case 0x8F95: /*GL_RG8_SNORM*/ return static_cast(spv::ImageFormat::Rg8Snorm); - case 0x8F99: /*GL_RG16_SNORM*/ return static_cast(spv::ImageFormat::Rg16Snorm); - case 0x8F94: /*GL_R8_SNORM*/ return static_cast(spv::ImageFormat::R8Snorm); - case 0x8F98: /*GL_R16_SNORM*/ return static_cast(spv::ImageFormat::R16Snorm); + case 0x8814: /*GL_RGBA32F*/ + return static_cast(spv::ImageFormat::Rgba32f); + case 0x881A: /*GL_RGBA16F*/ + return static_cast(spv::ImageFormat::Rgba16f); + case 0x8230: /*GL_RG32F*/ + return static_cast(spv::ImageFormat::Rg32f); + case 0x822F: /*GL_RG16F*/ + return static_cast(spv::ImageFormat::Rg16f); + case 0x8C3A: /*GL_R11F_G11F_B10F*/ + return static_cast(spv::ImageFormat::R11fG11fB10f); + case 0x822E: /*GL_R32F*/ + return static_cast(spv::ImageFormat::R32f); + case 0x822D: /*GL_R16F*/ + return static_cast(spv::ImageFormat::R16f); + case 0x8D70: /*GL_RGBA32UI*/ + return static_cast(spv::ImageFormat::Rgba32ui); + case 0x8D76: /*GL_RGBA16UI*/ + return static_cast(spv::ImageFormat::Rgba16ui); + case 0x8D7C: /*GL_RGBA8UI*/ + return static_cast(spv::ImageFormat::Rgba8ui); + case 0x906F: /*GL_RGB10_A2UI*/ + return static_cast(spv::ImageFormat::Rgb10a2ui); + case 0x823C: /*GL_RG32UI*/ + return static_cast(spv::ImageFormat::Rg32ui); + case 0x823A: /*GL_RG16UI*/ + return static_cast(spv::ImageFormat::Rg16ui); + case 0x8238: /*GL_RG8UI*/ + return static_cast(spv::ImageFormat::Rg8ui); + case 0x8236: /*GL_R32UI*/ + return static_cast(spv::ImageFormat::R32ui); + case 0x8234: /*GL_R16UI*/ + return static_cast(spv::ImageFormat::R16ui); + case 0x8232: /*GL_R8UI*/ + return static_cast(spv::ImageFormat::R8ui); + case 0x8D82: /*GL_RGBA32I*/ + return static_cast(spv::ImageFormat::Rgba32i); + case 0x8D88: /*GL_RGBA16I*/ + return static_cast(spv::ImageFormat::Rgba16i); + case 0x8D8E: /*GL_RGBA8I*/ + return static_cast(spv::ImageFormat::Rgba8i); + case 0x823B: /*GL_RG32I*/ + return static_cast(spv::ImageFormat::Rg32i); + case 0x8239: /*GL_RG16I*/ + return static_cast(spv::ImageFormat::Rg16i); + case 0x8237: /*GL_RG8I*/ + return static_cast(spv::ImageFormat::Rg8i); + case 0x8235: /*GL_R32I*/ + return static_cast(spv::ImageFormat::R32i); + case 0x8233: /*GL_R16I*/ + return static_cast(spv::ImageFormat::R16i); + case 0x8231: /*GL_R8I*/ + return static_cast(spv::ImageFormat::R8i); + case 0x8058: /*GL_RGBA8*/ + return static_cast(spv::ImageFormat::Rgba8); + case 0x805B: /*GL_RGBA16*/ + return static_cast(spv::ImageFormat::Rgba16); + case 0x8059: /*GL_RGB10_A2*/ + return static_cast(spv::ImageFormat::Rgb10A2); + case 0x822B: /*GL_RG8*/ + return static_cast(spv::ImageFormat::Rg8); + case 0x822C: /*GL_RG16*/ + return static_cast(spv::ImageFormat::Rg16); + case 0x8229: /*GL_R8*/ + return static_cast(spv::ImageFormat::R8); + case 0x822A: /*GL_R16*/ + return static_cast(spv::ImageFormat::R16); + case 0x8F97: /*GL_RGBA8_SNORM*/ + return static_cast(spv::ImageFormat::Rgba8Snorm); + case 0x8F9B: /*GL_RGBA16_SNORM*/ + return static_cast(spv::ImageFormat::Rgba16Snorm); + case 0x8F95: /*GL_RG8_SNORM*/ + return static_cast(spv::ImageFormat::Rg8Snorm); + case 0x8F99: /*GL_RG16_SNORM*/ + return static_cast(spv::ImageFormat::Rg16Snorm); + case 0x8F94: /*GL_R8_SNORM*/ + return static_cast(spv::ImageFormat::R8Snorm); + case 0x8F98: /*GL_R16_SNORM*/ + return static_cast(spv::ImageFormat::R16Snorm); default: return static_cast(spv::ImageFormat::Unknown); } @@ -295,45 +340,84 @@ namespace MobileGL { String BakeImageFormatsPass::EsslSpellingOfGLInternalFormat(Uint glInternalFormat) { switch (static_cast(SpirvImageFormatFromGLInternalFormat(glInternalFormat))) { - case spv::ImageFormat::Rgba32f: return "rgba32f"; - case spv::ImageFormat::Rgba16f: return "rgba16f"; - case spv::ImageFormat::R32f: return "r32f"; - case spv::ImageFormat::Rgba8: return "rgba8"; - case spv::ImageFormat::Rgba8Snorm: return "rgba8_snorm"; - case spv::ImageFormat::Rg32f: return "rg32f"; - case spv::ImageFormat::Rg16f: return "rg16f"; - case spv::ImageFormat::R11fG11fB10f: return "r11f_g11f_b10f"; - case spv::ImageFormat::R16f: return "r16f"; - case spv::ImageFormat::Rgba16: return "rgba16"; - case spv::ImageFormat::Rgb10A2: return "rgb10_a2"; - case spv::ImageFormat::Rg16: return "rg16"; - case spv::ImageFormat::Rg8: return "rg8"; - case spv::ImageFormat::R16: return "r16"; - case spv::ImageFormat::R8: return "r8"; - case spv::ImageFormat::Rgba16Snorm: return "rgba16_snorm"; - case spv::ImageFormat::Rg16Snorm: return "rg16_snorm"; - case spv::ImageFormat::Rg8Snorm: return "rg8_snorm"; - case spv::ImageFormat::R16Snorm: return "r16_snorm"; - case spv::ImageFormat::R8Snorm: return "r8_snorm"; - case spv::ImageFormat::Rgba32i: return "rgba32i"; - case spv::ImageFormat::Rgba16i: return "rgba16i"; - case spv::ImageFormat::Rgba8i: return "rgba8i"; - case spv::ImageFormat::R32i: return "r32i"; - case spv::ImageFormat::Rg32i: return "rg32i"; - case spv::ImageFormat::Rg16i: return "rg16i"; - case spv::ImageFormat::Rg8i: return "rg8i"; - case spv::ImageFormat::R16i: return "r16i"; - case spv::ImageFormat::R8i: return "r8i"; - case spv::ImageFormat::Rgba32ui: return "rgba32ui"; - case spv::ImageFormat::Rgba16ui: return "rgba16ui"; - case spv::ImageFormat::Rgba8ui: return "rgba8ui"; - case spv::ImageFormat::R32ui: return "r32ui"; - case spv::ImageFormat::Rgb10a2ui: return "rgb10_a2ui"; - case spv::ImageFormat::Rg32ui: return "rg32ui"; - case spv::ImageFormat::Rg16ui: return "rg16ui"; - case spv::ImageFormat::Rg8ui: return "rg8ui"; - case spv::ImageFormat::R16ui: return "r16ui"; - case spv::ImageFormat::R8ui: return "r8ui"; + case spv::ImageFormat::Rgba32f: + return "rgba32f"; + case spv::ImageFormat::Rgba16f: + return "rgba16f"; + case spv::ImageFormat::R32f: + return "r32f"; + case spv::ImageFormat::Rgba8: + return "rgba8"; + case spv::ImageFormat::Rgba8Snorm: + return "rgba8_snorm"; + case spv::ImageFormat::Rg32f: + return "rg32f"; + case spv::ImageFormat::Rg16f: + return "rg16f"; + case spv::ImageFormat::R11fG11fB10f: + return "r11f_g11f_b10f"; + case spv::ImageFormat::R16f: + return "r16f"; + case spv::ImageFormat::Rgba16: + return "rgba16"; + case spv::ImageFormat::Rgb10A2: + return "rgb10_a2"; + case spv::ImageFormat::Rg16: + return "rg16"; + case spv::ImageFormat::Rg8: + return "rg8"; + case spv::ImageFormat::R16: + return "r16"; + case spv::ImageFormat::R8: + return "r8"; + case spv::ImageFormat::Rgba16Snorm: + return "rgba16_snorm"; + case spv::ImageFormat::Rg16Snorm: + return "rg16_snorm"; + case spv::ImageFormat::Rg8Snorm: + return "rg8_snorm"; + case spv::ImageFormat::R16Snorm: + return "r16_snorm"; + case spv::ImageFormat::R8Snorm: + return "r8_snorm"; + case spv::ImageFormat::Rgba32i: + return "rgba32i"; + case spv::ImageFormat::Rgba16i: + return "rgba16i"; + case spv::ImageFormat::Rgba8i: + return "rgba8i"; + case spv::ImageFormat::R32i: + return "r32i"; + case spv::ImageFormat::Rg32i: + return "rg32i"; + case spv::ImageFormat::Rg16i: + return "rg16i"; + case spv::ImageFormat::Rg8i: + return "rg8i"; + case spv::ImageFormat::R16i: + return "r16i"; + case spv::ImageFormat::R8i: + return "r8i"; + case spv::ImageFormat::Rgba32ui: + return "rgba32ui"; + case spv::ImageFormat::Rgba16ui: + return "rgba16ui"; + case spv::ImageFormat::Rgba8ui: + return "rgba8ui"; + case spv::ImageFormat::R32ui: + return "r32ui"; + case spv::ImageFormat::Rgb10a2ui: + return "rgb10_a2ui"; + case spv::ImageFormat::Rg32ui: + return "rg32ui"; + case spv::ImageFormat::Rg16ui: + return "rg16ui"; + case spv::ImageFormat::Rg8ui: + return "rg8ui"; + case spv::ImageFormat::R16ui: + return "r16ui"; + case spv::ImageFormat::R8ui: + return "r8ui"; default: return {}; } @@ -599,8 +683,7 @@ namespace MobileGL { // Only an ID operand names a definition the clone has to sit behind. The // format operand is a LITERAL, and looking it up would resolve some unrelated // instruction that happens to carry that number as its result id. - Instruction* anchor = - valueIsId ? laterInGlobals(original, defUseMgr->GetDef(value)) : original; + Instruction* anchor = valueIsId ? laterInGlobals(original, defUseMgr->GetDef(value)) : original; if (anchor == nullptr) return 0; Instruction* inserted = clone.release(); inserted->InsertAfter(anchor); @@ -618,16 +701,16 @@ namespace MobileGL { bool changed = false; for (Candidate& candidate : candidates) { const uint32_t newImageId = cloneTypeWithOperand(candidate.chain.imageType, kImageFormatOperand, - static_cast(candidate.format), - /*valueIsId=*/false); + static_cast(candidate.format), + /*valueIsId=*/false); if (newImageId == 0) return Status::Failure; // The pointer-to-image type every access chain and every single-image // variable resolves through. uint32_t newPointeeId = newImageId; if (candidate.chain.arrayType != nullptr) { - newPointeeId = cloneTypeWithOperand(candidate.chain.arrayType, kArrayElementOperand, - newImageId, /*valueIsId=*/true); + newPointeeId = cloneTypeWithOperand(candidate.chain.arrayType, kArrayElementOperand, newImageId, + /*valueIsId=*/true); if (newPointeeId == 0) return Status::Failure; } const uint32_t newVariablePointerId = cloneTypeWithOperand( @@ -711,8 +794,7 @@ namespace MobileGL { spvtools::Optimizer::PassToken BakeImageFormatsPass::CreateBakeImageFormatsPass( GLFormatByName glFormatByName) { - return spvtools::Optimizer::PassToken( - spvtools::MakeUnique(Move(glFormatByName))); + return spvtools::Optimizer::PassToken(spvtools::MakeUnique(Move(glFormatByName))); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.h index 8de2ea8d..9cfe253d 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/BakeImageFormatsPass.h @@ -73,8 +73,7 @@ namespace MobileGL { // spelling, and names the module does not declare, are ignored. using GLFormatByName = UnorderedMap; - explicit BakeImageFormatsPass(GLFormatByName glFormatByName) - : m_glFormatByName(Move(glFormatByName)) {} + explicit BakeImageFormatsPass(GLFormatByName glFormatByName) : m_glFormatByName(Move(glFormatByName)) {} const char* name() const override { return "mobilegl-bake-image-formats"; } Status Process() override; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.cpp index eea3b38d..78ceaeaa 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.cpp @@ -53,7 +53,11 @@ namespace MobileGL { constexpr uint32_t kImageOperandsMaskOperand = 2; // The categories GL keeps a separate GL_MAX_*_SAMPLES ceiling for. - enum class SampleCategory { Color, Depth, Integer }; + enum class SampleCategory { + Color, + Depth, + Integer + }; // The two operations that can carry a Sample image-operand and take their // coordinate in in-operand 1. OpImageWrite can carry one too, but its operand @@ -248,8 +252,7 @@ namespace MobileGL { // No category is squeezed, so no fetch can be out of range. This is the whole // answer on a driver whose per-format ceilings all reach what MobileGL // advertises, and it costs nothing. - if (m_maxColorSamples >= m_advertisedMaxSamples && - m_maxIntegerSamples >= m_advertisedMaxSamples && + if (m_maxColorSamples >= m_advertisedMaxSamples && m_maxIntegerSamples >= m_advertisedMaxSamples && m_maxDepthSamples >= m_advertisedMaxSamples) { return Status::SuccessWithoutChange; } @@ -346,8 +349,7 @@ namespace MobileGL { // One sample exists, and its index is 0. const analysis::Constant* zero = constantMgr->GetConstant(sampleType, {0u}); const Instruction* zeroInst = - zero != nullptr ? constantMgr->GetDefiningInstruction(zero, sampleTypeId) - : nullptr; + zero != nullptr ? constantMgr->GetDefiningInstruction(zero, sampleTypeId) : nullptr; if (zeroInst == nullptr) { return Status::Failure; } @@ -373,26 +375,24 @@ namespace MobileGL { return Status::Failure; } const uint32_t resultId = irContext->TakeNextId(); - const analysis::Constant* bound = constantMgr->GetConstant( - sampleType, {static_cast(categoryMaxSamples - 1)}); + const analysis::Constant* bound = + constantMgr->GetConstant(sampleType, {static_cast(categoryMaxSamples - 1)}); const Instruction* boundInst = - bound != nullptr ? constantMgr->GetDefiningInstruction(bound, sampleTypeId) - : nullptr; + bound != nullptr ? constantMgr->GetDefiningInstruction(bound, sampleTypeId) : nullptr; if (resultId == 0u || boundInst == nullptr) { return Status::Failure; } - InstructionBuilder builder( - irContext, &instruction, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, &instruction, + IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping); Instruction* clamped = builder.AddInstruction(spvtools::MakeUnique( irContext, spv::Op::OpExtInst, sampleTypeId, resultId, - Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {glslStd450Id}}, - {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, - {sampleInteger->IsSigned() ? kGlslSMin : kGlslUMin}}, - {SPV_OPERAND_TYPE_ID, {sampleOperandId}}, - {SPV_OPERAND_TYPE_ID, {boundInst->result_id()}}})); + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {glslStd450Id}}, + {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, + {sampleInteger->IsSigned() ? kGlslSMin : kGlslUMin}}, + {SPV_OPERAND_TYPE_ID, {sampleOperandId}}, + {SPV_OPERAND_TYPE_ID, {boundInst->result_id()}}})); if (clamped == nullptr) { return Status::Failure; } @@ -413,9 +413,9 @@ namespace MobileGL { spvtools::Optimizer::PassToken ClampMultisampleFetchPass::CreateClampMultisampleFetchPass( const Int32 maxColorSamples, const Int32 maxIntegerSamples, const Int32 maxDepthSamples, const Int32 advertisedMaxSamples, const bool deferDirectUnsignedFetches) { - return spvtools::Optimizer::PassToken(spvtools::MakeUnique( - maxColorSamples, maxIntegerSamples, maxDepthSamples, advertisedMaxSamples, - deferDirectUnsignedFetches)); + return spvtools::Optimizer::PassToken( + spvtools::MakeUnique(maxColorSamples, maxIntegerSamples, maxDepthSamples, + advertisedMaxSamples, deferDirectUnsignedFetches)); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h index 18b07092..8e0f5549 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ClampMultisampleFetchPass.h @@ -72,13 +72,10 @@ namespace MobileGL { // The three backend-REAL per-category ceilings, plus the count the GL frontend // advertises (GL_Getter's GetAdvertisedMaxSamples). A category whose real ceiling // already reaches the advertised one is left completely alone. - ClampMultisampleFetchPass(Int32 maxColorSamples, Int32 maxIntegerSamples, - Int32 maxDepthSamples, Int32 advertisedMaxSamples, - bool deferDirectUnsignedFetches = false) - : m_maxColorSamples(maxColorSamples), - m_maxIntegerSamples(maxIntegerSamples), - m_maxDepthSamples(maxDepthSamples), - m_advertisedMaxSamples(advertisedMaxSamples), + ClampMultisampleFetchPass(Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples, + Int32 advertisedMaxSamples, bool deferDirectUnsignedFetches = false) + : m_maxColorSamples(maxColorSamples), m_maxIntegerSamples(maxIntegerSamples), + m_maxDepthSamples(maxDepthSamples), m_advertisedMaxSamples(advertisedMaxSamples), m_deferDirectUnsignedFetches(deferDirectUnsignedFetches) {} const char* name() const override { return "clamp-multisample-fetch"; } @@ -95,8 +92,8 @@ namespace MobileGL { static bool DeclaresMultisampledImage(spvtools::opt::IRContext* context); static spvtools::Optimizer::PassToken CreateClampMultisampleFetchPass( - Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples, - Int32 advertisedMaxSamples, bool deferDirectUnsignedFetches = false); + Int32 maxColorSamples, Int32 maxIntegerSamples, Int32 maxDepthSamples, Int32 advertisedMaxSamples, + bool deferDirectUnsignedFetches = false); private: Int32 m_maxColorSamples; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp index 1a2a7a66..352654a6 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp @@ -25,10 +25,10 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { namespace { - using spvtools::opt::IRContext; - using spvtools::opt::Instruction; - using spvtools::opt::Operand; using spvtools::opt::BasicBlock; + using spvtools::opt::Instruction; + using spvtools::opt::IRContext; + using spvtools::opt::Operand; using spvtools::opt::analysis::Array; using spvtools::opt::analysis::Type; @@ -67,8 +67,7 @@ namespace MobileGL { // Recursively rebuilds a pointee type, replacing the vec3 leaf with a // scalar array [3]. Returns the new type id. - uint32_t RebuildPointeeType(IRContext* context, uint32_t typeId, - uint32_t scalarArr3TypeId, + uint32_t RebuildPointeeType(IRContext* context, uint32_t typeId, uint32_t scalarArr3TypeId, const std::unordered_map& vec3ToArr3) { auto* defUseMgr = context->get_def_use_mgr(); auto* typeMgr = context->get_type_mgr(); @@ -83,16 +82,13 @@ namespace MobileGL { } if (typeInst->opcode() == spv::Op::OpTypeArray) { const uint32_t oldElemId = typeInst->GetSingleWordInOperand(0); - const uint32_t newElemId = - RebuildPointeeType(context, oldElemId, scalarArr3TypeId, vec3ToArr3); + const uint32_t newElemId = RebuildPointeeType(context, oldElemId, scalarArr3TypeId, vec3ToArr3); if (newElemId == oldElemId) { return typeId; } const uint32_t lengthId = typeInst->GetSingleWordInOperand(1); - const Array* oldArrTy = - typeMgr->GetType(typeId)->AsArray(); - Array newArrTy(typeMgr->GetType(newElemId), - oldArrTy->length_info()); + const Array* oldArrTy = typeMgr->GetType(typeId)->AsArray(); + Array newArrTy(typeMgr->GetType(newElemId), oldArrTy->length_info()); Type* regNewArrTy = typeMgr->GetRegisteredType(&newArrTy); return typeMgr->GetTypeInstruction(regNewArrTy); } @@ -114,8 +110,8 @@ namespace MobileGL { // Inserts an instruction before |where|, sets its debug info, and updates // def-use and block mapping. Returns a pointer to the inserted instruction. - Instruction* InsertBefore(BasicBlock* block, Instruction* where, - std::unique_ptr inst, IRContext* context) { + Instruction* InsertBefore(BasicBlock* block, Instruction* where, std::unique_ptr inst, + IRContext* context) { auto iter = BasicBlock::iterator(where).InsertBefore(std::move(inst)); if (where != nullptr) { iter->UpdateDebugInfoFrom(where); @@ -148,16 +144,14 @@ namespace MobileGL { const uint32_t acId = context->TakeNextId(); auto acInst = MakeUnique( context, spv::Op::OpAccessChain, ptrScalarTypeId, acId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, - {SPV_OPERAND_TYPE_ID, {constCId}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, + {SPV_OPERAND_TYPE_ID, {constCId}}}); InsertBefore(block, where, std::move(acInst), context); const uint32_t loadId = context->TakeNextId(); - auto loadInst = MakeUnique( - context, spv::Op::OpLoad, scalarTypeId, loadId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {acId}}}); + auto loadInst = + MakeUnique(context, spv::Op::OpLoad, scalarTypeId, loadId, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {acId}}}); for (uint32_t opIdx = 1; opIdx < sourceLoad->NumInOperands(); ++opIdx) { loadInst->AddOperand(Operand(sourceLoad->GetInOperand(opIdx))); } @@ -166,9 +160,8 @@ namespace MobileGL { } const uint32_t compositeId = context->TakeNextId(); - auto composite = MakeUnique( - context, spv::Op::OpCompositeConstruct, originalTypeId, compositeId, - std::initializer_list{}); + auto composite = MakeUnique(context, spv::Op::OpCompositeConstruct, originalTypeId, + compositeId, std::initializer_list{}); for (uint32_t componentId : componentIds) { composite->AddOperand({SPV_OPERAND_TYPE_ID, {componentId}}); } @@ -198,18 +191,16 @@ namespace MobileGL { const uint32_t elemPtrId = context->TakeNextId(); auto elemAc = MakeUnique( context, spv::Op::OpAccessChain, ptrDecomposedElemTypeId, elemPtrId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, - {SPV_OPERAND_TYPE_ID, {constIId}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, + {SPV_OPERAND_TYPE_ID, {constIId}}}); InsertBefore(block, where, std::move(elemAc), context); elementIds.push_back(BuildCompositeLoad(context, block, where, originalElemTypeId, elemPtrId, vec3ToArr3, sourceLoad)); } const uint32_t compositeId = context->TakeNextId(); - auto composite = MakeUnique( - context, spv::Op::OpCompositeConstruct, originalTypeId, compositeId, - std::initializer_list{}); + auto composite = MakeUnique(context, spv::Op::OpCompositeConstruct, originalTypeId, + compositeId, std::initializer_list{}); for (uint32_t elementId : elementIds) { composite->AddOperand({SPV_OPERAND_TYPE_ID, {elementId}}); } @@ -241,25 +232,22 @@ namespace MobileGL { const uint32_t extractId = context->TakeNextId(); auto extract = MakeUnique( context, spv::Op::OpCompositeExtract, scalarTypeId, extractId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {valueId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {c}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {valueId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {c}}}); InsertBefore(block, where, std::move(extract), context); const uint32_t constCId = constMgr->GetUIntConstId(c); const uint32_t acId = context->TakeNextId(); auto acInst = MakeUnique( context, spv::Op::OpAccessChain, ptrScalarTypeId, acId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, - {SPV_OPERAND_TYPE_ID, {constCId}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, + {SPV_OPERAND_TYPE_ID, {constCId}}}); InsertBefore(block, where, std::move(acInst), context); auto compStore = MakeUnique( context, spv::Op::OpStore, 0, 0, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {acId}}, - {SPV_OPERAND_TYPE_ID, {extractId}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {acId}}, + {SPV_OPERAND_TYPE_ID, {extractId}}}); for (uint32_t opIdx = 2; opIdx < sourceStore->NumInOperands(); ++opIdx) { compStore->AddOperand(Operand(sourceStore->GetInOperand(opIdx))); } @@ -287,22 +275,20 @@ namespace MobileGL { const uint32_t extractId = context->TakeNextId(); auto extract = MakeUnique( context, spv::Op::OpCompositeExtract, originalElemTypeId, extractId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {valueId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {valueId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i}}}); InsertBefore(block, where, std::move(extract), context); const uint32_t constIId = constMgr->GetUIntConstId(i); const uint32_t elemPtrId = context->TakeNextId(); auto elemAc = MakeUnique( context, spv::Op::OpAccessChain, ptrDecomposedElemTypeId, elemPtrId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, - {SPV_OPERAND_TYPE_ID, {constIId}}}); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {decomposedPtrId}}, + {SPV_OPERAND_TYPE_ID, {constIId}}}); InsertBefore(block, where, std::move(elemAc), context); - BuildCompositeStore(context, block, where, originalElemTypeId, elemPtrId, - extractId, vec3ToArr3, sourceStore); + BuildCompositeStore(context, block, where, originalElemTypeId, elemPtrId, extractId, + vec3ToArr3, sourceStore); } return; } @@ -336,8 +322,7 @@ namespace MobileGL { if (sawFirstVariable && (spvtools::opt::IsTypeInst(inst->opcode()) || - spvtools::opt::IsConstantInst(inst->opcode()) || - inst->opcode() == spv::Op::OpUndef)) { + spvtools::opt::IsConstantInst(inst->opcode()) || inst->opcode() == spv::Op::OpUndef)) { inst->InsertBefore(firstVariable); } } @@ -366,8 +351,7 @@ namespace MobileGL { std::unordered_map ptrCompositeToDecomposed; for (Instruction& typeInst : ctx->types_values()) { - if (typeInst.opcode() == spv::Op::OpTypeVector && - IsVec3Type(&typeInst) != 0) { + if (typeInst.opcode() == spv::Op::OpTypeVector && IsVec3Type(&typeInst) != 0) { vec3TypeIds.insert(typeInst.result_id()); } } @@ -413,8 +397,7 @@ namespace MobileGL { if (varInst.opcode() != spv::Op::OpVariable) { continue; } - const auto sc = static_cast( - varInst.GetSingleWordInOperand(0)); + const auto sc = static_cast(varInst.GetSingleWordInOperand(0)); if (sc != spv::StorageClass::Workgroup) { continue; } @@ -436,11 +419,9 @@ namespace MobileGL { if (pointeeId == vec3LeafId) { newPointeeId = arr3LeafId; } else { - newPointeeId = RebuildPointeeType(ctx, pointeeId, - arr3LeafId, vec3ToArr3); + newPointeeId = RebuildPointeeType(ctx, pointeeId, arr3LeafId, vec3ToArr3); } - const uint32_t newPtrId = typeMgr->FindPointerToType( - newPointeeId, spv::StorageClass::Workgroup); + const uint32_t newPtrId = typeMgr->FindPointerToType(newPointeeId, spv::StorageClass::Workgroup); varInst.SetResultType(newPtrId); defUseMgr->AnalyzeInstUse(&varInst); modified = true; @@ -454,8 +435,7 @@ namespace MobileGL { if (typeInst.opcode() != spv::Op::OpTypePointer) { continue; } - const auto sc = static_cast( - typeInst.GetSingleWordInOperand(0)); + const auto sc = static_cast(typeInst.GetSingleWordInOperand(0)); if (sc != spv::StorageClass::Workgroup) { continue; } @@ -468,12 +448,10 @@ namespace MobileGL { if (it == vec3ToArr3.end()) { continue; } - const uint32_t newPointeeId = - pointeeId == vec3LeafId - ? it->second - : RebuildPointeeType(ctx, pointeeId, it->second, vec3ToArr3); - const uint32_t newPtrId = - typeMgr->FindPointerToType(newPointeeId, spv::StorageClass::Workgroup); + const uint32_t newPointeeId = pointeeId == vec3LeafId + ? it->second + : RebuildPointeeType(ctx, pointeeId, it->second, vec3ToArr3); + const uint32_t newPtrId = typeMgr->FindPointerToType(newPointeeId, spv::StorageClass::Workgroup); ptrCompositeToDecomposed[typeInst.result_id()] = newPtrId; } @@ -604,21 +582,14 @@ namespace MobileGL { for (auto& bb : func) { for (auto& inst : bb) { const spv::Op op = inst.opcode(); - bool isAtomic = (op == spv::Op::OpAtomicLoad || - op == spv::Op::OpAtomicStore || - op == spv::Op::OpAtomicExchange || - op == spv::Op::OpAtomicCompareExchange || - op == spv::Op::OpAtomicIAdd || - op == spv::Op::OpAtomicISub || - op == spv::Op::OpAtomicSMin || - op == spv::Op::OpAtomicUMin || - op == spv::Op::OpAtomicSMax || - op == spv::Op::OpAtomicUMax || - op == spv::Op::OpAtomicAnd || - op == spv::Op::OpAtomicOr || - op == spv::Op::OpAtomicXor); - bool isCopyMem = (op == spv::Op::OpCopyMemory || - op == spv::Op::OpCopyMemorySized); + bool isAtomic = + (op == spv::Op::OpAtomicLoad || op == spv::Op::OpAtomicStore || + op == spv::Op::OpAtomicExchange || op == spv::Op::OpAtomicCompareExchange || + op == spv::Op::OpAtomicIAdd || op == spv::Op::OpAtomicISub || + op == spv::Op::OpAtomicSMin || op == spv::Op::OpAtomicUMin || + op == spv::Op::OpAtomicSMax || op == spv::Op::OpAtomicUMax || + op == spv::Op::OpAtomicAnd || op == spv::Op::OpAtomicOr || op == spv::Op::OpAtomicXor); + bool isCopyMem = (op == spv::Op::OpCopyMemory || op == spv::Op::OpCopyMemorySized); if (!isAtomic && !isCopyMem) { continue; } @@ -628,11 +599,9 @@ namespace MobileGL { if (ptrDef == nullptr) { continue; } - if (decomposedPtrTypeIds.find(ptrDef->type_id()) != - decomposedPtrTypeIds.end()) { - MOBILEGL_ASSERT(false, - "DecomposeWorkgroupVec3Pass: unsupported atomic/CopyMemory " - "on workgroup vec3 pointer"); + if (decomposedPtrTypeIds.find(ptrDef->type_id()) != decomposedPtrTypeIds.end()) { + MOBILEGL_ASSERT(false, "DecomposeWorkgroupVec3Pass: unsupported atomic/CopyMemory " + "on workgroup vec3 pointer"); return Status::Failure; } } @@ -644,8 +613,7 @@ namespace MobileGL { return Status::SuccessWithChange; } - spvtools::Optimizer::PassToken - DecomposeWorkgroupVec3Pass::CreateDecomposeWorkgroupVec3Pass() { + spvtools::Optimizer::PassToken DecomposeWorkgroupVec3Pass::CreateDecomposeWorkgroupVec3Pass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp index f418dc9e..f74d06d6 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp @@ -28,9 +28,7 @@ namespace MobileGL { struct MemberKey { uint32_t id = 0; uint32_t member = 0; - bool operator==(const MemberKey& other) const { - return id == other.id && member == other.member; - } + bool operator==(const MemberKey& other) const { return id == other.id && member == other.member; } }; // OpDecorate [literals...] @@ -58,8 +56,8 @@ namespace MobileGL { if (annotation.NumInOperands() <= kDecorateDecorationOperand) { continue; } - const auto decoration = static_cast( - annotation.GetSingleWordInOperand(kDecorateDecorationOperand)); + const auto decoration = + static_cast(annotation.GetSingleWordInOperand(kDecorateDecorationOperand)); const uint32_t target = annotation.GetSingleWordInOperand(kDecorateTargetOperand); if (decoration == spv::Decoration::Invariant) { invariantIds.push_back(target); @@ -75,9 +73,8 @@ namespace MobileGL { } const auto decoration = static_cast( annotation.GetSingleWordInOperand(kMemberDecorateDecorationOperand)); - const MemberKey key{ - annotation.GetSingleWordInOperand(kMemberDecorateStructOperand), - annotation.GetSingleWordInOperand(kMemberDecorateMemberOperand)}; + const MemberKey key{annotation.GetSingleWordInOperand(kMemberDecorateStructOperand), + annotation.GetSingleWordInOperand(kMemberDecorateMemberOperand)}; if (decoration == spv::Decoration::Invariant) { invariantMembers.push_back(key); } else if (decoration == spv::Decoration::BuiltIn && @@ -99,16 +96,14 @@ namespace MobileGL { irContext, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {target}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Invariant)}}})); + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Invariant)}}})); // Guard against a second Position decoration on the same target. invariantIds.push_back(target); changed = true; } for (const MemberKey& key : positionMembers) { - if (std::find(invariantMembers.begin(), invariantMembers.end(), key) != - invariantMembers.end()) { + if (std::find(invariantMembers.begin(), invariantMembers.end(), key) != invariantMembers.end()) { continue; } irContext->AddAnnotationInst(spvtools::MakeUnique( @@ -116,8 +111,7 @@ namespace MobileGL { std::initializer_list{ {SPV_OPERAND_TYPE_ID, {key.id}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {key.member}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Invariant)}}})); + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Invariant)}}})); invariantMembers.push_back(key); changed = true; } @@ -130,8 +124,7 @@ namespace MobileGL { return Status::SuccessWithChange; } - spvtools::Optimizer::PassToken - DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass() { + spvtools::Optimizer::PassToken DecoratePositionInvariantPass::CreateDecoratePositionInvariantPass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp index 131d6656..f58d79e8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.cpp @@ -31,9 +31,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { case spv::Op::OpTypeRuntimeArray: case spv::Op::OpTypePointer: case spv::Op::OpTypeFunction: - type.ForEachInId([&](const uint32_t* id) { - imageRelated |= imageTypes.count(*id) != 0; - }); + type.ForEachInId([&](const uint32_t* id) { imageRelated |= imageTypes.count(*id) != 0; }); break; default: break; @@ -51,8 +49,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { auto& candidates = canonical[key]; Instruction* match = nullptr; for (auto* candidate : candidates) { - if (context()->get_decoration_mgr()->HaveTheSameDecorations( - type.result_id(), candidate->result_id())) { + if (context()->get_decoration_mgr()->HaveTheSameDecorations(type.result_id(), candidate->result_id())) { match = candidate; break; } @@ -65,11 +62,12 @@ namespace MobileGL::MG_Util::ShaderTranspiler { context()->ReplaceAllUsesWith(type.result_id(), match->result_id()); duplicates.push_back(&type); } - for (auto* type : duplicates) context()->KillInst(type); + for (auto* type : duplicates) + context()->KillInst(type); return duplicates.empty() ? Status::SuccessWithoutChange : Status::SuccessWithChange; } spvtools::Optimizer::PassToken DeduplicateImageTypesPass::Create() { return spvtools::Optimizer::PassToken(spvtools::MakeUnique()); } -} +} // namespace MobileGL::MG_Util::ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.h index c37db93f..2ba868a4 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeduplicateImageTypesPass.h @@ -18,4 +18,4 @@ namespace MobileGL::MG_Util::ShaderTranspiler { Status Process() override; static spvtools::Optimizer::PassToken Create(); }; -} +} // namespace MobileGL::MG_Util::ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp index 5e98c0a1..626c8049 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeferGeometryEmitsPass.cpp @@ -42,7 +42,9 @@ namespace MobileGL { // only guards against a pathological one turning into a huge private array. constexpr uint32_t kMaxSnapshotScalars = 8192; - Operand Id(uint32_t id) { return {SPV_OPERAND_TYPE_ID, {id}}; } + Operand Id(uint32_t id) { + return {SPV_OPERAND_TYPE_ID, {id}}; + } std::unique_ptr Make(IRContext* context, spv::Op op, uint32_t typeId, uint32_t resultId, OperandList operands) { @@ -190,9 +192,9 @@ namespace MobileGL { return; } } - context->AddDebug2Inst(Make(context, spv::Op::OpName, 0, 0, - {Id(id), {SPV_OPERAND_TYPE_LITERAL_STRING, - spvtools::utils::MakeVector(name)}})); + context->AddDebug2Inst( + Make(context, spv::Op::OpName, 0, 0, + {Id(id), {SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(name)}})); } } // namespace @@ -206,16 +208,14 @@ namespace MobileGL { if (entryPoint != nullptr) return Status::SuccessWithoutChange; entryPoint = &candidate; } - if (entryPoint == nullptr || - static_cast(entryPoint->GetSingleWordInOperand(0)) != - spv::ExecutionModel::Geometry) { + if (entryPoint == nullptr || static_cast(entryPoint->GetSingleWordInOperand(0)) != + spv::ExecutionModel::Geometry) { return Status::SuccessWithoutChange; } const uint32_t bodyFunctionId = entryPoint->GetSingleWordInOperand(1); uint32_t maxVertices = 0; for (const Instruction& mode : irContext->module()->execution_modes()) { - if (mode.opcode() == spv::Op::OpExecutionMode && - mode.GetSingleWordInOperand(0) == bodyFunctionId && + if (mode.opcode() == spv::Op::OpExecutionMode && mode.GetSingleWordInOperand(0) == bodyFunctionId && static_cast(mode.GetSingleWordInOperand(1)) == spv::ExecutionMode::OutputVertices) { maxVertices = mode.GetSingleWordInOperand(2); @@ -230,10 +230,16 @@ namespace MobileGL { Bool streams = false; function.ForEachInst([&](Instruction* inst) { switch (inst->opcode()) { - case spv::Op::OpEmitVertex: emits.push_back(inst); break; - case spv::Op::OpEndPrimitive: cuts.push_back(inst); break; + case spv::Op::OpEmitVertex: + emits.push_back(inst); + break; + case spv::Op::OpEndPrimitive: + cuts.push_back(inst); + break; case spv::Op::OpEmitStreamVertex: - case spv::Op::OpEndStreamPrimitive: streams = true; break; + case spv::Op::OpEndStreamPrimitive: + streams = true; + break; default: if (!writesMemory && IsMemoryWrite(irContext, *inst)) writesMemory = true; break; @@ -270,7 +276,8 @@ namespace MobileGL { std::vector written(valueType->NumInOperands(), false); Bool dynamicMember = false; defUse->ForEachUser(&global, [&](Instruction* user) { - if (user->opcode() == spv::Op::OpStore && user->GetSingleWordInOperand(0) == global.result_id()) { + if (user->opcode() == spv::Op::OpStore && + user->GetSingleWordInOperand(0) == global.result_id()) { std::fill(written.begin(), written.end(), true); return; } @@ -278,7 +285,8 @@ namespace MobileGL { std::fill(written.begin(), written.end(), true); return; } - if (user->opcode() != spv::Op::OpAccessChain && user->opcode() != spv::Op::OpInBoundsAccessChain) + if (user->opcode() != spv::Op::OpAccessChain && + user->opcode() != spv::Op::OpInBoundsAccessChain) return; if (user->NumInOperands() < 2) return; const auto member = ConstantValue(irContext, user->GetSingleWordInOperand(1)); @@ -311,17 +319,21 @@ namespace MobileGL { const uint32_t lengthId = constMgr->GetUIntConstId(maxVertices); const uint32_t lastId = constMgr->GetUIntConstId(maxVertices - 1); const uint32_t cutLengthId = constMgr->GetUIntConstId(maxVertices + 1); - const uint32_t privateUintPointerId = typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Private); - const uint32_t functionUintPointerId = typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Function); + const uint32_t privateUintPointerId = + typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Private); + const uint32_t functionUintPointerId = + typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Function); for (Slot& slot : slots) { slot.snapshotElementPointerTypeId = typeMgr->FindPointerToType(slot.valueTypeId, spv::StorageClass::Private); if (slot.member >= 0) { slot.memberIndexId = constMgr->GetUIntConstId(uint32_t(slot.member)); - slot.outputPointerTypeId = typeMgr->FindPointerToType(slot.valueTypeId, spv::StorageClass::Output); + slot.outputPointerTypeId = + typeMgr->FindPointerToType(slot.valueTypeId, spv::StorageClass::Output); } } - const uint32_t bodyFunctionTypeId = irContext->GetFunction(bodyFunctionId)->DefInst().GetSingleWordInOperand(1); + const uint32_t bodyFunctionTypeId = + irContext->GetFunction(bodyFunctionId)->DefInst().GetSingleWordInOperand(1); // Hand-built from here on; analyses are rebuilt once at the end. auto newPrivateArray = [&](uint32_t elementTypeId, uint32_t lengthConstantId) { @@ -329,22 +341,23 @@ namespace MobileGL { irContext->AddType(Make(irContext, spv::Op::OpTypeArray, 0, arrayTypeId, {Id(elementTypeId), Id(lengthConstantId)})); const uint32_t pointerTypeId = irContext->TakeNextId(); - irContext->AddType(Make(irContext, spv::Op::OpTypePointer, 0, pointerTypeId, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Private)}}, - Id(arrayTypeId)})); + irContext->AddType(Make( + irContext, spv::Op::OpTypePointer, 0, pointerTypeId, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Private)}}, Id(arrayTypeId)})); const uint32_t variableId = irContext->TakeNextId(); - irContext->AddGlobalValue(Make(irContext, spv::Op::OpVariable, pointerTypeId, variableId, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, - {uint32_t(spv::StorageClass::Private)}}})); + irContext->AddGlobalValue( + Make(irContext, spv::Op::OpVariable, pointerTypeId, variableId, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Private)}}})); return variableId; }; - for (Slot& slot : slots) slot.snapshotVariableId = newPrivateArray(slot.valueTypeId, lengthId); + for (Slot& slot : slots) + slot.snapshotVariableId = newPrivateArray(slot.valueTypeId, lengthId); // One slot past max_vertices: where an EndPrimitive with no vertex before it lands. const uint32_t cutVariableId = newPrivateArray(uintTypeId, cutLengthId); const uint32_t countVariableId = irContext->TakeNextId(); - irContext->AddGlobalValue(Make(irContext, spv::Op::OpVariable, privateUintPointerId, countVariableId, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, - {uint32_t(spv::StorageClass::Private)}}})); + irContext->AddGlobalValue( + Make(irContext, spv::Op::OpVariable, privateUintPointerId, countVariableId, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Private)}}})); ReplaceName(irContext, countVariableId, "mg_deferredVertexCount"); ReplaceName(irContext, cutVariableId, "mg_deferredStripEnd"); @@ -366,7 +379,8 @@ namespace MobileGL { for (Instruction* emit : emits) { const uint32_t count = before(emit, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)}); const uint32_t inRange = before(emit, spv::Op::OpULessThan, boolTypeId, {Id(count), Id(lengthId)}); - const uint32_t index = before(emit, spv::Op::OpSelect, uintTypeId, {Id(inRange), Id(count), Id(lastId)}); + const uint32_t index = + before(emit, spv::Op::OpSelect, uintTypeId, {Id(inRange), Id(count), Id(lastId)}); for (const Slot& slot : slots) { const uint32_t source = outputPointer(emit, slot); const uint32_t value = before(emit, spv::Op::OpLoad, slot.valueTypeId, {Id(source)}); @@ -374,8 +388,8 @@ namespace MobileGL { {Id(slot.snapshotVariableId), Id(index)}); before(emit, spv::Op::OpStore, 0, {Id(target), Id(value)}); } - const uint32_t cut = before(emit, spv::Op::OpAccessChain, privateUintPointerId, - {Id(cutVariableId), Id(index)}); + const uint32_t cut = + before(emit, spv::Op::OpAccessChain, privateUintPointerId, {Id(cutVariableId), Id(index)}); before(emit, spv::Op::OpStore, 0, {Id(cut), Id(zeroId)}); const uint32_t next = before(emit, spv::Op::OpIAdd, uintTypeId, {Id(count), Id(oneId)}); before(emit, spv::Op::OpStore, 0, {Id(countVariableId), Id(next)}); @@ -384,16 +398,20 @@ namespace MobileGL { const uint32_t count = before(end, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)}); const uint32_t any = before(end, spv::Op::OpINotEqual, boolTypeId, {Id(count), Id(zeroId)}); const uint32_t previous = before(end, spv::Op::OpISub, uintTypeId, {Id(count), Id(oneId)}); - const uint32_t inRange = before(end, spv::Op::OpULessThan, boolTypeId, {Id(previous), Id(lengthId)}); + const uint32_t inRange = + before(end, spv::Op::OpULessThan, boolTypeId, {Id(previous), Id(lengthId)}); const uint32_t clamped = before(end, spv::Op::OpSelect, uintTypeId, {Id(inRange), Id(previous), Id(lastId)}); - const uint32_t index = before(end, spv::Op::OpSelect, uintTypeId, {Id(any), Id(clamped), Id(lengthId)}); - const uint32_t cut = before(end, spv::Op::OpAccessChain, privateUintPointerId, - {Id(cutVariableId), Id(index)}); + const uint32_t index = + before(end, spv::Op::OpSelect, uintTypeId, {Id(any), Id(clamped), Id(lengthId)}); + const uint32_t cut = + before(end, spv::Op::OpAccessChain, privateUintPointerId, {Id(cutVariableId), Id(index)}); before(end, spv::Op::OpStore, 0, {Id(cut), Id(oneId)}); } - for (Instruction* inst : emits) irContext->KillInst(inst); - for (Instruction* inst : cuts) irContext->KillInst(inst); + for (Instruction* inst : emits) + irContext->KillInst(inst); + for (Instruction* inst : cuts) + irContext->KillInst(inst); // ---- The new entry point: run the body, then replay ----------------------------- const uint32_t entryId = irContext->TakeNextId(); @@ -421,13 +439,15 @@ namespace MobileGL { }; auto start = newBlock(startLabel); - const uint32_t iterator = add(*start, spv::Op::OpVariable, functionUintPointerId, - {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}); + const uint32_t iterator = + add(*start, spv::Op::OpVariable, functionUintPointerId, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}); add(*start, spv::Op::OpStore, 0, {Id(countVariableId), Id(zeroId)}); add(*start, spv::Op::OpFunctionCall, voidTypeId, {Id(bodyFunctionId)}); const uint32_t recorded = add(*start, spv::Op::OpLoad, uintTypeId, {Id(countVariableId)}); const uint32_t fits = add(*start, spv::Op::OpULessThan, boolTypeId, {Id(recorded), Id(lengthId)}); - const uint32_t limit = add(*start, spv::Op::OpSelect, uintTypeId, {Id(fits), Id(recorded), Id(lengthId)}); + const uint32_t limit = + add(*start, spv::Op::OpSelect, uintTypeId, {Id(fits), Id(recorded), Id(lengthId)}); add(*start, spv::Op::OpStore, 0, {Id(iterator), Id(zeroId)}); add(*start, spv::Op::OpBranch, 0, {Id(headerLabel)}); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemoteFloat64Pass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemoteFloat64Pass.cpp index f9f8fef8..2f4a4d76 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemoteFloat64Pass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemoteFloat64Pass.cpp @@ -29,8 +29,8 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { namespace { - using spvtools::opt::IRContext; using spvtools::opt::Instruction; + using spvtools::opt::IRContext; using spvtools::opt::Operand; constexpr Uint32 kFloat64Width = 64; @@ -50,12 +50,14 @@ namespace MobileGL { // Offset decoration sets with it. Bool IsDuplicableType(spv::Op opcode) { switch (opcode) { - case spv::Op::OpTypeFloat: - case spv::Op::OpTypeVector: - case spv::Op::OpTypeMatrix: - case spv::Op::OpTypePointer: - case spv::Op::OpTypeFunction: return true; - default: return false; + case spv::Op::OpTypeFloat: + case spv::Op::OpTypeVector: + case spv::Op::OpTypeMatrix: + case spv::Op::OpTypePointer: + case spv::Op::OpTypeFunction: + return true; + default: + return false; } } @@ -89,8 +91,7 @@ namespace MobileGL { // demoted shader reads. class BlockRelayout { public: - BlockRelayout(IRContext* irContext, Bool std140) - : m_irContext(irContext), m_std140(std140) {} + BlockRelayout(IRContext* irContext, Bool std140) : m_irContext(irContext), m_std140(std140) {} // Size and alignment of `typeId`, QUEUING every offset/stride decoration it // implies on the way down. Zero size means "not a type this layout knows how @@ -143,42 +144,44 @@ namespace MobileGL { if (type == nullptr) return {}; switch (type->opcode()) { - case spv::Op::OpTypeInt: - case spv::Op::OpTypeFloat: { - const Uint32 bytes = type->GetSingleWordInOperand(0) / 8; - return {bytes, bytes}; + case spv::Op::OpTypeInt: + case spv::Op::OpTypeFloat: { + const Uint32 bytes = type->GetSingleWordInOperand(0) / 8; + return {bytes, bytes}; + } + case spv::Op::OpTypeBool: + return {4, 4}; + case spv::Op::OpTypeVector: { + const Extent component = Measure(type->GetSingleWordInOperand(0)); + if (component.size == 0) return {}; + const Uint32 count = type->GetSingleWordInOperand(1); + // A three-component vector aligns like a four-component one. + return {component.size * count, component.alignment * (count == 3 ? 4 : count)}; + } + case spv::Op::OpTypeMatrix: { + const Extent column = Measure(type->GetSingleWordInOperand(0)); + if (column.size == 0) return {}; + const Uint32 stride = MatrixOrArrayStride(column.alignment); + return {stride * type->GetSingleWordInOperand(1), stride}; + } + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: { + const Extent element = Measure(type->GetSingleWordInOperand(0)); + if (element.size == 0) return {}; + const Uint32 alignment = MatrixOrArrayStride(element.alignment); + const Uint32 stride = RoundUp(element.size, alignment); + SetTypeDecoration(typeId, spv::Decoration::ArrayStride, stride); + if (type->opcode() == spv::Op::OpTypeRuntimeArray) { + // An unsized array contributes its stride and nothing more; the + // block's size is whatever the application bound. + return {stride, alignment}; } - case spv::Op::OpTypeBool: return {4, 4}; - case spv::Op::OpTypeVector: { - const Extent component = Measure(type->GetSingleWordInOperand(0)); - if (component.size == 0) return {}; - const Uint32 count = type->GetSingleWordInOperand(1); - // A three-component vector aligns like a four-component one. - return {component.size * count, - component.alignment * (count == 3 ? 4 : count)}; - } - case spv::Op::OpTypeMatrix: { - const Extent column = Measure(type->GetSingleWordInOperand(0)); - if (column.size == 0) return {}; - const Uint32 stride = MatrixOrArrayStride(column.alignment); - return {stride * type->GetSingleWordInOperand(1), stride}; - } - case spv::Op::OpTypeArray: - case spv::Op::OpTypeRuntimeArray: { - const Extent element = Measure(type->GetSingleWordInOperand(0)); - if (element.size == 0) return {}; - const Uint32 alignment = MatrixOrArrayStride(element.alignment); - const Uint32 stride = RoundUp(element.size, alignment); - SetTypeDecoration(typeId, spv::Decoration::ArrayStride, stride); - if (type->opcode() == spv::Op::OpTypeRuntimeArray) { - // An unsized array contributes its stride and nothing more; the - // block's size is whatever the application bound. - return {stride, alignment}; - } - return {stride * ArrayLength(type->GetSingleWordInOperand(1)), alignment}; - } - case spv::Op::OpTypeStruct: return MeasureStruct(*type); - default: return {}; + return {stride * ArrayLength(type->GetSingleWordInOperand(1)), alignment}; + } + case spv::Op::OpTypeStruct: + return MeasureStruct(*type); + default: + return {}; } } @@ -199,8 +202,7 @@ namespace MobileGL { m_irContext->get_def_use_mgr()->GetDef(PeelArrays(memberTypeId)); if (memberType != nullptr && memberType->opcode() == spv::Op::OpTypeMatrix) { const Extent column = Measure(memberType->GetSingleWordInOperand(0)); - SetMemberDecoration(structType.result_id(), member, - spv::Decoration::MatrixStride, + SetMemberDecoration(structType.result_id(), member, spv::Decoration::MatrixStride, MatrixOrArrayStride(column.alignment)); } @@ -238,8 +240,7 @@ namespace MobileGL { m_pendingWrites.push_back({false, targetId, 0, decoration, value}); } - void SetMemberDecoration(Uint32 structId, Uint32 member, spv::Decoration decoration, - Uint32 value) { + void SetMemberDecoration(Uint32 structId, Uint32 member, spv::Decoration decoration, Uint32 value) { m_pendingWrites.push_back({true, structId, member, decoration, value}); } @@ -320,24 +321,25 @@ namespace MobileGL { return wideTypeIds.count(type.GetSingleWordInOperand(operand)) != 0; }; switch (type.opcode()) { - case spv::Op::OpTypeVector: - case spv::Op::OpTypeMatrix: - case spv::Op::OpTypeArray: - case spv::Op::OpTypeRuntimeArray: - if (contains(0)) wideTypeIds.insert(type.result_id()); - break; - case spv::Op::OpTypePointer: - if (contains(1)) wideTypeIds.insert(type.result_id()); - break; - case spv::Op::OpTypeStruct: - for (Uint32 member = 0; member < type.NumInOperands(); ++member) { - if (contains(member)) { - wideTypeIds.insert(type.result_id()); - break; - } + case spv::Op::OpTypeVector: + case spv::Op::OpTypeMatrix: + case spv::Op::OpTypeArray: + case spv::Op::OpTypeRuntimeArray: + if (contains(0)) wideTypeIds.insert(type.result_id()); + break; + case spv::Op::OpTypePointer: + if (contains(1)) wideTypeIds.insert(type.result_id()); + break; + case spv::Op::OpTypeStruct: + for (Uint32 member = 0; member < type.NumInOperands(); ++member) { + if (contains(member)) { + wideTypeIds.insert(type.result_id()); + break; } - break; - default: break; + } + break; + default: + break; } } @@ -365,32 +367,31 @@ namespace MobileGL { [&](Instruction* inst) { if (declineReason != nullptr) return; switch (inst->opcode()) { - case spv::Op::OpBitcast: { - // "Total bit width of Result Type and Operand must match" - true - // today, false the moment one of the two sides halves. - const Bool resultIs64 = float64ValueTypeIds.count(inst->type_id()) != 0; - const Bool operandIs64 = valueIsFloat64(inst->GetSingleWordInOperand(0)); - if (resultIs64 != operandIs64) { - declineReason = "an OpBitcast across the 64-bit boundary " - "(doubleBitsToUint64 / uint64BitsToDouble / " - "packDouble2x32)"; - } + case spv::Op::OpBitcast: { + // "Total bit width of Result Type and Operand must match" - true + // today, false the moment one of the two sides halves. + const Bool resultIs64 = float64ValueTypeIds.count(inst->type_id()) != 0; + const Bool operandIs64 = valueIsFloat64(inst->GetSingleWordInOperand(0)); + if (resultIs64 != operandIs64) { + declineReason = "an OpBitcast across the 64-bit boundary " + "(doubleBitsToUint64 / uint64BitsToDouble / " + "packDouble2x32)"; + } + break; + } + case spv::Op::OpExtInst: { + if (glslStd450SetId == 0 || inst->GetSingleWordInOperand(0) != glslStd450SetId) { break; } - case spv::Op::OpExtInst: { - if (glslStd450SetId == 0 || - inst->GetSingleWordInOperand(0) != glslStd450SetId) { - break; - } - const Uint32 extOpcode = inst->GetSingleWordInOperand(1); - if (extOpcode == GLSLstd450PackDouble2x32 || - extOpcode == GLSLstd450UnpackDouble2x32) { - declineReason = "GLSL.std.450 PackDouble2x32 / UnpackDouble2x32, " - "which are defined only for a 64-bit float"; - } - break; + const Uint32 extOpcode = inst->GetSingleWordInOperand(1); + if (extOpcode == GLSLstd450PackDouble2x32 || extOpcode == GLSLstd450UnpackDouble2x32) { + declineReason = "GLSL.std.450 PackDouble2x32 / UnpackDouble2x32, " + "which are defined only for a 64-bit float"; } - default: break; + break; + } + default: + break; } }, /*run_on_debug_line_insts=*/false); @@ -539,8 +540,7 @@ namespace MobileGL { /*run_on_debug_line_insts=*/false); for (Instruction* conversion : identityConversions) { - irContext->ReplaceAllUsesWith(conversion->result_id(), - conversion->GetSingleWordInOperand(0)); + irContext->ReplaceAllUsesWith(conversion->result_id(), conversion->GetSingleWordInOperand(0)); irContext->KillInst(conversion); } @@ -597,8 +597,7 @@ namespace MobileGL { // already correct. for (Instruction& variable : irContext->types_values()) { if (variable.opcode() != spv::Op::OpVariable) continue; - const auto storageClass = - static_cast(variable.GetSingleWordInOperand(0)); + const auto storageClass = static_cast(variable.GetSingleWordInOperand(0)); if (storageClass != spv::StorageClass::Uniform && storageClass != spv::StorageClass::StorageBuffer && storageClass != spv::StorageClass::PushConstant) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp index e85f4285..99e3e06f 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.cpp @@ -54,8 +54,8 @@ namespace MobileGL { bool IsFloat32Type(IRContext* ctx, uint32_t typeId) { Instruction* t = ctx->get_def_use_mgr()->GetDef(typeId); - return t != nullptr && t->opcode() == spv::Op::OpTypeFloat && - t->NumInOperands() >= 1 && t->GetSingleWordInOperand(0) == 32; + return t != nullptr && t->opcode() == spv::Op::OpTypeFloat && t->NumInOperands() >= 1 && + t->GetSingleWordInOperand(0) == 32; } // The value of a plain 32-bit OpConstant, or false (spec constants and anything @@ -79,8 +79,7 @@ namespace MobileGL { analysis::Float f(32); analysis::Type* floatReg = ctx->get_type_mgr()->GetRegisteredType(&f); const analysis::Array::LengthInfo lengthInfo{ - lengthConstId, - {static_cast(analysis::Array::LengthInfo::kConstant), lengthValue}}; + lengthConstId, {static_cast(analysis::Array::LengthInfo::kConstant), lengthValue}}; analysis::Array arr(floatReg, lengthInfo); return ctx->get_type_mgr()->GetTypeInstruction(&arr); } @@ -88,10 +87,8 @@ namespace MobileGL { void AddNameFor(IRContext* ctx, uint32_t id, const String& name) { std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {id}}); - operands.push_back( - {SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(name)}); - ctx->AddDebug2Inst( - spvtools::MakeUnique(ctx, spv::Op::OpName, 0, 0, operands)); + operands.push_back({SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(name)}); + ctx->AddDebug2Inst(spvtools::MakeUnique(ctx, spv::Op::OpName, 0, 0, operands)); } void AddLocationDecoration(IRContext* ctx, uint32_t id, uint32_t location) { @@ -99,8 +96,7 @@ namespace MobileGL { ctx, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {id}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Location)}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Location)}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {location}}})); } @@ -108,8 +104,7 @@ namespace MobileGL { // point, and registered with the def-use manager so ReplaceAllUsesWith may name // it before the end-of-pass invalidation. uint32_t CreateCarrierVariable(IRContext* ctx, Instruction* entryPoint, uint32_t pointeeTypeId, - spv::StorageClass storage, const String& name, - uint32_t location) { + spv::StorageClass storage, const String& name, uint32_t location) { const uint32_t ptrTypeId = ctx->get_type_mgr()->FindPointerToType(pointeeTypeId, storage); if (ptrTypeId == 0) return 0; const uint32_t varId = ctx->TakeNextId(); @@ -173,21 +168,16 @@ namespace MobileGL { }; for (Instruction& ann : ctx->annotations()) { if (ann.opcode() == spv::Op::OpMemberDecorate && ann.NumInOperands() >= 4 && - static_cast(ann.GetSingleWordInOperand(2)) == - spv::Decoration::BuiltIn) { - if (static_cast(ann.GetSingleWordInOperand(3)) == - spv::BuiltIn::PointSize) { - memberSites.push_back( - {ann.GetSingleWordInOperand(0), ann.GetSingleWordInOperand(1)}); + static_cast(ann.GetSingleWordInOperand(2)) == spv::Decoration::BuiltIn) { + if (static_cast(ann.GetSingleWordInOperand(3)) == spv::BuiltIn::PointSize) { + memberSites.push_back({ann.GetSingleWordInOperand(0), ann.GetSingleWordInOperand(1)}); } else if (isClipOrCull(ann.GetSingleWordInOperand(3))) { - clipCullSites.push_back( - {ann.GetSingleWordInOperand(0), ann.GetSingleWordInOperand(1)}); + clipCullSites.push_back({ann.GetSingleWordInOperand(0), ann.GetSingleWordInOperand(1)}); } } else if (ann.opcode() == spv::Op::OpDecorate && ann.NumInOperands() >= 3 && static_cast(ann.GetSingleWordInOperand(1)) == spv::Decoration::BuiltIn) { - if (static_cast(ann.GetSingleWordInOperand(2)) == - spv::BuiltIn::PointSize) { + if (static_cast(ann.GetSingleWordInOperand(2)) == spv::BuiltIn::PointSize) { Instruction* var = defUse->GetDef(ann.GetSingleWordInOperand(0)); if (var != nullptr && var->opcode() == spv::Op::OpVariable) { standaloneVars.push_back(var); @@ -287,8 +277,7 @@ namespace MobileGL { uint32_t memberConstId = 0; uint32_t ptrOutputFloatId = 0; if (blockVar != nullptr) { - memberConstId = ctx->get_constant_mgr()->GetSIntConstId( - static_cast(memberIndex)); + memberConstId = ctx->get_constant_mgr()->GetSIntConstId(static_cast(memberIndex)); ptrOutputFloatId = ctx->get_type_mgr()->FindPointerToType(floatTypeId, spv::StorageClass::Output); if (ptrOutputFloatId == 0) return decline("no Output float pointer type"); @@ -303,8 +292,7 @@ namespace MobileGL { for (auto funcIt = ctx->module()->begin(); funcIt != ctx->module()->end(); ++funcIt) { if (funcIt->result_id() != entryFunctionId) continue; funcIt->ForEachInst([&](Instruction* inst) { - if (inst->opcode() != spv::Op::OpReturn && - inst->opcode() != spv::Op::OpReturnValue) { + if (inst->opcode() != spv::Op::OpReturn && inst->opcode() != spv::Op::OpReturnValue) { return; } uint32_t valueId = 0; @@ -312,9 +300,8 @@ namespace MobileGL { const uint32_t chainId = ctx->TakeNextId(); inst->InsertBefore(spvtools::MakeUnique( ctx, spv::Op::OpAccessChain, ptrOutputFloatId, chainId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {blockVar->result_id()}}, - {SPV_OPERAND_TYPE_ID, {memberConstId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {blockVar->result_id()}}, + {SPV_OPERAND_TYPE_ID, {memberConstId}}})); valueId = ctx->TakeNextId(); inst->InsertBefore(spvtools::MakeUnique( ctx, spv::Op::OpLoad, floatTypeId, valueId, @@ -408,8 +395,7 @@ namespace MobileGL { chainUser->opcode() == spv::Op::OpInBoundsAccessChain) && chainUser->NumInOperands() >= 2) { uint32_t member = 0; - if (PlainConstantValue(ctx, chainUser->GetSingleWordInOperand(1), - member) && + if (PlainConstantValue(ctx, chainUser->GetSingleWordInOperand(1), member) && member == blockVar.memberIndex) { touchesPointSize = true; } @@ -437,8 +423,7 @@ namespace MobileGL { reason = "an access chain that continues past the PointSize member"; return; } - arrayedRedirects.push_back( - {user, blockVar.storage == spv::StorageClass::Input}); + arrayedRedirects.push_back({user, blockVar.storage == spv::StorageClass::Input}); return; } case spv::Op::OpLoad: @@ -519,8 +504,7 @@ namespace MobileGL { if (needsInputCarrier && m_options.inputCarrierName.empty()) { return decline("a PointSize read with no input carrier name to bind it to"); } - if ((needsOutputCarrier && !standaloneOutputSwapped) && - m_options.outputCarrierName.empty()) { + if ((needsOutputCarrier && !standaloneOutputSwapped) && m_options.outputCarrierName.empty()) { return decline("a PointSize write with no output carrier name to bind it to"); } @@ -545,8 +529,7 @@ namespace MobileGL { // built-in for a stage that may not declare it. A control stage asked to // produce a value it never computes is also a program reading undefined data // either way, so this declines rather than growing the pass for it. - if (needsOutputCarrier && !standaloneOutputSwapped && !outputCarrierHasWriter && - isTessControl) { + if (needsOutputCarrier && !standaloneOutputSwapped && !outputCarrierHasWriter && isTessControl) { return decline("a control stage asked to produce a point-size carrier it never writes"); } @@ -571,21 +554,18 @@ namespace MobileGL { if (outputArrayLengthValue == 0) { return decline("a control stage with neither gl_out nor OutputVertices"); } - outputArrayLengthConstId = - ctx->get_constant_mgr()->GetUIntConstId(outputArrayLengthValue); + outputArrayLengthConstId = ctx->get_constant_mgr()->GetUIntConstId(outputArrayLengthValue); } } - const bool anyWork = needsInputCarrier || needsOutputCarrier || - !standaloneSwaps.empty(); + const bool anyWork = needsInputCarrier || needsOutputCarrier || !standaloneSwaps.empty(); // Even with no access left to redirect (a dead read the sanitize chain already // removed), a declared TessellationPointSize/GeometryPointSize capability must // still be stripped - it alone makes the module unbuildable on the device. std::vector capabilitiesToStrip; for (Instruction& capability : ctx->module()->capabilities()) { if (capability.NumInOperands() < 1) continue; - const auto declared = - static_cast(capability.GetSingleWordInOperand(0)); + const auto declared = static_cast(capability.GetSingleWordInOperand(0)); if (declared == spv::Capability::TessellationPointSize || declared == spv::Capability::GeometryPointSize) { capabilitiesToStrip.push_back(&capability); @@ -613,8 +593,7 @@ namespace MobileGL { // variable leaves with the demotion. if (isTessControl && !blockVars.empty()) { bool clipOrCullIsLive = false; - const auto memberIsClipOrCull = [&](const uint32_t structId, - const uint32_t member) { + const auto memberIsClipOrCull = [&](const uint32_t structId, const uint32_t member) { for (const MemberSite& site : clipCullSites) { if (site.structId == structId && site.memberIndex == member) return true; } @@ -623,10 +602,8 @@ namespace MobileGL { for (Instruction& inst : ctx->module()->types_values()) { if (clipOrCullIsLive) break; if (inst.opcode() != spv::Op::OpVariable) continue; - const auto storage = - static_cast(inst.GetSingleWordInOperand(0)); - if (storage != spv::StorageClass::Input && - storage != spv::StorageClass::Output) { + const auto storage = static_cast(inst.GetSingleWordInOperand(0)); + if (storage != spv::StorageClass::Input && storage != spv::StorageClass::Output) { continue; } // A standalone clip/cull variable counts the moment anything but its @@ -642,8 +619,7 @@ namespace MobileGL { pointee = defUse->GetDef(pointee->GetSingleWordInOperand(0)); } const bool blockCarriesClipCull = - pointee != nullptr && pointee->opcode() == spv::Op::OpTypeStruct && - [&] { + pointee != nullptr && pointee->opcode() == spv::Op::OpTypeStruct && [&] { for (const MemberSite& site : clipCullSites) { if (site.structId == pointee->result_id()) return true; } @@ -677,8 +653,7 @@ namespace MobileGL { } uint32_t member = 0; if (user->NumInOperands() <= memberOperand || - !PlainConstantValue(ctx, user->GetSingleWordInOperand(memberOperand), - member)) { + !PlainConstantValue(ctx, user->GetSingleWordInOperand(memberOperand), member)) { clipOrCullIsLive = true; // cannot prove it misses clip/cull return; } @@ -688,9 +663,8 @@ namespace MobileGL { if (clipOrCullIsLive) { for (const BlockVar& blockVar : blockVars) { if (blockVar.storage == spv::StorageClass::Output) { - return decline( - "a control stage with live clip/cull distance, whose " - "redeclared output block would still print gl_PointSize"); + return decline("a control stage with live clip/cull distance, whose " + "redeclared output block would still print gl_PointSize"); } } } @@ -700,22 +674,19 @@ namespace MobileGL { const uint32_t floatTypeId = Float32Type(ctx); uint32_t inputCarrierId = 0; if (needsInputCarrier) { - const uint32_t arrayTypeId = ArrayOfFloat32Type( - ctx, arrayedInput->arrayLengthConstId, arrayedInput->arrayLengthValue); - inputCarrierId = - CreateCarrierVariable(ctx, entryPoint, arrayTypeId, spv::StorageClass::Input, - m_options.inputCarrierName, m_options.location); + const uint32_t arrayTypeId = + ArrayOfFloat32Type(ctx, arrayedInput->arrayLengthConstId, arrayedInput->arrayLengthValue); + inputCarrierId = CreateCarrierVariable(ctx, entryPoint, arrayTypeId, spv::StorageClass::Input, + m_options.inputCarrierName, m_options.location); } uint32_t outputCarrierId = 0; if (needsOutputCarrier && !standaloneOutputSwapped) { uint32_t pointeeTypeId = floatTypeId; if (isTessControl) { - pointeeTypeId = - ArrayOfFloat32Type(ctx, outputArrayLengthConstId, outputArrayLengthValue); + pointeeTypeId = ArrayOfFloat32Type(ctx, outputArrayLengthConstId, outputArrayLengthValue); } - outputCarrierId = - CreateCarrierVariable(ctx, entryPoint, pointeeTypeId, spv::StorageClass::Output, - m_options.outputCarrierName, m_options.location); + outputCarrierId = CreateCarrierVariable(ctx, entryPoint, pointeeTypeId, spv::StorageClass::Output, + m_options.outputCarrierName, m_options.location); } // Seed a forced carrier nothing writes, so the ES hop keeps it (see the @@ -726,22 +697,20 @@ namespace MobileGL { const uint32_t defaultPointSizeId = ctx->get_constant_mgr()->GetFloatConstId(1.0f); const uint32_t entryFunctionId = entryPoint->GetSingleWordInOperand(1); std::vector seedSites; - for (auto funcIt = ctx->module()->begin(); funcIt != ctx->module()->end(); - ++funcIt) { + for (auto funcIt = ctx->module()->begin(); funcIt != ctx->module()->end(); ++funcIt) { if (funcIt->result_id() != entryFunctionId) continue; funcIt->ForEachInst([&](Instruction* inst) { const bool emit = inst->opcode() == spv::Op::OpEmitVertex || inst->opcode() == spv::Op::OpEmitStreamVertex; - const bool ret = inst->opcode() == spv::Op::OpReturn || - inst->opcode() == spv::Op::OpReturnValue; + const bool ret = + inst->opcode() == spv::Op::OpReturn || inst->opcode() == spv::Op::OpReturnValue; if (isGeometry ? emit : ret) seedSites.push_back(inst); }); // A geometry stage with no EmitVertex emits nothing at all; seeding the // ends of the function still keeps the varying alive for the capture. if (isGeometry && seedSites.empty()) { funcIt->ForEachInst([&](Instruction* inst) { - if (inst->opcode() == spv::Op::OpReturn || - inst->opcode() == spv::Op::OpReturnValue) { + if (inst->opcode() == spv::Op::OpReturn || inst->opcode() == spv::Op::OpReturnValue) { seedSites.push_back(inst); } }); @@ -750,9 +719,8 @@ namespace MobileGL { for (Instruction* site : seedSites) { site->InsertBefore(spvtools::MakeUnique( ctx, spv::Op::OpStore, 0, 0, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {outputCarrierId}}, - {SPV_OPERAND_TYPE_ID, {defaultPointSizeId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {outputCarrierId}}, + {SPV_OPERAND_TYPE_ID, {defaultPointSizeId}}})); } } @@ -766,8 +734,8 @@ namespace MobileGL { for (const ArrayedRedirect& redirect : arrayedRedirects) { const uint32_t carrierId = redirect.input ? inputCarrierId : outputCarrierId; const Operand vertexIndex = redirect.chain->GetInOperand(1); - redirect.chain->SetInOperands(Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {carrierId}}, vertexIndex}); + redirect.chain->SetInOperands( + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {carrierId}}, vertexIndex}); } // Standalone form: the variable becomes its own carrier. for (const StandaloneSwap& swap : standaloneSwaps) { @@ -780,7 +748,8 @@ namespace MobileGL { oldNames.push_back(&debugInst); } } - for (Instruction* oldName : oldNames) ctx->KillInst(oldName); + for (Instruction* oldName : oldNames) + ctx->KillInst(oldName); AddNameFor(ctx, swap.var->result_id(), swap.input ? m_options.inputCarrierName : m_options.outputCarrierName); } @@ -797,8 +766,7 @@ namespace MobileGL { spvtools::Optimizer::PassToken DemotePointSizePass::CreateDemotePointSizePass( DemotePointSizeOptions options, DemotePointSizeReport* report) { - return spvtools::Optimizer::PassToken( - MakeUnique(Move(options), report)); + return spvtools::Optimizer::PassToken(MakeUnique(Move(options), report)); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.h index ab976b1c..c4063901 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DemotePointSizePass.h @@ -106,8 +106,8 @@ namespace MobileGL { const char* name() const override { return "mobilegl-demote-point-size"; } Status Process() override; - static spvtools::Optimizer::PassToken CreateDemotePointSizePass( - DemotePointSizeOptions options, DemotePointSizeReport* report); + static spvtools::Optimizer::PassToken CreateDemotePointSizePass(DemotePointSizeOptions options, + DemotePointSizeReport* report); private: DemotePointSizeOptions m_options; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp index 2bc1c8af..6ba46afa 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DeriveNumSubgroupsPass.cpp @@ -61,9 +61,8 @@ namespace MobileGL { bool IsUnsignedInt32(IRContext* context, uint32_t typeId) { const Instruction* type = context->get_def_use_mgr()->GetDef(typeId); - return type != nullptr && type->opcode() == spv::Op::OpTypeInt && - type->NumInOperands() >= 2 && type->GetSingleWordInOperand(0) == 32u && - type->GetSingleWordInOperand(1) == 0u; + return type != nullptr && type->opcode() == spv::Op::OpTypeInt && type->NumInOperands() >= 2 && + type->GetSingleWordInOperand(0) == 32u && type->GetSingleWordInOperand(1) == 0u; } uint32_t SynthesizeSubgroupSizeVariable(IRContext* context, uint32_t pointerTypeId) { @@ -71,16 +70,13 @@ namespace MobileGL { context->AddGlobalValue(spvtools::MakeUnique( context, spv::Op::OpVariable, pointerTypeId, variableId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); context->AddAnnotationInst(spvtools::MakeUnique( context, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {variableId}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::BuiltIn)}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, - {static_cast(spv::BuiltIn::SubgroupSize)}}})); + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::BuiltIn)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast(spv::BuiltIn::SubgroupSize)}}})); for (Instruction& entryPoint : context->module()->entry_points()) { entryPoint.AddOperand({SPV_OPERAND_TYPE_ID, {variableId}}); @@ -104,8 +100,7 @@ namespace MobileGL { defUseMgr->ForEachUser(numSubgroupsVar, [&](Instruction* user) { switch (user->opcode()) { case spv::Op::OpLoad: - if (user->NumInOperands() >= 1 && - user->GetSingleWordInOperand(0) == numSubgroupsVarId) { + if (user->NumInOperands() >= 1 && user->GetSingleWordInOperand(0) == numSubgroupsVarId) { numSubgroupsLoads.push_back(user); } else { sawUnexpectedUser = true; @@ -141,9 +136,8 @@ namespace MobileGL { } Instruction* workgroupSize = FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize); - if (workgroupSize == nullptr || - (workgroupSize->opcode() != spv::Op::OpConstantComposite && - workgroupSize->opcode() != spv::Op::OpSpecConstantComposite)) { + if (workgroupSize == nullptr || (workgroupSize->opcode() != spv::Op::OpConstantComposite && + workgroupSize->opcode() != spv::Op::OpSpecConstantComposite)) { return Status::Failure; } const Instruction* workgroupSizeType = defUseMgr->GetDef(workgroupSize->type_id()); @@ -155,8 +149,7 @@ namespace MobileGL { } Instruction* subgroupSizeVar = FindBuiltinDefinition(irContext, spv::BuiltIn::SubgroupSize); - if (subgroupSizeVar != nullptr && - !IsInputPointerTo(irContext, subgroupSizeVar, valueTypeId)) { + if (subgroupSizeVar != nullptr && !IsInputPointerTo(irContext, subgroupSizeVar, valueTypeId)) { return Status::Failure; } @@ -174,9 +167,9 @@ namespace MobileGL { } const uint32_t oneId = oneInst->result_id(); - const uint32_t subgroupSizeVarId = subgroupSizeVar != nullptr - ? subgroupSizeVar->result_id() - : SynthesizeSubgroupSizeVariable(irContext, numSubgroupsVar->type_id()); + const uint32_t subgroupSizeVarId = + subgroupSizeVar != nullptr ? subgroupSizeVar->result_id() + : SynthesizeSubgroupSizeVariable(irContext, numSubgroupsVar->type_id()); const uint32_t workgroupSizeId = workgroupSize->result_id(); // ceil(local invocation count / SubgroupSize): the subgroup count of a @@ -200,49 +193,41 @@ namespace MobileGL { load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeXId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeYId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1u}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {1u}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpCompositeExtract, valueTypeId, localSizeZId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {2u}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {workgroupSizeId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {2u}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpIMul, valueTypeId, localSizeXYId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {localSizeXId}}, - {SPV_OPERAND_TYPE_ID, {localSizeYId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {localSizeXId}}, + {SPV_OPERAND_TYPE_ID, {localSizeYId}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpIMul, valueTypeId, invocationCountId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {localSizeXYId}}, - {SPV_OPERAND_TYPE_ID, {localSizeZId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {localSizeXYId}}, + {SPV_OPERAND_TYPE_ID, {localSizeZId}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpISub, valueTypeId, adjustedCountId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {invocationCountId}}, - {SPV_OPERAND_TYPE_ID, {oneId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {invocationCountId}}, + {SPV_OPERAND_TYPE_ID, {oneId}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpLoad, valueTypeId, subgroupSizeId, std::initializer_list{{SPV_OPERAND_TYPE_ID, {subgroupSizeVarId}}})); load->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpUDiv, valueTypeId, quotientId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {adjustedCountId}}, - {SPV_OPERAND_TYPE_ID, {subgroupSizeId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {adjustedCountId}}, + {SPV_OPERAND_TYPE_ID, {subgroupSizeId}}})); // Preserve the original result id so every downstream use automatically sees // the derived value instead of the driver's NumSubgroups builtin. load->SetOpcode(spv::Op::OpIAdd); - load->SetInOperands(Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {quotientId}}, - {SPV_OPERAND_TYPE_ID, {oneId}}}); + load->SetInOperands( + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {quotientId}}, {SPV_OPERAND_TYPE_ID, {oneId}}}); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp index d58f3f32..5069bd0a 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp @@ -104,9 +104,12 @@ namespace MobileGL { // which is correct: `x == -0.0` and `x == 0.0` are the same // predicate in IEEE, and abs() maps both zeroes onto +0. switch (floatType->width()) { - case 32: return floatConstant->GetFloatValue() == 0.0f; - case 64: return floatConstant->GetDoubleValue() == 0.0; - default: return false; + case 32: + return floatConstant->GetFloatValue() == 0.0f; + case 64: + return floatConstant->GetDoubleValue() == 0.0; + default: + return false; } }; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp index ee08885c..b90aa792 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateNoPerspectivePass.cpp @@ -93,8 +93,8 @@ namespace MobileGL { // Multiply |valueId| (of type |valueTypeId|) by the scalar |scalarId|, inserting the op // before |before|. Returns the product's id. - uint32_t InsertScale(IRContext* ctx, Instruction* before, uint32_t valueTypeId, - uint32_t valueId, uint32_t scalarId, bool isVector) { + uint32_t InsertScale(IRContext* ctx, Instruction* before, uint32_t valueTypeId, uint32_t valueId, + uint32_t scalarId, bool isVector) { const uint32_t productId = ctx->TakeNextId(); const spv::Op op = isVector ? spv::Op::OpVectorTimesScalar : spv::Op::OpFMul; before->InsertBefore(spvtools::MakeUnique( @@ -115,10 +115,8 @@ namespace MobileGL { uint32_t member = 0; for (Instruction& ann : ctx->annotations()) { if (ann.opcode() == spv::Op::OpMemberDecorate && ann.NumInOperands() >= 4 && - static_cast(ann.GetSingleWordInOperand(2)) == - spv::Decoration::BuiltIn && - static_cast(ann.GetSingleWordInOperand(3)) == - spv::BuiltIn::Position) { + static_cast(ann.GetSingleWordInOperand(2)) == spv::Decoration::BuiltIn && + static_cast(ann.GetSingleWordInOperand(3)) == spv::BuiltIn::Position) { structId = ann.GetSingleWordInOperand(0); member = ann.GetSingleWordInOperand(1); break; @@ -148,8 +146,7 @@ namespace MobileGL { Instruction* FindBuiltinInput(IRContext* ctx, spv::BuiltIn builtin) { for (Instruction& ann : ctx->annotations()) { if (ann.opcode() != spv::Op::OpDecorate || ann.NumInOperands() < 3) continue; - if (static_cast(ann.GetSingleWordInOperand(1)) != - spv::Decoration::BuiltIn) + if (static_cast(ann.GetSingleWordInOperand(1)) != spv::Decoration::BuiltIn) continue; if (static_cast(ann.GetSingleWordInOperand(2)) != builtin) continue; Instruction* var = ctx->get_def_use_mgr()->GetDef(ann.GetSingleWordInOperand(0)); @@ -168,16 +165,13 @@ namespace MobileGL { ctx->AddGlobalValue(spvtools::MakeUnique( ctx, spv::Op::OpVariable, ptrType, varId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); ctx->AddAnnotationInst(spvtools::MakeUnique( ctx, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {varId}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::BuiltIn)}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, - {static_cast(spv::BuiltIn::FragCoord)}}})); + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::BuiltIn)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast(spv::BuiltIn::FragCoord)}}})); for (Instruction& ep : ctx->module()->entry_points()) { ep.AddOperand({SPV_OPERAND_TYPE_ID, {varId}}); } @@ -196,8 +190,7 @@ namespace MobileGL { std::vector decorationsToKill; for (Instruction& ann : ctx->annotations()) { if (ann.opcode() == spv::Op::OpDecorate && ann.NumInOperands() >= 2 && - static_cast(ann.GetSingleWordInOperand(1)) == - spv::Decoration::NoPerspective) { + static_cast(ann.GetSingleWordInOperand(1)) == spv::Decoration::NoPerspective) { plainVarIds.push_back(ann.GetSingleWordInOperand(0)); decorationsToKill.push_back(&ann); } else if (ann.opcode() == spv::Op::OpMemberDecorate && ann.NumInOperands() >= 3 && @@ -213,8 +206,7 @@ namespace MobileGL { return Status::SuccessWithoutChange; } - const spv::StorageClass wantStorage = - isVertex ? spv::StorageClass::Output : spv::StorageClass::Input; + const spv::StorageClass wantStorage = isVertex ? spv::StorageClass::Output : spv::StorageClass::Input; // Emulatable = plain variable of the stage's interface direction, float or floatN. struct Target { @@ -228,8 +220,7 @@ namespace MobileGL { for (const uint32_t id : plainVarIds) { Instruction* var = ctx->get_def_use_mgr()->GetDef(id); if (var == nullptr || var->opcode() != spv::Op::OpVariable) continue; - if (static_cast(var->GetSingleWordInOperand(0)) != wantStorage) - continue; + if (static_cast(var->GetSingleWordInOperand(0)) != wantStorage) continue; const uint32_t pointee = VariablePointeeType(ctx, var); uint32_t floatTypeId = 0; bool isVector = false; @@ -245,13 +236,14 @@ namespace MobileGL { if (!targets.empty()) { std::vector targetIds; targetIds.reserve(targets.size()); - for (const Target& t : targets) targetIds.push_back(t.var->result_id()); + for (const Target& t : targets) + targetIds.push_back(t.var->result_id()); for (Instruction& ann : ctx->annotations()) { if (ann.opcode() == spv::Op::OpDecorate && ann.NumInOperands() >= 2 && static_cast(ann.GetSingleWordInOperand(1)) == spv::Decoration::RelaxedPrecision && - std::find(targetIds.begin(), targetIds.end(), - ann.GetSingleWordInOperand(0)) != targetIds.end()) { + std::find(targetIds.begin(), targetIds.end(), ann.GetSingleWordInOperand(0)) != + targetIds.end()) { decorationsToKill.push_back(&ann); } } @@ -262,8 +254,7 @@ namespace MobileGL { uint32_t memberIndex = 0; uint32_t v4floatTypeId = 0; if (FindPositionBlock(ctx, blockVarId, memberIndex, v4floatTypeId)) { - const uint32_t ptrOutV4 = - PointerTypeTo(ctx, v4floatTypeId, spv::StorageClass::Output); + const uint32_t ptrOutV4 = PointerTypeTo(ctx, v4floatTypeId, spv::StorageClass::Output); const uint32_t memberConst = SignedIntConstant(ctx, static_cast(memberIndex)); const uint32_t floatTy = FloatType(ctx); @@ -281,8 +272,7 @@ namespace MobileGL { for (auto funcIt = ctx->module()->begin(); funcIt != ctx->module()->end(); ++funcIt) { if (funcIt->result_id() != entryFuncId) continue; funcIt->ForEachInst([&](Instruction* inst) { - if (inst->opcode() != spv::Op::OpReturn && - inst->opcode() != spv::Op::OpReturnValue) { + if (inst->opcode() != spv::Op::OpReturn && inst->opcode() != spv::Op::OpReturnValue) { return; } const uint32_t posPtrId = ctx->TakeNextId(); @@ -303,15 +293,12 @@ namespace MobileGL { const uint32_t valId = ctx->TakeNextId(); inst->InsertBefore(spvtools::MakeUnique( ctx, spv::Op::OpLoad, t.typeId, valId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {t.var->result_id()}}})); - const uint32_t scaledId = - InsertScale(ctx, inst, t.typeId, valId, wId, t.isVector); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {t.var->result_id()}}})); + const uint32_t scaledId = InsertScale(ctx, inst, t.typeId, valId, wId, t.isVector); inst->InsertBefore(spvtools::MakeUnique( ctx, spv::Op::OpStore, 0, 0, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {t.var->result_id()}}, - {SPV_OPERAND_TYPE_ID, {scaledId}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {t.var->result_id()}}, + {SPV_OPERAND_TYPE_ID, {scaledId}}})); } }); } @@ -383,8 +370,8 @@ namespace MobileGL { std::initializer_list{{SPV_OPERAND_TYPE_ID, {fcId}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {3u}}})); load->SetOpcode(loadIsVector ? spv::Op::OpVectorTimesScalar : spv::Op::OpFMul); - load->SetInOperands(Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {origId}}, {SPV_OPERAND_TYPE_ID, {wId}}}); + load->SetInOperands(Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {origId}}, + {SPV_OPERAND_TYPE_ID, {wId}}}); } } } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.cpp index 33692a64..aa28d941 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.cpp @@ -48,7 +48,9 @@ namespace MobileGL { constexpr uint32_t kGlslFindILsb = 73u; constexpr uint32_t kGlslFindUMsb = 75u; - Operand IdOp(uint32_t id) { return {SPV_OPERAND_TYPE_ID, {id}}; } + Operand IdOp(uint32_t id) { + return {SPV_OPERAND_TYPE_ID, {id}}; + } Instruction* FindBuiltinDefinition(IRContext* context, spv::BuiltIn builtin) { auto* defUseMgr = context->get_def_use_mgr(); @@ -69,13 +71,18 @@ namespace MobileGL { } // The scalar shapes a value participating in an emulated exchange may have. - enum class ScalarKind { Float, SInt, UInt, Bool }; + enum class ScalarKind { + Float, + SInt, + UInt, + Bool + }; struct ValueTypeInfo { ScalarKind kind = ScalarKind::UInt; - uint32_t componentCount = 1; // 1 for scalars, 2..4 for vectors - uint32_t typeId = 0; // the value's own type - uint32_t scalarTypeId = 0; // the component type + uint32_t componentCount = 1; // 1 for scalars, 2..4 for vectors + uint32_t typeId = 0; // the value's own type + uint32_t scalarTypeId = 0; // the component type }; bool ClassifyScalar(const Instruction* type, ScalarKind* kind) { @@ -121,14 +128,14 @@ namespace MobileGL { uint32_t Emit(spv::Op opcode, uint32_t typeId, Instruction::OperandList operands) { const uint32_t resultId = m_context->TakeNextId(); - m_anchor->InsertBefore(spvtools::MakeUnique( - m_context, opcode, typeId, resultId, std::move(operands))); + m_anchor->InsertBefore(spvtools::MakeUnique(m_context, opcode, typeId, resultId, + std::move(operands))); return resultId; } void EmitNoResult(spv::Op opcode, Instruction::OperandList operands) { - m_anchor->InsertBefore(spvtools::MakeUnique( - m_context, opcode, 0, 0, std::move(operands))); + m_anchor->InsertBefore( + spvtools::MakeUnique(m_context, opcode, 0, 0, std::move(operands))); } private: @@ -142,11 +149,11 @@ namespace MobileGL { Instruction* inst = nullptr; spv::Op opcode = spv::Op::OpNop; spv::GroupOperation groupOperation = spv::GroupOperation::Reduce; - uint32_t clusterSize = 0; // ClusteredReduce only - uint32_t quadDirection = 0; // QuadSwap only - ValueTypeInfo valueType{}; // participating value (where applicable) + uint32_t clusterSize = 0; // ClusteredReduce only + uint32_t quadDirection = 0; // QuadSwap only + ValueTypeInfo valueType{}; // participating value (where applicable) uint32_t valueId = 0; - uint32_t indexId = 0; // broadcast/shuffle/extract index operand + uint32_t indexId = 0; // broadcast/shuffle/extract index operand }; struct BarrierSite { @@ -156,8 +163,7 @@ namespace MobileGL { }; bool IsGroupNonUniformOpcode(spv::Op opcode) { - return opcode >= spv::Op::OpGroupNonUniformElect && - opcode <= spv::Op::OpGroupNonUniformQuadSwap; + return opcode >= spv::Op::OpGroupNonUniformElect && opcode <= spv::Op::OpGroupNonUniformQuadSwap; } } // namespace @@ -188,17 +194,15 @@ namespace MobileGL { // Subgroup builtin variables and their loads. static constexpr std::array kSubgroupBuiltins = { - spv::BuiltIn::SubgroupSize, spv::BuiltIn::SubgroupLocalInvocationId, - spv::BuiltIn::SubgroupId, spv::BuiltIn::NumSubgroups, - spv::BuiltIn::SubgroupEqMask, spv::BuiltIn::SubgroupGeMask, - spv::BuiltIn::SubgroupGtMask, spv::BuiltIn::SubgroupLeMask, + spv::BuiltIn::SubgroupSize, spv::BuiltIn::SubgroupLocalInvocationId, + spv::BuiltIn::SubgroupId, spv::BuiltIn::NumSubgroups, + spv::BuiltIn::SubgroupEqMask, spv::BuiltIn::SubgroupGeMask, + spv::BuiltIn::SubgroupGtMask, spv::BuiltIn::SubgroupLeMask, spv::BuiltIn::SubgroupLtMask}; const auto isMaskBuiltin = [](spv::BuiltIn builtin) { - return builtin == spv::BuiltIn::SubgroupEqMask || - builtin == spv::BuiltIn::SubgroupGeMask || - builtin == spv::BuiltIn::SubgroupGtMask || - builtin == spv::BuiltIn::SubgroupLeMask || + return builtin == spv::BuiltIn::SubgroupEqMask || builtin == spv::BuiltIn::SubgroupGeMask || + builtin == spv::BuiltIn::SubgroupGtMask || builtin == spv::BuiltIn::SubgroupLeMask || builtin == spv::BuiltIn::SubgroupLtMask; }; struct BuiltinUse { @@ -221,8 +225,7 @@ namespace MobileGL { defUseMgr->ForEachUser(variable, [&](Instruction* user) { switch (user->opcode()) { case spv::Op::OpLoad: - if (user->NumInOperands() >= 1 && - user->GetSingleWordInOperand(0) == variableId) { + if (user->NumInOperands() >= 1 && user->GetSingleWordInOperand(0) == variableId) { use.loads.push_back(user); } else { sawUnexpectedUser = true; @@ -236,8 +239,7 @@ namespace MobileGL { sawUnexpectedUser = true; return; } - const Instruction* index = - defUseMgr->GetDef(user->GetSingleWordInOperand(1)); + const Instruction* index = defUseMgr->GetDef(user->GetSingleWordInOperand(1)); if (index == nullptr || index->opcode() != spv::Op::OpConstant || (component = index->GetSingleWordInOperand(0)) > 3u) { sawUnexpectedUser = true; @@ -245,8 +247,7 @@ namespace MobileGL { } const uint32_t chainId = user->result_id(); defUseMgr->ForEachUser(user, [&](Instruction* chainUser) { - if (chainUser->opcode() == spv::Op::OpLoad && - chainUser->NumInOperands() >= 1 && + if (chainUser->opcode() == spv::Op::OpLoad && chainUser->NumInOperands() >= 1 && chainUser->GetSingleWordInOperand(0) == chainId) { use.componentLoads.emplace_back(chainUser, component); } else { @@ -349,15 +350,13 @@ namespace MobileGL { break; case spv::Op::OpGroupNonUniformQuadSwap: site.valueId = inst.GetSingleWordInOperand(1); - if (!resolveUintConstant(inst.GetSingleWordInOperand(2), - &site.quadDirection) || + if (!resolveUintConstant(inst.GetSingleWordInOperand(2), &site.quadDirection) || site.quadDirection > 2u) { sawUnsupported = true; } break; case spv::Op::OpGroupNonUniformBallotBitCount: - site.groupOperation = static_cast( - inst.GetSingleWordInOperand(1)); + site.groupOperation = static_cast(inst.GetSingleWordInOperand(1)); site.valueId = inst.GetSingleWordInOperand(2); break; case spv::Op::OpGroupNonUniformIAdd: @@ -376,15 +375,12 @@ namespace MobileGL { case spv::Op::OpGroupNonUniformLogicalAnd: case spv::Op::OpGroupNonUniformLogicalOr: case spv::Op::OpGroupNonUniformLogicalXor: - site.groupOperation = static_cast( - inst.GetSingleWordInOperand(1)); + site.groupOperation = static_cast(inst.GetSingleWordInOperand(1)); site.valueId = inst.GetSingleWordInOperand(2); if (site.groupOperation == spv::GroupOperation::ClusteredReduce) { if (inst.NumInOperands() < 4 || - !resolveUintConstant(inst.GetSingleWordInOperand(3), - &site.clusterSize) || - site.clusterSize == 0u || - (site.clusterSize & (site.clusterSize - 1u)) != 0u) { + !resolveUintConstant(inst.GetSingleWordInOperand(3), &site.clusterSize) || + site.clusterSize == 0u || (site.clusterSize & (site.clusterSize - 1u)) != 0u) { sawUnsupported = true; } } else if (site.groupOperation != spv::GroupOperation::Reduce && @@ -415,8 +411,7 @@ namespace MobileGL { const Instruction* value = defUseMgr->GetDef(site.valueId); if (value == nullptr || !ClassifyValueType(irContext, value->type_id(), &site.valueType) || - site.valueType.kind != ScalarKind::Bool || - site.valueType.componentCount != 1u) { + site.valueType.kind != ScalarKind::Bool || site.valueType.componentCount != 1u) { sawUnsupported = true; } break; @@ -452,14 +447,12 @@ namespace MobileGL { // is accepted; spec-constant sizes are not (GL has no specialization). uint32_t localSize[3] = {0, 0, 0}; bool haveLocalSize = false; - if (Instruction* workgroupSize = - FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize)) { + if (Instruction* workgroupSize = FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize)) { if (workgroupSize->opcode() == spv::Op::OpConstantComposite && workgroupSize->NumInOperands() == 3) { - haveLocalSize = - resolveUintConstant(workgroupSize->GetSingleWordInOperand(0), &localSize[0]) && - resolveUintConstant(workgroupSize->GetSingleWordInOperand(1), &localSize[1]) && - resolveUintConstant(workgroupSize->GetSingleWordInOperand(2), &localSize[2]); + haveLocalSize = resolveUintConstant(workgroupSize->GetSingleWordInOperand(0), &localSize[0]) && + resolveUintConstant(workgroupSize->GetSingleWordInOperand(1), &localSize[1]) && + resolveUintConstant(workgroupSize->GetSingleWordInOperand(2), &localSize[2]); } } if (!haveLocalSize) { @@ -478,8 +471,7 @@ namespace MobileGL { if (!haveLocalSize || localSize[0] == 0u || localSize[1] == 0u || localSize[2] == 0u) { return Status::Failure; } - const uint64_t total64 = - static_cast(localSize[0]) * localSize[1] * localSize[2]; + const uint64_t total64 = static_cast(localSize[0]) * localSize[1] * localSize[2]; if (total64 == 0u || total64 > (1u << 20)) { return Status::Failure; } @@ -501,13 +493,12 @@ namespace MobileGL { std::set scratchTypeKeys; uint64_t scratchBytes = 0; for (const GroupOpSite& site : groupOps) { - if (site.valueType.typeId == 0u) continue; // no shared-memory exchange + if (site.valueType.typeId == 0u) continue; // no shared-memory exchange const uint32_t key = site.valueType.kind == ScalarKind::Bool ? (0x80000000u | site.valueType.componentCount) : site.valueType.typeId; if (!scratchTypeKeys.insert(key).second) continue; - scratchBytes += static_cast(paddedSlots) * 4u * - site.valueType.componentCount; + scratchBytes += static_cast(paddedSlots) * 4u * site.valueType.componentCount; } if (scratchBytes > scratchBudgetBytes) { return Status::Failure; @@ -528,38 +519,31 @@ namespace MobileGL { const analysis::Type* type = typeMgr->GetType(uintTypeId); const analysis::Constant* constant = constMgr->GetConstant(type, {value}); const Instruction* inst = - constant != nullptr ? constMgr->GetDefiningInstruction(constant, uintTypeId) - : nullptr; + constant != nullptr ? constMgr->GetDefiningInstruction(constant, uintTypeId) : nullptr; return inst != nullptr ? inst->result_id() : 0u; }; const auto boolConst = [&](bool value) -> uint32_t { const analysis::Type* type = typeMgr->GetType(boolTypeId); - const analysis::Constant* constant = - constMgr->GetConstant(type, {value ? 1u : 0u}); + const analysis::Constant* constant = constMgr->GetConstant(type, {value ? 1u : 0u}); const Instruction* inst = - constant != nullptr ? constMgr->GetDefiningInstruction(constant, boolTypeId) - : nullptr; + constant != nullptr ? constMgr->GetDefiningInstruction(constant, boolTypeId) : nullptr; return inst != nullptr ? inst->result_id() : 0u; }; // A constant of an arbitrary 32-bit scalar type from its bit pattern, splat // to the value type when it is a vector. const auto typedConst = [&](const ValueTypeInfo& info, uint32_t scalarBits, - uint32_t scratchScalarTypeId, - uint32_t scratchTypeId) -> uint32_t { + uint32_t scratchScalarTypeId, uint32_t scratchTypeId) -> uint32_t { const analysis::Type* scalarType = typeMgr->GetType(scratchScalarTypeId); const analysis::Constant* scalar = constMgr->GetConstant(scalarType, {scalarBits}); if (scalar == nullptr) return 0u; - const Instruction* scalarInst = - constMgr->GetDefiningInstruction(scalar, scratchScalarTypeId); + const Instruction* scalarInst = constMgr->GetDefiningInstruction(scalar, scratchScalarTypeId); if (scalarInst == nullptr) return 0u; if (info.componentCount == 1u) return scalarInst->result_id(); const analysis::Type* vectorType = typeMgr->GetType(scratchTypeId); - std::vector componentIds(info.componentCount, - scalarInst->result_id()); + std::vector componentIds(info.componentCount, scalarInst->result_id()); const analysis::Constant* vector = constMgr->GetConstant(vectorType, componentIds); if (vector == nullptr) return 0u; - const Instruction* vectorInst = - constMgr->GetDefiningInstruction(vector, scratchTypeId); + const Instruction* vectorInst = constMgr->GetDefiningInstruction(vector, scratchTypeId); return vectorInst != nullptr ? vectorInst->result_id() : 0u; }; @@ -571,16 +555,15 @@ namespace MobileGL { const uint32_t cTotal = uintConst(totalInvocations); const uint32_t cAllOnes = uintConst(0xffffffffu); const uint32_t cTrue = boolConst(true); - if (c0 == 0u || c1 == 0u || c31 == 0u || c32 == 0u || cNotLane == 0u || - cTotal == 0u || cAllOnes == 0u || cTrue == 0u) { + if (c0 == 0u || c1 == 0u || c31 == 0u || c32 == 0u || cNotLane == 0u || cTotal == 0u || + cAllOnes == 0u || cTrue == 0u) { return Status::Failure; } // GLSL.std.450 import (for min/max combines and ballot find ops). uint32_t glslStd450Id = 0; for (const Instruction& import : irContext->module()->ext_inst_imports()) { - const std::string importName = - spvtools::utils::MakeString(import.GetInOperand(0).words); + const std::string importName = spvtools::utils::MakeString(import.GetInOperand(0).words); if (importName == "GLSL.std.450") { glslStd450Id = import.result_id(); break; @@ -592,34 +575,29 @@ namespace MobileGL { irContext->module()->AddExtInstImport(spvtools::MakeUnique( irContext, spv::Op::OpExtInstImport, 0, glslStd450Id, Instruction::OperandList{ - {SPV_OPERAND_TYPE_LITERAL_STRING, - spvtools::utils::MakeVector("GLSL.std.450")}})); + {SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector("GLSL.std.450")}})); return glslStd450Id; }; // gl_LocalInvocationIndex drives the whole virtual topology; synthesize the // builtin when the module never declared it. - Instruction* liiVariable = - FindBuiltinDefinition(irContext, spv::BuiltIn::LocalInvocationIndex); + Instruction* liiVariable = FindBuiltinDefinition(irContext, spv::BuiltIn::LocalInvocationIndex); uint32_t liiVariableId = 0; if (liiVariable != nullptr && liiVariable->opcode() == spv::Op::OpVariable) { liiVariableId = liiVariable->result_id(); } else { - const uint32_t pointerTypeId = - typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Input); + const uint32_t pointerTypeId = typeMgr->FindPointerToType(uintTypeId, spv::StorageClass::Input); if (pointerTypeId == 0u) return Status::Failure; liiVariableId = irContext->TakeNextId(); irContext->AddGlobalValue(spvtools::MakeUnique( irContext, spv::Op::OpVariable, pointerTypeId, liiVariableId, Instruction::OperandList{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); irContext->AddAnnotationInst(spvtools::MakeUnique( irContext, spv::Op::OpDecorate, 0, 0, Instruction::OperandList{ IdOp(liiVariableId), - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::BuiltIn)}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::BuiltIn)}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast(spv::BuiltIn::LocalInvocationIndex)}}})); for (Instruction& entryPoint : irContext->module()->entry_points()) { @@ -646,8 +624,7 @@ namespace MobileGL { if (m_atomicScratchReads) elementTypeId = uintTypeId; auto it = scratchByType.find(key); if (it != scratchByType.end()) return &it->second; - const uint32_t lengthId = - m_atomicScratchReads ? uintConst(paddedSlots * wordsPerSlot) : cPadded; + const uint32_t lengthId = m_atomicScratchReads ? uintConst(paddedSlots * wordsPerSlot) : cPadded; if (lengthId == 0u) return nullptr; const uint32_t arrayTypeId = irContext->TakeNextId(); irContext->AddType(spvtools::MakeUnique( @@ -662,16 +639,15 @@ namespace MobileGL { irContext->AddGlobalValue(spvtools::MakeUnique( irContext, spv::Op::OpVariable, arrayPointerTypeId, variableId, Instruction::OperandList{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Workgroup)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Workgroup)}}})); Scratch scratch{variableId, elementTypeId, elementPointerTypeId}; return &scratchByType.emplace(key, scratch).first->second; }; // The uint-domain twin of a participating type: bools travel as 0/1 words. struct ScratchTypeInfo { - uint32_t typeId = 0; // element type stored in scratch - uint32_t scalarTypeId = 0; // its component type + uint32_t typeId = 0; // element type stored in scratch + uint32_t scalarTypeId = 0; // its component type bool boolConverted = false; }; const auto scratchTypeFor = [&](const ValueTypeInfo& info) -> ScratchTypeInfo { @@ -705,32 +681,26 @@ namespace MobileGL { // actually has: the masks name lane positions, and GL/Vulkan define them for // every position below the subgroup size. const auto emitMaskWord = [&](Emitter& em, spv::BuiltIn builtin) -> uint32_t { - const uint32_t index = - em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); - const uint32_t lane = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(index), IdOp(c31)}); + const uint32_t index = em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); + const uint32_t lane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(index), IdOp(c31)}); const auto lowMaskThroughLane = [&](bool inclusive) -> uint32_t { if (!inclusive) { - const uint32_t bit = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(c1), IdOp(lane)}); + const uint32_t bit = + em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(c1), IdOp(lane)}); return em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(bit), IdOp(c1)}); } - const uint32_t lanePlusOne = - em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(lane), IdOp(c1)}); - const uint32_t atTop = em.Emit(spv::Op::OpUGreaterThanEqual, boolTypeId, - {IdOp(lanePlusOne), IdOp(c32)}); - const uint32_t shifted = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(c1), IdOp(lanePlusOne)}); - const uint32_t below = - em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(shifted), IdOp(c1)}); - return em.Emit(spv::Op::OpSelect, uintTypeId, - {IdOp(atTop), IdOp(cAllOnes), IdOp(below)}); + const uint32_t lanePlusOne = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(lane), IdOp(c1)}); + const uint32_t atTop = + em.Emit(spv::Op::OpUGreaterThanEqual, boolTypeId, {IdOp(lanePlusOne), IdOp(c32)}); + const uint32_t shifted = + em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(c1), IdOp(lanePlusOne)}); + const uint32_t below = em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(shifted), IdOp(c1)}); + return em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(atTop), IdOp(cAllOnes), IdOp(below)}); }; uint32_t word = 0; switch (builtin) { case spv::BuiltIn::SubgroupEqMask: - word = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(c1), IdOp(lane)}); + word = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(c1), IdOp(lane)}); break; case spv::BuiltIn::SubgroupLtMask: word = lowMaskThroughLane(false); @@ -763,15 +733,13 @@ namespace MobileGL { load->SetInOperands({IdOp(numVirtualConstId)}); break; case spv::BuiltIn::SubgroupLocalInvocationId: { - const uint32_t index = - em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); + const uint32_t index = em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); load->SetOpcode(spv::Op::OpBitwiseAnd); load->SetInOperands({IdOp(index), IdOp(c31)}); break; } case spv::BuiltIn::SubgroupId: { - const uint32_t index = - em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); + const uint32_t index = em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); load->SetOpcode(spv::Op::OpShiftRightLogical); load->SetInOperands({IdOp(index), IdOp(uintConst(kIdShift))}); break; @@ -780,8 +748,7 @@ namespace MobileGL { const uint32_t word = emitMaskWord(em, use.builtin); if (word == 0u) return Status::Failure; load->SetOpcode(spv::Op::OpCompositeConstruct); - load->SetInOperands( - {IdOp(word), IdOp(c0), IdOp(c0), IdOp(c0)}); + load->SetInOperands({IdOp(word), IdOp(c0), IdOp(c0), IdOp(c0)}); break; } } @@ -805,38 +772,29 @@ namespace MobileGL { Instruction* inst = site.inst; Emitter em(irContext, inst); - const uint32_t lii = - em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); - const uint32_t lane = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(lii), IdOp(c31)}); - const uint32_t base = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(lii), IdOp(cNotLane)}); + const uint32_t lii = em.Emit(spv::Op::OpLoad, uintTypeId, {IdOp(liiVariableId)}); + const uint32_t lane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(lii), IdOp(c31)}); + const uint32_t base = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(lii), IdOp(cNotLane)}); const auto barrier = [&]() { const uint32_t scopeId = uintConst(static_cast(spv::Scope::Workgroup)); - const uint32_t semanticsId = uintConst( - static_cast(spv::MemorySemanticsMask::AcquireRelease) | - static_cast(spv::MemorySemanticsMask::WorkgroupMemory)); - em.EmitNoResult(spv::Op::OpControlBarrier, - {IdOp(scopeId), IdOp(scopeId), IdOp(semanticsId)}); + const uint32_t semanticsId = + uintConst(static_cast(spv::MemorySemanticsMask::AcquireRelease) | + static_cast(spv::MemorySemanticsMask::WorkgroupMemory)); + em.EmitNoResult(spv::Op::OpControlBarrier, {IdOp(scopeId), IdOp(scopeId), IdOp(semanticsId)}); }; const ScratchTypeInfo scratchType = scratchTypeFor(site.valueType); const auto toScratch = [&](uint32_t valueId) -> uint32_t { if (!scratchType.boolConverted) return valueId; - const uint32_t ones = - typedConst(site.valueType, 1u, uintTypeId, scratchType.typeId); - const uint32_t zeros = - typedConst(site.valueType, 0u, uintTypeId, scratchType.typeId); - return em.Emit(spv::Op::OpSelect, scratchType.typeId, - {IdOp(valueId), IdOp(ones), IdOp(zeros)}); + const uint32_t ones = typedConst(site.valueType, 1u, uintTypeId, scratchType.typeId); + const uint32_t zeros = typedConst(site.valueType, 0u, uintTypeId, scratchType.typeId); + return em.Emit(spv::Op::OpSelect, scratchType.typeId, {IdOp(valueId), IdOp(ones), IdOp(zeros)}); }; const auto fromScratch = [&](uint32_t valueId) -> uint32_t { if (!scratchType.boolConverted) return valueId; - const uint32_t zeros = - typedConst(site.valueType, 0u, uintTypeId, scratchType.typeId); - return em.Emit(spv::Op::OpINotEqual, site.valueType.typeId, - {IdOp(valueId), IdOp(zeros)}); + const uint32_t zeros = typedConst(site.valueType, 0u, uintTypeId, scratchType.typeId); + return em.Emit(spv::Op::OpINotEqual, site.valueType.typeId, {IdOp(valueId), IdOp(zeros)}); }; // Atomic scratch reads keep each slot as componentCount uint words; the @@ -851,8 +809,8 @@ namespace MobileGL { const bool wordsNeedBitcast = scratchType.scalarTypeId != uintTypeId; const auto wordIndex = [&](uint32_t slotId, uint32_t component) -> uint32_t { if (wordsPerSlot == 1u) return slotId; - const uint32_t scaled = em.Emit(spv::Op::OpIMul, uintTypeId, - {IdOp(slotId), IdOp(uintConst(wordsPerSlot))}); + const uint32_t scaled = + em.Emit(spv::Op::OpIMul, uintTypeId, {IdOp(slotId), IdOp(uintConst(wordsPerSlot))}); return em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(scaled), IdOp(uintConst(component))}); }; @@ -861,9 +819,8 @@ namespace MobileGL { scratch = scratchFor(scratchType.typeId, wordsPerSlot); if (scratch == nullptr) return false; if (!m_atomicScratchReads) { - const uint32_t pointer = - em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, - {IdOp(scratch->variableId), IdOp(lii)}); + const uint32_t pointer = em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, + {IdOp(scratch->variableId), IdOp(lii)}); em.EmitNoResult(spv::Op::OpStore, {IdOp(pointer), IdOp(valueId)}); } else { const uint32_t words = @@ -874,9 +831,8 @@ namespace MobileGL { ? words : em.Emit(spv::Op::OpCompositeExtract, uintTypeId, {IdOp(words), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {c}}}); - const uint32_t pointer = - em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, - {IdOp(scratch->variableId), IdOp(wordIndex(lii, c))}); + const uint32_t pointer = em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, + {IdOp(scratch->variableId), IdOp(wordIndex(lii, c))}); em.EmitNoResult(spv::Op::OpStore, {IdOp(pointer), IdOp(word)}); } } @@ -885,20 +841,18 @@ namespace MobileGL { }; const auto readSlot = [&](uint32_t indexId) -> uint32_t { if (!m_atomicScratchReads) { - const uint32_t pointer = - em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, - {IdOp(scratch->variableId), IdOp(indexId)}); + const uint32_t pointer = em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, + {IdOp(scratch->variableId), IdOp(indexId)}); return em.Emit(spv::Op::OpLoad, scratchType.typeId, {IdOp(pointer)}); } const uint32_t scopeId = uintConst(static_cast(spv::Scope::Workgroup)); Instruction::OperandList words; uint32_t packed = 0; for (uint32_t c = 0; c < wordsPerSlot; ++c) { - const uint32_t pointer = - em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, - {IdOp(scratch->variableId), IdOp(wordIndex(indexId, c))}); - packed = em.Emit(spv::Op::OpAtomicLoad, uintTypeId, - {IdOp(pointer), IdOp(scopeId), IdOp(c0)}); + const uint32_t pointer = em.Emit(spv::Op::OpAccessChain, scratch->pointerTypeId, + {IdOp(scratch->variableId), IdOp(wordIndex(indexId, c))}); + packed = + em.Emit(spv::Op::OpAtomicLoad, uintTypeId, {IdOp(pointer), IdOp(scopeId), IdOp(c0)}); words.push_back(IdOp(packed)); } if (wordsPerSlot > 1u) { @@ -909,16 +863,15 @@ namespace MobileGL { }; // A select whose condition is a scalar bool but whose values may be // vectors; pre-SPIR-V-1.4 needs the condition splat to a bvec. - const auto guardedSelect = [&](uint32_t condId, uint32_t thenId, - uint32_t elseId) -> uint32_t { + const auto guardedSelect = [&](uint32_t condId, uint32_t thenId, uint32_t elseId) -> uint32_t { const uint32_t componentCount = site.valueType.componentCount; uint32_t conditionId = condId; if (componentCount > 1u) { const uint32_t conditionTypeId = boolVectorTypeId(componentCount); Instruction::OperandList splat; - for (uint32_t i = 0; i < componentCount; ++i) splat.push_back(IdOp(condId)); - conditionId = - em.Emit(spv::Op::OpCompositeConstruct, conditionTypeId, std::move(splat)); + for (uint32_t i = 0; i < componentCount; ++i) + splat.push_back(IdOp(condId)); + conditionId = em.Emit(spv::Op::OpCompositeConstruct, conditionTypeId, std::move(splat)); } return em.Emit(spv::Op::OpSelect, scratchType.typeId, {IdOp(conditionId), IdOp(thenId), IdOp(elseId)}); @@ -929,15 +882,14 @@ namespace MobileGL { return em.Emit(spv::Op::OpExtInst, typeId, {IdOp(setId), {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {instNumber}}, - IdOp(a), IdOp(b)}); + IdOp(a), + IdOp(b)}); }; - const auto extInst1 = [&](uint32_t typeId, uint32_t instNumber, - uint32_t a) -> uint32_t { + const auto extInst1 = [&](uint32_t typeId, uint32_t instNumber, uint32_t a) -> uint32_t { const uint32_t setId = ensureGlslStd450(); - return em.Emit(spv::Op::OpExtInst, typeId, - {IdOp(setId), - {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {instNumber}}, - IdOp(a)}); + return em.Emit( + spv::Op::OpExtInst, typeId, + {IdOp(setId), {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {instNumber}}, IdOp(a)}); }; // Combine two scratch-domain values with the site's operation. @@ -1037,15 +989,14 @@ namespace MobileGL { for (uint32_t k = 0; k < kWidth; ++k) { const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); - const uint32_t exists = em.Emit(spv::Op::OpULessThan, boolTypeId, - {IdOp(slot), IdOp(cTotal)}); + const uint32_t exists = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(slot), IdOp(cTotal)}); const uint32_t value = readSlot(slot); - const uint32_t guarded = em.Emit( - spv::Op::OpSelect, uintTypeId, - {IdOp(exists), IdOp(value), IdOp(neutral)}); + const uint32_t guarded = + em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(exists), IdOp(value), IdOp(neutral)}); acc = k == 0 ? guarded - : em.Emit(isAll ? spv::Op::OpBitwiseAnd : spv::Op::OpBitwiseOr, - uintTypeId, {IdOp(acc), IdOp(guarded)}); + : em.Emit(isAll ? spv::Op::OpBitwiseAnd : spv::Op::OpBitwiseOr, uintTypeId, + {IdOp(acc), IdOp(guarded)}); } barrier(); finish(em.Emit(spv::Op::OpINotEqual, boolTypeId, {IdOp(acc), IdOp(c0)})); @@ -1059,28 +1010,23 @@ namespace MobileGL { for (uint32_t k = 0; k < kWidth; ++k) { const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); - const uint32_t exists = em.Emit(spv::Op::OpULessThan, boolTypeId, - {IdOp(slot), IdOp(cTotal)}); + const uint32_t exists = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(slot), IdOp(cTotal)}); const uint32_t value = readSlot(slot); - const spv::Op compareOpcode = site.valueType.kind == ScalarKind::Float - ? spv::Op::OpFOrdEqual - : spv::Op::OpIEqual; + const spv::Op compareOpcode = + site.valueType.kind == ScalarKind::Float ? spv::Op::OpFOrdEqual : spv::Op::OpIEqual; uint32_t equal = 0; if (site.valueType.componentCount == 1u) { - equal = em.Emit(compareOpcode, boolTypeId, - {IdOp(value), IdOp(reference)}); + equal = em.Emit(compareOpcode, boolTypeId, {IdOp(value), IdOp(reference)}); } else { - const uint32_t comparisonTypeId = - boolVectorTypeId(site.valueType.componentCount); - const uint32_t componentsEqual = em.Emit( - compareOpcode, comparisonTypeId, {IdOp(value), IdOp(reference)}); + const uint32_t comparisonTypeId = boolVectorTypeId(site.valueType.componentCount); + const uint32_t componentsEqual = + em.Emit(compareOpcode, comparisonTypeId, {IdOp(value), IdOp(reference)}); equal = em.Emit(spv::Op::OpAll, boolTypeId, {IdOp(componentsEqual)}); } - const uint32_t guarded = em.Emit( - spv::Op::OpSelect, boolTypeId, - {IdOp(exists), IdOp(equal), IdOp(cTrue)}); - acc = em.Emit(spv::Op::OpLogicalAnd, boolTypeId, - {IdOp(acc), IdOp(guarded)}); + const uint32_t guarded = + em.Emit(spv::Op::OpSelect, boolTypeId, {IdOp(exists), IdOp(equal), IdOp(cTrue)}); + acc = em.Emit(spv::Op::OpLogicalAnd, boolTypeId, {IdOp(acc), IdOp(guarded)}); } barrier(); finish(acc); @@ -1103,46 +1049,39 @@ namespace MobileGL { break; case spv::Op::OpGroupNonUniformBroadcast: case spv::Op::OpGroupNonUniformShuffle: - sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(site.indexId), IdOp(c31)}); + sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(site.indexId), IdOp(c31)}); break; case spv::Op::OpGroupNonUniformShuffleXor: { - const uint32_t flipped = em.Emit( - spv::Op::OpBitwiseXor, uintTypeId, {IdOp(lane), IdOp(site.indexId)}); - sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(flipped), IdOp(c31)}); + const uint32_t flipped = + em.Emit(spv::Op::OpBitwiseXor, uintTypeId, {IdOp(lane), IdOp(site.indexId)}); + sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(flipped), IdOp(c31)}); break; } case spv::Op::OpGroupNonUniformShuffleUp: { const uint32_t shifted = em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(lane), IdOp(site.indexId)}); - sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(shifted), IdOp(c31)}); + sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c31)}); break; } case spv::Op::OpGroupNonUniformShuffleDown: { const uint32_t shifted = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(lane), IdOp(site.indexId)}); - sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(shifted), IdOp(c31)}); + sourceLane = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c31)}); break; } case spv::Op::OpGroupNonUniformQuadBroadcast: { - const uint32_t quadBase = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(lane), IdOp(uintConst(~3u & kLaneMask))}); + const uint32_t quadBase = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, + {IdOp(lane), IdOp(uintConst(~3u & kLaneMask))}); const uint32_t withinQuad = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(site.indexId), IdOp(uintConst(3u))}); - sourceLane = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(quadBase), IdOp(withinQuad)}); + em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(site.indexId), IdOp(uintConst(3u))}); + sourceLane = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(quadBase), IdOp(withinQuad)}); break; } case spv::Op::OpGroupNonUniformQuadSwap: { - const uint32_t partnerMask = uintConst( - site.quadDirection == 0u ? 1u : site.quadDirection == 1u ? 2u : 3u); - sourceLane = em.Emit(spv::Op::OpBitwiseXor, uintTypeId, - {IdOp(lane), IdOp(partnerMask)}); + const uint32_t partnerMask = uintConst(site.quadDirection == 0u ? 1u + : site.quadDirection == 1u ? 2u + : 3u); + sourceLane = em.Emit(spv::Op::OpBitwiseXor, uintTypeId, {IdOp(lane), IdOp(partnerMask)}); break; } default: @@ -1162,13 +1101,13 @@ namespace MobileGL { for (uint32_t k = 0; k < kWidth; ++k) { const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); - const uint32_t exists = em.Emit(spv::Op::OpULessThan, boolTypeId, - {IdOp(slot), IdOp(cTotal)}); + const uint32_t exists = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(slot), IdOp(cTotal)}); const uint32_t value = readSlot(slot); - const uint32_t guarded = em.Emit( - spv::Op::OpSelect, uintTypeId, {IdOp(exists), IdOp(value), IdOp(c0)}); - const uint32_t bit = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(guarded), IdOp(uintConst(k))}); + const uint32_t guarded = + em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(exists), IdOp(value), IdOp(c0)}); + const uint32_t bit = + em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(guarded), IdOp(uintConst(k))}); word = em.Emit(spv::Op::OpBitwiseOr, uintTypeId, {IdOp(word), IdOp(bit)}); } barrier(); @@ -1178,12 +1117,10 @@ namespace MobileGL { } case spv::Op::OpGroupNonUniformInverseBallot: { const uint32_t word = em.Emit(spv::Op::OpCompositeExtract, uintTypeId, - {IdOp(site.valueId), - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); - const uint32_t shifted = em.Emit(spv::Op::OpShiftRightLogical, uintTypeId, - {IdOp(word), IdOp(lane)}); - const uint32_t bit = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c1)}); + {IdOp(site.valueId), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); + const uint32_t shifted = + em.Emit(spv::Op::OpShiftRightLogical, uintTypeId, {IdOp(word), IdOp(lane)}); + const uint32_t bit = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c1)}); finish(em.Emit(spv::Op::OpINotEqual, boolTypeId, {IdOp(bit), IdOp(c0)})); break; } @@ -1197,34 +1134,29 @@ namespace MobileGL { {IdOp(site.valueId), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {2u}}}), em.Emit(spv::Op::OpCompositeExtract, uintTypeId, {IdOp(site.valueId), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {3u}}})}; - const uint32_t inFirstPair = em.Emit( - spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(uintConst(64u))}); - const uint32_t inWord0 = em.Emit( - spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(c32)}); - const uint32_t inWord2 = em.Emit( - spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(uintConst(96u))}); - const uint32_t firstPair = em.Emit( - spv::Op::OpSelect, uintTypeId, - {IdOp(inWord0), IdOp(words[0]), IdOp(words[1])}); - const uint32_t secondPair = em.Emit( - spv::Op::OpSelect, uintTypeId, - {IdOp(inWord2), IdOp(words[2]), IdOp(words[3])}); - const uint32_t word = em.Emit( - spv::Op::OpSelect, uintTypeId, - {IdOp(inFirstPair), IdOp(firstPair), IdOp(secondPair)}); - const uint32_t bitIndex = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(site.indexId), IdOp(c31)}); - const uint32_t shifted = em.Emit(spv::Op::OpShiftRightLogical, uintTypeId, - {IdOp(word), IdOp(bitIndex)}); - const uint32_t bit = - em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c1)}); + const uint32_t inFirstPair = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(uintConst(64u))}); + const uint32_t inWord0 = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(c32)}); + const uint32_t inWord2 = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(site.indexId), IdOp(uintConst(96u))}); + const uint32_t firstPair = + em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(inWord0), IdOp(words[0]), IdOp(words[1])}); + const uint32_t secondPair = + em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(inWord2), IdOp(words[2]), IdOp(words[3])}); + const uint32_t word = em.Emit(spv::Op::OpSelect, uintTypeId, + {IdOp(inFirstPair), IdOp(firstPair), IdOp(secondPair)}); + const uint32_t bitIndex = + em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(site.indexId), IdOp(c31)}); + const uint32_t shifted = + em.Emit(spv::Op::OpShiftRightLogical, uintTypeId, {IdOp(word), IdOp(bitIndex)}); + const uint32_t bit = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(shifted), IdOp(c1)}); finish(em.Emit(spv::Op::OpINotEqual, boolTypeId, {IdOp(bit), IdOp(c0)})); break; } case spv::Op::OpGroupNonUniformBallotBitCount: { const uint32_t word = em.Emit(spv::Op::OpCompositeExtract, uintTypeId, - {IdOp(site.valueId), - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); + {IdOp(site.valueId), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); uint32_t masked = 0; // Counted over the gl_SubgroupSize (32) bits of the x word - the value is // an arbitrary uvec4, and bits of lanes a partial subgroup lacks still @@ -1232,30 +1164,24 @@ namespace MobileGL { if (site.groupOperation == spv::GroupOperation::Reduce) { masked = word; } else { - const bool inclusive = - site.groupOperation == spv::GroupOperation::InclusiveScan; + const bool inclusive = site.groupOperation == spv::GroupOperation::InclusiveScan; uint32_t laneMask = 0; if (inclusive) { const uint32_t lanePlusOne = em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(lane), IdOp(c1)}); const uint32_t atTop = - em.Emit(spv::Op::OpUGreaterThanEqual, boolTypeId, - {IdOp(lanePlusOne), IdOp(c32)}); + em.Emit(spv::Op::OpUGreaterThanEqual, boolTypeId, {IdOp(lanePlusOne), IdOp(c32)}); const uint32_t shifted = - em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(c1), IdOp(lanePlusOne)}); - const uint32_t below = - em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(shifted), IdOp(c1)}); - laneMask = em.Emit(spv::Op::OpSelect, uintTypeId, - {IdOp(atTop), IdOp(cAllOnes), IdOp(below)}); - } else { - const uint32_t bit = em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, - {IdOp(c1), IdOp(lane)}); + em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(c1), IdOp(lanePlusOne)}); + const uint32_t below = em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(shifted), IdOp(c1)}); laneMask = - em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(bit), IdOp(c1)}); + em.Emit(spv::Op::OpSelect, uintTypeId, {IdOp(atTop), IdOp(cAllOnes), IdOp(below)}); + } else { + const uint32_t bit = + em.Emit(spv::Op::OpShiftLeftLogical, uintTypeId, {IdOp(c1), IdOp(lane)}); + laneMask = em.Emit(spv::Op::OpISub, uintTypeId, {IdOp(bit), IdOp(c1)}); } - masked = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(word), IdOp(laneMask)}); + masked = em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, {IdOp(word), IdOp(laneMask)}); } finish(em.Emit(spv::Op::OpBitCount, uintTypeId, {IdOp(masked)})); break; @@ -1263,13 +1189,11 @@ namespace MobileGL { case spv::Op::OpGroupNonUniformBallotFindLSB: case spv::Op::OpGroupNonUniformBallotFindMSB: { const uint32_t word = em.Emit(spv::Op::OpCompositeExtract, uintTypeId, - {IdOp(site.valueId), - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); + {IdOp(site.valueId), {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}}); // Over the gl_SubgroupSize (32) bits of the x word, like the bit count. const uint32_t masked = word; const uint32_t instNumber = - site.opcode == spv::Op::OpGroupNonUniformBallotFindLSB ? kGlslFindILsb - : kGlslFindUMsb; + site.opcode == spv::Op::OpGroupNonUniformBallotFindLSB ? kGlslFindILsb : kGlslFindUMsb; finish(extInst1(uintTypeId, instNumber, masked)); break; } @@ -1284,13 +1208,16 @@ namespace MobileGL { case spv::GroupOperation::Reduce: { uint32_t acc = readSlot(base); for (uint32_t k = 1; k < kWidth; ++k) { - const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(base), IdOp(uintConst(k))}); - const uint32_t exists = em.Emit(spv::Op::OpULessThan, boolTypeId, - {IdOp(slot), IdOp(cTotal)}); + const uint32_t slot = + em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); + const uint32_t exists = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(slot), IdOp(cTotal)}); const uint32_t value = readSlot(slot); const uint32_t combined = combine(acc, value); - if (combined == 0u) { ok = false; break; } + if (combined == 0u) { + ok = false; + break; + } acc = guardedSelect(exists, combined, acc); } result = acc; @@ -1302,13 +1229,15 @@ namespace MobileGL { uint32_t acc = readSlot(base); for (uint32_t k = 1; k < kWidth; ++k) { const uint32_t included = - em.Emit(spv::Op::OpULessThanEqual, boolTypeId, - {IdOp(uintConst(k)), IdOp(lane)}); - const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(base), IdOp(uintConst(k))}); + em.Emit(spv::Op::OpULessThanEqual, boolTypeId, {IdOp(uintConst(k)), IdOp(lane)}); + const uint32_t slot = + em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); const uint32_t value = readSlot(slot); const uint32_t combined = combine(acc, value); - if (combined == 0u) { ok = false; break; } + if (combined == 0u) { + ok = false; + break; + } acc = guardedSelect(included, combined, acc); } result = acc; @@ -1316,37 +1245,45 @@ namespace MobileGL { } case spv::GroupOperation::ExclusiveScan: { uint32_t acc = identity(); - if (acc == 0u) { ok = false; break; } + if (acc == 0u) { + ok = false; + break; + } for (uint32_t k = 0; k < kWidth; ++k) { - const uint32_t included = em.Emit( - spv::Op::OpULessThan, boolTypeId, {IdOp(uintConst(k)), IdOp(lane)}); - const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(base), IdOp(uintConst(k))}); + const uint32_t included = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(uintConst(k)), IdOp(lane)}); + const uint32_t slot = + em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(uintConst(k))}); const uint32_t value = readSlot(slot); const uint32_t combined = combine(acc, value); - if (combined == 0u) { ok = false; break; } + if (combined == 0u) { + ok = false; + break; + } acc = guardedSelect(included, combined, acc); } result = acc; break; } case spv::GroupOperation::ClusteredReduce: { - const uint32_t cluster = - site.clusterSize >= kWidth ? kWidth : site.clusterSize; - const uint32_t clusterBase = em.Emit( - spv::Op::OpBitwiseAnd, uintTypeId, - {IdOp(lane), IdOp(uintConst(~(cluster - 1u) & kLaneMask))}); - const uint32_t firstSlot = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(base), IdOp(clusterBase)}); + const uint32_t cluster = site.clusterSize >= kWidth ? kWidth : site.clusterSize; + const uint32_t clusterBase = + em.Emit(spv::Op::OpBitwiseAnd, uintTypeId, + {IdOp(lane), IdOp(uintConst(~(cluster - 1u) & kLaneMask))}); + const uint32_t firstSlot = + em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(base), IdOp(clusterBase)}); uint32_t acc = readSlot(firstSlot); for (uint32_t k = 1; k < cluster; ++k) { - const uint32_t slot = em.Emit(spv::Op::OpIAdd, uintTypeId, - {IdOp(firstSlot), IdOp(uintConst(k))}); - const uint32_t exists = em.Emit(spv::Op::OpULessThan, boolTypeId, - {IdOp(slot), IdOp(cTotal)}); + const uint32_t slot = + em.Emit(spv::Op::OpIAdd, uintTypeId, {IdOp(firstSlot), IdOp(uintConst(k))}); + const uint32_t exists = + em.Emit(spv::Op::OpULessThan, boolTypeId, {IdOp(slot), IdOp(cTotal)}); const uint32_t value = readSlot(slot); const uint32_t combined = combine(acc, value); - if (combined == 0u) { ok = false; break; } + if (combined == 0u) { + ok = false; + break; + } acc = guardedSelect(exists, combined, acc); } result = acc; @@ -1372,8 +1309,7 @@ namespace MobileGL { // Widen subgroup-scoped barriers to the workgroup scope the emulation // synchronizes at anyway; strictly stronger, so always safe. - const uint32_t workgroupScopeId = - uintConst(static_cast(spv::Scope::Workgroup)); + const uint32_t workgroupScopeId = uintConst(static_cast(spv::Scope::Workgroup)); if (workgroupScopeId == 0u) return Status::Failure; for (const BarrierSite& site : barriers) { if (site.inst->opcode() == spv::Op::OpControlBarrier) { @@ -1424,8 +1360,7 @@ namespace MobileGL { spv::Capability::GroupNonUniformPartitionedNV}; std::vector deadCapabilities; for (Instruction& capability : irContext->module()->capabilities()) { - const auto value = - static_cast(capability.GetSingleWordInOperand(0)); + const auto value = static_cast(capability.GetSingleWordInOperand(0)); for (const spv::Capability candidate : kSubgroupCapabilities) { if (value == candidate) { deadCapabilities.push_back(&capability); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.h index cb3b0ad7..c0f2e182 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EmulateSubgroupsPass.h @@ -64,14 +64,13 @@ namespace MobileGL { class EmulateSubgroupsPass : public spvtools::opt::Pass { public: EmulateSubgroupsPass(Uint32 maxWorkgroupScratchBytes, bool atomicScratchReads) - : m_maxWorkgroupScratchBytes(maxWorkgroupScratchBytes), - m_atomicScratchReads(atomicScratchReads) {} + : m_maxWorkgroupScratchBytes(maxWorkgroupScratchBytes), m_atomicScratchReads(atomicScratchReads) {} const char* name() const override { return "emulate-subgroups"; } Status Process() override; - static spvtools::Optimizer::PassToken CreateEmulateSubgroupsPass( - Uint32 maxWorkgroupScratchBytes, bool atomicScratchReads = false); + static spvtools::Optimizer::PassToken CreateEmulateSubgroupsPass(Uint32 maxWorkgroupScratchBytes, + bool atomicScratchReads = false); private: Uint32 m_maxWorkgroupScratchBytes; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp index dc4d8181..5544e5c9 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.cpp @@ -87,8 +87,7 @@ namespace MobileGL { type = defUseMgr->GetDef(type->GetSingleWordInOperand(0)); if (type == nullptr) return 0u; } - if (type->opcode() != spv::Op::OpTypeFloat || - type->GetSingleWordInOperand(0) != 32u) { + if (type->opcode() != spv::Op::OpTypeFloat || type->GetSingleWordInOperand(0) != 32u) { return 0u; } return components; @@ -105,8 +104,8 @@ namespace MobileGL { // budget check matters. Returns false for anything not modelled here, // which the caller answers by declining to grow at all rather than by // certifying growth against a total it knows is an underestimate. - bool WorkgroupTypeLayout(IRContext* context, uint32_t typeId, uint32_t* size, - uint32_t* alignment, uint32_t depth = 0u) { + bool WorkgroupTypeLayout(IRContext* context, uint32_t typeId, uint32_t* size, uint32_t* alignment, + uint32_t depth = 0u) { if (depth > 8u) return false; auto* defUseMgr = context->get_def_use_mgr(); const Instruction* type = defUseMgr->GetDef(typeId); @@ -127,8 +126,8 @@ namespace MobileGL { case spv::Op::OpTypeVector: { uint32_t componentSize = 0u; uint32_t componentAlignment = 0u; - if (!WorkgroupTypeLayout(context, type->GetSingleWordInOperand(0), - &componentSize, &componentAlignment, depth + 1u)) { + if (!WorkgroupTypeLayout(context, type->GetSingleWordInOperand(0), &componentSize, + &componentAlignment, depth + 1u)) { return false; } const uint32_t components = type->GetSingleWordInOperand(1); @@ -150,10 +149,9 @@ namespace MobileGL { if (type->opcode() == spv::Op::OpTypeMatrix) { count = type->GetSingleWordInOperand(1); } else { - const Instruction* length = - defUseMgr->GetDef(type->GetSingleWordInOperand(1)); + const Instruction* length = defUseMgr->GetDef(type->GetSingleWordInOperand(1)); if (length == nullptr || length->opcode() != spv::Op::OpConstant) { - return false; // spec-constant length: not sizeable here + return false; // spec-constant length: not sizeable here } count = length->GetSingleWordInOperand(0); } @@ -167,8 +165,8 @@ namespace MobileGL { for (uint32_t i = 0; i < type->NumInOperands(); ++i) { uint32_t memberSize = 0u; uint32_t memberAlignment = 0u; - if (!WorkgroupTypeLayout(context, type->GetSingleWordInOperand(i), - &memberSize, &memberAlignment, depth + 1u)) { + if (!WorkgroupTypeLayout(context, type->GetSingleWordInOperand(i), &memberSize, + &memberAlignment, depth + 1u)) { return false; } offset = RoundUp(offset, memberAlignment) + memberSize; @@ -222,14 +220,12 @@ namespace MobileGL { }; uint32_t localSize[3] = {0, 0, 0}; bool haveLocalSize = false; - if (Instruction* workgroupSize = - FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize)) { + if (Instruction* workgroupSize = FindBuiltinDefinition(irContext, spv::BuiltIn::WorkgroupSize)) { if (workgroupSize->opcode() == spv::Op::OpConstantComposite && workgroupSize->NumInOperands() == 3) { - haveLocalSize = - resolveUintConstant(workgroupSize->GetSingleWordInOperand(0), &localSize[0]) && - resolveUintConstant(workgroupSize->GetSingleWordInOperand(1), &localSize[1]) && - resolveUintConstant(workgroupSize->GetSingleWordInOperand(2), &localSize[2]); + haveLocalSize = resolveUintConstant(workgroupSize->GetSingleWordInOperand(0), &localSize[0]) && + resolveUintConstant(workgroupSize->GetSingleWordInOperand(1), &localSize[1]) && + resolveUintConstant(workgroupSize->GetSingleWordInOperand(2), &localSize[2]); } } if (!haveLocalSize) { @@ -248,13 +244,12 @@ namespace MobileGL { if (!haveLocalSize || localSize[0] == 0u || localSize[1] == 0u || localSize[2] == 0u) { return Status::SuccessWithoutChange; } - const uint64_t totalInvocations = - static_cast(localSize[0]) * localSize[1] * localSize[2]; + const uint64_t totalInvocations = static_cast(localSize[0]) * localSize[1] * localSize[2]; if (totalInvocations == 0u || totalInvocations > (1u << 20)) { return Status::SuccessWithoutChange; } - const uint32_t requiredLength = static_cast( - (totalInvocations + m_nativeSubgroupSize - 1u) / m_nativeSubgroupSize); + const uint32_t requiredLength = + static_cast((totalInvocations + m_nativeSubgroupSize - 1u) / m_nativeSubgroupSize); // Fingerprint 2: a subgroup scan over a 32-bit float value - the pack's // prefix-sum reduction, and the reason its scratch is indexed per subgroup. @@ -268,8 +263,7 @@ namespace MobileGL { continue; } if (inst.NumInOperands() < 2) continue; - if (!IsScanOrReduce(static_cast( - inst.GetSingleWordInOperand(1)))) { + if (!IsScanOrReduce(static_cast(inst.GetSingleWordInOperand(1)))) { continue; } if (Float32ComponentCount(irContext, inst.type_id()) != 0u) { @@ -283,10 +277,8 @@ namespace MobileGL { } // gl_SubgroupID, whose value range the pack's scratch size bakes in. - const Instruction* subgroupIdVariable = - FindBuiltinDefinition(irContext, spv::BuiltIn::SubgroupId); - if (subgroupIdVariable == nullptr || - subgroupIdVariable->opcode() != spv::Op::OpVariable) { + const Instruction* subgroupIdVariable = FindBuiltinDefinition(irContext, spv::BuiltIn::SubgroupId); + if (subgroupIdVariable == nullptr || subgroupIdVariable->opcode() != spv::Op::OpVariable) { return Status::SuccessWithoutChange; } const uint32_t subgroupIdVariableId = subgroupIdVariable->result_id(); @@ -316,8 +308,8 @@ namespace MobileGL { if (target == nullptr || target->opcode() != spv::Op::OpVariable) { continue; } - const auto storageClass = static_cast( - target->GetSingleWordInOperand(0)); + const auto storageClass = + static_cast(target->GetSingleWordInOperand(0)); if (storageClass != spv::StorageClass::Function && storageClass != spv::StorageClass::Private) { continue; @@ -340,22 +332,20 @@ namespace MobileGL { for (auto& function : *irContext->module()) { for (auto& block : function) { for (auto& inst : block) { - if (inst.result_id() == 0 || - subgroupIdValues.count(inst.result_id())) { + if (inst.result_id() == 0 || subgroupIdValues.count(inst.result_id())) { continue; } bool isSubgroupId = false; switch (inst.opcode()) { case spv::Op::OpLoad: { const uint32_t pointerId = inst.GetSingleWordInOperand(0); - isSubgroupId = pointerId == subgroupIdVariableId || - subgroupIdVariables.count(pointerId) != 0u; + isSubgroupId = + pointerId == subgroupIdVariableId || subgroupIdVariables.count(pointerId) != 0u; break; } case spv::Op::OpCopyObject: case spv::Op::OpBitcast: - isSubgroupId = - subgroupIdValues.count(inst.GetSingleWordInOperand(0)) != 0u; + isSubgroupId = subgroupIdValues.count(inst.GetSingleWordInOperand(0)) != 0u; break; default: break; @@ -384,12 +374,9 @@ namespace MobileGL { } if (inst.NumInOperands() < 2) continue; if (!subgroupIdValues.count(inst.GetSingleWordInOperand(1))) continue; - Instruction* baseVariable = - defUseMgr->GetDef(inst.GetSingleWordInOperand(0)); - if (baseVariable == nullptr || - baseVariable->opcode() != spv::Op::OpVariable || - static_cast( - baseVariable->GetSingleWordInOperand(0)) != + Instruction* baseVariable = defUseMgr->GetDef(inst.GetSingleWordInOperand(0)); + if (baseVariable == nullptr || baseVariable->opcode() != spv::Op::OpVariable || + static_cast(baseVariable->GetSingleWordInOperand(0)) != spv::StorageClass::Workgroup) { continue; } @@ -442,16 +429,14 @@ namespace MobileGL { if (pointerType == nullptr || pointerType->opcode() != spv::Op::OpTypePointer) { continue; } - const Instruction* arrayType = - defUseMgr->GetDef(pointerType->GetSingleWordInOperand(1)); + const Instruction* arrayType = defUseMgr->GetDef(pointerType->GetSingleWordInOperand(1)); if (arrayType == nullptr || arrayType->opcode() != spv::Op::OpTypeArray) { continue; } const uint32_t elementTypeId = arrayType->GetSingleWordInOperand(0); const uint32_t components = Float32ComponentCount(irContext, elementTypeId); if (components == 0u) continue; - const Instruction* lengthConstant = - defUseMgr->GetDef(arrayType->GetSingleWordInOperand(1)); + const Instruction* lengthConstant = defUseMgr->GetDef(arrayType->GetSingleWordInOperand(1)); if (lengthConstant == nullptr || lengthConstant->opcode() != spv::Op::OpConstant) { continue; } @@ -486,10 +471,9 @@ namespace MobileGL { const Instruction* pointerType = defUseMgr->GetDef(global.type_id()); uint32_t bytes = 0u; uint32_t alignment = 0u; - if (pointerType == nullptr || - pointerType->opcode() != spv::Op::OpTypePointer || - !WorkgroupTypeLayout(irContext, pointerType->GetSingleWordInOperand(1), - &bytes, &alignment)) { + if (pointerType == nullptr || pointerType->opcode() != spv::Op::OpTypePointer || + !WorkgroupTypeLayout(irContext, pointerType->GetSingleWordInOperand(1), &bytes, + &alignment)) { sawUnsizeable = true; break; } @@ -501,11 +485,11 @@ namespace MobileGL { if (sawUnsizeable) { return Status::SuccessWithoutChange; } - for (const Growth& growth : growths) declaredBytes += growth.addedBytes; + for (const Growth& growth : growths) + declaredBytes += growth.addedBytes; - const uint32_t deviceBudget = m_maxWorkgroupScratchBytes != 0u - ? m_maxWorkgroupScratchBytes - : kMinimumSharedMemoryBytes; + const uint32_t deviceBudget = + m_maxWorkgroupScratchBytes != 0u ? m_maxWorkgroupScratchBytes : kMinimumSharedMemoryBytes; if (declaredBytes > deviceBudget) { return Status::SuccessWithoutChange; } @@ -524,20 +508,17 @@ namespace MobileGL { const uint32_t newLengthId = irContext->TakeNextId(); variable->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpConstant, growth.lengthTypeId, newLengthId, - Instruction::OperandList{{SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, - {requiredLength}}})); + Instruction::OperandList{{SPV_OPERAND_TYPE_TYPED_LITERAL_NUMBER, {requiredLength}}})); const uint32_t newArrayTypeId = irContext->TakeNextId(); variable->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpTypeArray, 0, newArrayTypeId, - Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {growth.elementTypeId}}, - {SPV_OPERAND_TYPE_ID, {newLengthId}}})); + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {growth.elementTypeId}}, + {SPV_OPERAND_TYPE_ID, {newLengthId}}})); const uint32_t newPointerTypeId = irContext->TakeNextId(); variable->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpTypePointer, 0, newPointerTypeId, Instruction::OperandList{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Workgroup)}}, + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Workgroup)}}, {SPV_OPERAND_TYPE_ID, {newArrayTypeId}}})); variable->SetResultType(newPointerTypeId); @@ -547,11 +528,10 @@ namespace MobileGL { return Status::SuccessWithChange; } - spvtools::Optimizer::PassToken - FixIterationRPSubgroupScratchPass::CreateFixIterationRPSubgroupScratchPass( + spvtools::Optimizer::PassToken FixIterationRPSubgroupScratchPass::CreateFixIterationRPSubgroupScratchPass( const Uint32 nativeSubgroupSize, const Uint32 maxWorkgroupScratchBytes) { - return spvtools::Optimizer::PassToken(MakeUnique( - nativeSubgroupSize, maxWorkgroupScratchBytes)); + return spvtools::Optimizer::PassToken( + MakeUnique(nativeSubgroupSize, maxWorkgroupScratchBytes)); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.h index 19857bac..261f5573 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FixIterationRPSubgroupScratchPass.h @@ -53,10 +53,8 @@ namespace MobileGL { // maxWorkgroupScratchBytes; 0 falls back to the 16384-byte Vulkan minimum. class FixIterationRPSubgroupScratchPass : public spvtools::opt::Pass { public: - FixIterationRPSubgroupScratchPass(Uint32 nativeSubgroupSize, - Uint32 maxWorkgroupScratchBytes) - : m_nativeSubgroupSize(nativeSubgroupSize), - m_maxWorkgroupScratchBytes(maxWorkgroupScratchBytes) {} + FixIterationRPSubgroupScratchPass(Uint32 nativeSubgroupSize, Uint32 maxWorkgroupScratchBytes) + : m_nativeSubgroupSize(nativeSubgroupSize), m_maxWorkgroupScratchBytes(maxWorkgroupScratchBytes) {} const char* name() const override { return "fix-iterationrp-subgroup-scratch"; } Status Process() override; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp index e71f3a99..202a5b5d 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenAtomicCounterBlockPass.cpp @@ -77,8 +77,7 @@ namespace MobileGL { if (debug.opcode() != spv::Op::OpName || debug.NumInOperands() < 2) continue; if (debug.GetSingleWordInOperand(0) != id) continue; const std::string name = debug.GetInOperand(1).AsString(); - return name.compare(0, std::strlen(kAtomicCounterBlockPrefix), - kAtomicCounterBlockPrefix) == 0; + return name.compare(0, std::strlen(kAtomicCounterBlockPrefix), kAtomicCounterBlockPrefix) == 0; } return false; } @@ -86,10 +85,8 @@ namespace MobileGL { // The literal of the first OpMemberDecorate , or none. bool TryGetMemberDecorationLiteral(IRContext* context, uint32_t structId, uint32_t member, spv::Decoration kind, uint32_t* literal) { - for (Instruction* decoration : - context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || - decoration->NumInOperands() < 4 || + for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { + if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->NumInOperands() < 4 || decoration->GetSingleWordInOperand(1) != member || static_cast(decoration->GetSingleWordInOperand(2)) != kind) { continue; @@ -100,8 +97,7 @@ namespace MobileGL { return false; } - bool TryGetDecorationLiteral(IRContext* context, uint32_t id, spv::Decoration kind, - uint32_t* literal) { + bool TryGetDecorationLiteral(IRContext* context, uint32_t id, spv::Decoration kind, uint32_t* literal) { for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(id, false)) { if (decoration->opcode() != spv::Op::OpDecorate || decoration->NumInOperands() < 3 || static_cast(decoration->GetSingleWordInOperand(1)) != kind) { @@ -114,15 +110,13 @@ namespace MobileGL { } bool IsUint32Type(const Instruction* type) { - return type != nullptr && type->opcode() == spv::Op::OpTypeInt && - type->NumInOperands() >= 2 && type->GetSingleWordInOperand(0) == 32u && - type->GetSingleWordInOperand(1) == 0u; + return type != nullptr && type->opcode() == spv::Op::OpTypeInt && type->NumInOperands() >= 2 && + type->GetSingleWordInOperand(0) == 32u && type->GetSingleWordInOperand(1) == 0u; } // The member's shape as this pass needs it, or false when it is one the pass // cannot re-index. - bool DescribeMember(IRContext* context, uint32_t memberTypeId, uint32_t* uintTypeId, - MemberPlan* plan) { + bool DescribeMember(IRContext* context, uint32_t memberTypeId, uint32_t* uintTypeId, MemberPlan* plan) { auto* defUseMgr = context->get_def_use_mgr(); Instruction* memberType = defUseMgr->GetDef(memberTypeId); if (memberType == nullptr) return false; @@ -208,8 +202,7 @@ namespace MobileGL { return; case spv::Op::OpAccessChain: case spv::Op::OpInBoundsAccessChain: - if (user->NumInOperands() >= 2 && - user->GetSingleWordInOperand(0) == variableId) { + if (user->NumInOperands() >= 2 && user->GetSingleWordInOperand(0) == variableId) { chainsByStruct[structId].push_back(user); return; } @@ -231,8 +224,8 @@ namespace MobileGL { bool expressible = true; for (uint32_t member = 0; member < memberCount; ++member) { uint32_t byteOffset = 0; - if (!TryGetMemberDecorationLiteral(context, structId, member, - spv::Decoration::Offset, &byteOffset) || + if (!TryGetMemberDecorationLiteral(context, structId, member, spv::Decoration::Offset, + &byteOffset) || byteOffset % kCounterBytes != 0u) { expressible = false; break; @@ -272,8 +265,7 @@ namespace MobileGL { if (chains != chainsByStruct.end()) { for (Instruction* chain : chains->second) { const spvtools::opt::analysis::Constant* memberIndex = - context->get_constant_mgr()->FindDeclaredConstant( - chain->GetSingleWordInOperand(1)); + context->get_constant_mgr()->FindDeclaredConstant(chain->GetSingleWordInOperand(1)); if (memberIndex == nullptr || memberIndex->AsIntConstant() == nullptr) { expressible = false; break; @@ -297,8 +289,7 @@ namespace MobileGL { // The id of |value| as a constant of the same integer type as |likeId|. uint32_t ConstantLike(IRContext* context, uint32_t likeId, uint32_t value) { Instruction* likeDef = context->get_def_use_mgr()->GetDef(likeId); - const spvtools::opt::analysis::Type* type = - context->get_type_mgr()->GetType(likeDef->type_id()); + const spvtools::opt::analysis::Type* type = context->get_type_mgr()->GetType(likeDef->type_id()); const spvtools::opt::analysis::Constant* constant = context->get_constant_mgr()->GetConstant(type, {value}); return context->get_constant_mgr()->GetDefiningInstruction(constant)->result_id(); @@ -350,8 +341,8 @@ namespace MobileGL { // the constant's old position and therefore after its new one too, so no use is // left dangling - moving a definition earlier in the types/constants section // cannot invalidate anything. Ordering is all that changes; def-use is untouched. - uint32_t CreateCounterArrayTypeBefore(IRContext* context, Instruction* structType, - uint32_t uintTypeId, uint32_t length) { + uint32_t CreateCounterArrayTypeBefore(IRContext* context, Instruction* structType, uint32_t uintTypeId, + uint32_t length) { auto* constantMgr = context->get_constant_mgr(); const spvtools::opt::analysis::Type* uintType = context->get_type_mgr()->GetType(uintTypeId); if (uintType == nullptr) return 0; @@ -390,16 +381,13 @@ namespace MobileGL { // rather than where they sit - are deliberately kept. void StripMemberAnnotations(IRContext* context, uint32_t structId) { std::vector doomed; - for (Instruction* decoration : - context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || - decoration->NumInOperands() < 3) { + for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { + if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->NumInOperands() < 3) { continue; } const bool pastMemberZero = decoration->GetSingleWordInOperand(1) != 0u; - const bool isOffset = - static_cast(decoration->GetSingleWordInOperand(2)) == - spv::Decoration::Offset; + const bool isOffset = static_cast(decoration->GetSingleWordInOperand(2)) == + spv::Decoration::Offset; if (pastMemberZero || isOffset) doomed.push_back(decoration); } for (Instruction& debug : context->module()->debugs2()) { @@ -408,7 +396,8 @@ namespace MobileGL { if (debug.GetSingleWordInOperand(1) == 0u) continue; // member 0 keeps its name doomed.push_back(&debug); } - for (Instruction* inst : doomed) context->KillInst(inst); + for (Instruction* inst : doomed) + context->KillInst(inst); } } // namespace @@ -417,8 +406,7 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); if (!context) { // Unparseable here means unusable downstream too; let the ordinary transpile @@ -438,8 +426,8 @@ namespace MobileGL { bool modified = false; for (const BlockPlan& plan : plans) { const uint32_t structId = plan.structType->result_id(); - const uint32_t arrayTypeId = CreateCounterArrayTypeBefore( - irContext, plan.structType, plan.uintTypeId, plan.totalElements); + const uint32_t arrayTypeId = + CreateCounterArrayTypeBefore(irContext, plan.structType, plan.uintTypeId, plan.totalElements); if (arrayTypeId == 0) { MGLOG_D("[spirv] atomic-counter block %%%u: no legal place for the flattened array " "type; leaving the block alone", @@ -461,9 +449,8 @@ namespace MobileGL { operands.push_back(chain->GetInOperand(0)); operands.push_back({SPV_OPERAND_TYPE_ID, {ConstantLike(irContext, memberIndexId, 0u)}}); if (!memberPlan.isArray) { - operands.push_back( - {SPV_OPERAND_TYPE_ID, - {ConstantLike(irContext, memberIndexId, memberPlan.elementOffset)}}); + operands.push_back({SPV_OPERAND_TYPE_ID, + {ConstantLike(irContext, memberIndexId, memberPlan.elementOffset)}}); } else { const uint32_t elementId = chain->GetSingleWordInOperand(2); uint32_t shiftedId = elementId; @@ -475,16 +462,15 @@ namespace MobileGL { elementConstant->AsIntConstant()->GetU32BitValue() + memberPlan.elementOffset); } else { - InstructionBuilder builder( - irContext, chain, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, chain, + IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping); Instruction* elementDef = irContext->get_def_use_mgr()->GetDef(elementId); - shiftedId = builder - .AddBinaryOp(elementDef->type_id(), spv::Op::OpIAdd, - elementId, - ConstantLike(irContext, elementId, - memberPlan.elementOffset)) - ->result_id(); + shiftedId = + builder + .AddBinaryOp(elementDef->type_id(), spv::Op::OpIAdd, elementId, + ConstantLike(irContext, elementId, memberPlan.elementOffset)) + ->result_id(); } } operands.push_back({SPV_OPERAND_TYPE_ID, {shiftedId}}); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenFloat64StorageBlockPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenFloat64StorageBlockPass.cpp index 7f920ccf..bd218889 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenFloat64StorageBlockPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenFloat64StorageBlockPass.cpp @@ -52,9 +52,9 @@ namespace MobileGL { // member would turn a two-instruction copy into a 32k-instruction one. constexpr uint32_t kMaxLeavesPerAccess = 1024u; // The analyses every builder in this pass keeps current as it inserts. - constexpr IRContext::Analysis kPreservedAnalyses = static_cast( - static_cast(IRContext::kAnalysisDefUse) | - static_cast(IRContext::kAnalysisInstrToBlockMapping)); + constexpr IRContext::Analysis kPreservedAnalyses = + static_cast(static_cast(IRContext::kAnalysisDefUse) | + static_cast(IRContext::kAnalysisInstrToBlockMapping)); // How a type sits in memory, as the ENCLOSING struct member described it. // MatrixStride and RowMajor are member decorations rather than type decorations, @@ -99,8 +99,8 @@ namespace MobileGL { }; bool IsDoubleType(const Instruction* type) { - return type != nullptr && type->opcode() == spv::Op::OpTypeFloat && - type->NumInOperands() >= 1 && type->GetSingleWordInOperand(0) == 64u; + return type != nullptr && type->opcode() == spv::Op::OpTypeFloat && type->NumInOperands() >= 1 && + type->GetSingleWordInOperand(0) == 64u; } // Byte size of a scalar this pass can carry, or 0 for one it cannot. @@ -115,8 +115,7 @@ namespace MobileGL { return 0; } - bool TryGetDecorationLiteral(IRContext* context, uint32_t id, spv::Decoration kind, - uint32_t* literal) { + bool TryGetDecorationLiteral(IRContext* context, uint32_t id, spv::Decoration kind, uint32_t* literal) { for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(id, false)) { if (decoration->opcode() != spv::Op::OpDecorate || decoration->NumInOperands() < 3 || static_cast(decoration->GetSingleWordInOperand(1)) != kind) { @@ -130,10 +129,8 @@ namespace MobileGL { bool TryGetMemberDecorationLiteral(IRContext* context, uint32_t structId, uint32_t member, spv::Decoration kind, uint32_t* literal) { - for (Instruction* decoration : - context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || - decoration->NumInOperands() < 4 || + for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { + if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->NumInOperands() < 4 || decoration->GetSingleWordInOperand(1) != member || static_cast(decoration->GetSingleWordInOperand(2)) != kind) { continue; @@ -144,12 +141,9 @@ namespace MobileGL { return false; } - bool HasMemberDecoration(IRContext* context, uint32_t structId, uint32_t member, - spv::Decoration kind) { - for (Instruction* decoration : - context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || - decoration->NumInOperands() < 3 || + bool HasMemberDecoration(IRContext* context, uint32_t structId, uint32_t member, spv::Decoration kind) { + for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { + if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->NumInOperands() < 3 || decoration->GetSingleWordInOperand(1) != member) { continue; } @@ -182,8 +176,8 @@ namespace MobileGL { spv::Decoration::MatrixStride, &stride)) { cursor.matrixStride = stride; } - cursor.rowMajor = HasMemberDecoration(context, structType->result_id(), member, - spv::Decoration::RowMajor); + cursor.rowMajor = + HasMemberDecoration(context, structType->result_id(), member, spv::Decoration::RowMajor); return cursor; } @@ -199,8 +193,8 @@ namespace MobileGL { case spv::Op::OpTypeFloat: return ScalarByteSize(type); case spv::Op::OpTypeVector: { - const uint32_t component = ScalarByteSize( - context->get_def_use_mgr()->GetDef(type->GetSingleWordInOperand(0))); + const uint32_t component = + ScalarByteSize(context->get_def_use_mgr()->GetDef(type->GetSingleWordInOperand(0))); if (component == 0) return 0; return component * type->GetSingleWordInOperand(1); } @@ -210,8 +204,7 @@ namespace MobileGL { } case spv::Op::OpTypeArray: { uint32_t stride = 0; - if (!TryGetDecorationLiteral(context, cursor.typeId, spv::Decoration::ArrayStride, - &stride) || + if (!TryGetDecorationLiteral(context, cursor.typeId, spv::Decoration::ArrayStride, &stride) || stride == 0) { return 0; } @@ -226,8 +219,8 @@ namespace MobileGL { uint64_t end = 0; for (uint32_t member = 0; member < type->NumInOperands(); ++member) { uint32_t offset = 0; - if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, - spv::Decoration::Offset, &offset)) { + if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, spv::Decoration::Offset, + &offset)) { return 0; } const uint32_t size = LaidOutByteSize(context, MemberCursor(context, type, member)); @@ -266,22 +259,19 @@ namespace MobileGL { case spv::Op::OpTypeVector: { TypeCursor component; component.typeId = type->GetSingleWordInOperand(0); - const uint32_t repeats = - leafCount == nullptr ? 1u : type->GetSingleWordInOperand(1); + const uint32_t repeats = leafCount == nullptr ? 1u : type->GetSingleWordInOperand(1); for (uint32_t i = 0; i < repeats; ++i) { if (!CanDecompose(context, component, leafCount)) return false; } return true; } case spv::Op::OpTypeMatrix: { - if (cursor.matrixStride == 0 || cursor.rowMajor || - cursor.matrixStride % kWordBytes != 0) { + if (cursor.matrixStride == 0 || cursor.rowMajor || cursor.matrixStride % kWordBytes != 0) { return false; } TypeCursor column; column.typeId = type->GetSingleWordInOperand(0); - const uint32_t repeats = - leafCount == nullptr ? 1u : type->GetSingleWordInOperand(1); + const uint32_t repeats = leafCount == nullptr ? 1u : type->GetSingleWordInOperand(1); for (uint32_t i = 0; i < repeats; ++i) { if (!CanDecompose(context, column, leafCount)) return false; } @@ -289,8 +279,7 @@ namespace MobileGL { } case spv::Op::OpTypeArray: { uint32_t stride = 0; - if (!TryGetDecorationLiteral(context, cursor.typeId, spv::Decoration::ArrayStride, - &stride) || + if (!TryGetDecorationLiteral(context, cursor.typeId, spv::Decoration::ArrayStride, &stride) || stride == 0 || stride % kWordBytes != 0) { return false; } @@ -311,8 +300,8 @@ namespace MobileGL { case spv::Op::OpTypeStruct: { for (uint32_t member = 0; member < type->NumInOperands(); ++member) { uint32_t offset = 0; - if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, - spv::Decoration::Offset, &offset) || + if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, spv::Decoration::Offset, + &offset) || offset % kWordBytes != 0) { return false; } @@ -334,8 +323,8 @@ namespace MobileGL { // stride alongside. A runtime array anywhere else, one without a stride the // words can step by, or one whose element the rewrite could not take apart is a // shape this pass does not describe, and so is a bounded block it cannot size. - bool MeasureBlock(IRContext* context, const Instruction* structType, uint32_t* bytes, - bool* openEnded, uint32_t* tailStrideBytes) { + bool MeasureBlock(IRContext* context, const Instruction* structType, uint32_t* bytes, bool* openEnded, + uint32_t* tailStrideBytes) { *bytes = 0; *openEnded = false; *tailStrideBytes = 0; @@ -384,8 +373,7 @@ namespace MobileGL { return true; } - bool TypeContainsFloat64(IRContext* context, uint32_t typeId, - std::unordered_set& visiting) { + bool TypeContainsFloat64(IRContext* context, uint32_t typeId, std::unordered_set& visiting) { const Instruction* type = context->get_def_use_mgr()->GetDef(typeId); if (type == nullptr || !visiting.insert(typeId).second) return false; switch (type->opcode()) { @@ -420,8 +408,7 @@ namespace MobileGL { // Walks one access chain against the block's type tree, recording the byte offset // it names as a constant part plus a list of (index, stride) terms. False for any // shape the rewrite cannot address exactly. - bool PlanChain(IRContext* context, Instruction* chain, const TypeCursor& blockCursor, - ChainPlan* plan) { + bool PlanChain(IRContext* context, Instruction* chain, const TypeCursor& blockCursor, ChainPlan* plan) { TypeCursor cursor = blockCursor; uint64_t constantBytes = 0; for (uint32_t operand = 1; operand < chain->NumInOperands(); ++operand) { @@ -432,13 +419,12 @@ namespace MobileGL { uint32_t stride = 0; if (type->opcode() == spv::Op::OpTypeStruct) { uint32_t member = 0; - if (!TryGetConstantIndex(context, indexId, &member) || - member >= type->NumInOperands()) { + if (!TryGetConstantIndex(context, indexId, &member) || member >= type->NumInOperands()) { return false; } uint32_t offset = 0; - if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, - spv::Decoration::Offset, &offset) || + if (!TryGetMemberDecorationLiteral(context, cursor.typeId, member, spv::Decoration::Offset, + &offset) || offset % kWordBytes != 0) { return false; } @@ -466,8 +452,8 @@ namespace MobileGL { cursor.matrixStride = 0; break; case spv::Op::OpTypeVector: - stride = ScalarByteSize( - context->get_def_use_mgr()->GetDef(type->GetSingleWordInOperand(0))); + stride = + ScalarByteSize(context->get_def_use_mgr()->GetDef(type->GetSingleWordInOperand(0))); cursor.typeId = type->GetSingleWordInOperand(0); cursor.matrixStride = 0; break; @@ -508,8 +494,7 @@ namespace MobileGL { m_floatTypeId(floatTypeId) {} uint32_t UintConstant(uint32_t value) { - const spvtools::opt::analysis::Type* type = - m_context->get_type_mgr()->GetType(m_uintTypeId); + const spvtools::opt::analysis::Type* type = m_context->get_type_mgr()->GetType(m_uintTypeId); const spvtools::opt::analysis::Constant* constant = m_context->get_constant_mgr()->GetConstant(type, {value}); return m_context->get_constant_mgr()->GetDefiningInstruction(constant)->result_id(); @@ -523,22 +508,19 @@ namespace MobileGL { for (const auto& [indexId, wordsPerStep] : plan.dynamicTerms) { uint32_t term = AsUint(builder, indexId); if (wordsPerStep != 1u) { - term = Binary(builder, spv::Op::OpIMul, m_uintTypeId, term, - UintConstant(wordsPerStep)); + term = Binary(builder, spv::Op::OpIMul, m_uintTypeId, term, UintConstant(wordsPerStep)); } - total = total == 0 ? term - : Binary(builder, spv::Op::OpIAdd, m_uintTypeId, total, term); + total = total == 0 ? term : Binary(builder, spv::Op::OpIAdd, m_uintTypeId, total, term); } if (total == 0) return UintConstant(plan.constantWords); if (plan.constantWords == 0) return total; - return Binary(builder, spv::Op::OpIAdd, m_uintTypeId, total, - UintConstant(plan.constantWords)); + return Binary(builder, spv::Op::OpIAdd, m_uintTypeId, total, UintConstant(plan.constantWords)); } // Rebuilds the value an OpLoad of `cursor` would have produced, out of the // words that live at `baseWordId + relWords`. - uint32_t BuildValue(InstructionBuilder& builder, const Access& access, - const TypeCursor& cursor, uint32_t relWords) { + uint32_t BuildValue(InstructionBuilder& builder, const Access& access, const TypeCursor& cursor, + uint32_t relWords) { const Instruction* type = m_context->get_def_use_mgr()->GetDef(cursor.typeId); switch (type->opcode()) { case spv::Op::OpTypeInt: @@ -550,9 +532,7 @@ namespace MobileGL { .AddUnaryOp(m_floatTypeId, spv::Op::OpBitcast, NarrowDoubleBits(builder, lo, hi)) ->result_id(); - return builder - .AddUnaryOp(cursor.typeId, spv::Op::OpFConvert, narrowed) - ->result_id(); + return builder.AddUnaryOp(cursor.typeId, spv::Op::OpFConvert, narrowed)->result_id(); } const uint32_t word = LoadWord(builder, access, relWords); if (cursor.typeId == m_uintTypeId) return word; @@ -582,8 +562,7 @@ namespace MobileGL { TypeCursor element = cursor; element.typeId = type->GetSingleWordInOperand(0); uint32_t stride = 0; - TryGetDecorationLiteral(m_context, cursor.typeId, spv::Decoration::ArrayStride, - &stride); + TryGetDecorationLiteral(m_context, cursor.typeId, spv::Decoration::ArrayStride, &stride); const uint32_t count = m_context->get_constant_mgr() ->FindDeclaredConstant(type->GetSingleWordInOperand(1)) ->AsIntConstant() @@ -599,8 +578,8 @@ namespace MobileGL { std::vector parts; for (uint32_t member = 0; member < type->NumInOperands(); ++member) { uint32_t offset = 0; - TryGetMemberDecorationLiteral(m_context, cursor.typeId, member, - spv::Decoration::Offset, &offset); + TryGetMemberDecorationLiteral(m_context, cursor.typeId, member, spv::Decoration::Offset, + &offset); parts.push_back(BuildValue(builder, access, MemberCursor(m_context, type, member), relWords + offset / kWordBytes)); } @@ -614,9 +593,8 @@ namespace MobileGL { // The mirror image: writes `valueId` into the words at // `baseWordId + relWords`. `path` is the composite-extract index list that // reaches the part being written, empty at the root. - void StoreValue(InstructionBuilder& builder, const Access& access, - const TypeCursor& cursor, uint32_t relWords, uint32_t rootValueId, - std::vector& path) { + void StoreValue(InstructionBuilder& builder, const Access& access, const TypeCursor& cursor, + uint32_t relWords, uint32_t rootValueId, std::vector& path) { const Instruction* type = m_context->get_def_use_mgr()->GetDef(cursor.typeId); switch (type->opcode()) { case spv::Op::OpTypeInt: @@ -667,16 +645,15 @@ namespace MobileGL { TypeCursor element = cursor; element.typeId = type->GetSingleWordInOperand(0); uint32_t stride = 0; - TryGetDecorationLiteral(m_context, cursor.typeId, spv::Decoration::ArrayStride, - &stride); + TryGetDecorationLiteral(m_context, cursor.typeId, spv::Decoration::ArrayStride, &stride); const uint32_t count = m_context->get_constant_mgr() ->FindDeclaredConstant(type->GetSingleWordInOperand(1)) ->AsIntConstant() ->GetU32BitValue(); for (uint32_t i = 0; i < count; ++i) { path.push_back(i); - StoreValue(builder, access, element, relWords + i * (stride / kWordBytes), - rootValueId, path); + StoreValue(builder, access, element, relWords + i * (stride / kWordBytes), rootValueId, + path); path.pop_back(); } return; @@ -684,8 +661,8 @@ namespace MobileGL { case spv::Op::OpTypeStruct: { for (uint32_t member = 0; member < type->NumInOperands(); ++member) { uint32_t offset = 0; - TryGetMemberDecorationLiteral(m_context, cursor.typeId, member, - spv::Decoration::Offset, &offset); + TryGetMemberDecorationLiteral(m_context, cursor.typeId, member, spv::Decoration::Offset, + &offset); path.push_back(member); StoreValue(builder, access, MemberCursor(m_context, type, member), relWords + offset / kWordBytes, rootValueId, path); @@ -724,8 +701,7 @@ namespace MobileGL { count = Select(builder, tooShort, UintConstant(0), past); } if (strideWords != 1) { - count = Binary(builder, spv::Op::OpUDiv, m_uintTypeId, count, - UintConstant(strideWords)); + count = Binary(builder, spv::Op::OpUDiv, m_uintTypeId, count, UintConstant(strideWords)); } m_context->ReplaceAllUsesWith(arrayLength->result_id(), count); m_context->KillInst(arrayLength); @@ -733,8 +709,7 @@ namespace MobileGL { private: uint32_t ComponentWords(uint32_t componentTypeId) { - return ScalarByteSize(m_context->get_def_use_mgr()->GetDef(componentTypeId)) / - kWordBytes; + return ScalarByteSize(m_context->get_def_use_mgr()->GetDef(componentTypeId)) / kWordBytes; } uint32_t Binary(InstructionBuilder& builder, spv::Op opcode, uint32_t typeId, uint32_t a, @@ -744,8 +719,7 @@ namespace MobileGL { uint32_t Select(InstructionBuilder& builder, uint32_t condition, uint32_t whenTrue, uint32_t whenFalse) { - return builder - .AddTernaryOp(m_uintTypeId, spv::Op::OpSelect, condition, whenTrue, whenFalse) + return builder.AddTernaryOp(m_uintTypeId, spv::Op::OpSelect, condition, whenTrue, whenFalse) ->result_id(); } @@ -763,16 +737,16 @@ namespace MobileGL { uint32_t LoadWord(InstructionBuilder& builder, const Access& access, uint32_t words) { const uint32_t indexId = Offset(builder, access.baseWordId, words); - Instruction* pointer = builder.AddAccessChain( - access.wordPointerTypeId, access.variableId, {access.memberZeroId, indexId}); + Instruction* pointer = builder.AddAccessChain(access.wordPointerTypeId, access.variableId, + {access.memberZeroId, indexId}); return builder.AddLoad(m_uintTypeId, pointer->result_id())->result_id(); } void StoreWord(InstructionBuilder& builder, const Access& access, uint32_t words, uint32_t valueId) { const uint32_t indexId = Offset(builder, access.baseWordId, words); - Instruction* pointer = builder.AddAccessChain( - access.wordPointerTypeId, access.variableId, {access.memberZeroId, indexId}); + Instruction* pointer = builder.AddAccessChain(access.wordPointerTypeId, access.variableId, + {access.memberZeroId, indexId}); builder.AddStore(pointer->result_id(), valueId); } @@ -787,20 +761,20 @@ namespace MobileGL { // mantissa bits binary32 cannot hold. Straight-line by construction: every // case is an OpSelect, so this needs no control flow and never splits a block. uint32_t NarrowDoubleBits(InstructionBuilder& builder, uint32_t lo, uint32_t hi) { - const uint32_t sign = Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, hi, - UintConstant(0x80000000u)); - const uint32_t exponent = Binary( - builder, spv::Op::OpBitwiseAnd, m_uintTypeId, - Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, hi, UintConstant(20)), - UintConstant(0x7FFu)); - const uint32_t significandHigh = Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, - hi, UintConstant(0xFFFFFu)); + const uint32_t sign = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, hi, UintConstant(0x80000000u)); + const uint32_t exponent = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, + Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, hi, UintConstant(20)), + UintConstant(0x7FFu)); + const uint32_t significandHigh = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, hi, UintConstant(0xFFFFFu)); // The 23 bits binary32 keeps: 20 from the high word, 3 from the low one. - const uint32_t significand = Binary( - builder, spv::Op::OpBitwiseOr, m_uintTypeId, - Binary(builder, spv::Op::OpShiftLeftLogical, m_uintTypeId, significandHigh, - UintConstant(3)), - Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, lo, UintConstant(29))); + const uint32_t significand = + Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, + Binary(builder, spv::Op::OpShiftLeftLogical, m_uintTypeId, significandHigh, + UintConstant(3)), + Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, lo, UintConstant(29))); // 1023 - 127 = 896, so the binary32 exponent field is `exponent - 896` and // every bound on it is an unsigned comparison against that bias. @@ -808,25 +782,21 @@ namespace MobileGL { builder, spv::Op::OpBitwiseOr, m_uintTypeId, sign, Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, Binary(builder, spv::Op::OpShiftLeftLogical, m_uintTypeId, - Binary(builder, spv::Op::OpISub, m_uintTypeId, exponent, - UintConstant(896)), + Binary(builder, spv::Op::OpISub, m_uintTypeId, exponent, UintConstant(896)), UintConstant(23)), significand)); - const uint32_t infinityBits = Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, sign, - UintConstant(0x7F800000u)); + const uint32_t infinityBits = + Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, sign, UintConstant(0x7F800000u)); // A quiet NaN that stays one even when every significant bit sat in the 29 // low bits binary32 discards. - const uint32_t nanBits = - Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, infinityBits, - Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, - UintConstant(0x400000u), significand)); + const uint32_t nanBits = Binary( + builder, spv::Op::OpBitwiseOr, m_uintTypeId, infinityBits, + Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, UintConstant(0x400000u), significand)); - const uint32_t significandIsZero = - Binary(builder, spv::Op::OpIEqual, m_boolTypeId, - Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, significandHigh, lo), - UintConstant(0)); - const uint32_t maxExponentBits = - Select(builder, significandIsZero, infinityBits, nanBits); + const uint32_t significandIsZero = Binary( + builder, spv::Op::OpIEqual, m_boolTypeId, + Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, significandHigh, lo), UintConstant(0)); + const uint32_t maxExponentBits = Select(builder, significandIsZero, infinityBits, nanBits); const uint32_t isZeroExponent = Binary(builder, spv::Op::OpIEqual, m_boolTypeId, exponent, UintConstant(0)); @@ -834,10 +804,10 @@ namespace MobileGL { Binary(builder, spv::Op::OpIEqual, m_boolTypeId, exponent, UintConstant(0x7FFu)); // <= 896 is every magnitude binary32 could hold only as a subnormal; // >= 1151 is every one it cannot hold at all. - const uint32_t underflows = Binary(builder, spv::Op::OpULessThanEqual, m_boolTypeId, - exponent, UintConstant(896)); - const uint32_t overflows = Binary(builder, spv::Op::OpUGreaterThanEqual, m_boolTypeId, - exponent, UintConstant(1151)); + const uint32_t underflows = + Binary(builder, spv::Op::OpULessThanEqual, m_boolTypeId, exponent, UintConstant(896)); + const uint32_t overflows = + Binary(builder, spv::Op::OpUGreaterThanEqual, m_boolTypeId, exponent, UintConstant(1151)); uint32_t bits = Select(builder, overflows, infinityBits, normalBits); bits = Select(builder, underflows, sign, bits); @@ -846,27 +816,25 @@ namespace MobileGL { } // The inverse: a binary32 bit pattern -> the binary64 word pair for it. - void WidenFloatBits(InstructionBuilder& builder, uint32_t bits, uint32_t* lo, - uint32_t* hi) { - const uint32_t sign = Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, bits, - UintConstant(0x80000000u)); - const uint32_t exponent = Binary( - builder, spv::Op::OpBitwiseAnd, m_uintTypeId, - Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, bits, UintConstant(23)), - UintConstant(0xFFu)); - const uint32_t significand = Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, bits, - UintConstant(0x7FFFFFu)); - const uint32_t significandHigh = Binary(builder, spv::Op::OpShiftRightLogical, - m_uintTypeId, significand, UintConstant(3)); - const uint32_t significandLow = Binary(builder, spv::Op::OpShiftLeftLogical, - m_uintTypeId, significand, UintConstant(29)); + void WidenFloatBits(InstructionBuilder& builder, uint32_t bits, uint32_t* lo, uint32_t* hi) { + const uint32_t sign = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, bits, UintConstant(0x80000000u)); + const uint32_t exponent = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, + Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, bits, UintConstant(23)), + UintConstant(0xFFu)); + const uint32_t significand = + Binary(builder, spv::Op::OpBitwiseAnd, m_uintTypeId, bits, UintConstant(0x7FFFFFu)); + const uint32_t significandHigh = + Binary(builder, spv::Op::OpShiftRightLogical, m_uintTypeId, significand, UintConstant(3)); + const uint32_t significandLow = + Binary(builder, spv::Op::OpShiftLeftLogical, m_uintTypeId, significand, UintConstant(29)); const uint32_t normalHigh = Binary( builder, spv::Op::OpBitwiseOr, m_uintTypeId, sign, Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, Binary(builder, spv::Op::OpShiftLeftLogical, m_uintTypeId, - Binary(builder, spv::Op::OpIAdd, m_uintTypeId, exponent, - UintConstant(896)), + Binary(builder, spv::Op::OpIAdd, m_uintTypeId, exponent, UintConstant(896)), UintConstant(20)), significandHigh)); const uint32_t maxHigh = Binary(builder, spv::Op::OpBitwiseOr, m_uintTypeId, sign, @@ -933,8 +901,8 @@ namespace MobileGL { // NOTHING IS WRITTEN until every reason to decline has been ruled out, so a block // this returns 0 for leaves the module as it found it - which is what lets // Process() truthfully report SuccessWithoutChange for a module of only those. - uint32_t CreateWordArrayTypeBefore(IRContext* context, Instruction* structType, - uint32_t uintTypeId, uint32_t length, bool openEnded) { + uint32_t CreateWordArrayTypeBefore(IRContext* context, Instruction* structType, uint32_t uintTypeId, + uint32_t length, bool openEnded) { Instruction* uintType = context->get_def_use_mgr()->GetDef(uintTypeId); if (uintType == nullptr || uintType->opcode() != spv::Op::OpTypeInt) return 0; const bool hoistUint = !DeclaredBefore(context, uintTypeId, structType->result_id()); @@ -954,8 +922,7 @@ namespace MobileGL { if (position == context->types_values_end()) return 0; // Created in front of the block when it is not there yet, so the only way // this declines is a constant the module already declares after it. - Instruction* lengthInst = - constantMgr->GetDefiningInstruction(lengthConstant, 0, &position); + Instruction* lengthInst = constantMgr->GetDefiningInstruction(lengthConstant, 0, &position); if (lengthInst == nullptr) return 0; if (!DeclaredBefore(context, lengthInst->result_id(), structType->result_id())) return 0; lengthConstantId = lengthInst->result_id(); @@ -966,14 +933,13 @@ namespace MobileGL { if (hoistUint) uintType->InsertBefore(structType); std::unique_ptr arrayType = - openEnded - ? MakeUnique( - context, spv::Op::OpTypeRuntimeArray, 0, arrayTypeId, - std::initializer_list{{SPV_OPERAND_TYPE_ID, {uintTypeId}}}) - : MakeUnique( - context, spv::Op::OpTypeArray, 0, arrayTypeId, - std::initializer_list{{SPV_OPERAND_TYPE_ID, {uintTypeId}}, - {SPV_OPERAND_TYPE_ID, {lengthConstantId}}}); + openEnded ? MakeUnique( + context, spv::Op::OpTypeRuntimeArray, 0, arrayTypeId, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {uintTypeId}}}) + : MakeUnique( + context, spv::Op::OpTypeArray, 0, arrayTypeId, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {uintTypeId}}, + {SPV_OPERAND_TYPE_ID, {lengthConstantId}}}); Instruction* inserted = structType->InsertBefore(std::move(arrayType)); context->AnalyzeDefUse(inserted); context->get_decoration_mgr()->AddDecorationVal( @@ -987,12 +953,10 @@ namespace MobileGL { // taken only from ALL of them, because each is a promise the shader would break // if one member never made it. std::vector SurvivingAccessQualifiers(IRContext* context, uint32_t structId, - uint32_t memberCount) { - static constexpr spv::Decoration kAny[] = {spv::Decoration::Coherent, - spv::Decoration::Volatile}; - static constexpr spv::Decoration kAll[] = {spv::Decoration::Restrict, - spv::Decoration::NonWritable, - spv::Decoration::NonReadable}; + uint32_t memberCount) { + static constexpr spv::Decoration kAny[] = {spv::Decoration::Coherent, spv::Decoration::Volatile}; + static constexpr spv::Decoration kAll[] = {spv::Decoration::Restrict, spv::Decoration::NonWritable, + spv::Decoration::NonReadable}; std::vector surviving; for (const spv::Decoration kind : kAny) { for (uint32_t member = 0; member < memberCount; ++member) { @@ -1017,8 +981,7 @@ namespace MobileGL { // different thing entirely from the one that used to be member 0. void StripMemberAnnotations(IRContext* context, uint32_t structId) { std::vector doomed; - for (Instruction* decoration : - context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { + for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structId, false)) { if (decoration->opcode() == spv::Op::OpMemberDecorate) doomed.push_back(decoration); } for (Instruction& debug : context->module()->debugs2()) { @@ -1026,7 +989,8 @@ namespace MobileGL { if (debug.GetSingleWordInOperand(0) != structId) continue; doomed.push_back(&debug); } - for (Instruction* inst : doomed) context->KillInst(inst); + for (Instruction* inst : doomed) + context->KillInst(inst); } // Plans every shader storage block in the module that holds a 64-bit float and @@ -1045,8 +1009,7 @@ namespace MobileGL { const uint32_t storageClass = inst.GetSingleWordInOperand(0); const bool isStorageBufferClass = storageClass == static_cast(spv::StorageClass::StorageBuffer); - const bool isUniformClass = - storageClass == static_cast(spv::StorageClass::Uniform); + const bool isUniformClass = storageClass == static_cast(spv::StorageClass::Uniform); if (!isStorageBufferClass && !isUniformClass) continue; Instruction* pointerType = defUseMgr->GetDef(inst.type_id()); @@ -1250,8 +1213,8 @@ namespace MobileGL { bool touched = false; for (BlockPlan& plan : plans) { const uint32_t structId = plan.structType->result_id(); - const uint32_t arrayTypeId = CreateWordArrayTypeBefore( - irContext, plan.structType, uintTypeId, plan.wordCount, plan.openEnded); + const uint32_t arrayTypeId = CreateWordArrayTypeBefore(irContext, plan.structType, uintTypeId, + plan.wordCount, plan.openEnded); if (arrayTypeId == 0) { // Nothing was written for it, so the module is still the one that came in. MGLOG_D("[spirv] storage block %%%u: no legal place for the flattened word array; " @@ -1283,8 +1246,8 @@ namespace MobileGL { for (Instruction* store : chainPlan.stores) { InstructionBuilder builder(irContext, store, kPreservedAnalyses); std::vector path; - emitter.StoreValue(builder, access, chainPlan.pointee, 0, - store->GetSingleWordInOperand(1), path); + emitter.StoreValue(builder, access, chainPlan.pointee, 0, store->GetSingleWordInOperand(1), + path); irContext->KillInst(store); } irContext->KillInst(chainPlan.chain); @@ -1293,8 +1256,8 @@ namespace MobileGL { emitter.RewriteArrayLength(arrayLength, plan.wordCount, plan.tailStrideWords); } - const std::vector surviving = SurvivingAccessQualifiers( - irContext, structId, plan.structType->NumInOperands()); + const std::vector surviving = + SurvivingAccessQualifiers(irContext, structId, plan.structType->NumInOperands()); StripMemberAnnotations(irContext, structId); plan.structType->SetInOperands({{SPV_OPERAND_TYPE_ID, {arrayTypeId}}}); irContext->UpdateDefUse(plan.structType); @@ -1304,10 +1267,9 @@ namespace MobileGL { // AddMemberDecoration always carries a literal value; these have none, so // the instruction has to be spelled out. irContext->get_decoration_mgr()->AddDecoration( - spv::Op::OpMemberDecorate, - {{SPV_OPERAND_TYPE_ID, {structId}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, - {SPV_OPERAND_TYPE_DECORATION, {static_cast(kind)}}}); + spv::Op::OpMemberDecorate, {{SPV_OPERAND_TYPE_ID, {structId}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(kind)}}}); } modified = true; if (plan.openEnded) { @@ -1329,8 +1291,7 @@ namespace MobileGL { return Status::SuccessWithChange; } - spvtools::Optimizer::PassToken - FlattenFloat64StorageBlockPass::CreateFlattenFloat64StorageBlockPass() { + spvtools::Optimizer::PassToken FlattenFloat64StorageBlockPass::CreateFlattenFloat64StorageBlockPass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp index 59d170a0..a6f673d3 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.cpp @@ -22,8 +22,8 @@ #include namespace { - using spvtools::opt::IRContext; using spvtools::opt::Instruction; + using spvtools::opt::IRContext; using spvtools::opt::Operand; constexpr const char* kTargetTypeName = "DailyWeatherVariation"; @@ -186,10 +186,10 @@ namespace { void AddLocationDecoration(IRContext* context, uint32_t id, uint32_t location) { context->AddAnnotationInst(spvtools::MakeUnique( context, spv::Op::OpDecorate, 0, 0, - std::initializer_list{{SPV_OPERAND_TYPE_ID, {id}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Location)}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {location}}})); + std::initializer_list{ + {SPV_OPERAND_TYPE_ID, {id}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Location)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {location}}})); } std::optional GetVariableDecorationLiteral(IRContext* context, uint32_t id, spv::Decoration kind) { @@ -209,7 +209,8 @@ namespace { std::optional GetMemberDecorationLiteral(IRContext* context, uint32_t structTypeId, uint32_t memberIndex, spv::Decoration kind) { for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structTypeId, false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->GetSingleWordInOperand(1) != memberIndex || + if (decoration->opcode() != spv::Op::OpMemberDecorate || + decoration->GetSingleWordInOperand(1) != memberIndex || static_cast(decoration->GetSingleWordInOperand(2)) != kind || decoration->NumInOperands() < 4) { continue; @@ -239,8 +240,10 @@ namespace { void CloneMemberDecorations(IRContext* context, const Instruction* structType, uint32_t memberIndex, Instruction* target) { - for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(structType->result_id(), false)) { - if (decoration->opcode() != spv::Op::OpMemberDecorate || decoration->GetSingleWordInOperand(1) != memberIndex) { + for (Instruction* decoration : + context->get_decoration_mgr()->GetDecorationsFor(structType->result_id(), false)) { + if (decoration->opcode() != spv::Op::OpMemberDecorate || + decoration->GetSingleWordInOperand(1) != memberIndex) { continue; } @@ -304,7 +307,8 @@ namespace { } replacementChain->UpdateDebugInfoFrom(accessChain); - auto insertPoint = spvtools::opt::BasicBlock::iterator(accessChain).InsertBefore(std::move(replacementChain)); + auto insertPoint = + spvtools::opt::BasicBlock::iterator(accessChain).InsertBefore(std::move(replacementChain)); context->AnalyzeDefUse(&*insertPoint); context->set_instr_block(&*insertPoint, context->get_instr_block(accessChain)); context->ReplaceAllUsesWith(accessChain->result_id(), replacementId); @@ -490,14 +494,15 @@ namespace MobileGL { uint32_t locationCursor = GetVariableDecorationLiteral(irContext, targetVariable->result_id(), spv::Decoration::Location) .value_or(0u); - for (uint32_t memberIndex = 0; memberIndex < static_cast(kFlattenedMembers.size()); ++memberIndex) { - const uint32_t location = GetMemberDecorationLiteral(irContext, targetStructType->result_id(), memberIndex, - spv::Decoration::Location) + for (uint32_t memberIndex = 0; memberIndex < static_cast(kFlattenedMembers.size()); + ++memberIndex) { + const uint32_t location = GetMemberDecorationLiteral(irContext, targetStructType->result_id(), + memberIndex, spv::Decoration::Location) .value_or(locationCursor); Instruction* const memberVar = CreateFlattenedVariable( irContext, defUseMgr, typeMgr, targetVariable, targetStructType, memberIndex, location); - replacements.push_back({memberIndex, targetStructType->GetSingleWordInOperand(memberIndex), location, - memberVar}); + replacements.push_back( + {memberIndex, targetStructType->GetSingleWordInOperand(memberIndex), location, memberVar}); locationCursor = location + kFlattenedMembers[memberIndex].locationSpan; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.h index e658333a..dda4c2c5 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenInterfaceStructPass.h @@ -15,8 +15,10 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { - // This is a custom SPIR-V optimization pass that flattens the "DailyWeatherVariation" interface struct into separate variables. - // This is actually a workaround for Adreno drivers that fail to work with struct as an interface block. Mali drivers don't have this issue, but we want to keep the shader code consistent across platforms. + // This is a custom SPIR-V optimization pass that flattens the "DailyWeatherVariation" interface struct into + // separate variables. This is actually a workaround for Adreno drivers that fail to work with struct as an + // interface block. Mali drivers don't have this issue, but we want to keep the shader code consistent + // across platforms. class FlattenInterfaceStructPass : public spvtools::opt::Pass { public: const char* name() const override { return "flatten-interface-struct"; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp index 0a859557..75c4c4c1 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.cpp @@ -25,8 +25,8 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { namespace { - using spvtools::opt::IRContext; using spvtools::opt::Instruction; + using spvtools::opt::IRContext; using spvtools::opt::Operand; namespace analysis = spvtools::opt::analysis; @@ -83,8 +83,7 @@ namespace MobileGL { // place it", which declines the whole block rather than guessing. Uint32 LocationSpan(const analysis::Type* type) { if (type == nullptr) return 0; - if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || - type->AsBool() != nullptr) { + if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || type->AsBool() != nullptr) { return 1u; } if (const auto* vector = type->AsVector()) { @@ -205,8 +204,7 @@ namespace MobileGL { // publish once, at the end, which is silently the wrong data rather than a // failure - so those stages keep their blocks and the capture keeps its // spelling. - const auto executionModel = - static_cast(entryPoint->GetSingleWordInOperand(0)); + const auto executionModel = static_cast(entryPoint->GetSingleWordInOperand(0)); if (executionModel == spv::ExecutionModel::Geometry || executionModel == spv::ExecutionModel::TessellationControl) { MGLOG_D("FlattenXfbInterfaceBlocksPass: execution model %u publishes outputs outside " @@ -240,10 +238,8 @@ namespace MobileGL { for (Instruction& inst : irContext->types_values()) { if (inst.opcode() != spv::Op::OpVariable) continue; - const auto storageClass = - static_cast(inst.GetSingleWordInOperand(0)); - if (storageClass != spv::StorageClass::Input && - storageClass != spv::StorageClass::Output) { + const auto storageClass = static_cast(inst.GetSingleWordInOperand(0)); + if (storageClass != spv::StorageClass::Input && storageClass != spv::StorageClass::Output) { continue; } Instruction* pointerType = defUseMgr->GetDef(inst.type_id()); @@ -254,8 +250,7 @@ namespace MobileGL { if (structType == nullptr) continue; const String blockName = FindName(*irContext, pointeeTypeId); - if (blockName.empty() || - m_blockNames.find(blockName) == m_blockNames.end()) { + if (blockName.empty() || m_blockNames.find(blockName) == m_blockNames.end()) { continue; } @@ -266,8 +261,7 @@ namespace MobileGL { target.blockName = blockName; Uint32 blockLocation = 0; - const Bool hasBlockLocation = - FindLocationDecoration(*irContext, inst.result_id(), blockLocation); + const Bool hasBlockLocation = FindLocationDecoration(*irContext, inst.result_id(), blockLocation); Bool usable = true; for (auto& annotation : irContext->annotations()) { @@ -296,8 +290,7 @@ namespace MobileGL { break; } Uint32 memberLocation = 0; - if (FindMemberLocationDecoration(*irContext, pointeeTypeId, memberIndex, - memberLocation)) { + if (FindMemberLocationDecoration(*irContext, pointeeTypeId, memberIndex, memberLocation)) { member.location = memberLocation; member.hasLocation = true; } else if (hasBlockLocation) { @@ -337,7 +330,8 @@ namespace MobileGL { { auto& probeBlock = *entryFunction->begin(); auto probe = probeBlock.begin(); - while (probe != probeBlock.end() && probe->opcode() == spv::Op::OpVariable) ++probe; + while (probe != probeBlock.end() && probe->opcode() == spv::Op::OpVariable) + ++probe; if (probe == probeBlock.end()) return Status::SuccessWithoutChange; } @@ -391,8 +385,7 @@ namespace MobileGL { target.privatePointerTypeId = typeMgr->FindPointerToType(target.structTypeId, spv::StorageClass::Private); for (auto& member : target.members) { - member.interfacePointerTypeId = - typeMgr->FindPointerToType(member.typeId, target.storageClass); + member.interfacePointerTypeId = typeMgr->FindPointerToType(member.typeId, target.storageClass); member.privatePointerTypeId = typeMgr->FindPointerToType(member.typeId, spv::StorageClass::Private); } @@ -415,8 +408,7 @@ namespace MobileGL { irContext->AddGlobalValue(spvtools::MakeUnique( irContext, spv::Op::OpVariable, member.interfacePointerTypeId, member.variableId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(target.storageClass)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(target.storageClass)}}})); // The name IS the contract: it is what the rewritten capture request // asks the driver for, and what makes the producer and consumer of a @@ -425,18 +417,16 @@ namespace MobileGL { std::vector nameOperands; nameOperands.push_back({SPV_OPERAND_TYPE_ID, {member.variableId}}); nameOperands.push_back( - {SPV_OPERAND_TYPE_LITERAL_STRING, - spvtools::utils::MakeVector(flatName)}); - newNames.push_back(spvtools::MakeUnique(irContext, spv::Op::OpName, 0, 0, - nameOperands)); + {SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(flatName)}); + newNames.push_back( + spvtools::MakeUnique(irContext, spv::Op::OpName, 0, 0, nameOperands)); if (member.hasLocation) { newDecorations.push_back(spvtools::MakeUnique( irContext, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {member.variableId}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Location)}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Location)}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member.location}}})); } } @@ -448,8 +438,7 @@ namespace MobileGL { if (annotation.GetSingleWordInOperand(0) != target.structTypeId) continue; const Uint32 memberIndex = annotation.GetSingleWordInOperand(1); if (memberIndex >= target.members.size()) continue; - const auto decoration = - static_cast(annotation.GetSingleWordInOperand(2)); + const auto decoration = static_cast(annotation.GetSingleWordInOperand(2)); if (!IsMemberDecorationToCarry(decoration)) continue; std::vector operands; operands.push_back({SPV_OPERAND_TYPE_ID, {target.members[memberIndex].variableId}}); @@ -467,8 +456,7 @@ namespace MobileGL { // A struct type shared by two flattened variables would otherwise have its // Block decoration killed twice - and the second kill is a use-after-free, // not a no-op. - const Bool structTypeAlreadyStripped = - strippedBlockStructTypes.count(target.structTypeId) != 0; + const Bool structTypeAlreadyStripped = strippedBlockStructTypes.count(target.structTypeId) != 0; strippedBlockStructTypes.insert(target.structTypeId); for (auto& annotation : irContext->annotations()) { if (annotation.opcode() != spv::Op::OpDecorate) continue; @@ -593,8 +581,7 @@ namespace MobileGL { const Uint32 memberPointerId = irContext->TakeNextId(); const Uint32 loadedId = irContext->TakeNextId(); returnInst->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpAccessChain, member.privatePointerTypeId, - memberPointerId, + irContext, spv::Op::OpAccessChain, member.privatePointerTypeId, memberPointerId, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {target.variable->result_id()}}, {SPV_OPERAND_TYPE_ID, {memberIndexConstants[memberIndex]}}})); @@ -618,8 +605,8 @@ namespace MobileGL { spv::StorageClass::Private) { continue; } - pointer->SetResultType(typeMgr->FindPointerToType(resultType->GetSingleWordInOperand(1), - spv::StorageClass::Private)); + pointer->SetResultType( + typeMgr->FindPointerToType(resultType->GetSingleWordInOperand(1), spv::StorageClass::Private)); } if (m_flattenedBlockNames != nullptr) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.h index 0fd4cf00..58b81ae9 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FlattenXfbInterfaceBlocksPass.h @@ -48,8 +48,7 @@ namespace MobileGL { // "StageData.attrib[0]". `flattenedBlockNames` receives the subset this pass // actually rewrote, so the caller can rename exactly those capture requests // and leave the rest spelled as the application wrote them. - FlattenXfbInterfaceBlocksPass(const std::set& blockNames, - std::set* flattenedBlockNames) + FlattenXfbInterfaceBlocksPass(const std::set& blockNames, std::set* flattenedBlockNames) : m_blockNames(blockNames), m_flattenedBlockNames(flattenedBlockNames) {} const char* name() const override { return "mobilegl-flatten-xfb-interface-blocks"; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp index 64cd9c94..b05e59d3 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/FullCoverageSampleMaskPass.cpp @@ -34,8 +34,7 @@ namespace MobileGL { if (static_cast(annotation.GetSingleWordInOperand(1)) != spv::Decoration::BuiltIn) continue; - if (static_cast(annotation.GetSingleWordInOperand(2)) != - spv::BuiltIn::SampleMask) + if (static_cast(annotation.GetSingleWordInOperand(2)) != spv::BuiltIn::SampleMask) continue; Instruction* variable = defUseMgr->GetDef(annotation.GetSingleWordInOperand(0)); if (variable != nullptr && variable->opcode() == spv::Op::OpVariable && @@ -116,7 +115,8 @@ namespace MobileGL { const Instruction* allOnesInst = allOnes ? constantMgr->GetDefiningInstruction(allOnes, elementTypeId) : nullptr; if (elementTypeId == 0 || allOnesInst == nullptr) return Status::Failure; - for (Instruction* store : elementStores) store->SetInOperand(1, {allOnesInst->result_id()}); + for (Instruction* store : elementStores) + store->SetInOperand(1, {allOnesInst->result_id()}); if (!arrayStores.empty()) { // The array's length is a constant (the builtin is sized at link time); a @@ -133,7 +133,8 @@ namespace MobileGL { const Instruction* allOnesArrayInst = allOnesArray ? constantMgr->GetDefiningInstruction(allOnesArray, arrayTypeId) : nullptr; if (arrayTypeId == 0 || allOnesArrayInst == nullptr) return Status::Failure; - for (Instruction* store : arrayStores) store->SetInOperand(1, {allOnesArrayInst->result_id()}); + for (Instruction* store : arrayStores) + store->SetInOperand(1, {allOnesArrayInst->result_id()}); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeFragmentOutputIndexPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeFragmentOutputIndexPass.cpp index 620a17fc..37865523 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeFragmentOutputIndexPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeFragmentOutputIndexPass.cpp @@ -114,8 +114,8 @@ namespace MobileGL { // driver rejects. bool IsConstantIndex(IRContext* context, uint32_t indexId) { Instruction* def = context->get_def_use_mgr()->GetDef(indexId); - return def != nullptr && (def->opcode() == spv::Op::OpConstant || - def->opcode() == spv::Op::OpConstantNull); + return def != nullptr && + (def->opcode() == spv::Op::OpConstant || def->opcode() == spv::Op::OpConstantNull); } // Access chains that index a fragment output array with a non-constant. @@ -172,9 +172,8 @@ namespace MobileGL { operands.push_back(accessChain->GetInOperand(i)); } } - return builder.AddInstruction(MakeUnique(context, accessChain->opcode(), - accessChain->type_id(), - context->TakeNextId(), operands)); + return builder.AddInstruction(MakeUnique( + context, accessChain->opcode(), accessChain->type_id(), context->TakeNextId(), operands)); } // The id of |element| as a constant of the same integer type as |indexId|. @@ -195,10 +194,8 @@ namespace MobileGL { if (indexDef == nullptr) { return false; } - const spvtools::opt::analysis::Type* type = - context->get_type_mgr()->GetType(indexDef->type_id()); - const spvtools::opt::analysis::Integer* integer = - type != nullptr ? type->AsInteger() : nullptr; + const spvtools::opt::analysis::Type* type = context->get_type_mgr()->GetType(indexDef->type_id()); + const spvtools::opt::analysis::Integer* integer = type != nullptr ? type->AsInteger() : nullptr; return integer != nullptr && integer->width() == 32; } @@ -207,8 +204,8 @@ namespace MobileGL { // vector of the same width - built by broadcasting the scalar comparison. // Anything that is neither scalar nor vector (a matrix or struct element) is // refused: pre-1.4 OpSelect cannot express it either. - bool TryGetSelectConditionType(IRContext* context, uint32_t resultTypeId, - uint32_t* conditionTypeId, uint32_t* dimension) { + bool TryGetSelectConditionType(IRContext* context, uint32_t resultTypeId, uint32_t* conditionTypeId, + uint32_t* dimension) { auto* typeMgr = context->get_type_mgr(); const spvtools::opt::analysis::Type* resultType = typeMgr->GetType(resultTypeId); if (resultType == nullptr) { @@ -261,14 +258,12 @@ namespace MobileGL { } } // namespace - bool LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing( - const std::vector& binary) { + bool LegalizeFragmentOutputIndexPass::BinaryHasDynamicOutputIndexing(const std::vector& binary) { if (binary.empty()) { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); if (!context) { return false; @@ -317,8 +312,7 @@ namespace MobileGL { // cost proportional to the output array's length. spvtools::opt::LoopDescriptor* loops = irContext->GetLoopDescriptor(function); size_t nestIterations = 1; - for (spvtools::opt::Loop* loop = (*loops)[block->id()]; loop != nullptr; - loop = loop->GetParent()) { + for (spvtools::opt::Loop* loop = (*loops)[block->id()]; loop != nullptr; loop = loop->GetParent()) { size_t iterations = 0; if (!TryGetUnrollTripCount(loop, &iterations)) { break; @@ -340,8 +334,7 @@ namespace MobileGL { static_cast(spv::LoopControlMask::MaskNone)) { break; } - mergeInst->SetOperand( - 2, {static_cast(spv::LoopControlMask::Unroll)}); + mergeInst->SetOperand(2, {static_cast(spv::LoopControlMask::Unroll)}); nestIterations *= iterations; modified = true; } @@ -518,8 +511,7 @@ namespace MobileGL { irContext->set_instr_block(casePtr->GetLabelInst(), casePtr); InstructionBuilder caseBuilder( - irContext, casePtr, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + irContext, casePtr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); const uint32_t constantId = ConstantLikeIndex(irContext, indexId, element); Instruction* elementChain = CloneChainWithConstantIndex(caseBuilder, irContext, accessChain, constantId); @@ -538,16 +530,15 @@ namespace MobileGL { insertAfter = casePtr; } - InstructionBuilder switchBuilder( - irContext, block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder switchBuilder(irContext, block, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); switchBuilder.AddSwitch(indexId, mergeLabelId, targets, mergeLabelId); if (irContext->get_def_use_mgr()->NumUsers(accessChain) == 0) { irContext->KillInst(accessChain); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); - MGLOG_D("[spirv] fragment-output index: lowered a dynamic write to a %u-way switch", - arrayLength); + MGLOG_D("[spirv] fragment-output index: lowered a dynamic write to a %u-way switch", arrayLength); return LoweringOutcome::Changed; } @@ -566,8 +557,8 @@ namespace MobileGL { const uint32_t boolTypeId = irContext->get_type_mgr()->GetBoolTypeId(); const uint32_t indexId = accessChain->GetSingleWordInOperand(1); - InstructionBuilder builder( - irContext, load, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, load, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); uint32_t selectedId = 0; for (uint32_t element = 0; element < arrayLength; ++element) { @@ -582,16 +573,13 @@ namespace MobileGL { continue; } - Instruction* isElement = - builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); + Instruction* isElement = builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); uint32_t conditionId = isElement->result_id(); if (dimension > 1) { std::vector components(dimension, conditionId); conditionId = builder.AddCompositeConstruct(conditionTypeId, components)->result_id(); } - selectedId = builder - .AddSelect(load->type_id(), conditionId, elementLoad->result_id(), - selectedId) + selectedId = builder.AddSelect(load->type_id(), conditionId, elementLoad->result_id(), selectedId) ->result_id(); } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp index c96c8da8..873aeaa1 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.cpp @@ -74,8 +74,7 @@ namespace MobileGL { bool HasDecoration(IRContext* context, uint32_t id, spv::Decoration kind) { for (Instruction* decoration : context->get_decoration_mgr()->GetDecorationsFor(id, false)) { - if (decoration->opcode() != spv::Op::OpDecorate || - decoration->NumInOperands() < 2) { + if (decoration->opcode() != spv::Op::OpDecorate || decoration->NumInOperands() < 2) { continue; } if (static_cast(decoration->GetSingleWordInOperand(1)) == kind) { @@ -110,8 +109,7 @@ namespace MobileGL { if (inst.opcode() != spv::Op::OpVariable) { continue; } - const auto storageClass = - static_cast(inst.GetSingleWordInOperand(0)); + const auto storageClass = static_cast(inst.GetSingleWordInOperand(0)); if (storageClass != spv::StorageClass::StorageBuffer && storageClass != spv::StorageClass::Uniform && storageClass != spv::StorageClass::UniformConstant) { @@ -135,8 +133,7 @@ namespace MobileGL { if (storageClass == spv::StorageClass::UniformConstant) { // OpTypeImage // - if (elementType->opcode() != spv::Op::OpTypeImage || - elementType->NumInOperands() < 6 || + if (elementType->opcode() != spv::Op::OpTypeImage || elementType->NumInOperands() < 6 || elementType->GetSingleWordInOperand(5) != 2u) { continue; } @@ -147,10 +144,8 @@ namespace MobileGL { } const bool isStorageBlock = storageClass == spv::StorageClass::StorageBuffer - ? HasDecoration(context, elementType->result_id(), - spv::Decoration::Block) - : HasDecoration(context, elementType->result_id(), - spv::Decoration::BufferBlock); + ? HasDecoration(context, elementType->result_id(), spv::Decoration::Block) + : HasDecoration(context, elementType->result_id(), spv::Decoration::BufferBlock); if (!isStorageBlock) { continue; } @@ -173,8 +168,8 @@ namespace MobileGL { // driver rejects. bool IsConstantIndex(IRContext* context, uint32_t indexId) { Instruction* def = context->get_def_use_mgr()->GetDef(indexId); - return def != nullptr && (def->opcode() == spv::Op::OpConstant || - def->opcode() == spv::Op::OpConstantNull); + return def != nullptr && + (def->opcode() == spv::Op::OpConstant || def->opcode() == spv::Op::OpConstantNull); } // Access chains that index an array of storage blocks or of images with a @@ -185,8 +180,7 @@ namespace MobileGL { // already inside one element. std::vector CollectDynamicIndexUses(IRContext* context) { std::vector uses; - const std::unordered_map resourceArrays = - CollectResourceArrays(context); + const std::unordered_map resourceArrays = CollectResourceArrays(context); if (resourceArrays.empty()) { return uses; } @@ -208,8 +202,7 @@ namespace MobileGL { if (IsConstantIndex(context, inst.GetSingleWordInOperand(1))) { continue; } - uses.push_back( - {&inst, arrayIt->second.length, arrayIt->second.isImage}); + uses.push_back({&inst, arrayIt->second.length, arrayIt->second.isImage}); } } } @@ -230,9 +223,8 @@ namespace MobileGL { operands.push_back(accessChain->GetInOperand(i)); } } - return builder.AddInstruction(MakeUnique(context, accessChain->opcode(), - accessChain->type_id(), - context->TakeNextId(), operands)); + return builder.AddInstruction(MakeUnique( + context, accessChain->opcode(), accessChain->type_id(), context->TakeNextId(), operands)); } // The id of |element| as a constant of the same integer type as |indexId|. @@ -253,10 +245,8 @@ namespace MobileGL { if (indexDef == nullptr) { return false; } - const spvtools::opt::analysis::Type* type = - context->get_type_mgr()->GetType(indexDef->type_id()); - const spvtools::opt::analysis::Integer* integer = - type != nullptr ? type->AsInteger() : nullptr; + const spvtools::opt::analysis::Type* type = context->get_type_mgr()->GetType(indexDef->type_id()); + const spvtools::opt::analysis::Integer* integer = type != nullptr ? type->AsInteger() : nullptr; return integer != nullptr && integer->width() == 32; } @@ -265,8 +255,8 @@ namespace MobileGL { // vector of the same width - built by broadcasting the scalar comparison. // Anything that is neither scalar nor vector (a matrix or struct element) is // refused: pre-1.4 OpSelect cannot express it either. - bool TryGetSelectConditionType(IRContext* context, uint32_t resultTypeId, - uint32_t* conditionTypeId, uint32_t* dimension) { + bool TryGetSelectConditionType(IRContext* context, uint32_t resultTypeId, uint32_t* conditionTypeId, + uint32_t* dimension) { auto* typeMgr = context->get_type_mgr(); const spvtools::opt::analysis::Type* resultType = typeMgr->GetType(resultTypeId); if (resultType == nullptr) { @@ -325,8 +315,7 @@ namespace MobileGL { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); if (!context) { return false; @@ -382,8 +371,7 @@ namespace MobileGL { // the ARRAY LENGTH rather than to the trip counts. spvtools::opt::LoopDescriptor* loops = irContext->GetLoopDescriptor(function); size_t nestIterations = 1; - for (spvtools::opt::Loop* loop = (*loops)[block->id()]; loop != nullptr; - loop = loop->GetParent()) { + for (spvtools::opt::Loop* loop = (*loops)[block->id()]; loop != nullptr; loop = loop->GetParent()) { size_t iterations = 0; if (!TryGetUnrollTripCount(loop, &iterations)) { break; @@ -405,8 +393,7 @@ namespace MobileGL { static_cast(spv::LoopControlMask::MaskNone)) { break; } - mergeInst->SetOperand( - 2, {static_cast(spv::LoopControlMask::Unroll)}); + mergeInst->SetOperand(2, {static_cast(spv::LoopControlMask::Unroll)}); nestIterations *= iterations; modified = true; } @@ -462,13 +449,11 @@ namespace MobileGL { return Status::SuccessWithChange; } - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerOneChain(Instruction* accessChain, uint32_t arrayLength, - bool isImageArray) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerOneChain( + Instruction* accessChain, uint32_t arrayLength, bool isImageArray) { auto* irContext = context(); if (arrayLength == 0 || arrayLength > kMaxLoweredArrayLength) { - MGLOG_D("[spirv] resource array index: array length %u is not lowerable", - arrayLength); + MGLOG_D("[spirv] resource array index: array length %u is not lowerable", arrayLength); return LoweringOutcome::Declined; } if (!IsLowerableIndexType(irContext, accessChain->GetSingleWordInOperand(1))) { @@ -541,9 +526,8 @@ namespace MobileGL { // every path. An index outside [0, length) reaches the default target, which is // the merge block: nothing is stored, which is what indexing a block array out of // range already meant. - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerStore(Instruction* accessChain, uint32_t arrayLength, - Instruction* store) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerStore( + Instruction* accessChain, uint32_t arrayLength, Instruction* store) { auto* irContext = context(); BasicBlock* block = irContext->get_instr_block(store); if (block == nullptr) { @@ -589,8 +573,7 @@ namespace MobileGL { irContext->set_instr_block(casePtr->GetLabelInst(), casePtr); InstructionBuilder caseBuilder( - irContext, casePtr, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + irContext, casePtr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); const uint32_t constantId = ConstantLikeIndex(irContext, indexId, element); Instruction* elementChain = CloneChainWithConstantIndex(caseBuilder, irContext, accessChain, constantId); @@ -609,16 +592,15 @@ namespace MobileGL { insertAfter = casePtr; } - InstructionBuilder switchBuilder( - irContext, block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder switchBuilder(irContext, block, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); switchBuilder.AddSwitch(indexId, mergeLabelId, targets, mergeLabelId); if (irContext->get_def_use_mgr()->NumUsers(accessChain) == 0) { irContext->KillInst(accessChain); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); - MGLOG_D("[spirv] storage-block array index: lowered a dynamic write to a %u-way switch", - arrayLength); + MGLOG_D("[spirv] storage-block array index: lowered a dynamic write to a %u-way switch", arrayLength); return LoweringOutcome::Changed; } @@ -626,9 +608,8 @@ namespace MobileGL { // pick with OpSelect. Reading the elements the shader did not ask for is safe - // every one of them is a storage block this stage already declares, and an ES // driver bounds-checks a storage buffer read that lands outside what is bound. - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerLoad(Instruction* accessChain, uint32_t arrayLength, - Instruction* load) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerLoad( + Instruction* accessChain, uint32_t arrayLength, Instruction* load) { auto* irContext = context(); uint32_t conditionTypeId = 0; uint32_t dimension = 0; @@ -639,8 +620,8 @@ namespace MobileGL { const uint32_t boolTypeId = irContext->get_type_mgr()->GetBoolTypeId(); const uint32_t indexId = accessChain->GetSingleWordInOperand(1); - InstructionBuilder builder( - irContext, load, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, load, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); uint32_t selectedId = 0; for (uint32_t element = 0; element < arrayLength; ++element) { @@ -655,16 +636,13 @@ namespace MobileGL { continue; } - Instruction* isElement = - builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); + Instruction* isElement = builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); uint32_t conditionId = isElement->result_id(); if (dimension > 1) { std::vector components(dimension, conditionId); conditionId = builder.AddCompositeConstruct(conditionTypeId, components)->result_id(); } - selectedId = builder - .AddSelect(load->type_id(), conditionId, elementLoad->result_id(), - selectedId) + selectedId = builder.AddSelect(load->type_id(), conditionId, elementLoad->result_id(), selectedId) ->result_id(); } @@ -682,8 +660,8 @@ namespace MobileGL { // consume that object. So this resolves the chain one CONSUMER at a time - the // round loop in LowerToConstantSwitch recollects after each - and refuses anything // that is not a plain read or write of the loaded image. - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerImageChain(Instruction* accessChain, uint32_t arrayLength) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerImageChain( + Instruction* accessChain, uint32_t arrayLength) { auto* irContext = context(); std::vector loads; @@ -774,8 +752,7 @@ namespace MobileGL { // Drops |load| and |accessChain| once the rewrite above has taken their last user, // in that order - the load is what uses the chain. Anything still using either is // another consumer a later round will come back for. - void LegalizeResourceArrayIndexPass::KillImageChainIfDead(Instruction* accessChain, - Instruction* load) { + void LegalizeResourceArrayIndexPass::KillImageChainIfDead(Instruction* accessChain, Instruction* load) { auto* irContext = context(); if (irContext->get_def_use_mgr()->NumUsers(load) == 0) { irContext->KillInst(load); @@ -791,9 +768,8 @@ namespace MobileGL { // write still runs exactly once on every path, and an index outside [0, length) // reaches the default target - the merge block - so nothing is written, which is // what indexing an image array out of range already meant. - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerImageWrite(Instruction* accessChain, uint32_t arrayLength, - Instruction* load, Instruction* imageWrite) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerImageWrite( + Instruction* accessChain, uint32_t arrayLength, Instruction* load, Instruction* imageWrite) { auto* irContext = context(); BasicBlock* block = irContext->get_instr_block(imageWrite); if (block == nullptr) { @@ -839,13 +815,11 @@ namespace MobileGL { irContext->set_instr_block(casePtr->GetLabelInst(), casePtr); InstructionBuilder caseBuilder( - irContext, casePtr, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + irContext, casePtr, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); const uint32_t constantId = ConstantLikeIndex(irContext, indexId, element); Instruction* elementChain = CloneChainWithConstantIndex(caseBuilder, irContext, accessChain, constantId); - Instruction* elementImage = - caseBuilder.AddLoad(imageTypeId, elementChain->result_id()); + Instruction* elementImage = caseBuilder.AddLoad(imageTypeId, elementChain->result_id()); std::vector writeOperands; writeOperands.push_back({SPV_OPERAND_TYPE_ID, {elementImage->result_id()}}); @@ -860,14 +834,13 @@ namespace MobileGL { insertAfter = casePtr; } - InstructionBuilder switchBuilder( - irContext, block, IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder switchBuilder(irContext, block, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); switchBuilder.AddSwitch(indexId, mergeLabelId, targets, mergeLabelId); KillImageChainIfDead(accessChain, load); irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); - MGLOG_D("[spirv] image array index: lowered a dynamic imageStore to a %u-way switch", - arrayLength); + MGLOG_D("[spirv] image array index: lowered a dynamic imageStore to a %u-way switch", arrayLength); return LoweringOutcome::Changed; } @@ -885,15 +858,12 @@ namespace MobileGL { // an error, and never a fault - which the select then discards. A size query does not // even touch memory. Contrast an imageAtomic*, which LowerImageChain refuses for // exactly the opposite reason. - LegalizeResourceArrayIndexPass::LoweringOutcome - LegalizeResourceArrayIndexPass::LowerImageReadOrQuery(Instruction* accessChain, - uint32_t arrayLength, Instruction* load, - Instruction* consumer) { + LegalizeResourceArrayIndexPass::LoweringOutcome LegalizeResourceArrayIndexPass::LowerImageReadOrQuery( + Instruction* accessChain, uint32_t arrayLength, Instruction* load, Instruction* consumer) { auto* irContext = context(); uint32_t conditionTypeId = 0; uint32_t dimension = 0; - if (!TryGetSelectConditionType(irContext, consumer->type_id(), &conditionTypeId, - &dimension)) { + if (!TryGetSelectConditionType(irContext, consumer->type_id(), &conditionTypeId, &dimension)) { MGLOG_D("[spirv] image array index: result type is not selectable, declining"); return LoweringOutcome::Declined; } @@ -905,9 +875,8 @@ namespace MobileGL { tailOperands.push_back(consumer->GetInOperand(i)); } - InstructionBuilder builder( - irContext, consumer, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, consumer, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); uint32_t selectedId = 0; for (uint32_t element = 0; element < arrayLength; ++element) { @@ -921,9 +890,8 @@ namespace MobileGL { for (const Operand& tailOperand : tailOperands) { elementOperands.push_back(tailOperand); } - Instruction* elementResult = builder.AddInstruction( - MakeUnique(irContext, consumer->opcode(), consumer->type_id(), - irContext->TakeNextId(), elementOperands)); + Instruction* elementResult = builder.AddInstruction(MakeUnique( + irContext, consumer->opcode(), consumer->type_id(), irContext->TakeNextId(), elementOperands)); if (element == 0) { // Element 0 is the else-arm of the whole ladder, so an out-of-range // index reads it - an undefined element for an undefined index. @@ -931,17 +899,15 @@ namespace MobileGL { continue; } - Instruction* isElement = - builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); + Instruction* isElement = builder.AddBinaryOp(boolTypeId, spv::Op::OpIEqual, indexId, constantId); uint32_t conditionId = isElement->result_id(); if (dimension > 1) { std::vector components(dimension, conditionId); conditionId = builder.AddCompositeConstruct(conditionTypeId, components)->result_id(); } - selectedId = builder - .AddSelect(consumer->type_id(), conditionId, - elementResult->result_id(), selectedId) - ->result_id(); + selectedId = + builder.AddSelect(consumer->type_id(), conditionId, elementResult->result_id(), selectedId) + ->result_id(); } irContext->ReplaceAllUsesWith(consumer->result_id(), selectedId); @@ -954,14 +920,12 @@ namespace MobileGL { return LoweringOutcome::Changed; } - spvtools::Optimizer::PassToken - LegalizeResourceArrayIndexPass::CreateMarkLoopsForUnrollPass() { + spvtools::Optimizer::PassToken LegalizeResourceArrayIndexPass::CreateMarkLoopsForUnrollPass() { return spvtools::Optimizer::PassToken( MakeUnique(Mode::MarkLoopsForUnroll)); } - spvtools::Optimizer::PassToken - LegalizeResourceArrayIndexPass::CreateLowerToConstantSwitchPass() { + spvtools::Optimizer::PassToken LegalizeResourceArrayIndexPass::CreateLowerToConstantSwitchPass() { return spvtools::Optimizer::PassToken( MakeUnique(Mode::LowerToConstantSwitch)); } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.h index e6ca9025..df2e6509 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LegalizeResourceArrayIndexPass.h @@ -106,9 +106,8 @@ namespace MobileGL { explicit LegalizeResourceArrayIndexPass(Mode mode) : m_mode(mode) {} const char* name() const override { - return m_mode == Mode::MarkLoopsForUnroll - ? "mobilegl-mark-resource-array-index-loops" - : "mobilegl-lower-resource-array-index"; + return m_mode == Mode::MarkLoopsForUnroll ? "mobilegl-mark-resource-array-index-loops" + : "mobilegl-lower-resource-array-index"; } Status Process() override; @@ -141,13 +140,11 @@ namespace MobileGL { LoweringOutcome LowerLoad(spvtools::opt::Instruction* accessChain, uint32_t arrayLength, spvtools::opt::Instruction* load); LoweringOutcome LowerImageChain(spvtools::opt::Instruction* accessChain, uint32_t arrayLength); - void KillImageChainIfDead(spvtools::opt::Instruction* accessChain, - spvtools::opt::Instruction* load); + void KillImageChainIfDead(spvtools::opt::Instruction* accessChain, spvtools::opt::Instruction* load); LoweringOutcome LowerImageWrite(spvtools::opt::Instruction* accessChain, uint32_t arrayLength, spvtools::opt::Instruction* load, spvtools::opt::Instruction* imageWrite); - LoweringOutcome LowerImageReadOrQuery(spvtools::opt::Instruction* accessChain, - uint32_t arrayLength, + LoweringOutcome LowerImageReadOrQuery(spvtools::opt::Instruction* accessChain, uint32_t arrayLength, spvtools::opt::Instruction* load, spvtools::opt::Instruction* consumer); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp index ac8610b9..2175a7b8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DArrayImagesPass.cpp @@ -204,8 +204,7 @@ namespace MobileGL { if (!QueriesImageSize(instruction.opcode()) || instruction.NumInOperands() < 1) { continue; } - if (scope.Covers( - ResolveImageType(context.get(), instruction.GetSingleWordInOperand(0)))) { + if (scope.Covers(ResolveImageType(context.get(), instruction.GetSingleWordInOperand(0)))) { traits.queriesImageSize = true; return traits; } @@ -236,8 +235,7 @@ namespace MobileGL { for (auto& block : function) { for (auto& instruction : block) { if (QueriesImageSize(instruction.opcode()) && instruction.NumInOperands() >= 1 && - scope.Covers( - ResolveImageType(irContext, instruction.GetSingleWordInOperand(0)))) { + scope.Covers(ResolveImageType(irContext, instruction.GetSingleWordInOperand(0)))) { return Status::SuccessWithoutChange; } } @@ -284,36 +282,32 @@ namespace MobileGL { if (arrayed && (coordinateVector == nullptr || coordinateVector->element_count() != 2)) { return Status::Failure; } - const auto* component = - arrayed ? coordinateVector->element_type() : coordinateType; + const auto* component = arrayed ? coordinateVector->element_type() : coordinateType; const auto* componentInteger = component != nullptr ? component->AsInteger() : nullptr; if (componentInteger == nullptr) return Status::Failure; spvtools::opt::analysis::Vector widenedVector(component, arrayed ? 3 : 2); const uint32_t widenedTypeId = typeMgr->GetTypeInstruction(&widenedVector); const uint32_t intTypeId = typeMgr->GetTypeInstruction(component); - const uint32_t zeroId = componentInteger->IsSigned() - ? constantMgr->GetSIntConstId(0) - : constantMgr->GetUIntConstId(0); + const uint32_t zeroId = componentInteger->IsSigned() ? constantMgr->GetSIntConstId(0) + : constantMgr->GetUIntConstId(0); if (widenedTypeId == 0 || intTypeId == 0 || zeroId == 0) { return Status::Failure; } - InstructionBuilder builder( - irContext, &instruction, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, &instruction, + IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping); Instruction* widened = nullptr; if (arrayed) { - Instruction* u = - builder.AddCompositeExtract(intTypeId, coordinateId, {0}); - Instruction* layer = - builder.AddCompositeExtract(intTypeId, coordinateId, {1}); + Instruction* u = builder.AddCompositeExtract(intTypeId, coordinateId, {0}); + Instruction* layer = builder.AddCompositeExtract(intTypeId, coordinateId, {1}); if (u == nullptr || layer == nullptr) { return Status::Failure; } - widened = builder.AddCompositeConstruct( - widenedTypeId, {u->result_id(), zeroId, layer->result_id()}); + widened = builder.AddCompositeConstruct(widenedTypeId, + {u->result_id(), zeroId, layer->result_id()}); } else { widened = builder.AddCompositeConstruct(widenedTypeId, {coordinateId, zeroId}); } diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DSampledImagesPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DSampledImagesPass.cpp index 7c8e406c..edfe7e79 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DSampledImagesPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/Lower1DSampledImagesPass.cpp @@ -46,8 +46,7 @@ namespace MobileGL { bool Is1DSampledImageType(const Instruction* imageType) { return imageType != nullptr && imageType->opcode() == spv::Op::OpTypeImage && imageType->NumInOperands() > kSampledOperand && - static_cast(imageType->GetSingleWordInOperand(kDimOperand)) == - spv::Dim::Dim1D && + static_cast(imageType->GetSingleWordInOperand(kDimOperand)) == spv::Dim::Dim1D && imageType->GetSingleWordInOperand(kSampledOperand) == 1u; } @@ -84,11 +83,10 @@ namespace MobileGL { // Each names its element type in its last in-operand, except arrays, // whose element type is the FIRST. Both are reached here because a // sampler uniform may be declared as an array of samplers. - type = defUseMgr->GetDef( - type->opcode() == spv::Op::OpTypeArray || - type->opcode() == spv::Op::OpTypeRuntimeArray - ? type->GetSingleWordInOperand(0) - : type->GetSingleWordInOperand(type->NumInOperands() - 1)); + type = defUseMgr->GetDef(type->opcode() == spv::Op::OpTypeArray || + type->opcode() == spv::Op::OpTypeRuntimeArray + ? type->GetSingleWordInOperand(0) + : type->GetSingleWordInOperand(type->NumInOperands() - 1)); continue; default: return nullptr; @@ -225,11 +223,9 @@ namespace MobileGL { bool Any() const { return gradX != 0 || constOffset != 0 || offset != 0; } }; - OperandPositions LocateOperands(const Instruction& instruction, - uint32_t imageOperandsIndex) { + OperandPositions LocateOperands(const Instruction& instruction, uint32_t imageOperandsIndex) { OperandPositions positions; - if (imageOperandsIndex == 0xFFFFFFFFu || - instruction.NumInOperands() <= imageOperandsIndex) { + if (imageOperandsIndex == 0xFFFFFFFFu || instruction.NumInOperands() <= imageOperandsIndex) { return positions; } const uint32_t mask = instruction.GetSingleWordInOperand(imageOperandsIndex); @@ -297,8 +293,7 @@ namespace MobileGL { const analysis::Type* type = context->get_type_mgr()->GetType(def->type_id()); if (type == nullptr) return shape; const analysis::Vector* asVector = type->AsVector(); - const analysis::Type* component = - asVector != nullptr ? asVector->element_type() : type; + const analysis::Type* component = asVector != nullptr ? asVector->element_type() : type; if (component == nullptr) return shape; if (component->AsInteger() == nullptr && component->AsFloat() == nullptr) { return shape; @@ -341,8 +336,7 @@ namespace MobileGL { for (auto& function : *context->module()) { for (auto& block : function) { for (auto& instruction : block) { - const OpClassification classification = - ClassifyOpcode(instruction.opcode()); + const OpClassification classification = ClassifyOpcode(instruction.opcode()); if (classification.kind != OpKind::Texel || instruction.NumInOperands() <= classification.coordinateOperand) { continue; @@ -380,15 +374,12 @@ namespace MobileGL { RewritePlan PlanRewrite(IRContext* context, const LoweringScope& scope) { RewritePlan plan; - const auto covered = [&scope](const Instruction* type) { - return scope.Covers(type); - }; + const auto covered = [&scope](const Instruction* type) { return scope.Covers(type); }; for (auto& function : *context->module()) { for (auto& block : function) { for (auto& instruction : block) { - const OpClassification classification = - ClassifyOpcode(instruction.opcode()); + const OpClassification classification = ClassifyOpcode(instruction.opcode()); if (classification.kind == OpKind::NotImageOp || classification.kind == OpKind::DimIndependentQuery) { @@ -443,19 +434,16 @@ namespace MobileGL { // (GetPlaneCoordSize(1D) == 1). Checking it up front is what // keeps the apply phase total. const ValueShape coordinate = DescribeValue( - context, instruction.GetSingleWordInOperand( - classification.coordinateOperand)); + context, instruction.GetSingleWordInOperand(classification.coordinateOperand)); if (coordinate.componentCount == 0u || coordinate.componentCount > 3u) { plan.declined = true; return plan; } - const uint32_t scalarOperands[] = {positions.gradX, positions.gradY, - positions.offset, + const uint32_t scalarOperands[] = {positions.gradX, positions.gradY, positions.offset, positions.constOffset}; for (const uint32_t position : scalarOperands) { if (position == 0u) continue; - if (!DescribeValue(context, - instruction.GetSingleWordInOperand(position)) + if (!DescribeValue(context, instruction.GetSingleWordInOperand(position)) .IsScalar()) { plan.declined = true; return plan; @@ -466,14 +454,12 @@ namespace MobileGL { // possible if the operand really is one. if (positions.constOffset != 0u && context->get_constant_mgr()->FindDeclaredConstant( - instruction.GetSingleWordInOperand(positions.constOffset)) == - nullptr) { + instruction.GetSingleWordInOperand(positions.constOffset)) == nullptr) { plan.declined = true; return plan; } - plan.sites.push_back( - {&instruction, classification.coordinateOperand, positions}); + plan.sites.push_back({&instruction, classification.coordinateOperand, positions}); } } } @@ -481,14 +467,12 @@ namespace MobileGL { } } // namespace - bool Lower1DSampledImagesPass::BinaryHasOffsetOrGrad1DSampledImage( - const Vector& binary) { + bool Lower1DSampledImagesPass::BinaryHasOffsetOrGrad1DSampledImage(const Vector& binary) { if (binary.empty()) { return false; } std::unique_ptr context = spvtools::BuildModule( - SPV_ENV_VULKAN_1_1, - [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, + SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); if (!context) { return false; @@ -515,21 +499,17 @@ namespace MobileGL { // pattern, which for a float zero is 0 as well - so one helper serves the integer // coordinate of a fetch, the float coordinate of a sample and the float gradients // alike, without a second spelling to keep in step. - const auto zeroOf = [&](const analysis::Type* componentType, - uint32_t componentTypeId) -> uint32_t { - const analysis::Constant* constant = - constantMgr->GetConstant(componentType, {0u}); + const auto zeroOf = [&](const analysis::Type* componentType, uint32_t componentTypeId) -> uint32_t { + const analysis::Constant* constant = constantMgr->GetConstant(componentType, {0u}); if (constant == nullptr) return 0u; - const Instruction* defining = - constantMgr->GetDefiningInstruction(constant, componentTypeId); + const Instruction* defining = constantMgr->GetDefiningInstruction(constant, componentTypeId); return defining != nullptr ? defining->result_id() : 0u; }; // The whole of the arity repair, in one place: insert a zero at component 1. // Scalar u becomes (u, 0); (u, layer) becomes (u, 0, layer); (u, q) becomes // (u, 0, q). See the header for why one rule covers every shape. - const auto widen = [&](uint32_t valueId, Instruction* before, - bool mustBeConstant) -> uint32_t { + const auto widen = [&](uint32_t valueId, Instruction* before, bool mustBeConstant) -> uint32_t { const ValueShape shape = DescribeValue(irContext, valueId); if (shape.componentCount == 0u) return 0u; @@ -550,18 +530,15 @@ namespace MobileGL { if (!shape.IsScalar()) return 0u; const analysis::Type* widenedType = typeMgr->GetType(widenedTypeId); const analysis::Constant* widenedConstant = - widenedType != nullptr - ? constantMgr->GetConstant(widenedType, {valueId, zeroId}) - : nullptr; + widenedType != nullptr ? constantMgr->GetConstant(widenedType, {valueId, zeroId}) : nullptr; if (widenedConstant == nullptr) return 0u; const Instruction* defining = constantMgr->GetDefiningInstruction(widenedConstant, widenedTypeId); return defining != nullptr ? defining->result_id() : 0u; } - InstructionBuilder builder( - irContext, before, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, before, + IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); std::vector componentIds; componentIds.reserve(shape.componentCount + 1u); if (shape.IsScalar()) { @@ -569,15 +546,13 @@ namespace MobileGL { componentIds.push_back(zeroId); } else { for (uint32_t i = 0; i < shape.componentCount; ++i) { - Instruction* extracted = - builder.AddCompositeExtract(componentTypeId, valueId, {i}); + Instruction* extracted = builder.AddCompositeExtract(componentTypeId, valueId, {i}); if (extracted == nullptr) return 0u; componentIds.push_back(extracted->result_id()); if (i == 0u) componentIds.push_back(zeroId); } } - Instruction* widened = - builder.AddCompositeConstruct(widenedTypeId, componentIds); + Instruction* widened = builder.AddCompositeConstruct(widenedTypeId, componentIds); return widened != nullptr ? widened->result_id() : 0u; }; @@ -589,19 +564,16 @@ namespace MobileGL { bool mustBeConstant; }; const Target targets[] = { - {site.coordinateOperand, false}, - {site.operands.gradX, false}, - {site.operands.gradY, false}, - {site.operands.offset, false}, + {site.coordinateOperand, false}, {site.operands.gradX, false}, + {site.operands.gradY, false}, {site.operands.offset, false}, {site.operands.constOffset, true}, }; for (const Target& target : targets) { // Position 0 is the image operand, so it is this plan's "absent" marker // for everything except the coordinate, which is never 0. if (target.position == 0u) continue; - const uint32_t widenedId = - widen(instruction->GetSingleWordInOperand(target.position), instruction, - target.mustBeConstant); + const uint32_t widenedId = widen(instruction->GetSingleWordInOperand(target.position), + instruction, target.mustBeConstant); if (widenedId == 0u) { // Reachable only if the module's shapes disagree with what the plan // recorded. Failing here makes the caller keep the input binary, @@ -631,8 +603,7 @@ namespace MobileGL { // instruction valid and RemoveDuplicates collapses the pair. if (!AnyDim1DSampledTypeLeft(irContext)) { for (Instruction& capability : irContext->capabilities()) { - const auto value = - static_cast(capability.GetSingleWordInOperand(0)); + const auto value = static_cast(capability.GetSingleWordInOperand(0)); if (value == spv::Capability::Sampled1D) { capability.SetInOperand(0, {static_cast(spv::Capability::Shader)}); } @@ -644,8 +615,7 @@ namespace MobileGL { } spvtools::Optimizer::PassToken Lower1DSampledImagesPass::CreateLower1DSampledImagesPass() { - return spvtools::Optimizer::PassToken( - spvtools::MakeUnique()); + return spvtools::Optimizer::PassToken(spvtools::MakeUnique()); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp index e778e3a1..474b7ad8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerPixelCenterIntegerPass.cpp @@ -77,8 +77,7 @@ namespace MobileGL { std::vector modes; for (Instruction& mode : irContext->module()->execution_modes()) { - if (mode.opcode() == spv::Op::OpExecutionMode && - mode.NumInOperands() > kExecutionModeModeOperand && + if (mode.opcode() == spv::Op::OpExecutionMode && mode.NumInOperands() > kExecutionModeModeOperand && static_cast(mode.GetSingleWordInOperand(kExecutionModeModeOperand)) == spv::ExecutionMode::PixelCenterInteger) { modes.push_back(&mode); @@ -94,7 +93,8 @@ namespace MobileGL { const auto* floatType = typeMgr->GetType(floatTypeId); if (floatType == nullptr || floatType->AsFloat() == nullptr) return 0; const auto* constant = constantMgr->GetConstant(floatType, {std::bit_cast(value)}); - const Instruction* inst = constant ? constantMgr->GetDefiningInstruction(constant, floatTypeId) : nullptr; + const Instruction* inst = + constant ? constantMgr->GetDefiningInstruction(constant, floatTypeId) : nullptr; return inst ? inst->result_id() : 0; }; @@ -155,7 +155,8 @@ namespace MobileGL { } if (refused) return Status::Failure; - for (Instruction* mode : modes) irContext->KillInst(mode); + for (Instruction* mode : modes) + irContext->KillInst(mode); // `%r = OpLoad %ty %ptr` becomes `%fresh = OpLoad %ty %ptr` followed by // `%r = OpFSub %ty %fresh %offset`: every existing use of %r picks the shifted @@ -170,10 +171,10 @@ namespace MobileGL { Instruction* next = shifted.load->NextNode(); if (next == nullptr) return Status::Failure; shifted.load->SetResultId(freshId); - next->InsertBefore(MakeUnique( - irContext, spv::Op::OpFSub, typeId, resultId, - Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {freshId}}, - {SPV_OPERAND_TYPE_ID, {shifted.offsetId}}})); + next->InsertBefore( + MakeUnique(irContext, spv::Op::OpFSub, typeId, resultId, + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {freshId}}, + {SPV_OPERAND_TYPE_ID, {shifted.offsetId}}})); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp index 93a63dba..36ff88bd 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerViewportIndexPass.cpp @@ -51,10 +51,8 @@ namespace MobileGL { // global has no defined value to read, so lowering that one would answer the // shader with garbage instead of the viewport it asked for. That case is left for // the driver to reject. - Instruction* GetDecoratedViewportIndexOutput(IRContext* context, - const Instruction& annotation) { - Instruction* variable = - context->get_def_use_mgr()->GetDef(annotation.GetSingleWordInOperand(0)); + Instruction* GetDecoratedViewportIndexOutput(IRContext* context, const Instruction& annotation) { + Instruction* variable = context->get_def_use_mgr()->GetDef(annotation.GetSingleWordInOperand(0)); if (variable == nullptr || variable->opcode() != spv::Op::OpVariable || static_cast(variable->GetSingleWordInOperand(0)) != spv::StorageClass::Output) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp index 28471ded..79d869a0 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp @@ -27,8 +27,8 @@ namespace MobileGL { namespace ShaderTranspiler { namespace { using spvtools::opt::Instruction; - using spvtools::opt::IRContext; using spvtools::opt::InstructionBuilder; + using spvtools::opt::IRContext; // Image operations whose coordinate operand (in-operand 1) is a plain // normalized coordinate and nothing else. The Dref *sample* forms are absent: @@ -80,7 +80,7 @@ namespace MobileGL { // The bare image an OpImageQuerySizeLod needs. An operation on a sampled image // has to unwrap it first; one already holding a bare image is used as is. uint32_t GetQueryableImage(IRContext* context, InstructionBuilder& builder, uint32_t imageOperandId, - uint32_t imageTypeId) { + uint32_t imageTypeId) { auto* defUseMgr = context->get_def_use_mgr(); Instruction* object = defUseMgr->GetDef(imageOperandId); if (object == nullptr) return 0; @@ -136,9 +136,9 @@ namespace MobileGL { } const uint32_t coordinateId = instruction.GetSingleWordInOperand(1); - InstructionBuilder builder( - irContext, &instruction, - IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); + InstructionBuilder builder(irContext, &instruction, + IRContext::kAnalysisDefUse | + IRContext::kAnalysisInstrToBlockMapping); const uint32_t queryableImageId = GetQueryableImage(irContext, builder, imageOperandId, imageType->result_id()); if (queryableImageId == 0) { @@ -151,8 +151,8 @@ namespace MobileGL { queryableImageId, lodZeroId); Instruction* sizeFloat = builder.AddUnaryOp(float2TypeId, spv::Op::OpConvertSToF, size->result_id()); - Instruction* normalized = builder.AddBinaryOp( - float2TypeId, spv::Op::OpFDiv, coordinateId, sizeFloat->result_id()); + Instruction* normalized = builder.AddBinaryOp(float2TypeId, spv::Op::OpFDiv, coordinateId, + sizeFloat->result_id()); instruction.SetInOperand(1, {normalized->result_id()}); irContext->UpdateDefUse(&instruction); rewroteCoordinate = true; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp index 77a19ebe..b1413a1e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp @@ -23,8 +23,8 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { namespace { - using spvtools::opt::IRContext; using spvtools::opt::Instruction; + using spvtools::opt::IRContext; using spvtools::opt::Operand; namespace analysis = spvtools::opt::analysis; @@ -69,8 +69,7 @@ namespace MobileGL { for (Instruction& inst : irContext->types_values()) { if (inst.opcode() != spv::Op::OpVariable) continue; - if (static_cast(inst.GetSingleWordInOperand(0)) != - spv::StorageClass::Input) { + if (static_cast(inst.GetSingleWordInOperand(0)) != spv::StorageClass::Input) { continue; } Instruction* pointerType = defUseMgr->GetDef(inst.type_id()); @@ -83,8 +82,8 @@ namespace MobileGL { // an input over two attribute locations. Left alone; the vertex-input // factory declines the matching attribute for the same reason. MGLOG_E_ONCE("PackDoubleVertexInputsPass: vertex input %%%u is a %u-component 64-bit " - "float; only double and dvec2 inputs can be packed", - inst.result_id(), components); + "float; only double and dvec2 inputs can be packed", + inst.result_id(), components); continue; } targets.push_back({&inst, pointeeTypeId, components}); @@ -138,18 +137,15 @@ namespace MobileGL { irContext->AddGlobalValue(spvtools::MakeUnique( irContext, spv::Op::OpVariable, packedPointerTypeId, packedVariableId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); // The interface decorations belong to whatever is actually the Input now. std::vector deadDecorations; for (auto& annotation : irContext->annotations()) { if (annotation.opcode() != spv::Op::OpDecorate) continue; if (annotation.GetSingleWordInOperand(0) != oldVariableId) continue; - const auto decoration = - static_cast(annotation.GetSingleWordInOperand(1)); - if (decoration == spv::Decoration::Location || - decoration == spv::Decoration::Component || + const auto decoration = static_cast(annotation.GetSingleWordInOperand(1)); + if (decoration == spv::Decoration::Location || decoration == spv::Decoration::Component || decoration == spv::Decoration::RelaxedPrecision) { annotation.SetInOperand(0, {packedVariableId}); } else { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PrivateToEntryLocalPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PrivateToEntryLocalPass.cpp index 74196cf4..655bb79b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PrivateToEntryLocalPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PrivateToEntryLocalPass.cpp @@ -141,21 +141,19 @@ namespace MobileGL { // The cases here have to match the cases in UpdateUse: a use the rewrite // does not know how to update disqualifies the variable. switch (inst->opcode()) { - case spv::Op::OpLoad: - case spv::Op::OpImageTexelPointer: // treat like a load - return true; - case spv::Op::OpStore: - // Storing the variable's ADDRESS somewhere else escapes it. - return inst->GetOperand(1).AsId() != variableId; - case spv::Op::OpAccessChain: - return context()->get_def_use_mgr()->WhileEachUser( - inst, [this, inst](const Instruction* user) { - return IsValidUse(user, inst->result_id()); - }); - case spv::Op::OpName: - return true; - default: - return spvOpcodeIsDecoration(inst->opcode()); + case spv::Op::OpLoad: + case spv::Op::OpImageTexelPointer: // treat like a load + return true; + case spv::Op::OpStore: + // Storing the variable's ADDRESS somewhere else escapes it. + return inst->GetOperand(1).AsId() != variableId; + case spv::Op::OpAccessChain: + return context()->get_def_use_mgr()->WhileEachUser( + inst, [this, inst](const Instruction* user) { return IsValidUse(user, inst->result_id()); }); + case spv::Op::OpName: + return true; + default: + return spvOpcodeIsDecoration(inst->opcode()); } } @@ -166,8 +164,7 @@ namespace MobileGL { std::unique_ptr var(variable); // take ownership context()->ForgetUses(variable); - variable->SetInOperand(kVariableStorageClassInIdx, - {uint32_t(spv::StorageClass::Function)}); + variable->SetInOperand(kVariableStorageClassInIdx, {uint32_t(spv::StorageClass::Function)}); const uint32_t newTypeId = GetNewType(variable->type_id()); if (newTypeId == 0) { @@ -185,10 +182,8 @@ namespace MobileGL { uint32_t PrivateToEntryLocalPass::GetNewType(uint32_t oldTypeId) { auto* typeMgr = context()->get_type_mgr(); Instruction* oldTypeInst = get_def_use_mgr()->GetDef(oldTypeId); - const uint32_t pointeeTypeId = - oldTypeInst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx); - const uint32_t newTypeId = - typeMgr->FindPointerToType(pointeeTypeId, spv::StorageClass::Function); + const uint32_t pointeeTypeId = oldTypeInst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx); + const uint32_t newTypeId = typeMgr->FindPointerToType(pointeeTypeId, spv::StorageClass::Function); if (newTypeId != 0) { context()->UpdateDefUse(context()->get_def_use_mgr()->GetDef(newTypeId)); } @@ -198,31 +193,30 @@ namespace MobileGL { bool PrivateToEntryLocalPass::UpdateUse(Instruction* inst, Instruction* user) { // The cases here have to match the cases in IsValidUse. switch (inst->opcode()) { - case spv::Op::OpLoad: - case spv::Op::OpStore: - case spv::Op::OpImageTexelPointer: // treat like a load - // Fine as-is: their type is the pointed-to type, which is unchanged. - break; - case spv::Op::OpAccessChain: { - context()->ForgetUses(inst); - const uint32_t newTypeId = GetNewType(inst->type_id()); - if (newTypeId == 0) { - return false; - } - inst->SetResultType(newTypeId); - context()->AnalyzeUses(inst); - if (!UpdateUses(inst)) { - return false; - } - break; + case spv::Op::OpLoad: + case spv::Op::OpStore: + case spv::Op::OpImageTexelPointer: // treat like a load + // Fine as-is: their type is the pointed-to type, which is unchanged. + break; + case spv::Op::OpAccessChain: { + context()->ForgetUses(inst); + const uint32_t newTypeId = GetNewType(inst->type_id()); + if (newTypeId == 0) { + return false; } - case spv::Op::OpName: - case spv::Op::OpEntryPoint: // handled separately in Process() - break; - default: - assert(spvOpcodeIsDecoration(inst->opcode()) && - "PrivateToEntryLocalPass: unexpected use opcode"); - break; + inst->SetResultType(newTypeId); + context()->AnalyzeUses(inst); + if (!UpdateUses(inst)) { + return false; + } + break; + } + case spv::Op::OpName: + case spv::Op::OpEntryPoint: // handled separately in Process() + break; + default: + assert(spvOpcodeIsDecoration(inst->opcode()) && "PrivateToEntryLocalPass: unexpected use opcode"); + break; } (void)user; return true; @@ -231,8 +225,7 @@ namespace MobileGL { bool PrivateToEntryLocalPass::UpdateUses(Instruction* inst) { const uint32_t id = inst->result_id(); std::vector uses; - context()->get_def_use_mgr()->ForEachUser(id, - [&uses](Instruction* use) { uses.push_back(use); }); + context()->get_def_use_mgr()->ForEachUser(id, [&uses](Instruction* use) { uses.push_back(use); }); for (Instruction* use : uses) { if (!UpdateUse(use, inst)) { return false; @@ -242,8 +235,7 @@ namespace MobileGL { } spvtools::Optimizer::PassToken PrivateToEntryLocalPass::CreatePrivateToEntryLocalPass() { - return spvtools::Optimizer::PassToken( - spvtools::MakeUnique()); + return spvtools::Optimizer::PassToken(spvtools::MakeUnique()); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp index 2ece4125..60516b55 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp @@ -63,17 +63,14 @@ namespace MobileGL { context->AddGlobalValue(spvtools::MakeUnique( context, spv::Op::OpVariable, pointerTypeId, variableId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); context->AddAnnotationInst(spvtools::MakeUnique( context, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {variableId}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::BuiltIn)}}, - {SPV_OPERAND_TYPE_LITERAL_INTEGER, - {static_cast(spv::BuiltIn::BaseInstance)}}})); + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::BuiltIn)}}, + {SPV_OPERAND_TYPE_LITERAL_INTEGER, {static_cast(spv::BuiltIn::BaseInstance)}}})); // Input variables must appear in the entry point interface list. for (Instruction& entryPoint : context->module()->entry_points()) { @@ -97,8 +94,7 @@ namespace MobileGL { // Collect every load of the InstanceIndex variable before mutating anything. std::vector instanceLoads; defUseMgr->ForEachUser(instanceIndexVar, [&](Instruction* user) { - if (user->opcode() == spv::Op::OpLoad && - user->GetSingleWordInOperand(0) == instanceIndexVarId) { + if (user->opcode() == spv::Op::OpLoad && user->GetSingleWordInOperand(0) == instanceIndexVarId) { instanceLoads.push_back(user); } }); @@ -114,8 +110,8 @@ namespace MobileGL { Instruction* baseInstanceVar = FindBuiltinInputVariable(irContext, spv::BuiltIn::BaseInstance); const uint32_t baseInstanceVarId = (baseInstanceVar != nullptr) - ? baseInstanceVar->result_id() - : SynthesizeBaseInstanceVariable(irContext, instanceIndexVar); + ? baseInstanceVar->result_id() + : SynthesizeBaseInstanceVariable(irContext, instanceIndexVar); // Replace each `OpLoad %ty %res %instanceIndex` with // OpLoad %ty %fresh1 %instanceIndex @@ -135,9 +131,8 @@ namespace MobileGL { std::initializer_list{{SPV_OPERAND_TYPE_ID, {baseInstanceVarId}}})); loadInst->SetOpcode(spv::Op::OpISub); - loadInst->SetInOperands(Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {instanceValueId}}, - {SPV_OPERAND_TYPE_ID, {baseValueId}}}); + loadInst->SetInOperands(Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {instanceValueId}}, + {SPV_OPERAND_TYPE_ID, {baseValueId}}}); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp index 6fc8adfa..c0e99c92 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameBuiltinShadowingFunctionsPass.cpp @@ -39,22 +39,21 @@ namespace MobileGL { // glslang mangles function OpNames as "name("; the base name is // everything before the '(' (entry points like "main" carry no mangling). const std::string mangled = debugInst.GetInOperand(1).AsString(); - const std::string_view baseName = - std::string_view(mangled).substr(0, mangled.find('(')); + const std::string_view baseName = std::string_view(mangled).substr(0, mangled.find('(')); if (!IsEsslBuiltinFunctionName(baseName)) { continue; } - debugInst.SetInOperand(1, spvtools::utils::MakeVector( - "mg_" + mangled)); + debugInst.SetInOperand( + 1, spvtools::utils::MakeVector("mg_" + mangled)); modified = true; } return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } - spvtools::Optimizer::PassToken - RenameBuiltinShadowingFunctionsPass::CreateRenameBuiltinShadowingFunctionsPass() { + spvtools::Optimizer::PassToken RenameBuiltinShadowingFunctionsPass:: + CreateRenameBuiltinShadowingFunctionsPass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp index 32967af7..ba65fc55 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp @@ -26,7 +26,7 @@ namespace MobileGL { } const char* GetConflictingFunctionParameterCompatName(spvtools::opt::IRContext* context, - spvtools::opt::Instruction& nameInst) { + spvtools::opt::Instruction& nameInst) { if (nameInst.opcode() != spv::Op::OpName || nameInst.NumInOperands() < 2) { return nullptr; } @@ -61,7 +61,8 @@ namespace MobileGL { return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; } - spvtools::Optimizer::PassToken RenameSamplerFunctionParameterPass::CreateRenameSamplerFunctionParameterPass() { + spvtools::Optimizer::PassToken RenameSamplerFunctionParameterPass:: + CreateRenameSamplerFunctionParameterPass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp index 329edff5..8e0364c9 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/SplitArrayVertexInputsPass.cpp @@ -25,8 +25,8 @@ namespace MobileGL { namespace MG_Util { namespace ShaderTranspiler { namespace { - using spvtools::opt::IRContext; using spvtools::opt::Instruction; + using spvtools::opt::IRContext; using spvtools::opt::Operand; namespace analysis = spvtools::opt::analysis; @@ -39,8 +39,7 @@ namespace MobileGL { Uint32 LocationsPerElement(const analysis::Type* type) { if (type == nullptr) return 0; // Any scalar takes one location, a 64-bit one included. - if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || - type->AsBool() != nullptr) { + if (type->AsFloat() != nullptr || type->AsInteger() != nullptr || type->AsBool() != nullptr) { return 1u; } if (const auto* vector = type->AsVector()) { @@ -109,8 +108,7 @@ namespace MobileGL { for (Instruction& inst : irContext->types_values()) { if (inst.opcode() != spv::Op::OpVariable) continue; - if (static_cast(inst.GetSingleWordInOperand(0)) != - spv::StorageClass::Input) { + if (static_cast(inst.GetSingleWordInOperand(0)) != spv::StorageClass::Input) { continue; } Instruction* pointerType = defUseMgr->GetDef(inst.type_id()); @@ -176,10 +174,9 @@ namespace MobileGL { // it) either cannot happen for a vertex input or would need the callee // rewritten as well, and silently getting that wrong is worse than // leaving the module for SPIRV-Cross to reject out loud. - const Bool indexes = - opcode == spv::Op::OpAccessChain || - opcode == spv::Op::OpInBoundsAccessChain || - opcode == spv::Op::OpCopyObject; + const Bool indexes = opcode == spv::Op::OpAccessChain || + opcode == spv::Op::OpInBoundsAccessChain || + opcode == spv::Op::OpCopyObject; if (indexes) { if (inst.NumInOperands() > 0 && derivedPointers.count(inst.GetSingleWordInOperand(0)) != 0) { @@ -265,17 +262,14 @@ namespace MobileGL { const Uint32 elementVariableId = irContext->TakeNextId(); elementVariableIds[element] = elementVariableId; irContext->AddGlobalValue(spvtools::MakeUnique( - irContext, spv::Op::OpVariable, target.elementInputPointerTypeId, - elementVariableId, + irContext, spv::Op::OpVariable, target.elementInputPointerTypeId, elementVariableId, std::initializer_list{ - {SPV_OPERAND_TYPE_STORAGE_CLASS, - {static_cast(spv::StorageClass::Input)}}})); + {SPV_OPERAND_TYPE_STORAGE_CLASS, {static_cast(spv::StorageClass::Input)}}})); newDecorations.push_back(spvtools::MakeUnique( irContext, spv::Op::OpDecorate, 0, 0, std::initializer_list{ {SPV_OPERAND_TYPE_ID, {elementVariableId}}, - {SPV_OPERAND_TYPE_DECORATION, - {static_cast(spv::Decoration::Location)}}, + {SPV_OPERAND_TYPE_DECORATION, {static_cast(spv::Decoration::Location)}}, {SPV_OPERAND_TYPE_LITERAL_INTEGER, {target.baseLocation + element * target.locationsPerElement}}})); } @@ -288,12 +282,9 @@ namespace MobileGL { for (auto& annotation : irContext->annotations()) { if (annotation.opcode() != spv::Op::OpDecorate) continue; if (annotation.GetSingleWordInOperand(0) != oldVariableId) continue; - const auto decoration = - static_cast(annotation.GetSingleWordInOperand(1)); - if (decoration == spv::Decoration::RelaxedPrecision || - decoration == spv::Decoration::Flat || - decoration == spv::Decoration::NoPerspective || - decoration == spv::Decoration::Centroid || + const auto decoration = static_cast(annotation.GetSingleWordInOperand(1)); + if (decoration == spv::Decoration::RelaxedPrecision || decoration == spv::Decoration::Flat || + decoration == spv::Decoration::NoPerspective || decoration == spv::Decoration::Centroid || decoration == spv::Decoration::Sample) { for (const Uint32 elementVariableId : elementVariableIds) { std::vector operands; @@ -301,8 +292,8 @@ namespace MobileGL { for (Uint32 i = 1; i < annotation.NumInOperands(); ++i) { operands.push_back(annotation.GetInOperand(i)); } - newDecorations.push_back(spvtools::MakeUnique( - irContext, spv::Op::OpDecorate, 0, 0, operands)); + newDecorations.push_back( + spvtools::MakeUnique(irContext, spv::Op::OpDecorate, 0, 0, operands)); } } deadDecorations.push_back(&annotation); @@ -329,8 +320,8 @@ namespace MobileGL { std::vector rebuilt; for (Uint32 i = 0; i < interfaceOperands.size(); ++i) { const Operand& operand = interfaceOperands[i]; - if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID && - operand.words.size() == 1 && operand.words[0] == oldVariableId) { + if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID && operand.words.size() == 1 && + operand.words[0] == oldVariableId) { for (const Uint32 elementVariableId : elementVariableIds) { rebuilt.push_back({SPV_OPERAND_TYPE_ID, {elementVariableId}}); } @@ -347,15 +338,12 @@ namespace MobileGL { const Uint32 elementPointerId = irContext->TakeNextId(); insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpLoad, target.elementTypeId, loadedId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {elementVariableIds[element]}}})); + std::initializer_list{{SPV_OPERAND_TYPE_ID, {elementVariableIds[element]}}})); ++insertPoint; insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpAccessChain, target.elementPrivatePointerTypeId, - elementPointerId, - std::initializer_list{ - {SPV_OPERAND_TYPE_ID, {oldVariableId}}, - {SPV_OPERAND_TYPE_ID, {indexConstantIds[element]}}})); + irContext, spv::Op::OpAccessChain, target.elementPrivatePointerTypeId, elementPointerId, + std::initializer_list{{SPV_OPERAND_TYPE_ID, {oldVariableId}}, + {SPV_OPERAND_TYPE_ID, {indexConstantIds[element]}}})); ++insertPoint; insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpStore, 0, 0, @@ -378,8 +366,8 @@ namespace MobileGL { spv::StorageClass::Private) { continue; } - pointer->SetResultType(typeMgr->FindPointerToType(resultType->GetSingleWordInOperand(1), - spv::StorageClass::Private)); + pointer->SetResultType( + typeMgr->FindPointerToType(resultType->GetSingleWordInOperand(1), spv::StorageClass::Private)); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.cpp index ef940a2b..09c03c50 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.cpp @@ -61,10 +61,8 @@ namespace MobileGL { const std::unordered_set& blockStructIds, spv::StorageClass& outStorageClass) { if (variable.opcode() != spv::Op::OpVariable) return 0; - const auto storageClass = - static_cast(variable.GetSingleWordInOperand(0)); - if (storageClass != spv::StorageClass::Input && - storageClass != spv::StorageClass::Output) { + const auto storageClass = static_cast(variable.GetSingleWordInOperand(0)); + if (storageClass != spv::StorageClass::Input && storageClass != spv::StorageClass::Output) { return 0; } @@ -87,8 +85,7 @@ namespace MobileGL { return pointeeId; } - Bool DirectionIsArmed(spv::StorageClass storageClass, Bool stripInputBlocks, - Bool stripOutputBlocks) { + Bool DirectionIsArmed(spv::StorageClass storageClass, Bool stripInputBlocks, Bool stripOutputBlocks) { return storageClass == spv::StorageClass::Input ? stripInputBlocks : stripOutputBlocks; } } // namespace @@ -155,16 +152,14 @@ namespace MobileGL { for (Instruction& annotation : irContext->module()->annotations()) { if (annotation.opcode() == spv::Op::OpDecorate) { if (!isLocationOrComponent(annotation.GetSingleWordInOperand(1))) continue; - if (armedVariableIds.find(annotation.GetSingleWordInOperand(0)) == - armedVariableIds.end()) { + if (armedVariableIds.find(annotation.GetSingleWordInOperand(0)) == armedVariableIds.end()) { continue; } toKill.push_back(&annotation); } else if (annotation.opcode() == spv::Op::OpMemberDecorate) { // OpMemberDecorate ... if (!isLocationOrComponent(annotation.GetSingleWordInOperand(2))) continue; - if (armedStructIds.find(annotation.GetSingleWordInOperand(0)) == - armedStructIds.end()) { + if (armedStructIds.find(annotation.GetSingleWordInOperand(0)) == armedStructIds.end()) { continue; } toKill.push_back(&annotation); @@ -180,8 +175,8 @@ namespace MobileGL { spvtools::Optimizer::PassToken StripIoBlockLocationsPass::CreateStripIoBlockLocationsPass( Bool stripInputBlocks, Bool stripOutputBlocks, Bool* strippedAny) { - return spvtools::Optimizer::PassToken(spvtools::MakeUnique( - stripInputBlocks, stripOutputBlocks, strippedAny)); + return spvtools::Optimizer::PassToken( + spvtools::MakeUnique(stripInputBlocks, stripOutputBlocks, strippedAny)); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.h index 9b5f102a..d9be3fe2 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripIoBlockLocationsPass.h @@ -70,16 +70,16 @@ namespace MobileGL { // `stripOutputBlocks` the ones it PRODUCES. `strippedAny`, when non-null, // receives whether this stage actually had one, so the caller can decline the // re-serialised module when there was nothing to strip. - StripIoBlockLocationsPass(Bool stripInputBlocks, Bool stripOutputBlocks, - Bool* strippedAny = nullptr) + StripIoBlockLocationsPass(Bool stripInputBlocks, Bool stripOutputBlocks, Bool* strippedAny = nullptr) : m_stripInputBlocks(stripInputBlocks), m_stripOutputBlocks(stripOutputBlocks), m_strippedAny(strippedAny) {} const char* name() const override { return "mobilegl-strip-io-block-locations"; } Status Process() override; - static spvtools::Optimizer::PassToken CreateStripIoBlockLocationsPass( - Bool stripInputBlocks, Bool stripOutputBlocks, Bool* strippedAny); + static spvtools::Optimizer::PassToken CreateStripIoBlockLocationsPass(Bool stripInputBlocks, + Bool stripOutputBlocks, + Bool* strippedAny); private: Bool m_stripInputBlocks = false; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp index 8cc02ec1..cccc571e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp @@ -110,8 +110,8 @@ namespace MobileGL { return Status::SuccessWithChange; } - spvtools::Optimizer::PassToken - StripUboMemberRelaxedPrecisionPass::CreateStripUboMemberRelaxedPrecisionPass() { + spvtools::Optimizer::PassToken StripUboMemberRelaxedPrecisionPass:: + CreateStripUboMemberRelaxedPrecisionPass() { return spvtools::Optimizer::PassToken(MakeUnique()); } } // namespace ShaderTranspiler diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUniformLocationsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUniformLocationsPass.cpp index 6469f943..f325a1a3 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUniformLocationsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUniformLocationsPass.cpp @@ -24,23 +24,21 @@ namespace MobileGL { if (annotation.opcode() != spv::Op::OpDecorate) { continue; } - if (annotation.GetSingleWordInOperand(1) != - static_cast(spv::Decoration::Location)) { + if (annotation.GetSingleWordInOperand(1) != static_cast(spv::Decoration::Location)) { continue; } - Instruction* target = - get_def_use_mgr()->GetDef(annotation.GetSingleWordInOperand(0)); + Instruction* target = get_def_use_mgr()->GetDef(annotation.GetSingleWordInOperand(0)); if (target == nullptr || target->opcode() != spv::Op::OpVariable) { continue; } switch (spv::StorageClass(target->GetSingleWordInOperand(0))) { - case spv::StorageClass::UniformConstant: - case spv::StorageClass::Uniform: - case spv::StorageClass::StorageBuffer: - toKill.push_back(&annotation); - break; - default: - break; + case spv::StorageClass::UniformConstant: + case spv::StorageClass::Uniform: + case spv::StorageClass::StorageBuffer: + toKill.push_back(&annotation); + break; + default: + break; } } @@ -51,8 +49,7 @@ namespace MobileGL { } spvtools::Optimizer::PassToken StripUniformLocationsPass::CreateStripUniformLocationsPass() { - return spvtools::Optimizer::PassToken( - spvtools::MakeUnique()); + return spvtools::Optimizer::PassToken(spvtools::MakeUnique()); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp index 666c0613..5ac12ce5 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.cpp @@ -66,10 +66,8 @@ namespace MobileGL { const std::unordered_set& blockStructIds, spv::StorageClass& outStorageClass) { if (variable.opcode() != spv::Op::OpVariable) return 0; - const auto storageClass = - static_cast(variable.GetSingleWordInOperand(0)); - if (storageClass != spv::StorageClass::Input && - storageClass != spv::StorageClass::Output) { + const auto storageClass = static_cast(variable.GetSingleWordInOperand(0)); + if (storageClass != spv::StorageClass::Input && storageClass != spv::StorageClass::Output) { return 0; } @@ -129,8 +127,7 @@ namespace MobileGL { std::unordered_map storageMaskByStructId; }; - IoBlockIndex IndexIoBlocks(IRContext* irContext, - const std::unordered_set& blockStructIds) { + IoBlockIndex IndexIoBlocks(IRContext* irContext, const std::unordered_set& blockStructIds) { IoBlockIndex index; for (Instruction& variable : irContext->module()->types_values()) { spv::StorageClass storageClass = spv::StorageClass::Input; @@ -196,8 +193,7 @@ namespace MobileGL { const Bool isInput = direction == 0; const std::map& byName = isInput ? index.inputStructByName : index.outputStructByName; - const std::map& renames = - isInput ? m_inputBlockRenames : m_outputBlockRenames; + const std::map& renames = isInput ? m_inputBlockRenames : m_outputBlockRenames; const Uint32 wantedMask = isInput ? kSeenAsInput : kSeenAsOutput; for (const auto& block : byName) { @@ -221,10 +217,10 @@ namespace MobileGL { } spvtools::Optimizer::PassToken UniquifyIoBlockNamesPass::CreateUniquifyIoBlockNamesPass( - const std::map& inputBlockRenames, - const std::map& outputBlockRenames, std::set* renamedBlockNames) { - return spvtools::Optimizer::PassToken(MakeUnique( - inputBlockRenames, outputBlockRenames, renamedBlockNames)); + const std::map& inputBlockRenames, const std::map& outputBlockRenames, + std::set* renamedBlockNames) { + return spvtools::Optimizer::PassToken( + MakeUnique(inputBlockRenames, outputBlockRenames, renamedBlockNames)); } } // namespace ShaderTranspiler } // namespace MG_Util diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.h b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.h index 11e0594e..731148c7 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/UniquifyIoBlockNamesPass.h @@ -77,8 +77,7 @@ namespace MobileGL { static spvtools::Optimizer::PassToken CreateUniquifyIoBlockNamesPass( const std::map& inputBlockRenames, - const std::map& outputBlockRenames, - std::set* renamedBlockNames); + const std::map& outputBlockRenames, std::set* renamedBlockNames); private: std::map m_inputBlockRenames; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/WidenImageFormatsPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/WidenImageFormatsPass.cpp index c152e59d..1489a8d8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/WidenImageFormatsPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/WidenImageFormatsPass.cpp @@ -136,43 +136,62 @@ namespace MobileGL { ImageFormatWidening WideningOfSpirvImageFormat(spv::ImageFormat format) { switch (format) { // Float. - case spv::ImageFormat::Rg32f: return {spv::ImageFormat::Rgba32f, 2}; - case spv::ImageFormat::Rg16f: return {spv::ImageFormat::Rgba16f, 2}; - case spv::ImageFormat::R16f: return {spv::ImageFormat::Rgba16f, 1}; + case spv::ImageFormat::Rg32f: + return {spv::ImageFormat::Rgba32f, 2}; + case spv::ImageFormat::Rg16f: + return {spv::ImageFormat::Rgba16f, 2}; + case spv::ImageFormat::R16f: + return {spv::ImageFormat::Rgba16f, 1}; // Not a channel widening but a lossless re-encoding - see above. Three // channels, so the fourth reads as the 1 GL defines for a format without one. - case spv::ImageFormat::R11fG11fB10f: return {spv::ImageFormat::Rgba16f, 3}; + case spv::ImageFormat::R11fG11fB10f: + return {spv::ImageFormat::Rgba16f, 3}; // Unsigned normalized. - case spv::ImageFormat::Rg8: return {spv::ImageFormat::Rgba8, 2}; - case spv::ImageFormat::R8: return {spv::ImageFormat::Rgba8, 1}; + case spv::ImageFormat::Rg8: + return {spv::ImageFormat::Rgba8, 2}; + case spv::ImageFormat::R8: + return {spv::ImageFormat::Rgba8, 1}; // Signed normalized. - case spv::ImageFormat::Rg8Snorm: return {spv::ImageFormat::Rgba8Snorm, 2}; - case spv::ImageFormat::R8Snorm: return {spv::ImageFormat::Rgba8Snorm, 1}; + case spv::ImageFormat::Rg8Snorm: + return {spv::ImageFormat::Rgba8Snorm, 2}; + case spv::ImageFormat::R8Snorm: + return {spv::ImageFormat::Rgba8Snorm, 1}; // Signed integer. - case spv::ImageFormat::Rg32i: return {spv::ImageFormat::Rgba32i, 2}; - case spv::ImageFormat::Rg16i: return {spv::ImageFormat::Rgba16i, 2}; - case spv::ImageFormat::R16i: return {spv::ImageFormat::Rgba16i, 1}; - case spv::ImageFormat::Rg8i: return {spv::ImageFormat::Rgba8i, 2}; - case spv::ImageFormat::R8i: return {spv::ImageFormat::Rgba8i, 1}; + case spv::ImageFormat::Rg32i: + return {spv::ImageFormat::Rgba32i, 2}; + case spv::ImageFormat::Rg16i: + return {spv::ImageFormat::Rgba16i, 2}; + case spv::ImageFormat::R16i: + return {spv::ImageFormat::Rgba16i, 1}; + case spv::ImageFormat::Rg8i: + return {spv::ImageFormat::Rgba8i, 2}; + case spv::ImageFormat::R8i: + return {spv::ImageFormat::Rgba8i, 1}; // Unsigned integer. - case spv::ImageFormat::Rg32ui: return {spv::ImageFormat::Rgba32ui, 2}; - case spv::ImageFormat::Rg16ui: return {spv::ImageFormat::Rgba16ui, 2}; - case spv::ImageFormat::R16ui: return {spv::ImageFormat::Rgba16ui, 1}; - case spv::ImageFormat::Rg8ui: return {spv::ImageFormat::Rgba8ui, 2}; - case spv::ImageFormat::R8ui: return {spv::ImageFormat::Rgba8ui, 1}; + case spv::ImageFormat::Rg32ui: + return {spv::ImageFormat::Rgba32ui, 2}; + case spv::ImageFormat::Rg16ui: + return {spv::ImageFormat::Rgba16ui, 2}; + case spv::ImageFormat::R16ui: + return {spv::ImageFormat::Rgba16ui, 1}; + case spv::ImageFormat::Rg8ui: + return {spv::ImageFormat::Rgba8ui, 2}; + case spv::ImageFormat::R8ui: + return {spv::ImageFormat::Rgba8ui, 1}; // FOUR channels, so there is no surplus channel to mask and no access is // rewritten - 10, 10, 10 and 2 bits of unsigned integer all fit in sixteen. - case spv::ImageFormat::Rgb10a2ui: return {spv::ImageFormat::Rgba16ui, 4}; + case spv::ImageFormat::Rgb10a2ui: + return {spv::ImageFormat::Rgba16ui, 4}; // Unsigned normalized, carried as codes in [0, 2^b - 1]. case spv::ImageFormat::Rgba16: - return {spv::ImageFormat::Rgba16ui, 4, - {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; + return { + spv::ImageFormat::Rgba16ui, 4, {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; case spv::ImageFormat::Rg16: - return {spv::ImageFormat::Rgba16ui, 2, - {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; + return { + spv::ImageFormat::Rgba16ui, 2, {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; case spv::ImageFormat::R16: - return {spv::ImageFormat::Rgba16ui, 1, - {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; + return { + spv::ImageFormat::Rgba16ui, 1, {kUnorm16Max, kUnorm16Max, kUnorm16Max, kUnorm16Max}, false}; // The one entry whose channels are not all the same width, which is the whole // reason ChannelMax is per channel rather than one number. case spv::ImageFormat::Rgb10A2: @@ -181,14 +200,14 @@ namespace MobileGL { // 2^(b-1) - 1]. The carrier's channel is UNSIGNED, so the code's sixteen bits // are stored verbatim and sign-extended again on the way out. case spv::ImageFormat::Rgba16Snorm: - return {spv::ImageFormat::Rgba16ui, 4, - {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; + return { + spv::ImageFormat::Rgba16ui, 4, {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; case spv::ImageFormat::Rg16Snorm: - return {spv::ImageFormat::Rgba16ui, 2, - {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; + return { + spv::ImageFormat::Rgba16ui, 2, {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; case spv::ImageFormat::R16Snorm: - return {spv::ImageFormat::Rgba16ui, 1, - {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; + return { + spv::ImageFormat::Rgba16ui, 1, {kSnorm16Max, kSnorm16Max, kSnorm16Max, kSnorm16Max}, true}; default: return {}; } @@ -200,45 +219,84 @@ namespace MobileGL { // order, as BakeImageFormatsPass::SpirvImageFormatFromGLInternalFormat. spv::ImageFormat SpirvImageFormatOfGL(Uint glInternalFormat) { switch (glInternalFormat) { - case 0x8814: /*GL_RGBA32F*/ return spv::ImageFormat::Rgba32f; - case 0x881A: /*GL_RGBA16F*/ return spv::ImageFormat::Rgba16f; - case 0x8230: /*GL_RG32F*/ return spv::ImageFormat::Rg32f; - case 0x822F: /*GL_RG16F*/ return spv::ImageFormat::Rg16f; - case 0x8C3A: /*GL_R11F_G11F_B10F*/ return spv::ImageFormat::R11fG11fB10f; - case 0x822E: /*GL_R32F*/ return spv::ImageFormat::R32f; - case 0x822D: /*GL_R16F*/ return spv::ImageFormat::R16f; - case 0x8D70: /*GL_RGBA32UI*/ return spv::ImageFormat::Rgba32ui; - case 0x8D76: /*GL_RGBA16UI*/ return spv::ImageFormat::Rgba16ui; - case 0x8D7C: /*GL_RGBA8UI*/ return spv::ImageFormat::Rgba8ui; - case 0x906F: /*GL_RGB10_A2UI*/ return spv::ImageFormat::Rgb10a2ui; - case 0x823C: /*GL_RG32UI*/ return spv::ImageFormat::Rg32ui; - case 0x823A: /*GL_RG16UI*/ return spv::ImageFormat::Rg16ui; - case 0x8238: /*GL_RG8UI*/ return spv::ImageFormat::Rg8ui; - case 0x8236: /*GL_R32UI*/ return spv::ImageFormat::R32ui; - case 0x8234: /*GL_R16UI*/ return spv::ImageFormat::R16ui; - case 0x8232: /*GL_R8UI*/ return spv::ImageFormat::R8ui; - case 0x8D82: /*GL_RGBA32I*/ return spv::ImageFormat::Rgba32i; - case 0x8D88: /*GL_RGBA16I*/ return spv::ImageFormat::Rgba16i; - case 0x8D8E: /*GL_RGBA8I*/ return spv::ImageFormat::Rgba8i; - case 0x823B: /*GL_RG32I*/ return spv::ImageFormat::Rg32i; - case 0x8239: /*GL_RG16I*/ return spv::ImageFormat::Rg16i; - case 0x8237: /*GL_RG8I*/ return spv::ImageFormat::Rg8i; - case 0x8235: /*GL_R32I*/ return spv::ImageFormat::R32i; - case 0x8233: /*GL_R16I*/ return spv::ImageFormat::R16i; - case 0x8231: /*GL_R8I*/ return spv::ImageFormat::R8i; - case 0x8058: /*GL_RGBA8*/ return spv::ImageFormat::Rgba8; - case 0x805B: /*GL_RGBA16*/ return spv::ImageFormat::Rgba16; - case 0x8059: /*GL_RGB10_A2*/ return spv::ImageFormat::Rgb10A2; - case 0x822B: /*GL_RG8*/ return spv::ImageFormat::Rg8; - case 0x822C: /*GL_RG16*/ return spv::ImageFormat::Rg16; - case 0x8229: /*GL_R8*/ return spv::ImageFormat::R8; - case 0x822A: /*GL_R16*/ return spv::ImageFormat::R16; - case 0x8F97: /*GL_RGBA8_SNORM*/ return spv::ImageFormat::Rgba8Snorm; - case 0x8F9B: /*GL_RGBA16_SNORM*/ return spv::ImageFormat::Rgba16Snorm; - case 0x8F95: /*GL_RG8_SNORM*/ return spv::ImageFormat::Rg8Snorm; - case 0x8F99: /*GL_RG16_SNORM*/ return spv::ImageFormat::Rg16Snorm; - case 0x8F94: /*GL_R8_SNORM*/ return spv::ImageFormat::R8Snorm; - case 0x8F98: /*GL_R16_SNORM*/ return spv::ImageFormat::R16Snorm; + case 0x8814: /*GL_RGBA32F*/ + return spv::ImageFormat::Rgba32f; + case 0x881A: /*GL_RGBA16F*/ + return spv::ImageFormat::Rgba16f; + case 0x8230: /*GL_RG32F*/ + return spv::ImageFormat::Rg32f; + case 0x822F: /*GL_RG16F*/ + return spv::ImageFormat::Rg16f; + case 0x8C3A: /*GL_R11F_G11F_B10F*/ + return spv::ImageFormat::R11fG11fB10f; + case 0x822E: /*GL_R32F*/ + return spv::ImageFormat::R32f; + case 0x822D: /*GL_R16F*/ + return spv::ImageFormat::R16f; + case 0x8D70: /*GL_RGBA32UI*/ + return spv::ImageFormat::Rgba32ui; + case 0x8D76: /*GL_RGBA16UI*/ + return spv::ImageFormat::Rgba16ui; + case 0x8D7C: /*GL_RGBA8UI*/ + return spv::ImageFormat::Rgba8ui; + case 0x906F: /*GL_RGB10_A2UI*/ + return spv::ImageFormat::Rgb10a2ui; + case 0x823C: /*GL_RG32UI*/ + return spv::ImageFormat::Rg32ui; + case 0x823A: /*GL_RG16UI*/ + return spv::ImageFormat::Rg16ui; + case 0x8238: /*GL_RG8UI*/ + return spv::ImageFormat::Rg8ui; + case 0x8236: /*GL_R32UI*/ + return spv::ImageFormat::R32ui; + case 0x8234: /*GL_R16UI*/ + return spv::ImageFormat::R16ui; + case 0x8232: /*GL_R8UI*/ + return spv::ImageFormat::R8ui; + case 0x8D82: /*GL_RGBA32I*/ + return spv::ImageFormat::Rgba32i; + case 0x8D88: /*GL_RGBA16I*/ + return spv::ImageFormat::Rgba16i; + case 0x8D8E: /*GL_RGBA8I*/ + return spv::ImageFormat::Rgba8i; + case 0x823B: /*GL_RG32I*/ + return spv::ImageFormat::Rg32i; + case 0x8239: /*GL_RG16I*/ + return spv::ImageFormat::Rg16i; + case 0x8237: /*GL_RG8I*/ + return spv::ImageFormat::Rg8i; + case 0x8235: /*GL_R32I*/ + return spv::ImageFormat::R32i; + case 0x8233: /*GL_R16I*/ + return spv::ImageFormat::R16i; + case 0x8231: /*GL_R8I*/ + return spv::ImageFormat::R8i; + case 0x8058: /*GL_RGBA8*/ + return spv::ImageFormat::Rgba8; + case 0x805B: /*GL_RGBA16*/ + return spv::ImageFormat::Rgba16; + case 0x8059: /*GL_RGB10_A2*/ + return spv::ImageFormat::Rgb10A2; + case 0x822B: /*GL_RG8*/ + return spv::ImageFormat::Rg8; + case 0x822C: /*GL_RG16*/ + return spv::ImageFormat::Rg16; + case 0x8229: /*GL_R8*/ + return spv::ImageFormat::R8; + case 0x822A: /*GL_R16*/ + return spv::ImageFormat::R16; + case 0x8F97: /*GL_RGBA8_SNORM*/ + return spv::ImageFormat::Rgba8Snorm; + case 0x8F9B: /*GL_RGBA16_SNORM*/ + return spv::ImageFormat::Rgba16Snorm; + case 0x8F95: /*GL_RG8_SNORM*/ + return spv::ImageFormat::Rg8Snorm; + case 0x8F99: /*GL_RG16_SNORM*/ + return spv::ImageFormat::Rg16Snorm; + case 0x8F94: /*GL_R8_SNORM*/ + return spv::ImageFormat::R8Snorm; + case 0x8F98: /*GL_R16_SNORM*/ + return spv::ImageFormat::R16Snorm; default: return spv::ImageFormat::Unknown; } @@ -246,16 +304,26 @@ namespace MobileGL { Uint GLInternalFormatOfSpirvImageFormat(spv::ImageFormat format) { switch (format) { - case spv::ImageFormat::Rgba32f: return 0x8814; // GL_RGBA32F - case spv::ImageFormat::Rgba16f: return 0x881A; // GL_RGBA16F - case spv::ImageFormat::Rgba8: return 0x8058; // GL_RGBA8 - case spv::ImageFormat::Rgba8Snorm: return 0x8F97; // GL_RGBA8_SNORM - case spv::ImageFormat::Rgba32i: return 0x8D82; // GL_RGBA32I - case spv::ImageFormat::Rgba16i: return 0x8D88; // GL_RGBA16I - case spv::ImageFormat::Rgba8i: return 0x8D8E; // GL_RGBA8I - case spv::ImageFormat::Rgba32ui: return 0x8D70; // GL_RGBA32UI - case spv::ImageFormat::Rgba16ui: return 0x8D76; // GL_RGBA16UI - case spv::ImageFormat::Rgba8ui: return 0x8D7C; // GL_RGBA8UI + case spv::ImageFormat::Rgba32f: + return 0x8814; // GL_RGBA32F + case spv::ImageFormat::Rgba16f: + return 0x881A; // GL_RGBA16F + case spv::ImageFormat::Rgba8: + return 0x8058; // GL_RGBA8 + case spv::ImageFormat::Rgba8Snorm: + return 0x8F97; // GL_RGBA8_SNORM + case spv::ImageFormat::Rgba32i: + return 0x8D82; // GL_RGBA32I + case spv::ImageFormat::Rgba16i: + return 0x8D88; // GL_RGBA16I + case spv::ImageFormat::Rgba8i: + return 0x8D8E; // GL_RGBA8I + case spv::ImageFormat::Rgba32ui: + return 0x8D70; // GL_RGBA32UI + case spv::ImageFormat::Rgba16ui: + return 0x8D76; // GL_RGBA16UI + case spv::ImageFormat::Rgba8ui: + return 0x8D7C; // GL_RGBA8UI default: // Only the carriers need the reverse direction, and every carrier is one // of the four-channel core formats above. @@ -336,20 +404,21 @@ namespace MobileGL { BufferImageSplit SplitOfBufferImageFormat(spv::ImageFormat format) { switch (format) { - case spv::ImageFormat::Rg32f: return {spv::ImageFormat::R32f, 2}; - case spv::ImageFormat::Rg32i: return {spv::ImageFormat::R32i, 2}; - case spv::ImageFormat::Rg32ui: return {spv::ImageFormat::R32ui, 2}; + case spv::ImageFormat::Rg32f: + return {spv::ImageFormat::R32f, 2}; + case spv::ImageFormat::Rg32i: + return {spv::ImageFormat::R32i, 2}; + case spv::ImageFormat::Rg32ui: + return {spv::ImageFormat::R32ui, 2}; default: return {}; } } - Bool IsSplittableBufferImageType(const Instruction* type, - bool onlyFormatsSpirvCrossRefusesToPrint) { + Bool IsSplittableBufferImageType(const Instruction* type, bool onlyFormatsSpirvCrossRefusesToPrint) { if (type == nullptr || type->opcode() != spv::Op::OpTypeImage) return false; if (type->GetSingleWordInOperand(kImageSampledOperand) != kSampledStorageImage) return false; - if (static_cast(type->GetSingleWordInOperand(kImageDimOperand)) != - spv::Dim::Buffer) { + if (static_cast(type->GetSingleWordInOperand(kImageDimOperand)) != spv::Dim::Buffer) { return false; } const auto format = @@ -365,8 +434,7 @@ namespace MobileGL { return true; } - Bool IsWidenableStorageImageType(const Instruction* type, - bool onlyFormatsSpirvCrossRefusesToPrint) { + Bool IsWidenableStorageImageType(const Instruction* type, bool onlyFormatsSpirvCrossRefusesToPrint) { if (type == nullptr || type->opcode() != spv::Op::OpTypeImage) return false; if (type->GetSingleWordInOperand(kImageSampledOperand) != kSampledStorageImage) return false; // A BUFFER image is never widened, whatever its format. Widening works because @@ -384,8 +452,7 @@ namespace MobileGL { // the application's buffer. It is SPLIT instead where its format allows // (IsSplittableBufferImageType), which addresses the same bytes rather than // restriding them, and left alone where it does not. - if (static_cast(type->GetSingleWordInOperand(kImageDimOperand)) == - spv::Dim::Buffer) { + if (static_cast(type->GetSingleWordInOperand(kImageDimOperand)) == spv::Dim::Buffer) { return false; } const auto format = @@ -432,18 +499,18 @@ namespace MobileGL { // longer describes the function bodies. struct NormalizedCarrierMaterial { uint32_t Glsl450Id = 0; - uint32_t FloatTypeId = 0; // the component types, kept only so the declaration - uint32_t IntTypeId = 0; // order below can put each vector after its own - uint32_t UintTypeId = 0; // component - and the last is the image's new Sampled Type - uint32_t UvecTypeId = 0; // uvec4: what an OpImageRead of the carrier yields - uint32_t IvecTypeId = 0; // ivec4: the sign-extended snorm code - uint32_t FvecTypeId = 0; // vec4: what the shader asked for - uint32_t ShiftWidthId = 0; // ivec4(16), the snorm sign extension + uint32_t FloatTypeId = 0; // the component types, kept only so the declaration + uint32_t IntTypeId = 0; // order below can put each vector after its own + uint32_t UintTypeId = 0; // component - and the last is the image's new Sampled Type + uint32_t UvecTypeId = 0; // uvec4: what an OpImageRead of the carrier yields + uint32_t IvecTypeId = 0; // ivec4: the sign-extended snorm code + uint32_t FvecTypeId = 0; // vec4: what the shader asked for + uint32_t ShiftWidthId = 0; // ivec4(16), the snorm sign extension uint32_t LowWordMaskId = 0; // uvec4(0xFFFF) - uint32_t ZeroId = 0; // vec4(0.0) - uint32_t OneId = 0; // vec4(1.0) - uint32_t MinusOneId = 0; // vec4(-1.0) - uint32_t HalfId = 0; // vec4(0.5) + uint32_t ZeroId = 0; // vec4(0.0) + uint32_t OneId = 0; // vec4(1.0) + uint32_t MinusOneId = 0; // vec4(-1.0) + uint32_t HalfId = 0; // vec4(0.5) explicit operator bool() const { return UvecTypeId != 0u; } }; @@ -465,13 +532,12 @@ namespace MobileGL { uint32_t MakeScalarConstant(IRContext* context, analysis::Type* scalarType, uint32_t word) { const analysis::Constant* constant = context->get_constant_mgr()->GetConstant(scalarType, {word}); if (constant == nullptr) return 0u; - const Instruction* definition = - context->get_constant_mgr()->GetDefiningInstruction(constant); + const Instruction* definition = context->get_constant_mgr()->GetDefiningInstruction(constant); return definition == nullptr ? 0u : definition->result_id(); } - uint32_t MakeSplatVec4Constant(IRContext* context, uint32_t vectorTypeId, - analysis::Type* scalarType, uint32_t word) { + uint32_t MakeSplatVec4Constant(IRContext* context, uint32_t vectorTypeId, analysis::Type* scalarType, + uint32_t word) { const uint32_t scalarId = MakeScalarConstant(context, scalarType, word); if (scalarId == 0u) return 0u; const uint32_t componentIds[4] = {scalarId, scalarId, scalarId, scalarId}; @@ -567,8 +633,8 @@ namespace MobileGL { if (floatReg == nullptr) return 0u; uint32_t componentIds[4] = {0u, 0u, 0u, 0u}; for (uint32_t i = 0; i < 4; ++i) { - componentIds[i] = MakeScalarConstant(context, floatReg, - FloatBits(static_cast(channelMax[i]))); + componentIds[i] = + MakeScalarConstant(context, floatReg, FloatBits(static_cast(channelMax[i]))); if (componentIds[i] == 0u) return 0u; } return MakeVec4Constant(context, material.FvecTypeId, componentIds); @@ -576,32 +642,32 @@ namespace MobileGL { } // namespace Uint WidenImageFormatsPass::WidenedCoreEsslImageFormat(Uint glInternalFormat) { - const ImageFormatWidening widening = - WideningOfSpirvImageFormat(SpirvImageFormatOfGL(glInternalFormat)); + const ImageFormatWidening widening = WideningOfSpirvImageFormat(SpirvImageFormatOfGL(glInternalFormat)); if (!widening) return 0; return GLInternalFormatOfSpirvImageFormat(widening.Carrier); } Uint WidenImageFormatsPass::SplitCoreEsslBufferImageFormat(Uint glInternalFormat) { - const BufferImageSplit split = - SplitOfBufferImageFormat(SpirvImageFormatOfGL(glInternalFormat)); + const BufferImageSplit split = SplitOfBufferImageFormat(SpirvImageFormatOfGL(glInternalFormat)); if (!split) return 0; switch (split.Base) { - case spv::ImageFormat::R32f: return 0x822E; // GL_R32F - case spv::ImageFormat::R32i: return 0x8235; // GL_R32I - case spv::ImageFormat::R32ui: return 0x8236; // GL_R32UI + case spv::ImageFormat::R32f: + return 0x822E; // GL_R32F + case spv::ImageFormat::R32i: + return 0x8235; // GL_R32I + case spv::ImageFormat::R32ui: + return 0x8236; // GL_R32UI default: return 0; } } - bool WidenImageFormatsPass::NormalizedImageCarrierCodes(Uint glInternalFormat, - Uint32 (&outChannelMax)[4], + bool WidenImageFormatsPass::NormalizedImageCarrierCodes(Uint glInternalFormat, Uint32 (&outChannelMax)[4], bool& outSignedNormalized) { - const ImageFormatWidening widening = - WideningOfSpirvImageFormat(SpirvImageFormatOfGL(glInternalFormat)); + const ImageFormatWidening widening = WideningOfSpirvImageFormat(SpirvImageFormatOfGL(glInternalFormat)); if (!widening || !widening.CarriesNormalizedCodes()) return false; - for (Uint i = 0; i < 4; ++i) outChannelMax[i] = widening.ChannelMax[i]; + for (Uint i = 0; i < 4; ++i) + outChannelMax[i] = widening.ChannelMax[i]; outSignedNormalized = widening.SignedNormalized; return true; } @@ -610,8 +676,8 @@ namespace MobileGL { return ChannelsOfSpirvImageFormat(SpirvImageFormatOfGL(glInternalFormat)); } - bool WidenImageFormatsPass::DeclaresWidenableImageFormat( - IRContext* context, const bool onlyFormatsSpirvCrossRefusesToPrint) { + bool WidenImageFormatsPass::DeclaresWidenableImageFormat(IRContext* context, + const bool onlyFormatsSpirvCrossRefusesToPrint) { if (context == nullptr) { return false; } @@ -624,8 +690,8 @@ namespace MobileGL { return false; } - bool WidenImageFormatsPass::DeclaresWidenableImageFormat( - const Vector& binary, const bool onlyFormatsSpirvCrossRefusesToPrint) { + bool WidenImageFormatsPass::DeclaresWidenableImageFormat(const Vector& binary, + const bool onlyFormatsSpirvCrossRefusesToPrint) { std::unique_ptr context = spvtools::BuildModule( SPV_ENV_VULKAN_1_1, [](spv_message_level_t, const char*, const spv_position_t&, const char*) {}, binary.data(), binary.size()); @@ -670,11 +736,10 @@ namespace MobileGL { static_cast(type->GetSingleWordInOperand(kImageFormatOperand)); const ImageFormatWidening widening = WideningOfSpirvImageFormat(format); anyNormalizedCarrier = anyNormalizedCarrier || widening.CarriesNormalizedCodes(); - widenedByTypeId.emplace( - type->result_id(), - WidenedImage{widening.Carrier, widening.Channels, - type->GetSingleWordInOperand(kImageSampledTypeOperand), 0u, - widening.SignedNormalized}); + widenedByTypeId.emplace(type->result_id(), + WidenedImage{widening.Carrier, widening.Channels, + type->GetSingleWordInOperand(kImageSampledTypeOperand), 0u, + widening.SignedNormalized}); } // Collect the accesses BEFORE anything is mutated, and refuse the whole rewrite if @@ -689,10 +754,8 @@ namespace MobileGL { // stage that addresses one image right and the other wrong. std::map splitByTypeId; for (Instruction* type : bufferSplitTypes) { - splitByTypeId.emplace( - type->result_id(), - SplitOfBufferImageFormat( - static_cast(type->GetSingleWordInOperand(kImageFormatOperand)))); + splitByTypeId.emplace(type->result_id(), SplitOfBufferImageFormat(static_cast( + type->GetSingleWordInOperand(kImageFormatOperand)))); } std::vector reads; @@ -701,8 +764,8 @@ namespace MobileGL { std::vector splitWrites; std::vector splitSizeQueries; Bool rewritable = true; - for (auto funcIt = irContext->module()->begin(); - funcIt != irContext->module()->end() && rewritable; ++funcIt) { + for (auto funcIt = irContext->module()->begin(); funcIt != irContext->module()->end() && rewritable; + ++funcIt) { funcIt->ForEachInst([&](Instruction* inst) { if (!rewritable) return; switch (inst->opcode()) { @@ -728,12 +791,18 @@ namespace MobileGL { } if (splitByTypeId.count(imageTypeId) != 0) { switch (inst->opcode()) { - case spv::Op::OpImageRead: splitReads.push_back(inst); return; - case spv::Op::OpImageWrite: splitWrites.push_back(inst); return; + case spv::Op::OpImageRead: + splitReads.push_back(inst); + return; + case spv::Op::OpImageWrite: + splitWrites.push_back(inst); + return; // imageSize() has to be halved with everything else: the ES view has // twice the texels the application's format describes, and a shader // that walks the buffer by its own size would run off the end of it. - case spv::Op::OpImageQuerySize: splitSizeQueries.push_back(inst); return; + case spv::Op::OpImageQuerySize: + splitSizeQueries.push_back(inst); + return; default: rewritable = false; return; @@ -787,15 +856,14 @@ namespace MobileGL { uint32_t oneWord = 0; std::unique_ptr component; - if (sampledType->opcode() == spv::Op::OpTypeFloat && - sampledType->GetSingleWordInOperand(0) == 32) { + if (sampledType->opcode() == spv::Op::OpTypeFloat && sampledType->GetSingleWordInOperand(0) == 32) { component = spvtools::MakeUnique(32); oneWord = 0x3F800000u; // 1.0f } else if (sampledType->opcode() == spv::Op::OpTypeInt && sampledType->GetSingleWordInOperand(0) == 32) { // OpTypeInt in-operands: 0 width, 1 signedness. - component = spvtools::MakeUnique( - 32, sampledType->GetSingleWordInOperand(1) != 0); + component = + spvtools::MakeUnique(32, sampledType->GetSingleWordInOperand(1) != 0); oneWord = 1u; } else { return false; @@ -823,9 +891,9 @@ namespace MobileGL { if (vectorReg == nullptr) return false; // A vector constant's "literal words" are the IDS of its components // (ConstantManager::CreateConstant -> GetConstantsFromIds). - const analysis::Constant* zeroOne = constantMgr->GetConstant( - vectorReg, {zeroInst->result_id(), zeroInst->result_id(), zeroInst->result_id(), - oneInst->result_id()}); + const analysis::Constant* zeroOne = + constantMgr->GetConstant(vectorReg, {zeroInst->result_id(), zeroInst->result_id(), + zeroInst->result_id(), oneInst->result_id()}); if (zeroOne == nullptr) return false; const Instruction* zeroOneInst = constantMgr->GetDefiningInstruction(zeroOne); if (zeroOneInst == nullptr) return false; @@ -845,8 +913,7 @@ namespace MobileGL { std::vector components; components.reserve(4); for (uint32_t i = 0; i < 4; ++i) { - components.push_back( - {SPV_OPERAND_TYPE_LITERAL_INTEGER, {i < channels ? i : 4u + i}}); + components.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {i < channels ? i : 4u + i}}); } return components; }; @@ -925,8 +992,7 @@ namespace MobileGL { {SPV_OPERAND_TYPE_ID, {componentsId}}})); before->InsertBefore(spvtools::MakeUnique( irContext, spv::Op::OpIAdd, coord->type_id(), secondId, - Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {firstId}}, - {SPV_OPERAND_TYPE_ID, {oneId}}})); + Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {firstId}}, {SPV_OPERAND_TYPE_ID, {oneId}}})); outFirst = firstId; outSecond = secondId; return true; @@ -951,8 +1017,8 @@ namespace MobileGL { for (Instruction* type : bufferSplitTypes) { uint32_t unusedConstantId = 0; uint32_t unusedVec4TypeId = 0; - if (!resolveMaskMaterial(type->GetSingleWordInOperand(kImageSampledTypeOperand), - unusedConstantId, unusedVec4TypeId)) { + if (!resolveMaskMaterial(type->GetSingleWordInOperand(kImageSampledTypeOperand), unusedConstantId, + unusedVec4TypeId)) { return Status::SuccessWithoutChange; } } @@ -1016,11 +1082,10 @@ namespace MobileGL { // just exist. See HoistTypeDeclarationsBefore: the type manager appends, and // the image type that names the new uint32 is already further up. if (firstNormalizedImageType != nullptr) { - HoistTypeDeclarationsBefore( - irContext, firstNormalizedImageType, - {normalizedMaterial.FloatTypeId, normalizedMaterial.IntTypeId, - normalizedMaterial.UintTypeId, normalizedMaterial.FvecTypeId, - normalizedMaterial.IvecTypeId, normalizedMaterial.UvecTypeId}); + HoistTypeDeclarationsBefore(irContext, firstNormalizedImageType, + {normalizedMaterial.FloatTypeId, normalizedMaterial.IntTypeId, + normalizedMaterial.UintTypeId, normalizedMaterial.FvecTypeId, + normalizedMaterial.IvecTypeId, normalizedMaterial.UvecTypeId}); } } @@ -1047,14 +1112,13 @@ namespace MobileGL { // two-channel format does not have: the mask shuffle runs on the float side either // way, so whatever the surplus channels hold is discarded on the way out and // written as the format's own 0 and 1 on the way in. - auto insertUnpack = [&](Instruction* before, const WidenedImage& widened, - uint32_t rawId) -> uint32_t { + auto insertUnpack = [&](Instruction* before, const WidenedImage& widened, uint32_t rawId) -> uint32_t { const auto emit = [&](spv::Op opcode, uint32_t typeId, Instruction::OperandList operands) -> uint32_t { const uint32_t resultId = irContext->TakeNextId(); if (resultId == 0u) return 0u; - before->InsertBefore(spvtools::MakeUnique(irContext, opcode, typeId, resultId, - Move(operands))); + before->InsertBefore( + spvtools::MakeUnique(irContext, opcode, typeId, resultId, Move(operands))); return resultId; }; // The raw texel through an OpCopyObject before anything reads it, which costs @@ -1069,19 +1133,16 @@ namespace MobileGL { // the emitter does; the copy is the cheapest way to stay out of it, and every // other driver folds it away. const uint32_t texelId = - emit(spv::Op::OpCopyObject, normalizedMaterial.UvecTypeId, - {{SPV_OPERAND_TYPE_ID, {rawId}}}); + emit(spv::Op::OpCopyObject, normalizedMaterial.UvecTypeId, {{SPV_OPERAND_TYPE_ID, {rawId}}}); if (texelId == 0u) return 0u; uint32_t codeId = 0; if (widened.SignedNormalized) { const uint32_t asIntId = - emit(spv::Op::OpBitcast, normalizedMaterial.IvecTypeId, - {{SPV_OPERAND_TYPE_ID, {texelId}}}); + emit(spv::Op::OpBitcast, normalizedMaterial.IvecTypeId, {{SPV_OPERAND_TYPE_ID, {texelId}}}); if (asIntId == 0u) return 0u; - const uint32_t shiftedUpId = - emit(spv::Op::OpShiftLeftLogical, normalizedMaterial.IvecTypeId, - {{SPV_OPERAND_TYPE_ID, {asIntId}}, - {SPV_OPERAND_TYPE_ID, {normalizedMaterial.ShiftWidthId}}}); + const uint32_t shiftedUpId = emit(spv::Op::OpShiftLeftLogical, normalizedMaterial.IvecTypeId, + {{SPV_OPERAND_TYPE_ID, {asIntId}}, + {SPV_OPERAND_TYPE_ID, {normalizedMaterial.ShiftWidthId}}}); if (shiftedUpId == 0u) return 0u; const uint32_t signExtendedId = emit(spv::Op::OpShiftRightArithmetic, normalizedMaterial.IvecTypeId, @@ -1108,30 +1169,27 @@ namespace MobileGL { {SPV_OPERAND_TYPE_ID, {normalizedMaterial.MinusOneId}}}); }; - auto insertPack = [&](Instruction* before, const WidenedImage& widened, - uint32_t valueId) -> uint32_t { + auto insertPack = [&](Instruction* before, const WidenedImage& widened, uint32_t valueId) -> uint32_t { const auto emit = [&](spv::Op opcode, uint32_t typeId, Instruction::OperandList operands) -> uint32_t { const uint32_t resultId = irContext->TakeNextId(); if (resultId == 0u) return 0u; - before->InsertBefore(spvtools::MakeUnique(irContext, opcode, typeId, resultId, - Move(operands))); + before->InsertBefore( + spvtools::MakeUnique(irContext, opcode, typeId, resultId, Move(operands))); return resultId; }; const uint32_t lowBoundId = widened.SignedNormalized ? normalizedMaterial.MinusOneId : normalizedMaterial.ZeroId; - const uint32_t clampedId = - emit(spv::Op::OpExtInst, normalizedMaterial.FvecTypeId, - {{SPV_OPERAND_TYPE_ID, {normalizedMaterial.Glsl450Id}}, - {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {kGlslFClamp}}, - {SPV_OPERAND_TYPE_ID, {valueId}}, - {SPV_OPERAND_TYPE_ID, {lowBoundId}}, - {SPV_OPERAND_TYPE_ID, {normalizedMaterial.OneId}}}); + const uint32_t clampedId = emit(spv::Op::OpExtInst, normalizedMaterial.FvecTypeId, + {{SPV_OPERAND_TYPE_ID, {normalizedMaterial.Glsl450Id}}, + {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {kGlslFClamp}}, + {SPV_OPERAND_TYPE_ID, {valueId}}, + {SPV_OPERAND_TYPE_ID, {lowBoundId}}, + {SPV_OPERAND_TYPE_ID, {normalizedMaterial.OneId}}}); if (clampedId == 0u) return 0u; const uint32_t scaledId = emit(spv::Op::OpFMul, normalizedMaterial.FvecTypeId, - {{SPV_OPERAND_TYPE_ID, {clampedId}}, - {SPV_OPERAND_TYPE_ID, {widened.DenominatorId}}}); + {{SPV_OPERAND_TYPE_ID, {clampedId}}, {SPV_OPERAND_TYPE_ID, {widened.DenominatorId}}}); if (scaledId == 0u) return 0u; uint32_t biasId = normalizedMaterial.HalfId; if (widened.SignedNormalized) { @@ -1142,9 +1200,9 @@ namespace MobileGL { {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {kGlslFSign}}, {SPV_OPERAND_TYPE_ID, {scaledId}}}); if (signId == 0u) return 0u; - biasId = emit(spv::Op::OpFMul, normalizedMaterial.FvecTypeId, - {{SPV_OPERAND_TYPE_ID, {signId}}, - {SPV_OPERAND_TYPE_ID, {normalizedMaterial.HalfId}}}); + biasId = + emit(spv::Op::OpFMul, normalizedMaterial.FvecTypeId, + {{SPV_OPERAND_TYPE_ID, {signId}}, {SPV_OPERAND_TYPE_ID, {normalizedMaterial.HalfId}}}); if (biasId == 0u) return 0u; } const uint32_t roundedId = @@ -1161,9 +1219,9 @@ namespace MobileGL { const uint32_t asUintId = emit(spv::Op::OpBitcast, normalizedMaterial.UvecTypeId, {{SPV_OPERAND_TYPE_ID, {signedCodeId}}}); if (asUintId == 0u) return 0u; - return emit(spv::Op::OpBitwiseAnd, normalizedMaterial.UvecTypeId, - {{SPV_OPERAND_TYPE_ID, {asUintId}}, - {SPV_OPERAND_TYPE_ID, {normalizedMaterial.LowWordMaskId}}}); + return emit( + spv::Op::OpBitwiseAnd, normalizedMaterial.UvecTypeId, + {{SPV_OPERAND_TYPE_ID, {asUintId}}, {SPV_OPERAND_TYPE_ID, {normalizedMaterial.LowWordMaskId}}}); }; // Masks first, while every image type still carries its ORIGINAL format: the @@ -1201,8 +1259,8 @@ namespace MobileGL { for (const Operand& component : maskComponents(widened->Channels)) { shuffleOperands.push_back(component); } - write->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpVectorShuffle, vec4TypeId, maskedId, shuffleOperands)); + write->InsertBefore(spvtools::MakeUnique(irContext, spv::Op::OpVectorShuffle, + vec4TypeId, maskedId, shuffleOperands)); storedId = maskedId; } // The pack goes AFTER the mask, so the carrier's surplus channels are written @@ -1246,8 +1304,8 @@ namespace MobileGL { readOperands.push_back(read->GetInOperand(i)); } read->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpImageRead, - normalized ? normalizedMaterial.UvecTypeId : vec4TypeId, rawReadId, readOperands)); + irContext, spv::Op::OpImageRead, normalized ? normalizedMaterial.UvecTypeId : vec4TypeId, + rawReadId, readOperands)); uint32_t loadedId = rawReadId; if (normalized) { loadedId = insertUnpack(read, *widened, rawReadId); @@ -1308,8 +1366,8 @@ namespace MobileGL { shuffleOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {4u}}); shuffleOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {4u}}); shuffleOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {7u}}); - write->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpVectorShuffle, vec4TypeId, maskedId, shuffleOperands)); + write->InsertBefore(spvtools::MakeUnique(irContext, spv::Op::OpVectorShuffle, + vec4TypeId, maskedId, shuffleOperands)); componentTexelIds[component] = maskedId; } // The FIRST component's write is the inserted one and the second is the @@ -1321,8 +1379,8 @@ namespace MobileGL { } firstWriteOperands[kImageAccessCoordinateOperand] = {SPV_OPERAND_TYPE_ID, {firstCoordId}}; firstWriteOperands[kImageWriteTexelOperand] = {SPV_OPERAND_TYPE_ID, {componentTexelIds[0]}}; - write->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpImageWrite, 0, 0, firstWriteOperands)); + write->InsertBefore( + spvtools::MakeUnique(irContext, spv::Op::OpImageWrite, 0, 0, firstWriteOperands)); write->SetInOperand(kImageAccessCoordinateOperand, {secondCoordId}); write->SetInOperand(kImageWriteTexelOperand, {componentTexelIds[1]}); } @@ -1368,8 +1426,8 @@ namespace MobileGL { gatherOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {4u}}); gatherOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}); gatherOperands.push_back({SPV_OPERAND_TYPE_LITERAL_INTEGER, {0u}}); - read->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpVectorShuffle, vec4TypeId, gatheredId, gatherOperands)); + read->InsertBefore(spvtools::MakeUnique(irContext, spv::Op::OpVectorShuffle, + vec4TypeId, gatheredId, gatherOperands)); read->SetOpcode(spv::Op::OpVectorShuffle); Instruction::OperandList maskOperands{{SPV_OPERAND_TYPE_ID, {gatheredId}}, @@ -1398,12 +1456,10 @@ namespace MobileGL { for (uint32_t i = 0; i < query->NumInOperands(); ++i) { queryOperands.push_back(query->GetInOperand(i)); } - query->InsertBefore(spvtools::MakeUnique( - irContext, spv::Op::OpImageQuerySize, query->type_id(), rawSizeId, queryOperands)); - query->SetOpcode(resultType->GetSingleWordInOperand(1) != 0 ? spv::Op::OpSDiv - : spv::Op::OpUDiv); - query->SetInOperands({{SPV_OPERAND_TYPE_ID, {rawSizeId}}, - {SPV_OPERAND_TYPE_ID, {componentsId}}}); + query->InsertBefore(spvtools::MakeUnique(irContext, spv::Op::OpImageQuerySize, + query->type_id(), rawSizeId, queryOperands)); + query->SetOpcode(resultType->GetSingleWordInOperand(1) != 0 ? spv::Op::OpSDiv : spv::Op::OpUDiv); + query->SetInOperands({{SPV_OPERAND_TYPE_ID, {rawSizeId}}, {SPV_OPERAND_TYPE_ID, {componentsId}}}); } for (Instruction* type : bufferSplitTypes) { diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp index 5044e258..21acb530 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroBaseVertexPass.cpp @@ -133,8 +133,7 @@ namespace MobileGL { // %res makes every downstream use pick the zero up with no further rewriting. for (size_t i = 0; i < baseVertexLoads.size(); ++i) { baseVertexLoads[i]->SetOpcode(spv::Op::OpCopyObject); - baseVertexLoads[i]->SetInOperands(Instruction::OperandList{ - {SPV_OPERAND_TYPE_ID, {zeroIds[i]}}}); + baseVertexLoads[i]->SetInOperands(Instruction::OperandList{{SPV_OPERAND_TYPE_ID, {zeroIds[i]}}}); } irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp index 0f0f8c09..66262701 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/ZeroLayerOutputPass.cpp @@ -95,7 +95,8 @@ namespace MobileGL { if (valueTypeId == 0 || zeroInst == nullptr) return Status::Failure; const uint32_t zeroId = zeroInst->result_id(); - for (Instruction* store : stores) store->SetInOperand(1, {zeroId}); + for (Instruction* store : stores) + store->SetInOperand(1, {zeroId}); irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone); return Status::SuccessWithChange; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp index 8fa2d4c3..ed59c06e 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.cpp @@ -21,27 +21,41 @@ namespace MobileGL { auto signedness = member.numeric.scalar.signedness; if (flags & SPV_REFLECT_TYPE_FLAG_FLOAT) { switch (width) { - case 16: return SPVC_BASETYPE_FP16; - case 32: return SPVC_BASETYPE_FP32; - case 64: return SPVC_BASETYPE_FP64; - default: return SPVC_BASETYPE_UNKNOWN; + case 16: + return SPVC_BASETYPE_FP16; + case 32: + return SPVC_BASETYPE_FP32; + case 64: + return SPVC_BASETYPE_FP64; + default: + return SPVC_BASETYPE_UNKNOWN; } } else if (flags & SPV_REFLECT_TYPE_FLAG_INT) { if (signedness) { switch (width) { - case 8: return SPVC_BASETYPE_INT8; - case 16: return SPVC_BASETYPE_INT16; - case 32: return SPVC_BASETYPE_INT32; - case 64: return SPVC_BASETYPE_INT64; - default: return SPVC_BASETYPE_UNKNOWN; + case 8: + return SPVC_BASETYPE_INT8; + case 16: + return SPVC_BASETYPE_INT16; + case 32: + return SPVC_BASETYPE_INT32; + case 64: + return SPVC_BASETYPE_INT64; + default: + return SPVC_BASETYPE_UNKNOWN; } } else { switch (width) { - case 8: return SPVC_BASETYPE_UINT8; - case 16: return SPVC_BASETYPE_UINT16; - case 32: return SPVC_BASETYPE_UINT32; - case 64: return SPVC_BASETYPE_UINT64; - default: return SPVC_BASETYPE_UNKNOWN; + case 8: + return SPVC_BASETYPE_UINT8; + case 16: + return SPVC_BASETYPE_UINT16; + case 32: + return SPVC_BASETYPE_UINT32; + case 64: + return SPVC_BASETYPE_UINT64; + default: + return SPVC_BASETYPE_UNKNOWN; } } } else if (flags & SPV_REFLECT_TYPE_FLAG_BOOL) { @@ -107,8 +121,8 @@ namespace MobileGL { const Uint32 extent = member.array.dims[dim]; if (extent == 0) { MGLOG_W_ONCE("RecordGlobalUboLeaf: multi-dimensional uniform '%s' has a non-constant " - "dimension, recording the base entry only", - name.c_str()); + "dimension, recording the base entry only", + name.c_str()); RecordGlobalUboLeafEntry(member, name, offsetInUBO, arrayStride, member.size, metadata); return; } @@ -127,8 +141,8 @@ namespace MobileGL { for (const Uint32 index : indices) { elementName += "[" + std::to_string(index) + "]"; } - RecordGlobalUboLeafEntry(member, elementName, offsetInUBO + subArray * subArrayStride, - arrayStride, subArrayStride, metadata); + RecordGlobalUboLeafEntry(member, elementName, offsetInUBO + subArray * subArrayStride, arrayStride, + subArrayStride, metadata); for (SizeT dim = indices.size(); dim-- > 0;) { if (++indices[dim] < member.array.dims[dim]) break; indices[dim] = 0; @@ -165,8 +179,8 @@ namespace MobileGL { // Arrays of arrays of structs cannot be declared in the GL 3.3-era GLSL // MobileGL ingests; record the base so at least element 0 resolves. MGLOG_W_ONCE("FlattenGlobalUboMember: multi-dimensional struct array '%s' is not supported, " - "flattening element 0 only", - name.c_str()); + "flattening element 0 only", + name.c_str()); } const Uint32 elementCount = member.array.dims[0] > 0 ? member.array.dims[0] : 1; @@ -180,8 +194,7 @@ namespace MobileGL { } } - SpvcSession::SpvcSession(const Vector& spirv, Flags usage) - : usage(usage) { + SpvcSession::SpvcSession(const Vector& spirv, Flags usage) : usage(usage) { if (usage & SessionUsageBit::Transpile) { const SpvId* p_spirv = spirv.data(); size_t word_count = spirv.size(); @@ -200,8 +213,8 @@ namespace MobileGL { ir = nullptr; return; } - if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, - SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler) != SPVC_SUCCESS) { + if (spvc_context_create_compiler(context, SPVC_BACKEND_GLSL, ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, + &compiler) != SPVC_SUCCESS) { compiler = nullptr; return; } @@ -210,8 +223,8 @@ namespace MobileGL { return; } } else if (usage & SessionUsageBit::Reflection) { - SpvReflectResult result = spvReflectCreateShaderModule( - spirv.size() * sizeof(uint32_t), spirv.data(), &reflectModule); + SpvReflectResult result = + spvReflectCreateShaderModule(spirv.size() * sizeof(uint32_t), spirv.data(), &reflectModule); reflectModuleValid = (result == SPV_REFLECT_RESULT_SUCCESS); } } @@ -382,8 +395,8 @@ namespace MobileGL { SPVC_CHK_INIT const spvc_reflected_resource* list = nullptr; size_t count = 0; - SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( - resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, &list, &count)); + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, + &list, &count)); for (size_t i = 0; i < count; ++i) { auto& resource = list[i]; // Two spellings, because neither one alone identifies the block the GL @@ -438,15 +451,15 @@ namespace MobileGL { SPVC_CHK_INIT const spvc_reflected_resource* list = nullptr; size_t count = 0; - SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( - resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, &list, &count)); + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, + &list, &count)); for (size_t i = 0; i < count; ++i) { auto& resource = list[i]; // The block TYPE name: glslang gives the synthesized block an EMPTY instance // name, so resource.name carries nothing to match on. Read before Compile(), // which is where SPIRV-Cross renames the reserved "gl_" prefix away. - const Int glBinding = AtomicCounterBlockBinding( - spvc_compiler_get_name(compiler, resource.base_type_id)); + const Int glBinding = + AtomicCounterBlockBinding(spvc_compiler_get_name(compiler, resource.base_type_id)); if (glBinding < 0) continue; const Int esslBinding = topBinding - glBinding; if (esslBinding < 0) { @@ -468,8 +481,8 @@ namespace MobileGL { SPVC_CHK_INIT const spvc_reflected_resource* list = nullptr; size_t count = 0; - SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( - resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, &list, &count)); + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STAGE_OUTPUT, + &list, &count)); Bool dualSourceIndex = false; for (size_t i = 0; i < count; ++i) { const spvc_reflected_resource& resource = list[i]; @@ -495,8 +508,8 @@ namespace MobileGL { SPVC_CHK_INIT const spvc_reflected_resource* list = nullptr; size_t count = 0; - SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( - resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, &list, &count)); + SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_BUFFER, + &list, &count)); for (size_t i = 0; i < count; ++i) { const spvc_reflected_resource& resource = list[i]; // The variable itself, for a block the application qualified as a whole. @@ -540,8 +553,7 @@ namespace MobileGL { } // A double occupies two component slots per scalar (GL 4.6 core 11.1.2.1). const spvc_basetype base = spvc_type_get_basetype(type); - if (base == SPVC_BASETYPE_FP64 || base == SPVC_BASETYPE_INT64 || - base == SPVC_BASETYPE_UINT64) { + if (base == SPVC_BASETYPE_FP64 || base == SPVC_BASETYPE_INT64 || base == SPVC_BASETYPE_UINT64) { components *= 2; } return components; @@ -627,12 +639,12 @@ namespace MobileGL { // GL's capture interface spells an application block's member // "Block.member"; a redeclared built-in block contributes its members // by their own names, which the built-in walk below handles. - capture.name = blockName.empty() ? String(memberName) - : blockName + "." + String(memberName); + capture.name = + blockName.empty() ? String(memberName) : blockName + "." + String(memberName); capture.buffer = buffer; capture.stride = stride; - capture.offset = spvc_compiler_get_member_decoration(compiler, output.base_type_id, - member, SpvDecorationOffset); + capture.offset = spvc_compiler_get_member_decoration(compiler, output.base_type_id, member, + SpvDecorationOffset); capture.componentCount = XfbComponentCount(compiler, spvc_type_get_member_type(type, member)); captures.push_back(Move(capture)); @@ -648,8 +660,7 @@ namespace MobileGL { const spvc_reflected_builtin_resource* builtins = nullptr; SizeT builtinCount = 0; if (spvc_resources_get_builtin_resource_list_for_type( - resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) == - SPVC_SUCCESS) { + resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) == SPVC_SUCCESS) { for (SizeT i = 0; i < builtinCount; ++i) { const spvc_reflected_builtin_resource& entry = builtins[i]; const char* name = XfbBuiltInName(entry.builtin); @@ -661,8 +672,7 @@ namespace MobileGL { const spvc_type blockType = spvc_compiler_get_type_handle(compiler, entry.resource.base_type_id); - if (blockType == nullptr || - spvc_type_get_basetype(blockType) != SPVC_BASETYPE_STRUCT) { + if (blockType == nullptr || spvc_type_get_basetype(blockType) != SPVC_BASETYPE_STRUCT) { continue; } // The member index is not in the reflection entry, so it is recovered by @@ -680,14 +690,14 @@ namespace MobileGL { } if (spvc_compiler_has_member_decoration(compiler, entry.resource.base_type_id, member, SpvDecorationOffset) != SPVC_TRUE) { - break; // this built-in is present but not captured + break; // this built-in is present but not captured } SpirvXfbCapture capture; capture.name = name; capture.buffer = buffer; capture.stride = stride; - capture.offset = spvc_compiler_get_member_decoration( - compiler, entry.resource.base_type_id, member, SpvDecorationOffset); + capture.offset = spvc_compiler_get_member_decoration(compiler, entry.resource.base_type_id, + member, SpvDecorationOffset); capture.componentCount = XfbComponentCount(compiler, spvc_type_get_member_type(blockType, member)); captures.push_back(Move(capture)); @@ -736,8 +746,7 @@ namespace MobileGL { const spvc_reflected_builtin_resource* builtins = nullptr; SizeT builtinCount = 0; if (spvc_resources_get_builtin_resource_list_for_type( - resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) == - SPVC_SUCCESS) { + resources, SPVC_BUILTIN_RESOURCE_TYPE_STAGE_OUTPUT, &builtins, &builtinCount) == SPVC_SUCCESS) { for (SizeT i = 0; i < builtinCount; ++i) { stripVariable(builtins[i].resource.id, builtins[i].resource.base_type_id); } @@ -834,7 +843,8 @@ namespace MobileGL { const String word(keyword); for (SizeT at = head.find(word); at != String::npos; at = head.find(word, at + 1)) { const Bool startOk = at == 0 || !IsIdentifierChar(head[at - 1]); - const Bool endOk = at + word.size() >= head.size() || !IsIdentifierChar(head[at + word.size()]); + const Bool endOk = + at + word.size() >= head.size() || !IsIdentifierChar(head[at + word.size()]); if (startOk && endOk) return true; } } @@ -885,8 +895,8 @@ namespace MobileGL { while (lineBegin < essl.size()) { SizeT lineEnd = essl.find('\n', lineBegin); const SizeT next = lineEnd == String::npos ? essl.size() : lineEnd + 1; - const String line = essl.substr(lineBegin, (lineEnd == String::npos ? essl.size() : lineEnd) - - lineBegin); + const String line = + essl.substr(lineBegin, (lineEnd == String::npos ? essl.size() : lineEnd) - lineBegin); if (!IsInterfaceDeclarationOf(line, sentinel)) { rewritten.append(essl, lineBegin, next - lineBegin); } @@ -930,7 +940,7 @@ namespace MobileGL { size_t count = 0; SPVC_CHK_RESULT(spvc_resources_get_resource_list_for_type( - resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);) + resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &list, &count);) for (size_t i = 0; i < count; ++i) { if (spvc_compiler_has_decoration(compiler, list[i].id, SpvDecorationBuiltIn)) { continue; diff --git a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h index 105add73..cd667627 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h +++ b/MobileGL/MG_Util/ShaderTranspiler/SpvcSession.h @@ -62,11 +62,11 @@ namespace MobileGL { // so a module that carries them and an implementation that ignores them capture // nothing at all. struct SpirvXfbCapture { - String name; // the GL interface name: "gl_Position", or "Block.member" - Uint32 buffer = 0; // XfbBuffer on the declaring variable - Uint32 offset = 0; // Offset on the variable or on the member - Uint32 stride = 0; // XfbStride on the declaring variable - Uint32 componentCount = 0; // how many 32-bit components the capture occupies + String name; // the GL interface name: "gl_Position", or "Block.member" + Uint32 buffer = 0; // XfbBuffer on the declaring variable + Uint32 offset = 0; // Offset on the variable or on the member + Uint32 stride = 0; // XfbStride on the declaring variable + Uint32 componentCount = 0; // how many 32-bit components the capture occupies }; enum class SessionUsageBit { @@ -90,8 +90,7 @@ namespace MobileGL { public: SpvcSession() = default; - explicit SpvcSession(const Vector& spirv, - Flags usage); + explicit SpvcSession(const Vector& spirv, Flags usage); SpvcSession(SpvcSession&) = delete; @@ -222,8 +221,7 @@ namespace MobileGL { // sets `outUnknownConstantId` when an id the caller passed is not a // specialization constant of this module, which the extension makes // GL_INVALID_VALUE. - Bool SetSpecializationConstants(const Vector& constantIds, - const Vector& constantValues, + Bool SetSpecializationConstants(const Vector& constantIds, const Vector& constantValues, Uint32& outUnknownConstantId); spvc_result Compile(const char** result); const SpvcMetadata& GetMetadata() const; diff --git a/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp b/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp index de61f3ee..290f730c 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.cpp @@ -14,8 +14,8 @@ namespace MobileGL::MG_Util::ShaderTranspiler { namespace { // Tags keep two different key builders from ever producing the same blob, // even if their inputs happened to serialize identically. - constexpr Uint32 kSpirvKeyTag = 0x4d474c31u; // "MGL1" - constexpr Uint32 kEsslKeyTag = 0x4d474c32u; // "MGL2" + constexpr Uint32 kSpirvKeyTag = 0x4d474c31u; // "MGL1" + constexpr Uint32 kEsslKeyTag = 0x4d474c32u; // "MGL2" constexpr Uint32 kParseVerdictKeyTag = 0x4d474c43u; // "MGLC" - L1c, the compile half // Bumped whenever the SHAPE of a key changes (a field added, a field's @@ -102,7 +102,8 @@ namespace MobileGL::MG_Util::ShaderTranspiler { void TranslationKeyBuilder::TextList(const Vector& values) { Value(static_cast(values.size())); - for (const String& value : values) Text(value); + for (const String& value : values) + Text(value); } void TranslationKeyBuilder::StringMap(const std::map& map) { @@ -115,7 +116,8 @@ namespace MobileGL::MG_Util::ShaderTranspiler { void TranslationKeyBuilder::NameSet(const std::set& names) { Value(static_cast(names.size())); - for (const String& name : names) Text(name); + for (const String& name : names) + Text(name); } TranslationCacheKey MakeTranslationCacheKey(String blob) { @@ -170,9 +172,9 @@ namespace MobileGL::MG_Util::ShaderTranspiler { // Leaked for the same exit-order reason as the other two; see the note below. BoundedTranslationCache& GetShaderParseVerdictCache() { - static auto* const kCache = new BoundedTranslationCache( - "ShaderTranslationCache L1c (GLSL->parse verdict)", kParseVerdictCacheMaxEntries, - kParseVerdictCacheMaxBytes); + static auto* const kCache = + new BoundedTranslationCache("ShaderTranslationCache L1c (GLSL->parse verdict)", + kParseVerdictCacheMaxEntries, kParseVerdictCacheMaxBytes); return *kCache; } @@ -200,8 +202,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { static const UnorderedMap kEmptyFormats; builder.NameMap(inputs.glFormatByUniformName ? *inputs.glFormatByUniformName : kEmptyFormats); static const UnorderedMap kEmptyBindings; - builder.NameMap(inputs.storageBlockBindingOverrides ? *inputs.storageBlockBindingOverrides - : kEmptyBindings); + builder.NameMap(inputs.storageBlockBindingOverrides ? *inputs.storageBlockBindingOverrides : kEmptyBindings); static const std::map kEmptyRenames; builder.StringMap(inputs.inputBlockRenames ? *inputs.inputBlockRenames : kEmptyRenames); builder.StringMap(inputs.outputBlockRenames ? *inputs.outputBlockRenames : kEmptyRenames); @@ -214,7 +215,8 @@ namespace MobileGL::MG_Util::ShaderTranspiler { SizeT EsslTranslationResultBytes(const EsslTranslationResult& result) { SizeT bytes = result.essl.size(); - for (const String& name : result.flattenedXfbBlockNames) bytes += name.size(); + for (const String& name : result.flattenedXfbBlockNames) + bytes += name.size(); bytes += result.atomicCounterGlBindings.size() * sizeof(Int); return bytes; } diff --git a/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.h b/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.h index 803a7b73..52fdde17 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.h +++ b/MobileGL/MG_Util/ShaderTranspiler/TranslationCache.h @@ -119,9 +119,9 @@ namespace MobileGL::MG_Util::ShaderTranspiler { static_assert(std::is_trivially_copyable_v); Vector> sorted; sorted.reserve(map.size()); - for (const auto& [name, value] : map) sorted.emplace_back(StringView(name), value); - std::sort(sorted.begin(), sorted.end(), - [](const auto& a, const auto& b) { return a.first < b.first; }); + for (const auto& [name, value] : map) + sorted.emplace_back(StringView(name), value); + std::sort(sorted.begin(), sorted.end(), [](const auto& a, const auto& b) { return a.first < b.first; }); Value(static_cast(sorted.size())); for (const auto& [name, value] : sorted) { Text(name); @@ -281,14 +281,11 @@ namespace MobileGL::MG_Util::ShaderTranspiler { const Uint64 lookups = stats.hits + stats.misses; MGLOG_D("%s: %llu/%llu hits (%.1f%%), %llu inserts, %llu evictions, %llu oversize, " "%llu duplicate, %zu entries / %zu KiB", - m_name, static_cast(stats.hits), - static_cast(lookups), + m_name, static_cast(stats.hits), static_cast(lookups), lookups ? 100.0 * static_cast(stats.hits) / static_cast(lookups) : 0.0, - static_cast(stats.inserts), - static_cast(stats.evictions), + static_cast(stats.inserts), static_cast(stats.evictions), static_cast(stats.rejectedOversize), - static_cast(stats.duplicateInserts), EntryCount(), - StoredBytes() / 1024u); + static_cast(stats.duplicateInserts), EntryCount(), StoredBytes() / 1024u); } // Tests only: makes the caps small enough to exercise eviction without @@ -313,8 +310,7 @@ namespace MobileGL::MG_Util::ShaderTranspiler { using EntryList = std::list; void EvictUntilWithinBudgetLocked() { - while (!m_entries.empty() && - (m_entries.size() > m_maxEntries || m_storedBytes > m_maxBytes)) { + while (!m_entries.empty() && (m_entries.size() > m_maxEntries || m_storedBytes > m_maxBytes)) { const auto victim = m_entries.begin(); m_storedBytes -= victim->bytes; m_index.erase(victim->key); diff --git a/MobileGL/MG_Util/ShaderTranspiler/Types.h b/MobileGL/MG_Util/ShaderTranspiler/Types.h index 051ce951..785f5a45 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/Types.h +++ b/MobileGL/MG_Util/ShaderTranspiler/Types.h @@ -107,9 +107,9 @@ namespace MobileGL { return std::min(std::max(limit, 0), storageBindings); }; const Int stageStorageBlocks[ATOMIC_COUNTER_STAGE_COUNT] = { - p.MaxVertexShaderStorageBlocks, p.MaxTessControlShaderStorageBlocks, + p.MaxVertexShaderStorageBlocks, p.MaxTessControlShaderStorageBlocks, p.MaxTessEvaluationShaderStorageBlocks, p.MaxGeometryShaderStorageBlocks, - p.MaxFragmentShaderStorageBlocks, p.MaxComputeShaderStorageBlocks}; + p.MaxFragmentShaderStorageBlocks, p.MaxComputeShaderStorageBlocks}; AtomicCounterLimits limits; Int bufferSum = 0; for (SizeT stage = 0; stage < ATOMIC_COUNTER_STAGE_COUNT; ++stage) { @@ -217,11 +217,11 @@ namespace MobileGL { // Zero or negative means "no ceiling to enforce for this kind" - a backendless // environment, which every unit test and the pre-init preload path run in. struct ResourceBindingLimits { - Int MaxSamplerBindings = 0; // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS - Int MaxImageBindings = 0; // GL_MAX_IMAGE_UNITS - Int MaxUniformBufferBindings = 0; // GL_MAX_UNIFORM_BUFFER_BINDINGS - Int MaxShaderStorageBufferBindings = 0; // GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS - Int MaxAtomicCounterBufferBindings = 0; // GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS + Int MaxSamplerBindings = 0; // GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS + Int MaxImageBindings = 0; // GL_MAX_IMAGE_UNITS + Int MaxUniformBufferBindings = 0; // GL_MAX_UNIFORM_BUFFER_BINDINGS + Int MaxShaderStorageBufferBindings = 0; // GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS + Int MaxAtomicCounterBufferBindings = 0; // GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS }; struct ProgramAttrib { diff --git a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp index d68be20e..2b1ebfe8 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.cpp @@ -168,7 +168,8 @@ namespace MobileGL { const int key = buildStorageKey(EShLangFragment, glslang::EvqInOut); for (int location = top; location >= 0; --location) { bool free = true; - for (int i = 0; i < size && free; ++i) free = checkEmpty(key, location + i); + for (int i = 0; i < size && free; ++i) + free = checkEmpty(key, location + i); if (!free) continue; reserveSlot(key, location, size); ent.symbol->getWritableType().getQualifier().layoutLocation = location; @@ -212,7 +213,7 @@ namespace MobileGL { // is the single derivation, and neither site computes a ceiling of its own. void TMglGlslIoResolver::CheckDeclaredBindingRange(const glslang::TType& type, const glslang::TString& name) { if (m_bindingLimits == nullptr || m_bindingViolation == nullptr) return; - if (!m_bindingViolation->empty()) return; // first violation wins; the link is already lost + if (!m_bindingViolation->empty()) return; // first violation wins; the link is already lost const glslang::TQualifier& qualifier = type.getQualifier(); const char* kind = nullptr; @@ -232,8 +233,8 @@ namespace MobileGL { // TRAILING NUMBER is the GL binding the shader asked for (ParseContextBase:: // growAtomicCounterBlock names it from bufferBinding). That name is the only surviving // record of the declaration, so it is what the counter ceiling is read off. - const Int counterBinding = MG_Util::ShaderTranspiler::AtomicCounterBlockGlBinding( - StringView(name.c_str(), name.size())); + const Int counterBinding = + MG_Util::ShaderTranspiler::AtomicCounterBlockGlBinding(StringView(name.c_str(), name.size())); if (counterBinding >= 0) { kind = "atomic_uint"; limitName = "GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS"; @@ -275,8 +276,8 @@ namespace MobileGL { const long long lastBinding = binding + elementCount - 1; if (lastBinding < static_cast(limit)) return; - String message = "Error: layout(binding = " + std::to_string(binding) + ") on " + kind + " '" + - String(name.c_str()) + "'"; + String message = + "Error: layout(binding = " + std::to_string(binding) + ") on " + kind + " '" + String(name.c_str()) + "'"; if (elementCount > 1) { message += " (an array of " + std::to_string(elementCount) + ", occupying bindings " + std::to_string(binding) + ".." + std::to_string(lastBinding) + ")"; diff --git a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h index 3a871923..dc1c881b 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h +++ b/MobileGL/MG_Util/ShaderTranspiler/glslang/TMglGlslIoResolver.h @@ -33,8 +33,7 @@ namespace MobileGL { ExplicitVarSlotMap* opaqueUniformBindings, std::set* storageBlocksWithoutBinding = nullptr, std::set* uniformBlocksWithoutBinding = nullptr, - const ResourceBindingLimits* bindingLimits = nullptr, - String* bindingViolation = nullptr) + const ResourceBindingLimits* bindingLimits = nullptr, String* bindingViolation = nullptr) : TDefaultGlslIoResolver(intermediate), m_explicitVertexIns(vertexIns), m_explicitFragOuts(fragOuts), m_explicitFragOutIndices(fragOutIndices), m_explicitOpaqueUniformBindings(opaqueUniformBindings), m_storageBlocksWithoutBinding(storageBlocksWithoutBinding), @@ -45,8 +44,7 @@ namespace MobileGL { const ExplicitVarSlotMap& fragOutIndices, ExplicitVarSlotMap* opaqueUniformBindings, std::set* storageBlocksWithoutBinding = nullptr, std::set* uniformBlocksWithoutBinding = nullptr, - const ResourceBindingLimits* bindingLimits = nullptr, - String* bindingViolation = nullptr) + const ResourceBindingLimits* bindingLimits = nullptr, String* bindingViolation = nullptr) : TMglGlslIoResolver(*program.getIntermediate(stage), vertexIns, fragOuts, fragOutIndices, opaqueUniformBindings, storageBlocksWithoutBinding, uniformBlocksWithoutBinding, bindingLimits, bindingViolation) {} diff --git a/MobileGL/MG_Util/Texture/EtcDecoder.cpp b/MobileGL/MG_Util/Texture/EtcDecoder.cpp index aba8bce5..0e0390cc 100644 --- a/MobileGL/MG_Util/Texture/EtcDecoder.cpp +++ b/MobileGL/MG_Util/Texture/EtcDecoder.cpp @@ -15,7 +15,8 @@ namespace MobileGL::MG_Util::EtcDecoder { // GL 4.6 core C.2 reads every block as a big-endian integer; bit 63 is the MSB of byte 0. Uint64 ReadBigEndian64(const Uint8* bytes) { Uint64 value = 0; - for (Int i = 0; i < 8; ++i) value = (value << 8) | bytes[i]; + for (Int i = 0; i < 8; ++i) + value = (value << 8) | bytes[i]; return value; } @@ -23,25 +24,35 @@ namespace MobileGL::MG_Util::EtcDecoder { return static_cast((word >> low) & ((Uint64{1} << (high - low + 1)) - 1)); } - Uint8 Clamp255(Int value) { return static_cast(std::clamp(value, 0, 255)); } - Int Extend4(Uint32 v) { return static_cast((v << 4) | v); } - Int Extend5(Uint32 v) { return static_cast((v << 3) | (v >> 2)); } - Int Extend6(Uint32 v) { return static_cast((v << 2) | (v >> 4)); } - Int Extend7(Uint32 v) { return static_cast((v << 1) | (v >> 6)); } + Uint8 Clamp255(Int value) { + return static_cast(std::clamp(value, 0, 255)); + } + Int Extend4(Uint32 v) { + return static_cast((v << 4) | v); + } + Int Extend5(Uint32 v) { + return static_cast((v << 3) | (v >> 2)); + } + Int Extend6(Uint32 v) { + return static_cast((v << 2) | (v >> 4)); + } + Int Extend7(Uint32 v) { + return static_cast((v << 1) | (v >> 6)); + } // Table C.12: intensity modifiers of the individual/differential modes, indexed by the // codeword; a texel's (msb, lsb) picks +a, +b, -a, -b. - constexpr Int kIntensityModifiers[8][2] = {{2, 8}, {5, 17}, {9, 29}, {13, 42}, + constexpr Int kIntensityModifiers[8][2] = {{2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183}}; // Table C.14: the T and H mode distances. constexpr Int kDistances[8] = {3, 6, 11, 16, 23, 32, 41, 64}; // Table C.16: the EAC modifier tables (alpha, R11 and RG11 share them). constexpr Int kEacModifiers[16][8] = { - {-3, -6, -9, -15, 2, 5, 8, 14}, {-3, -7, -10, -13, 2, 6, 9, 12}, {-2, -5, -8, -13, 1, 4, 7, 12}, - {-2, -4, -6, -13, 1, 3, 5, 12}, {-3, -6, -8, -12, 2, 5, 7, 11}, {-3, -7, -9, -11, 2, 6, 8, 10}, - {-4, -7, -8, -11, 3, 6, 7, 10}, {-3, -5, -8, -11, 2, 4, 7, 10}, {-2, -6, -8, -10, 1, 5, 7, 9}, - {-2, -5, -8, -10, 1, 4, 7, 9}, {-2, -4, -8, -10, 1, 3, 7, 9}, {-2, -5, -7, -10, 1, 4, 6, 9}, - {-3, -4, -7, -10, 2, 3, 6, 9}, {-1, -2, -3, -10, 0, 1, 2, 9}, {-4, -6, -8, -9, 3, 5, 7, 8}, + {-3, -6, -9, -15, 2, 5, 8, 14}, {-3, -7, -10, -13, 2, 6, 9, 12}, {-2, -5, -8, -13, 1, 4, 7, 12}, + {-2, -4, -6, -13, 1, 3, 5, 12}, {-3, -6, -8, -12, 2, 5, 7, 11}, {-3, -7, -9, -11, 2, 6, 8, 10}, + {-4, -7, -8, -11, 3, 6, 7, 10}, {-3, -5, -8, -11, 2, 4, 7, 10}, {-2, -6, -8, -10, 1, 5, 7, 9}, + {-2, -5, -8, -10, 1, 4, 7, 9}, {-2, -4, -8, -10, 1, 3, 7, 9}, {-2, -5, -7, -10, 1, 4, 6, 9}, + {-3, -4, -7, -10, 2, 3, 6, 9}, {-1, -2, -3, -10, 0, 1, 2, 9}, {-4, -6, -8, -9, 3, 5, 7, 8}, {-3, -5, -7, -9, 2, 4, 6, 8}}; // One ETC2 RGB block (with the punch-through alpha variant) into 16 RGBA texels, @@ -152,10 +163,9 @@ namespace MobileGL::MG_Util::EtcDecoder { } if (b2 < 0 || b2 > 31) { // Planar mode: always opaque, an origin and two gradients. - const Int o[3] = {Extend6(Bits(word, 62, 57)), - Extend7((Bits(word, 56, 56) << 6) | Bits(word, 54, 49)), - Extend6((Bits(word, 48, 48) << 5) | (Bits(word, 44, 43) << 3) | - Bits(word, 41, 39))}; + const Int o[3] = { + Extend6(Bits(word, 62, 57)), Extend7((Bits(word, 56, 56) << 6) | Bits(word, 54, 49)), + Extend6((Bits(word, 48, 48) << 5) | (Bits(word, 44, 43) << 3) | Bits(word, 41, 39))}; const Int h[3] = {Extend6((Bits(word, 38, 34) << 1) | Bits(word, 32, 32)), Extend7(Bits(word, 31, 25)), Extend6(Bits(word, 24, 19))}; const Int v[3] = {Extend6(Bits(word, 18, 13)), Extend7(Bits(word, 12, 6)), @@ -261,7 +271,9 @@ namespace MobileGL::MG_Util::EtcDecoder { } } // namespace - Bool IsEtcFormat(GLenum compressedFormat) { return DecodedTexelBytes(compressedFormat) != 0; } + Bool IsEtcFormat(GLenum compressedFormat) { + return DecodedTexelBytes(compressedFormat) != 0; + } SizeT DecodedTexelBytes(GLenum compressedFormat) { switch (compressedFormat) { @@ -290,12 +302,14 @@ namespace MobileGL::MG_Util::EtcDecoder { case GL_COMPRESSED_RGB8_ETC2: case GL_COMPRESSED_SRGB8_ETC2: DecodeEtc2Rgb(block, false, rgba); - for (Int t = 0; t < 16; ++t) std::copy(rgba[t], rgba[t] + 3, out + t * 3); + for (Int t = 0; t < 16; ++t) + std::copy(rgba[t], rgba[t] + 3, out + t * 3); return true; case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: DecodeEtc2Rgb(block, true, rgba); - for (Int t = 0; t < 16; ++t) std::copy(rgba[t], rgba[t] + 4, out + t * 4); + for (Int t = 0; t < 16; ++t) + std::copy(rgba[t], rgba[t] + 4, out + t * 4); return true; case GL_COMPRESSED_RGBA8_ETC2_EAC: case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: { @@ -313,7 +327,8 @@ namespace MobileGL::MG_Util::EtcDecoder { const Bool isSigned = compressedFormat == GL_COMPRESSED_SIGNED_R11_EAC; Int red[16]; DecodeEac11(block, isSigned, red); - for (Int t = 0; t < 16; ++t) out[t] = Eac11ToByte(red[t], isSigned); + for (Int t = 0; t < 16; ++t) + out[t] = Eac11ToByte(red[t], isSigned); return true; } case GL_COMPRESSED_RG11_EAC: diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp index a698c4cf..8df66869 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.cpp @@ -145,83 +145,187 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { return true; case TextureInternalFormat::R8: - case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::Red: + out = {1, ShadowComponent::UNorm8, false}; + return true; case TextureInternalFormat::RG8: - case TextureInternalFormat::RG: out = {2, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::RG: + out = {2, ShadowComponent::UNorm8, false}; + return true; case TextureInternalFormat::RGB8: case TextureInternalFormat::RGB: - case TextureInternalFormat::SRGB8: out = {3, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::SRGB8: + out = {3, ShadowComponent::UNorm8, false}; + return true; case TextureInternalFormat::RGBA8: case TextureInternalFormat::RGBA: - case TextureInternalFormat::SRGB8Alpha8: out = {4, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::SRGB8Alpha8: + out = {4, ShadowComponent::UNorm8, false}; + return true; // Legacy desktop-GL sized normalized formats are stored in the closest ES-legal layout // (see TextureFormatProcessor::NormalizePixelFormat): 8-bit unorm for <=8-bit channels, // 16-bit unorm for 10/12-bit channels. case TextureInternalFormat::R3G3B2: case TextureInternalFormat::RGB4: - case TextureInternalFormat::RGB5: out = {3, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::RGB5: + out = {3, ShadowComponent::UNorm8, false}; + return true; case TextureInternalFormat::RGBA2: case TextureInternalFormat::RGBA4: - case TextureInternalFormat::RGB5A1: out = {4, ShadowComponent::UNorm8, false}; return true; + case TextureInternalFormat::RGB5A1: + out = {4, ShadowComponent::UNorm8, false}; + return true; case TextureInternalFormat::RGB10: - case TextureInternalFormat::RGB12: out = {3, ShadowComponent::UNorm16, false}; return true; - case TextureInternalFormat::RGBA12: out = {4, ShadowComponent::UNorm16, false}; return true; + case TextureInternalFormat::RGB12: + out = {3, ShadowComponent::UNorm16, false}; + return true; + case TextureInternalFormat::RGBA12: + out = {4, ShadowComponent::UNorm16, false}; + return true; - case TextureInternalFormat::R8Snorm: out = {1, ShadowComponent::SNorm8, false}; return true; - case TextureInternalFormat::RG8Snorm: out = {2, ShadowComponent::SNorm8, false}; return true; - case TextureInternalFormat::RGB8Snorm: out = {3, ShadowComponent::SNorm8, false}; return true; - case TextureInternalFormat::RGBA8Snorm: out = {4, ShadowComponent::SNorm8, false}; return true; + case TextureInternalFormat::R8Snorm: + out = {1, ShadowComponent::SNorm8, false}; + return true; + case TextureInternalFormat::RG8Snorm: + out = {2, ShadowComponent::SNorm8, false}; + return true; + case TextureInternalFormat::RGB8Snorm: + out = {3, ShadowComponent::SNorm8, false}; + return true; + case TextureInternalFormat::RGBA8Snorm: + out = {4, ShadowComponent::SNorm8, false}; + return true; - case TextureInternalFormat::R16: out = {1, ShadowComponent::UNorm16, false}; return true; - case TextureInternalFormat::RG16: out = {2, ShadowComponent::UNorm16, false}; return true; - case TextureInternalFormat::RGB16: out = {3, ShadowComponent::UNorm16, false}; return true; - case TextureInternalFormat::RGBA16: out = {4, ShadowComponent::UNorm16, false}; return true; + case TextureInternalFormat::R16: + out = {1, ShadowComponent::UNorm16, false}; + return true; + case TextureInternalFormat::RG16: + out = {2, ShadowComponent::UNorm16, false}; + return true; + case TextureInternalFormat::RGB16: + out = {3, ShadowComponent::UNorm16, false}; + return true; + case TextureInternalFormat::RGBA16: + out = {4, ShadowComponent::UNorm16, false}; + return true; - case TextureInternalFormat::R16Snorm: out = {1, ShadowComponent::SNorm16, false}; return true; - case TextureInternalFormat::RG16Snorm: out = {2, ShadowComponent::SNorm16, false}; return true; - case TextureInternalFormat::RGB16Snorm: out = {3, ShadowComponent::SNorm16, false}; return true; - case TextureInternalFormat::RGBA16Snorm: out = {4, ShadowComponent::SNorm16, false}; return true; + case TextureInternalFormat::R16Snorm: + out = {1, ShadowComponent::SNorm16, false}; + return true; + case TextureInternalFormat::RG16Snorm: + out = {2, ShadowComponent::SNorm16, false}; + return true; + case TextureInternalFormat::RGB16Snorm: + out = {3, ShadowComponent::SNorm16, false}; + return true; + case TextureInternalFormat::RGBA16Snorm: + out = {4, ShadowComponent::SNorm16, false}; + return true; - case TextureInternalFormat::R16F: out = {1, ShadowComponent::Half, false}; return true; - case TextureInternalFormat::RG16F: out = {2, ShadowComponent::Half, false}; return true; - case TextureInternalFormat::RGB16F: out = {3, ShadowComponent::Half, false}; return true; - case TextureInternalFormat::RGBA16F: out = {4, ShadowComponent::Half, false}; return true; + case TextureInternalFormat::R16F: + out = {1, ShadowComponent::Half, false}; + return true; + case TextureInternalFormat::RG16F: + out = {2, ShadowComponent::Half, false}; + return true; + case TextureInternalFormat::RGB16F: + out = {3, ShadowComponent::Half, false}; + return true; + case TextureInternalFormat::RGBA16F: + out = {4, ShadowComponent::Half, false}; + return true; - case TextureInternalFormat::R32F: out = {1, ShadowComponent::Float32, false}; return true; - case TextureInternalFormat::RG32F: out = {2, ShadowComponent::Float32, false}; return true; - case TextureInternalFormat::RGB32F: out = {3, ShadowComponent::Float32, false}; return true; - case TextureInternalFormat::RGBA32F: out = {4, ShadowComponent::Float32, false}; return true; + case TextureInternalFormat::R32F: + out = {1, ShadowComponent::Float32, false}; + return true; + case TextureInternalFormat::RG32F: + out = {2, ShadowComponent::Float32, false}; + return true; + case TextureInternalFormat::RGB32F: + out = {3, ShadowComponent::Float32, false}; + return true; + case TextureInternalFormat::RGBA32F: + out = {4, ShadowComponent::Float32, false}; + return true; - case TextureInternalFormat::R8UI: out = {1, ShadowComponent::UInt8, true}; return true; - case TextureInternalFormat::RG8UI: out = {2, ShadowComponent::UInt8, true}; return true; - case TextureInternalFormat::RGB8UI: out = {3, ShadowComponent::UInt8, true}; return true; - case TextureInternalFormat::RGBA8UI: out = {4, ShadowComponent::UInt8, true}; return true; + case TextureInternalFormat::R8UI: + out = {1, ShadowComponent::UInt8, true}; + return true; + case TextureInternalFormat::RG8UI: + out = {2, ShadowComponent::UInt8, true}; + return true; + case TextureInternalFormat::RGB8UI: + out = {3, ShadowComponent::UInt8, true}; + return true; + case TextureInternalFormat::RGBA8UI: + out = {4, ShadowComponent::UInt8, true}; + return true; - case TextureInternalFormat::R8I: out = {1, ShadowComponent::Int8, true}; return true; - case TextureInternalFormat::RG8I: out = {2, ShadowComponent::Int8, true}; return true; - case TextureInternalFormat::RGB8I: out = {3, ShadowComponent::Int8, true}; return true; - case TextureInternalFormat::RGBA8I: out = {4, ShadowComponent::Int8, true}; return true; + case TextureInternalFormat::R8I: + out = {1, ShadowComponent::Int8, true}; + return true; + case TextureInternalFormat::RG8I: + out = {2, ShadowComponent::Int8, true}; + return true; + case TextureInternalFormat::RGB8I: + out = {3, ShadowComponent::Int8, true}; + return true; + case TextureInternalFormat::RGBA8I: + out = {4, ShadowComponent::Int8, true}; + return true; - case TextureInternalFormat::R16UI: out = {1, ShadowComponent::UInt16, true}; return true; - case TextureInternalFormat::RG16UI: out = {2, ShadowComponent::UInt16, true}; return true; - case TextureInternalFormat::RGB16UI: out = {3, ShadowComponent::UInt16, true}; return true; - case TextureInternalFormat::RGBA16UI: out = {4, ShadowComponent::UInt16, true}; return true; + case TextureInternalFormat::R16UI: + out = {1, ShadowComponent::UInt16, true}; + return true; + case TextureInternalFormat::RG16UI: + out = {2, ShadowComponent::UInt16, true}; + return true; + case TextureInternalFormat::RGB16UI: + out = {3, ShadowComponent::UInt16, true}; + return true; + case TextureInternalFormat::RGBA16UI: + out = {4, ShadowComponent::UInt16, true}; + return true; - case TextureInternalFormat::R16I: out = {1, ShadowComponent::Int16, true}; return true; - case TextureInternalFormat::RG16I: out = {2, ShadowComponent::Int16, true}; return true; - case TextureInternalFormat::RGB16I: out = {3, ShadowComponent::Int16, true}; return true; - case TextureInternalFormat::RGBA16I: out = {4, ShadowComponent::Int16, true}; return true; + case TextureInternalFormat::R16I: + out = {1, ShadowComponent::Int16, true}; + return true; + case TextureInternalFormat::RG16I: + out = {2, ShadowComponent::Int16, true}; + return true; + case TextureInternalFormat::RGB16I: + out = {3, ShadowComponent::Int16, true}; + return true; + case TextureInternalFormat::RGBA16I: + out = {4, ShadowComponent::Int16, true}; + return true; - case TextureInternalFormat::R32UI: out = {1, ShadowComponent::UInt32, true}; return true; - case TextureInternalFormat::RG32UI: out = {2, ShadowComponent::UInt32, true}; return true; - case TextureInternalFormat::RGB32UI: out = {3, ShadowComponent::UInt32, true}; return true; - case TextureInternalFormat::RGBA32UI: out = {4, ShadowComponent::UInt32, true}; return true; + case TextureInternalFormat::R32UI: + out = {1, ShadowComponent::UInt32, true}; + return true; + case TextureInternalFormat::RG32UI: + out = {2, ShadowComponent::UInt32, true}; + return true; + case TextureInternalFormat::RGB32UI: + out = {3, ShadowComponent::UInt32, true}; + return true; + case TextureInternalFormat::RGBA32UI: + out = {4, ShadowComponent::UInt32, true}; + return true; - case TextureInternalFormat::R32I: out = {1, ShadowComponent::Int32, true}; return true; - case TextureInternalFormat::RG32I: out = {2, ShadowComponent::Int32, true}; return true; - case TextureInternalFormat::RGB32I: out = {3, ShadowComponent::Int32, true}; return true; - case TextureInternalFormat::RGBA32I: out = {4, ShadowComponent::Int32, true}; return true; + case TextureInternalFormat::R32I: + out = {1, ShadowComponent::Int32, true}; + return true; + case TextureInternalFormat::RG32I: + out = {2, ShadowComponent::Int32, true}; + return true; + case TextureInternalFormat::RGB32I: + out = {3, ShadowComponent::Int32, true}; + return true; + case TextureInternalFormat::RGBA32I: + out = {4, ShadowComponent::Int32, true}; + return true; default: // Packed internal layouts (RGB10A2, RGB9E5, ...), depth/stencil and unsized formats @@ -266,14 +370,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { // The one client (format, type) pair whose word is bit-identical to the packed internal // word, if any. Everything else has to go through the decode/encode conversion. - Bool IsRawPackedPixelPair(PackedInternalKind kind, TextureInputFormat format, - TexturePixelDataType type) { + Bool IsRawPackedPixelPair(PackedInternalKind kind, TextureInputFormat format, TexturePixelDataType type) { switch (kind) { case PackedInternalKind::UNorm2101010Rev: return format == TextureInputFormat::RGBA && type == TexturePixelDataType::UnsignedInt2101010Rev; case PackedInternalKind::UInt2101010Rev: - return format == TextureInputFormat::RGBAInteger && - type == TexturePixelDataType::UnsignedInt2101010Rev; + return format == TextureInputFormat::RGBAInteger && type == TexturePixelDataType::UnsignedInt2101010Rev; case PackedInternalKind::FloatR11G11B10: return format == TextureInputFormat::RGB && type == TexturePixelDataType::UnsignedInt101111Rev; case PackedInternalKind::FloatRGB9E5: @@ -321,31 +423,71 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Bool GetUnpackChannelMapping(TextureInputFormat format, UnpackChannelMapping& out) { switch (format) { - case TextureInputFormat::Red: out = {{0, -1, -1, -1}, 1, false}; return true; - case TextureInputFormat::RInteger: out = {{0, -1, -1, -1}, 1, true}; return true; - case TextureInputFormat::Green: out = {{-1, 0, -1, -1}, 1, false}; return true; - case TextureInputFormat::GreenInteger: out = {{-1, 0, -1, -1}, 1, true}; return true; - case TextureInputFormat::Blue: out = {{-1, -1, 0, -1}, 1, false}; return true; - case TextureInputFormat::BlueInteger: out = {{-1, -1, 0, -1}, 1, true}; return true; - case TextureInputFormat::Alpha: out = {{-1, -1, -1, 0}, 1, false}; return true; - case TextureInputFormat::AlphaInteger: out = {{-1, -1, -1, 0}, 1, true}; return true; - case TextureInputFormat::RG: out = {{0, 1, -1, -1}, 2, false}; return true; - case TextureInputFormat::RGInteger: out = {{0, 1, -1, -1}, 2, true}; return true; - case TextureInputFormat::RGB: out = {{0, 1, 2, -1}, 3, false}; return true; - case TextureInputFormat::RGBInteger: out = {{0, 1, 2, -1}, 3, true}; return true; - case TextureInputFormat::BGR: out = {{2, 1, 0, -1}, 3, false}; return true; - case TextureInputFormat::BGRInteger: out = {{2, 1, 0, -1}, 3, true}; return true; - case TextureInputFormat::RGBA: out = {{0, 1, 2, 3}, 4, false}; return true; - case TextureInputFormat::RGBAInteger: out = {{0, 1, 2, 3}, 4, true}; return true; - case TextureInputFormat::BGRA: out = {{2, 1, 0, 3}, 4, false}; return true; - case TextureInputFormat::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true; + case TextureInputFormat::Red: + out = {{0, -1, -1, -1}, 1, false}; + return true; + case TextureInputFormat::RInteger: + out = {{0, -1, -1, -1}, 1, true}; + return true; + case TextureInputFormat::Green: + out = {{-1, 0, -1, -1}, 1, false}; + return true; + case TextureInputFormat::GreenInteger: + out = {{-1, 0, -1, -1}, 1, true}; + return true; + case TextureInputFormat::Blue: + out = {{-1, -1, 0, -1}, 1, false}; + return true; + case TextureInputFormat::BlueInteger: + out = {{-1, -1, 0, -1}, 1, true}; + return true; + case TextureInputFormat::Alpha: + out = {{-1, -1, -1, 0}, 1, false}; + return true; + case TextureInputFormat::AlphaInteger: + out = {{-1, -1, -1, 0}, 1, true}; + return true; + case TextureInputFormat::RG: + out = {{0, 1, -1, -1}, 2, false}; + return true; + case TextureInputFormat::RGInteger: + out = {{0, 1, -1, -1}, 2, true}; + return true; + case TextureInputFormat::RGB: + out = {{0, 1, 2, -1}, 3, false}; + return true; + case TextureInputFormat::RGBInteger: + out = {{0, 1, 2, -1}, 3, true}; + return true; + case TextureInputFormat::BGR: + out = {{2, 1, 0, -1}, 3, false}; + return true; + case TextureInputFormat::BGRInteger: + out = {{2, 1, 0, -1}, 3, true}; + return true; + case TextureInputFormat::RGBA: + out = {{0, 1, 2, 3}, 4, false}; + return true; + case TextureInputFormat::RGBAInteger: + out = {{0, 1, 2, 3}, 4, true}; + return true; + case TextureInputFormat::BGRA: + out = {{2, 1, 0, 3}, 4, false}; + return true; + case TextureInputFormat::BGRAInteger: + out = {{2, 1, 0, 3}, 4, true}; + return true; // A depth value converts like a single normalized/float channel. - case TextureInputFormat::DepthComponent: out = {{0, -1, -1, -1}, 1, false}; return true; + case TextureInputFormat::DepthComponent: + out = {{0, -1, -1, -1}, 1, false}; + return true; // A stencil index is a single INTEGER channel (GL 4.6 core 8.4.4.3). Without this the // upload fell to the raw-memcpy branch, which copies the client element width into the // one-byte STENCIL_INDEX8 shadow verbatim - right for GL_UNSIGNED_BYTE and wrong for // every wider type. The state layer keeps this paired with stencil-only storage. - case TextureInputFormat::StencilIndex: out = {{0, -1, -1, -1}, 1, true}; return true; + case TextureInputFormat::StencilIndex: + out = {{0, -1, -1, -1}, 1, true}; + return true; default: return false; // packed depth-stencil / unknown } @@ -360,18 +502,42 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Bool GetPackedTypeLayout(TexturePixelDataType type, PackedTypeLayout& out) { switch (type) { - case TexturePixelDataType::UnsignedByte332: out = {3, {3, 3, 2, 0}, 8, false}; return true; - case TexturePixelDataType::UnsignedByte233Rev: out = {3, {3, 3, 2, 0}, 8, true}; return true; - case TexturePixelDataType::UnsignedShort565: out = {3, {5, 6, 5, 0}, 16, false}; return true; - case TexturePixelDataType::UnsignedShort565Rev: out = {3, {5, 6, 5, 0}, 16, true}; return true; - case TexturePixelDataType::UnsignedShort4444: out = {4, {4, 4, 4, 4}, 16, false}; return true; - case TexturePixelDataType::UnsignedShort4444Rev: out = {4, {4, 4, 4, 4}, 16, true}; return true; - case TexturePixelDataType::UnsignedShort5551: out = {4, {5, 5, 5, 1}, 16, false}; return true; - case TexturePixelDataType::UnsignedShort1555Rev: out = {4, {5, 5, 5, 1}, 16, true}; return true; - case TexturePixelDataType::UnsignedInt8888: out = {4, {8, 8, 8, 8}, 32, false}; return true; - case TexturePixelDataType::UnsignedInt8888Rev: out = {4, {8, 8, 8, 8}, 32, true}; return true; - case TexturePixelDataType::UnsignedInt1010102: out = {4, {10, 10, 10, 2}, 32, false}; return true; - case TexturePixelDataType::UnsignedInt2101010Rev: out = {4, {10, 10, 10, 2}, 32, true}; return true; + case TexturePixelDataType::UnsignedByte332: + out = {3, {3, 3, 2, 0}, 8, false}; + return true; + case TexturePixelDataType::UnsignedByte233Rev: + out = {3, {3, 3, 2, 0}, 8, true}; + return true; + case TexturePixelDataType::UnsignedShort565: + out = {3, {5, 6, 5, 0}, 16, false}; + return true; + case TexturePixelDataType::UnsignedShort565Rev: + out = {3, {5, 6, 5, 0}, 16, true}; + return true; + case TexturePixelDataType::UnsignedShort4444: + out = {4, {4, 4, 4, 4}, 16, false}; + return true; + case TexturePixelDataType::UnsignedShort4444Rev: + out = {4, {4, 4, 4, 4}, 16, true}; + return true; + case TexturePixelDataType::UnsignedShort5551: + out = {4, {5, 5, 5, 1}, 16, false}; + return true; + case TexturePixelDataType::UnsignedShort1555Rev: + out = {4, {5, 5, 5, 1}, 16, true}; + return true; + case TexturePixelDataType::UnsignedInt8888: + out = {4, {8, 8, 8, 8}, 32, false}; + return true; + case TexturePixelDataType::UnsignedInt8888Rev: + out = {4, {8, 8, 8, 8}, 32, true}; + return true; + case TexturePixelDataType::UnsignedInt1010102: + out = {4, {10, 10, 10, 2}, 32, false}; + return true; + case TexturePixelDataType::UnsignedInt2101010Rev: + out = {4, {10, 10, 10, 2}, 32, true}; + return true; default: return false; // shared-exponent / packed-float / depth-stencil types stay on the legacy path } @@ -541,8 +707,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { out.isPacked = isPacked; out.type = type; out.inputPixelSize = GetInputBytesPerPixel(format, type); - const Bool isPackedFloatWord = type == TexturePixelDataType::UnsignedInt5999Rev || - type == TexturePixelDataType::UnsignedInt101111Rev; + const Bool isPackedFloatWord = + type == TexturePixelDataType::UnsignedInt5999Rev || type == TexturePixelDataType::UnsignedInt101111Rev; out.swapGroupSize = isPacked ? static_cast(packed.totalBits / 8) : (isPackedFloatWord ? 4 : GetBaseTexturePixelDataTypeSize(type)); out.internalIsPacked = hasPackedInternal; @@ -652,10 +818,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Int shift; if (packed.reversed) { shift = 0; - for (Int i = 0; i < position; ++i) shift += packed.width[i]; + for (Int i = 0; i < position; ++i) + shift += packed.width[i]; } else { shift = packed.totalBits; - for (Int i = 0; i <= position; ++i) shift -= packed.width[i]; + for (Int i = 0; i <= position; ++i) + shift -= packed.width[i]; } outWidth = packed.width[position]; const Uint32 mask = (1u << outWidth) - 1u; @@ -693,8 +861,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { Memcpy(dst, &v, sizeof(v)); break; case ShadowComponent::UNorm32: { - const auto out = static_cast( - std::llround(static_cast(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0)); + const auto out = + static_cast(std::llround(static_cast(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0)); Memcpy(dst, &out, sizeof(out)); break; } @@ -769,8 +937,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { continue; } for (Int ch = 0; ch < conv.internal.channelCount; ++ch) { - EncodeShadowComponentInt(d + static_cast(ch) * dstComponentSize, - conv.internal.component, rgba[ch]); + EncodeShadowComponentInt(d + static_cast(ch) * dstComponentSize, conv.internal.component, + rgba[ch]); } } else { Float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f}; @@ -873,8 +1041,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { // format (the stencil half becomes zero); the generic channel converter cannot // express the packed shadow words, so convert here. const Bool packedDepthStencilInternal = targetInternalFormat == TextureInternalFormat::Depth24Stencil8 || - targetInternalFormat == TextureInternalFormat::DepthStencil || - targetInternalFormat == TextureInternalFormat::Depth32FStencil8; + targetInternalFormat == TextureInternalFormat::DepthStencil || + targetInternalFormat == TextureInternalFormat::Depth32FStencil8; if (!isBitmap && packedDepthStencilInternal && textureInputFormat == TextureInputFormat::DepthComponent && (inputDataType == TexturePixelDataType::Float || inputDataType == TexturePixelDataType::UnsignedInt || inputDataType == TexturePixelDataType::UnsignedShort)) { @@ -886,7 +1054,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { outSize = 0; return nullptr; } - const Uint8* srcBase = static_cast(inputPixels) + + const Uint8* srcBase = + static_cast(inputPixels) + static_cast(params.SkipImages) * static_cast(effectiveHeight) * inputRowStride + static_cast(params.SkipRows) * inputRowStride + static_cast(params.SkipPixels) * pixelSize; @@ -894,8 +1063,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { for (Int z = 0; z < std::max(depth, 1); ++z) { for (Int y = 0; y < height; ++y) { const Uint8* srcRow = srcBase + - static_cast(z) * static_cast(effectiveHeight) * inputRowStride + - static_cast(y) * inputRowStride; + static_cast(z) * static_cast(effectiveHeight) * inputRowStride + + static_cast(y) * inputRowStride; for (Int x = 0; x < width; ++x) { Float depthValue = 0.0f; if (inputDataType == TexturePixelDataType::Float) { @@ -996,8 +1165,7 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { if (swapGroup == 0) swapGroup = GetBaseTexturePixelDataTypeSize(inputDataType); if (swapGroup > 1) { MGLOG_D("%s: SwapBytes (group %d)", __func__, static_cast(swapGroup)); - SwapBytes(layerDst, swapGroup, - static_cast(copyWidth) * pixelSize / swapGroup); + SwapBytes(layerDst, swapGroup, static_cast(copyWidth) * pixelSize / swapGroup); } } @@ -1018,10 +1186,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { return outputPixels; } - Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, - TextureInputFormat textureInputFormat, - TexturePixelDataType inputDataType, - const void* inputPixel, + Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, TextureInputFormat textureInputFormat, + TexturePixelDataType inputDataType, const void* inputPixel, Vector& outputPixel) { outputPixel.clear(); // A stencil index became a transferable format when STENCIL_INDEX8 texture storage did (see @@ -1034,9 +1200,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { PixelStoreParameters params{}; params.Alignment = 1; SizeT convertedSize = 0; - void* converted = ProcessTexturePixelsDataUnpack( - inputPixel, params, targetInternalFormat, textureInputFormat, inputDataType, {1, 1, 1}, false, - convertedSize); + void* converted = ProcessTexturePixelsDataUnpack(inputPixel, params, targetInternalFormat, textureInputFormat, + inputDataType, {1, 1, 1}, false, convertedSize); const SizeT expectedSize = MG_Util::GetSizedInternalFormatSizeInBytes(targetInternalFormat); if (converted == nullptr || convertedSize != expectedSize || expectedSize == 0) { if (converted != nullptr) free(converted); diff --git a/MobileGL/MG_Util/Texture/PixelStoreProcessor.h b/MobileGL/MG_Util/Texture/PixelStoreProcessor.h index fc778245..3ac3f77e 100644 --- a/MobileGL/MG_Util/Texture/PixelStoreProcessor.h +++ b/MobileGL/MG_Util/Texture/PixelStoreProcessor.h @@ -21,10 +21,8 @@ namespace MobileGL::MG_Util::PixelStoreProcessor { TextureInternalFormat srcInternalFormat, TexturePixelDataType srcDataType, TextureInputFormat dstInputFormat, TexturePixelDataType dstDataType, IntVec3 dimension, Bool isBitmap, SizeT& outSize); - Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, - TextureInputFormat textureInputFormat, - TexturePixelDataType inputDataType, - const void* inputPixel, + Bool ConvertOnePixelToInternal(TextureInternalFormat targetInternalFormat, TextureInputFormat textureInputFormat, + TexturePixelDataType inputDataType, const void* inputPixel, Vector& outputPixel); void ProcessColorSwizzle(void* data, SizeT pixelCount, const Vector& swizzle); diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp index 7296bbb9..97ab4501 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.cpp @@ -10,9 +10,8 @@ #include "MG_Util/Converters/GLToStr/GLEnumConverter.h" namespace MobileGL::MG_Util::TextureFormatProcessor { - Flags - GetApplicablePixelFormatNormalizeOptions(GLenum internalFormat, - Flags options) { + Flags GetApplicablePixelFormatNormalizeOptions( + GLenum internalFormat, Flags options) { Flags applicableOptions; switch (internalFormat) { case GL_DEPTH_COMPONENT32: @@ -384,8 +383,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { *outInternalFormat = GL_RGBA8; break; case GL_RGBA12: - *outInternalFormat = - (options & PixelFormatNormalizeOptionBit::NoNorm16) ? GL_RGBA32F : GL_RGBA16; + *outInternalFormat = (options & PixelFormatNormalizeOptionBit::NoNorm16) ? GL_RGBA32F : GL_RGBA16; break; default: *outInternalFormat = internalFormat; @@ -566,7 +564,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { default: MGLOG_E_ONCE("NormalizePixelFormat: outFormat: unhandled internalFormat: %s", - MG_Util::ConvertGLEnumToString(internalFormat).c_str()); + MG_Util::ConvertGLEnumToString(internalFormat).c_str()); // Fallback handling for other formats // Try to infer format from internal format name if (strstr(MG_Util::ConvertGLEnumToString(internalFormat).c_str(), "RGBA") != nullptr) { @@ -621,8 +619,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { case GL_RG16_SNORM: case GL_R16_SNORM: if ((options & PixelFormatNormalizeOptionBit::NoNorm16) || - (internalFormat == GL_RGB16_SNORM && - (options & PixelFormatNormalizeOptionBit::NoRGB16Snorm)) || + (internalFormat == GL_RGB16_SNORM && (options & PixelFormatNormalizeOptionBit::NoRGB16Snorm)) || (options & PixelFormatNormalizeOptionBit::NoSnorm16) || (options & PixelFormatNormalizeOptionBit::NoSnorm16RenderTarget)) { *outType = GL_FLOAT; @@ -798,7 +795,7 @@ namespace MobileGL::MG_Util::TextureFormatProcessor { default: MGLOG_E_ONCE("NormalizePixelFormat: outType: unhandled internalFormat: %s", - MG_Util::ConvertGLEnumToString(internalFormat).c_str()); + MG_Util::ConvertGLEnumToString(internalFormat).c_str()); // Fallback handling for other formats *outType = GL_UNSIGNED_BYTE; break; diff --git a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h index 67430e6f..c323009b 100644 --- a/MobileGL/MG_Util/Texture/TextureFormatProcessor.h +++ b/MobileGL/MG_Util/Texture/TextureFormatProcessor.h @@ -60,9 +60,8 @@ namespace MobileGL { None = 0, }; namespace MG_Util::TextureFormatProcessor { - Flags - GetApplicablePixelFormatNormalizeOptions(GLenum internalFormat, - Flags options); + Flags GetApplicablePixelFormatNormalizeOptions( + GLenum internalFormat, Flags options); void NormalizePixelFormat(GLenum internalFormat, Flags options, GLenum* outInternalFormat, GLenum* outFormat, GLenum* outType); } // namespace MG_Util::TextureFormatProcessor diff --git a/MobileGL/MG_Util/X11/DisplayGuard.cpp b/MobileGL/MG_Util/X11/DisplayGuard.cpp index 4beab4b3..b9b6fd26 100644 --- a/MobileGL/MG_Util/X11/DisplayGuard.cpp +++ b/MobileGL/MG_Util/X11/DisplayGuard.cpp @@ -38,7 +38,8 @@ namespace MobileGL::MG_Util::X11 { Int32 ToNumber(const char* begin, const char* end) { if (!AllDigits(begin, end) || end - begin > 6) return -1; Int32 value = 0; - for (const char* p = begin; p != end; ++p) value = value * 10 + (*p - '0'); + for (const char* p = begin; p != end; ++p) + value = value * 10 + (*p - '0'); return value; } @@ -66,7 +67,8 @@ namespace MobileGL::MG_Util::X11 { if (std::strncmp(path, kSocketDirectory, dirLength) == 0) { const char* digits = path + dirLength; const char* end = digits; - while (*end >= '0' && *end <= '9') ++end; + while (*end >= '0' && *end <= '9') + ++end; return ToNumber(digits, end); } // [protocol/][host]:display[.screen]: the LAST colon (an IPv6 host has colons of its own). @@ -74,15 +76,16 @@ namespace MobileGL::MG_Util::X11 { if (colon == nullptr) return -1; const char* digits = colon + 1; const char* end = digits; - while (*end >= '0' && *end <= '9') ++end; + while (*end >= '0' && *end <= '9') + ++end; if (*end != '\0' && *end != '.') return -1; (void)length; return ToNumber(digits, end); } Bool IsXServerProgram(const String& basename) { - static const char* const kServers[] = {"Xwayland", "Xorg", "Xorg.bin", "X", "Xvfb", "Xephyr", - "Xnest", "Xvnc", "Xwin", "XWin", "Xquartz", "Xfake"}; + static const char* const kServers[] = {"Xwayland", "Xorg", "Xorg.bin", "X", "Xvfb", "Xephyr", + "Xnest", "Xvnc", "Xwin", "XWin", "Xquartz", "Xfake"}; for (const char* server : kServers) { if (basename == server) return true; } diff --git a/scripts/data/structure_baseline.json b/scripts/data/structure_baseline.json index d3c6e428..bbb2f372 100644 --- a/scripts/data/structure_baseline.json +++ b/scripts/data/structure_baseline.json @@ -1,5 +1,5 @@ { - "file_excess_lines": 23081, - "function_excess_lines": 15862, + "file_excess_lines": 22926, + "function_excess_lines": 15516, "layering_violations": 63 } diff --git a/tools/cts/platform/tcuMobileGLPlatform.cpp b/tools/cts/platform/tcuMobileGLPlatform.cpp index 80e605c1..651142e2 100644 --- a/tools/cts/platform/tcuMobileGLPlatform.cpp +++ b/tools/cts/platform/tcuMobileGLPlatform.cpp @@ -82,36 +82,31 @@ using std::vector; #define EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR 0x00000004 #endif -namespace tcu -{ -namespace mobilegl -{ +namespace tcu { + namespace mobilegl { -static string getLibraryName(void) -{ - const char *env = std::getenv("MOBILEGL_CTS_LIB"); - return (env && env[0]) ? string(env) : string("libMobileGL.so"); -} + static string getLibraryName(void) { + const char* env = std::getenv("MOBILEGL_CTS_LIB"); + return (env && env[0]) ? string(env) : string("libMobileGL.so"); + } #if defined(__ANDROID__) -//! Window surfaces default on: they are the only kind DirectVulkan can use -//! on Android (its pbuffer path needs VK_EXT_headless_surface). -static bool useWindowSurface(void) -{ - const char *env = std::getenv("MOBILEGL_CTS_SURFACE"); - return !(env && string(env) == "pbuffer"); -} + //! Window surfaces default on: they are the only kind DirectVulkan can use + //! on Android (its pbuffer path needs VK_EXT_headless_surface). + static bool useWindowSurface(void) { + const char* env = std::getenv("MOBILEGL_CTS_SURFACE"); + return !(env && string(env) == "pbuffer"); + } #else -//! Desktop: pbuffer only. VK_EXT_headless_surface is available there and no -//! Activity-free native window abstraction exists. -static bool useWindowSurface(void) -{ - return false; -} + //! Desktop: pbuffer only. VK_EXT_headless_surface is available there and no + //! Activity-free native window abstraction exists. + static bool useWindowSurface(void) { + return false; + } #endif #if defined(__ANDROID__) -/*--------------------------------------------------------------------*//*! + /*--------------------------------------------------------------------*//*! * \brief A real ANativeWindow with no Activity behind it. * * AImageReader's window is an ordinary BufferQueue producer, so both @@ -119,381 +114,305 @@ static bool useWindowSurface(void) * listener must drain the queue: without it the producer blocks once maxImages * buffers are in flight and the next swap deadlocks. *//*--------------------------------------------------------------------*/ -class ImageReaderWindow -{ -public: - ImageReaderWindow(int width, int height) : m_reader(nullptr), m_window(nullptr) - { - const media_status_t status = - AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | - AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, - kMaxImages, &m_reader); - if (status != AMEDIA_OK || m_reader == nullptr) - throw tcu::ResourceError("AImageReader_newWithUsage() failed"); + class ImageReaderWindow { + public: + ImageReaderWindow(int width, int height) : m_reader(nullptr), m_window(nullptr) { + const media_status_t status = AImageReader_newWithUsage(width, height, AIMAGE_FORMAT_RGBA_8888, + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | + AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, + kMaxImages, &m_reader); + if (status != AMEDIA_OK || m_reader == nullptr) + throw tcu::ResourceError("AImageReader_newWithUsage() failed"); - AImageReader_ImageListener listener = {this, onImageAvailable}; - AImageReader_setImageListener(m_reader, &listener); + AImageReader_ImageListener listener = {this, onImageAvailable}; + AImageReader_setImageListener(m_reader, &listener); - if (AImageReader_getWindow(m_reader, &m_window) != AMEDIA_OK || m_window == nullptr) - { - AImageReader_delete(m_reader); - m_reader = nullptr; - throw tcu::ResourceError("AImageReader_getWindow() failed"); - } - ANativeWindow_acquire(m_window); - } + if (AImageReader_getWindow(m_reader, &m_window) != AMEDIA_OK || m_window == nullptr) { + AImageReader_delete(m_reader); + m_reader = nullptr; + throw tcu::ResourceError("AImageReader_getWindow() failed"); + } + ANativeWindow_acquire(m_window); + } - ~ImageReaderWindow(void) - { - if (m_window != nullptr) - ANativeWindow_release(m_window); - if (m_reader != nullptr) - { - AImageReader_setImageListener(m_reader, nullptr); - AImageReader_delete(m_reader); - } - } + ~ImageReaderWindow(void) { + if (m_window != nullptr) ANativeWindow_release(m_window); + if (m_reader != nullptr) { + AImageReader_setImageListener(m_reader, nullptr); + AImageReader_delete(m_reader); + } + } - ANativeWindow *getWindow(void) const - { - return m_window; - } + ANativeWindow* getWindow(void) const { return m_window; } -private: - static const int kMaxImages = 4; + private: + static const int kMaxImages = 4; - static void onImageAvailable(void *, AImageReader *reader) - { - AImage *image = nullptr; - if (AImageReader_acquireNextImage(reader, &image) == AMEDIA_OK && image != nullptr) - AImage_delete(image); - } + static void onImageAvailable(void*, AImageReader* reader) { + AImage* image = nullptr; + if (AImageReader_acquireNextImage(reader, &image) == AMEDIA_OK && image != nullptr) + AImage_delete(image); + } - ImageReaderWindow(const ImageReaderWindow &); - ImageReaderWindow &operator=(const ImageReaderWindow &); + ImageReaderWindow(const ImageReaderWindow&); + ImageReaderWindow& operator=(const ImageReaderWindow&); - AImageReader *m_reader; - ANativeWindow *m_window; -}; + AImageReader* m_reader; + ANativeWindow* m_window; + }; #else -//! Never instantiated on desktop; keeps EglRenderContext's member deletable. -class ImageReaderWindow -{ -}; + //! Never instantiated on desktop; keeps EglRenderContext's member deletable. + class ImageReaderWindow {}; #endif -class GetProcFuncLoader : public glw::FunctionLoader -{ -public: - GetProcFuncLoader(const eglw::Library &egl) : m_egl(egl) - { - } + class GetProcFuncLoader : public glw::FunctionLoader { + public: + GetProcFuncLoader(const eglw::Library& egl) : m_egl(egl) {} - glw::GenericFuncType get(const char *name) const - { - return (glw::GenericFuncType)m_egl.getProcAddress(name); - } + glw::GenericFuncType get(const char* name) const { + return (glw::GenericFuncType)m_egl.getProcAddress(name); + } -protected: - const eglw::Library &m_egl; -}; + protected: + const eglw::Library& m_egl; + }; -class EglRenderContext : public glu::RenderContext -{ -public: - EglRenderContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine, - const glu::RenderContext *sharedContext); - ~EglRenderContext(void); + class EglRenderContext : public glu::RenderContext { + public: + EglRenderContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine, + const glu::RenderContext* sharedContext); + ~EglRenderContext(void); - glu::ContextType getType(void) const - { - return m_contextType; - } - eglw::EGLContext getEglContext(void) const - { - return m_eglContext; - } - const glw::Functions &getFunctions(void) const - { - return m_glFunctions; - } - const tcu::RenderTarget &getRenderTarget(void) const - { - return m_renderTarget; - } - void postIterate(void); - void makeCurrent(void); + glu::ContextType getType(void) const { return m_contextType; } + eglw::EGLContext getEglContext(void) const { return m_eglContext; } + const glw::Functions& getFunctions(void) const { return m_glFunctions; } + const tcu::RenderTarget& getRenderTarget(void) const { return m_renderTarget; } + void postIterate(void); + void makeCurrent(void); - glw::GenericFuncType getProcAddress(const char *name) const - { - return (glw::GenericFuncType)m_egl.getProcAddress(name); - } + glw::GenericFuncType getProcAddress(const char* name) const { + return (glw::GenericFuncType)m_egl.getProcAddress(name); + } -private: - const eglw::DefaultLibrary m_egl; - const glu::ContextType m_contextType; - eglw::EGLDisplay m_eglDisplay; - eglw::EGLContext m_eglContext; - eglw::EGLSurface m_eglSurface; - ImageReaderWindow *m_window; - glw::Functions m_glFunctions; - tcu::RenderTarget m_renderTarget; - eglw::EGLContext m_sharedEglContext; -}; + private: + const eglw::DefaultLibrary m_egl; + const glu::ContextType m_contextType; + eglw::EGLDisplay m_eglDisplay; + eglw::EGLContext m_eglContext; + eglw::EGLSurface m_eglSurface; + ImageReaderWindow* m_window; + glw::Functions m_glFunctions; + tcu::RenderTarget m_renderTarget; + eglw::EGLContext m_sharedEglContext; + }; -class ContextFactory : public glu::ContextFactory -{ -public: - ContextFactory(void) : glu::ContextFactory("default", "MobileGL EGL context") - { - } + class ContextFactory : public glu::ContextFactory { + public: + ContextFactory(void) : glu::ContextFactory("default", "MobileGL EGL context") {} - glu::RenderContext *createContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine, - const glu::RenderContext *sharedContext) const - { - return new EglRenderContext(config, cmdLine, sharedContext); - } -}; + glu::RenderContext* createContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine, + const glu::RenderContext* sharedContext) const { + return new EglRenderContext(config, cmdLine, sharedContext); + } + }; -class Platform : public tcu::Platform, public glu::Platform -{ -public: - Platform(void) - { - m_contextFactoryRegistry.registerFactory(new ContextFactory()); - } + class Platform : public tcu::Platform, public glu::Platform { + public: + Platform(void) { m_contextFactoryRegistry.registerFactory(new ContextFactory()); } - const glu::Platform &getGLPlatform(void) const - { - return *this; - } -}; + const glu::Platform& getGLPlatform(void) const { return *this; } + }; -EglRenderContext::EglRenderContext(const glu::RenderConfig &config, const tcu::CommandLine &cmdLine, - const glu::RenderContext *sharedContext) - : m_egl(getLibraryName().c_str()) - , m_contextType(config.type) - , m_eglDisplay(EGL_NO_DISPLAY) - , m_eglContext(EGL_NO_CONTEXT) - , m_eglSurface(EGL_NO_SURFACE) - , m_window(nullptr) - , m_renderTarget(config.width, config.height, - tcu::PixelFormat(config.redBits, config.greenBits, config.blueBits, config.alphaBits), - config.depthBits, config.stencilBits, config.numSamples) - , m_sharedEglContext(EGL_NO_CONTEXT) -{ - DE_UNREF(cmdLine); + EglRenderContext::EglRenderContext(const glu::RenderConfig& config, const tcu::CommandLine& cmdLine, + const glu::RenderContext* sharedContext) + : m_egl(getLibraryName().c_str()), m_contextType(config.type), m_eglDisplay(EGL_NO_DISPLAY), + m_eglContext(EGL_NO_CONTEXT), m_eglSurface(EGL_NO_SURFACE), m_window(nullptr), + m_renderTarget(config.width, config.height, + tcu::PixelFormat(config.redBits, config.greenBits, config.blueBits, config.alphaBits), + config.depthBits, config.stencilBits, config.numSamples), + m_sharedEglContext(EGL_NO_CONTEXT) { + DE_UNREF(cmdLine); - const glu::ContextType &contextType = config.type; - const bool isES = glu::isContextTypeES(contextType); - eglw::EGLint eglMajorVersion = 0; - eglw::EGLint eglMinorVersion = 0; + const glu::ContextType& contextType = config.type; + const bool isES = glu::isContextTypeES(contextType); + eglw::EGLint eglMajorVersion = 0; + eglw::EGLint eglMinorVersion = 0; - m_eglDisplay = m_egl.getDisplay(EGL_DEFAULT_DISPLAY); - EGLU_CHECK_MSG(m_egl, "eglGetDisplay()"); - if (m_eglDisplay == EGL_NO_DISPLAY) - throw tcu::ResourceError("eglGetDisplay() failed"); + m_eglDisplay = m_egl.getDisplay(EGL_DEFAULT_DISPLAY); + EGLU_CHECK_MSG(m_egl, "eglGetDisplay()"); + if (m_eglDisplay == EGL_NO_DISPLAY) throw tcu::ResourceError("eglGetDisplay() failed"); - EGLU_CHECK_CALL(m_egl, initialize(m_eglDisplay, &eglMajorVersion, &eglMinorVersion)); + EGLU_CHECK_CALL(m_egl, initialize(m_eglDisplay, &eglMajorVersion, &eglMinorVersion)); - // MobileGL cannot make a context current without a surface, so - // SURFACETYPE_DONT_CARE (which is what --deqp-surface-type=fbo requests) - // still gets a real one. - bool wantWindow = false; - switch (config.surfaceType) - { - case glu::RenderConfig::SURFACETYPE_WINDOW: - wantWindow = true; - break; - case glu::RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE: - case glu::RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC: - wantWindow = false; - break; - case glu::RenderConfig::SURFACETYPE_DONT_CARE: - wantWindow = useWindowSurface(); - break; - default: - TCU_CHECK_INTERNAL(false); - } + // MobileGL cannot make a context current without a surface, so + // SURFACETYPE_DONT_CARE (which is what --deqp-surface-type=fbo requests) + // still gets a real one. + bool wantWindow = false; + switch (config.surfaceType) { + case glu::RenderConfig::SURFACETYPE_WINDOW: + wantWindow = true; + break; + case glu::RenderConfig::SURFACETYPE_OFFSCREEN_NATIVE: + case glu::RenderConfig::SURFACETYPE_OFFSCREEN_GENERIC: + wantWindow = false; + break; + case glu::RenderConfig::SURFACETYPE_DONT_CARE: + wantWindow = useWindowSurface(); + break; + default: + TCU_CHECK_INTERNAL(false); + } - const int width = (config.width == glu::RenderConfig::DONT_CARE) ? 256 : config.width; - const int height = (config.height == glu::RenderConfig::DONT_CARE) ? 256 : config.height; + const int width = (config.width == glu::RenderConfig::DONT_CARE) ? 256 : config.width; + const int height = (config.height == glu::RenderConfig::DONT_CARE) ? 256 : config.height; - vector cfgAttribs; - cfgAttribs.push_back(EGL_RENDERABLE_TYPE); - if (isES) - { - switch (contextType.getMajorVersion()) - { - case 3: - cfgAttribs.push_back(EGL_OPENGL_ES3_BIT); - break; - case 2: - cfgAttribs.push_back(EGL_OPENGL_ES2_BIT); - break; - default: - cfgAttribs.push_back(EGL_OPENGL_ES_BIT); - } - } - else - { - // Desktop GL, which is the whole point of this port. - cfgAttribs.push_back(EGL_OPENGL_BIT); - } + vector cfgAttribs; + cfgAttribs.push_back(EGL_RENDERABLE_TYPE); + if (isES) { + switch (contextType.getMajorVersion()) { + case 3: + cfgAttribs.push_back(EGL_OPENGL_ES3_BIT); + break; + case 2: + cfgAttribs.push_back(EGL_OPENGL_ES2_BIT); + break; + default: + cfgAttribs.push_back(EGL_OPENGL_ES_BIT); + } + } else { + // Desktop GL, which is the whole point of this port. + cfgAttribs.push_back(EGL_OPENGL_BIT); + } - cfgAttribs.push_back(EGL_SURFACE_TYPE); - cfgAttribs.push_back(wantWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT); + cfgAttribs.push_back(EGL_SURFACE_TYPE); + cfgAttribs.push_back(wantWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT); - static const struct - { - eglw::EGLint attrib; - int glu::RenderConfig::*field; - } s_sizeAttribs[] = { - {EGL_RED_SIZE, &glu::RenderConfig::redBits}, {EGL_GREEN_SIZE, &glu::RenderConfig::greenBits}, - {EGL_BLUE_SIZE, &glu::RenderConfig::blueBits}, {EGL_ALPHA_SIZE, &glu::RenderConfig::alphaBits}, - {EGL_DEPTH_SIZE, &glu::RenderConfig::depthBits}, {EGL_STENCIL_SIZE, &glu::RenderConfig::stencilBits}, - {EGL_SAMPLES, &glu::RenderConfig::numSamples}, - }; - for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_sizeAttribs); ndx++) - { - const int value = config.*(s_sizeAttribs[ndx].field); - if (value != glu::RenderConfig::DONT_CARE) - { - cfgAttribs.push_back(s_sizeAttribs[ndx].attrib); - cfgAttribs.push_back(value); - } - } - cfgAttribs.push_back(EGL_NONE); + static const struct { + eglw::EGLint attrib; + int glu::RenderConfig::* field; + } s_sizeAttribs[] = { + {EGL_RED_SIZE, &glu::RenderConfig::redBits}, {EGL_GREEN_SIZE, &glu::RenderConfig::greenBits}, + {EGL_BLUE_SIZE, &glu::RenderConfig::blueBits}, {EGL_ALPHA_SIZE, &glu::RenderConfig::alphaBits}, + {EGL_DEPTH_SIZE, &glu::RenderConfig::depthBits}, {EGL_STENCIL_SIZE, &glu::RenderConfig::stencilBits}, + {EGL_SAMPLES, &glu::RenderConfig::numSamples}, + }; + for (size_t ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_sizeAttribs); ndx++) { + const int value = config.*(s_sizeAttribs[ndx].field); + if (value != glu::RenderConfig::DONT_CARE) { + cfgAttribs.push_back(s_sizeAttribs[ndx].attrib); + cfgAttribs.push_back(value); + } + } + cfgAttribs.push_back(EGL_NONE); - eglw::EGLConfig eglConfig = nullptr; - eglw::EGLint numConfigs = 0; - EGLU_CHECK_CALL(m_egl, chooseConfig(m_eglDisplay, &cfgAttribs[0], &eglConfig, 1, &numConfigs)); - if (numConfigs < 1) - throw tcu::NotSupportedError("No matching EGL config for the requested context"); + eglw::EGLConfig eglConfig = nullptr; + eglw::EGLint numConfigs = 0; + EGLU_CHECK_CALL(m_egl, chooseConfig(m_eglDisplay, &cfgAttribs[0], &eglConfig, 1, &numConfigs)); + if (numConfigs < 1) throw tcu::NotSupportedError("No matching EGL config for the requested context"); - if (wantWindow) - { + if (wantWindow) { #if defined(__ANDROID__) - m_window = new ImageReaderWindow(width, height); + m_window = new ImageReaderWindow(width, height); - eglw::EGLint visualId = 0; - if (m_egl.getConfigAttrib(m_eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &visualId) && visualId != 0) - ANativeWindow_setBuffersGeometry(m_window->getWindow(), width, height, visualId); + eglw::EGLint visualId = 0; + if (m_egl.getConfigAttrib(m_eglDisplay, eglConfig, EGL_NATIVE_VISUAL_ID, &visualId) && visualId != 0) + ANativeWindow_setBuffersGeometry(m_window->getWindow(), width, height, visualId); - m_eglSurface = m_egl.createWindowSurface(m_eglDisplay, eglConfig, - (eglw::EGLNativeWindowType)m_window->getWindow(), nullptr); - EGLU_CHECK_MSG(m_egl, "eglCreateWindowSurface()"); + m_eglSurface = m_egl.createWindowSurface(m_eglDisplay, eglConfig, + (eglw::EGLNativeWindowType)m_window->getWindow(), nullptr); + EGLU_CHECK_MSG(m_egl, "eglCreateWindowSurface()"); #else - throw tcu::NotSupportedError("Window surfaces are not supported by the desktop MobileGL platform"); + throw tcu::NotSupportedError("Window surfaces are not supported by the desktop MobileGL platform"); #endif - } - else - { - const eglw::EGLint surfaceAttribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; - m_eglSurface = m_egl.createPbufferSurface(m_eglDisplay, eglConfig, surfaceAttribs); - EGLU_CHECK_MSG(m_egl, "eglCreatePbufferSurface()"); - } + } else { + const eglw::EGLint surfaceAttribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; + m_eglSurface = m_egl.createPbufferSurface(m_eglDisplay, eglConfig, surfaceAttribs); + EGLU_CHECK_MSG(m_egl, "eglCreatePbufferSurface()"); + } - if (m_eglSurface == EGL_NO_SURFACE) - throw tcu::ResourceError("Failed to create EGL surface"); + if (m_eglSurface == EGL_NO_SURFACE) throw tcu::ResourceError("Failed to create EGL surface"); - vector ctxAttribs; - ctxAttribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR); - ctxAttribs.push_back(contextType.getMajorVersion()); - ctxAttribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR); - ctxAttribs.push_back(contextType.getMinorVersion()); + vector ctxAttribs; + ctxAttribs.push_back(EGL_CONTEXT_MAJOR_VERSION_KHR); + ctxAttribs.push_back(contextType.getMajorVersion()); + ctxAttribs.push_back(EGL_CONTEXT_MINOR_VERSION_KHR); + ctxAttribs.push_back(contextType.getMinorVersion()); - switch (contextType.getProfile()) - { - case glu::PROFILE_ES: - EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_ES_API)); - break; - case glu::PROFILE_CORE: - EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API)); - ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR); - ctxAttribs.push_back(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR); - break; - case glu::PROFILE_COMPATIBILITY: - EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API)); - ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR); - ctxAttribs.push_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR); - break; - default: - TCU_CHECK_INTERNAL(false); - } + switch (contextType.getProfile()) { + case glu::PROFILE_ES: + EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_ES_API)); + break; + case glu::PROFILE_CORE: + EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API)); + ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR); + ctxAttribs.push_back(EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR); + break; + case glu::PROFILE_COMPATIBILITY: + EGLU_CHECK_CALL(m_egl, bindAPI(EGL_OPENGL_API)); + ctxAttribs.push_back(EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR); + ctxAttribs.push_back(EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR); + break; + default: + TCU_CHECK_INTERNAL(false); + } - eglw::EGLint flags = 0; - if ((contextType.getFlags() & glu::CONTEXT_DEBUG) != 0) - flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; - if ((contextType.getFlags() & glu::CONTEXT_ROBUST) != 0) - flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; - if ((contextType.getFlags() & glu::CONTEXT_FORWARD_COMPATIBLE) != 0) - flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; - if (flags != 0) - { - ctxAttribs.push_back(EGL_CONTEXT_FLAGS_KHR); - ctxAttribs.push_back(flags); - } - ctxAttribs.push_back(EGL_NONE); + eglw::EGLint flags = 0; + if ((contextType.getFlags() & glu::CONTEXT_DEBUG) != 0) flags |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR; + if ((contextType.getFlags() & glu::CONTEXT_ROBUST) != 0) flags |= EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR; + if ((contextType.getFlags() & glu::CONTEXT_FORWARD_COMPATIBLE) != 0) + flags |= EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR; + if (flags != 0) { + ctxAttribs.push_back(EGL_CONTEXT_FLAGS_KHR); + ctxAttribs.push_back(flags); + } + ctxAttribs.push_back(EGL_NONE); - const EglRenderContext *sharedEglRenderContext = dynamic_cast(sharedContext); - m_sharedEglContext = sharedEglRenderContext ? sharedEglRenderContext->getEglContext() : EGL_NO_CONTEXT; + const EglRenderContext* sharedEglRenderContext = dynamic_cast(sharedContext); + m_sharedEglContext = sharedEglRenderContext ? sharedEglRenderContext->getEglContext() : EGL_NO_CONTEXT; - m_eglContext = m_egl.createContext(m_eglDisplay, eglConfig, m_sharedEglContext, &ctxAttribs[0]); - EGLU_CHECK_MSG(m_egl, "eglCreateContext()"); - if (!m_eglContext) - throw tcu::ResourceError("eglCreateContext() failed"); + m_eglContext = m_egl.createContext(m_eglDisplay, eglConfig, m_sharedEglContext, &ctxAttribs[0]); + EGLU_CHECK_MSG(m_egl, "eglCreateContext()"); + if (!m_eglContext) throw tcu::ResourceError("eglCreateContext() failed"); - // MobileGL requires draw == read. - EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); + // MobileGL requires draw == read. + EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); - // MobileGL advertises EGL 1.5, so eglGetProcAddress resolves core entry - // points too; there is no separate GL library to dlopen. - GetProcFuncLoader funcLoader(m_egl); - glu::initCoreFunctions(&m_glFunctions, &funcLoader, contextType.getAPI()); - glu::initExtensionFunctions(&m_glFunctions, &funcLoader, contextType.getAPI()); -} - -EglRenderContext::~EglRenderContext(void) -{ - try - { - if (m_eglDisplay != EGL_NO_DISPLAY) - { - m_egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - - if (m_eglContext != EGL_NO_CONTEXT) - m_egl.destroyContext(m_eglDisplay, m_eglContext); - - if (m_eglSurface != EGL_NO_SURFACE) - m_egl.destroySurface(m_eglDisplay, m_eglSurface); - - if (m_sharedEglContext == EGL_NO_CONTEXT) - m_egl.terminate(m_eglDisplay); + // MobileGL advertises EGL 1.5, so eglGetProcAddress resolves core entry + // points too; there is no separate GL library to dlopen. + GetProcFuncLoader funcLoader(m_egl); + glu::initCoreFunctions(&m_glFunctions, &funcLoader, contextType.getAPI()); + glu::initExtensionFunctions(&m_glFunctions, &funcLoader, contextType.getAPI()); } - } - catch (...) - { - } - delete m_window; -} + EglRenderContext::~EglRenderContext(void) { + try { + if (m_eglDisplay != EGL_NO_DISPLAY) { + m_egl.makeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); -void EglRenderContext::makeCurrent(void) -{ - EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); -} + if (m_eglContext != EGL_NO_CONTEXT) m_egl.destroyContext(m_eglDisplay, m_eglContext); -void EglRenderContext::postIterate(void) -{ - m_glFunctions.finish(); -} + if (m_eglSurface != EGL_NO_SURFACE) m_egl.destroySurface(m_eglDisplay, m_eglSurface); -} // namespace mobilegl + if (m_sharedEglContext == EGL_NO_CONTEXT) m_egl.terminate(m_eglDisplay); + } + } + catch (...) { + } + + delete m_window; + } + + void EglRenderContext::makeCurrent(void) { + EGLU_CHECK_CALL(m_egl, makeCurrent(m_eglDisplay, m_eglSurface, m_eglSurface, m_eglContext)); + } + + void EglRenderContext::postIterate(void) { + m_glFunctions.finish(); + } + + } // namespace mobilegl } // namespace tcu -tcu::Platform *createPlatform(void) -{ +tcu::Platform* createPlatform(void) { return new tcu::mobilegl::Platform(); } diff --git a/tools/cts/platform/tcuMobileGLPlatform.hpp b/tools/cts/platform/tcuMobileGLPlatform.hpp index 722fbe39..1ad1d279 100644 --- a/tools/cts/platform/tcuMobileGLPlatform.hpp +++ b/tools/cts/platform/tcuMobileGLPlatform.hpp @@ -23,11 +23,10 @@ #include "tcuDefs.hpp" -namespace tcu -{ -class Platform; -} +namespace tcu { + class Platform; +} // namespace tcu -tcu::Platform *createPlatform(void); +tcu::Platform* createPlatform(void); #endif // _TCUMOBILEGLPLATFORM_HPP diff --git a/tools/cts/probe/mgprobe.c b/tools/cts/probe/mgprobe.c index be275bba..3a443be7 100644 --- a/tools/cts/probe/mgprobe.c +++ b/tools/cts/probe/mgprobe.c @@ -33,15 +33,15 @@ #include #include -typedef void *EGLDisplay; -typedef void *EGLConfig; -typedef void *EGLSurface; -typedef void *EGLContext; +typedef void* EGLDisplay; +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; typedef int EGLint; typedef unsigned int EGLBoolean; typedef unsigned int EGLenum; -typedef void *EGLNativeDisplayType; -typedef void *EGLNativeWindowType; +typedef void* EGLNativeDisplayType; +typedef void* EGLNativeWindowType; #define EGL_DEFAULT_DISPLAY ((EGLNativeDisplayType)0) #define EGL_NO_CONTEXT ((EGLContext)0) @@ -88,43 +88,45 @@ typedef void *EGLNativeWindowType; #define GL_RENDERBUFFER 0x8D41 typedef EGLDisplay (*P_getDisplay)(EGLNativeDisplayType); -typedef EGLBoolean (*P_initialize)(EGLDisplay, EGLint *, EGLint *); +typedef EGLBoolean (*P_initialize)(EGLDisplay, EGLint*, EGLint*); typedef EGLBoolean (*P_bindAPI)(EGLenum); -typedef EGLBoolean (*P_chooseConfig)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *); -typedef EGLBoolean (*P_getConfigAttrib)(EGLDisplay, EGLConfig, EGLint, EGLint *); -typedef EGLSurface (*P_createWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *); -typedef EGLSurface (*P_createPbufferSurface)(EGLDisplay, EGLConfig, const EGLint *); -typedef EGLContext (*P_createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint *); +typedef EGLBoolean (*P_chooseConfig)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); +typedef EGLBoolean (*P_getConfigAttrib)(EGLDisplay, EGLConfig, EGLint, EGLint*); +typedef EGLSurface (*P_createWindowSurface)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint*); +typedef EGLSurface (*P_createPbufferSurface)(EGLDisplay, EGLConfig, const EGLint*); +typedef EGLContext (*P_createContext)(EGLDisplay, EGLConfig, EGLContext, const EGLint*); typedef EGLBoolean (*P_makeCurrent)(EGLDisplay, EGLSurface, EGLSurface, EGLContext); typedef EGLint (*P_getError)(void); -typedef const unsigned char *(*P_glGetString)(unsigned int); -typedef void (*P_glGetIntegerv)(unsigned int, int *); +typedef const unsigned char* (*P_glGetString)(unsigned int); +typedef void (*P_glGetIntegerv)(unsigned int, int*); typedef void (*P_glClearColor)(float, float, float, float); typedef void (*P_glClear)(unsigned int); typedef void (*P_glFinish)(void); -typedef void (*P_glReadPixels)(int, int, int, int, unsigned int, unsigned int, void *); +typedef void (*P_glReadPixels)(int, int, int, int, unsigned int, unsigned int, void*); typedef unsigned int (*P_glGetError)(void); -typedef void (*P_glGenTextures)(int, unsigned int *); +typedef void (*P_glGenTextures)(int, unsigned int*); typedef void (*P_glBindTexture)(unsigned int, unsigned int); -typedef void (*P_glTexImage2D)(unsigned int, int, int, int, int, int, unsigned int, unsigned int, const void *); +typedef void (*P_glTexImage2D)(unsigned int, int, int, int, int, int, unsigned int, unsigned int, const void*); typedef void (*P_glTexParameteri)(unsigned int, unsigned int, int); -typedef void (*P_glGenFramebuffers)(int, unsigned int *); +typedef void (*P_glGenFramebuffers)(int, unsigned int*); typedef void (*P_glBindFramebuffer)(unsigned int, unsigned int); typedef void (*P_glFramebufferTexture2D)(unsigned int, unsigned int, unsigned int, unsigned int, int); typedef unsigned int (*P_glCheckFramebufferStatus)(unsigned int); typedef void (*P_glViewport)(int, int, int, int); -typedef void (*P_glGenRenderbuffers)(int, unsigned int *); +typedef void (*P_glGenRenderbuffers)(int, unsigned int*); typedef void (*P_glBindRenderbuffer)(unsigned int, unsigned int); typedef void (*P_glRenderbufferStorage)(unsigned int, unsigned int, int, int); typedef void (*P_glFramebufferRenderbuffer)(unsigned int, unsigned int, unsigned int, unsigned int); -static void *g_lib; -static void *S(const char *n) { return dlsym(g_lib, n); } +static void* g_lib; +static void* S(const char* n) { + return dlsym(g_lib, n); +} -static void on_image(void *ctx, AImageReader *r) { +static void on_image(void* ctx, AImageReader* r) { (void)ctx; - AImage *img = NULL; + AImage* img = NULL; /* Drain the queue, or the producer blocks once maxImages are in flight. */ if (AImageReader_acquireNextImage(r, &img) == AMEDIA_OK && img) AImage_delete(img); } @@ -136,18 +138,23 @@ static int near8(unsigned got, int want, int tol) { return d <= tol && d >= -tol; } -int main(int argc, char **argv) { - const char *backend = "DirectGLES"; - const char *surface = "pbuffer"; - const char *libpath = "libMobileGL.so"; +int main(int argc, char** argv) { + const char* backend = "DirectGLES"; + const char* surface = "pbuffer"; + const char* libpath = "libMobileGL.so"; for (int i = 1; i < argc; ++i) { - if (!strcmp(argv[i], "--backend") && i + 1 < argc) backend = argv[++i]; - else if (!strcmp(argv[i], "--surface") && i + 1 < argc) surface = argv[++i]; - else if (!strcmp(argv[i], "--lib") && i + 1 < argc) libpath = argv[++i]; + if (!strcmp(argv[i], "--backend") && i + 1 < argc) + backend = argv[++i]; + else if (!strcmp(argv[i], "--surface") && i + 1 < argc) + surface = argv[++i]; + else if (!strcmp(argv[i], "--lib") && i + 1 < argc) + libpath = argv[++i]; else { - fprintf(stderr, "usage: %s [--backend DirectGLES|DirectVulkan]" - " [--surface pbuffer|imagereader] [--lib path]\n", argv[0]); + fprintf(stderr, + "usage: %s [--backend DirectGLES|DirectVulkan]" + " [--surface pbuffer|imagereader] [--lib path]\n", + argv[0]); return 2; } } @@ -159,13 +166,13 @@ int main(int argc, char **argv) { printf("mgprobe backend=%s surface=%s lib=%s\n", backend, surface, libpath); int useWindow = !strcmp(surface, "imagereader"); - ANativeWindow *win = NULL; - AImageReader *reader = NULL; + ANativeWindow* win = NULL; + AImageReader* reader = NULL; if (useWindow) { if (AImageReader_newWithUsage(DIM, DIM, AIMAGE_FORMAT_RGBA_8888, - AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | - AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, - 4, &reader) != AMEDIA_OK || !reader) { + AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE | AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT, + 4, &reader) != AMEDIA_OK || + !reader) { printf("FAIL AImageReader_newWithUsage\n"); return 3; } @@ -210,12 +217,23 @@ int main(int argc, char **argv) { return 7; } - const EGLint cfgAttribs[] = { - EGL_SURFACE_TYPE, useWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 24, EGL_STENCIL_SIZE, 8, - EGL_NONE}; + const EGLint cfgAttribs[] = {EGL_SURFACE_TYPE, + useWindow ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 24, + EGL_STENCIL_SIZE, + 8, + EGL_NONE}; EGLConfig cfg = 0; EGLint ncfg = 0; if (!eglChooseConfig_(dpy, cfgAttribs, &cfg, 1, &ncfg) || ncfg < 1) { @@ -234,14 +252,13 @@ int main(int argc, char **argv) { surf = eglCreatePbufferSurface_(dpy, cfg, sa); } if (surf == EGL_NO_SURFACE) { - printf("FAIL create%sSurface err=0x%x\n", useWindow ? "Window" : "Pbuffer", - eglGetError_ ? eglGetError_() : 0); + printf("FAIL create%sSurface err=0x%x\n", useWindow ? "Window" : "Pbuffer", eglGetError_ ? eglGetError_() : 0); return 9; } const EGLint ctxAttribs[] = { - EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, - EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE}; + EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 3, EGL_CONTEXT_OPENGL_PROFILE_MASK, + EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT, EGL_NONE}; EGLContext ctx = eglCreateContext_(dpy, cfg, EGL_NO_CONTEXT, ctxAttribs); if (ctx == EGL_NO_CONTEXT) { printf("FAIL eglCreateContext(3.3 core) err=0x%x\n", eglGetError_ ? eglGetError_() : 0); @@ -272,18 +289,18 @@ int main(int argc, char **argv) { P_glGenRenderbuffers glGenRenderbuffers_ = (P_glGenRenderbuffers)S("glGenRenderbuffers"); P_glBindRenderbuffer glBindRenderbuffer_ = (P_glBindRenderbuffer)S("glBindRenderbuffer"); P_glRenderbufferStorage glRenderbufferStorage_ = (P_glRenderbufferStorage)S("glRenderbufferStorage"); - P_glFramebufferRenderbuffer glFramebufferRenderbuffer_ = (P_glFramebufferRenderbuffer)S("glFramebufferRenderbuffer"); + P_glFramebufferRenderbuffer glFramebufferRenderbuffer_ = + (P_glFramebufferRenderbuffer)S("glFramebufferRenderbuffer"); int major = -1, minor = -1, profile = -1; glGetIntegerv_(GL_MAJOR_VERSION, &major); glGetIntegerv_(GL_MINOR_VERSION, &minor); glGetIntegerv_(GL_CONTEXT_PROFILE_MASK, &profile); - printf(" GL_VENDOR %s\n", (const char *)glGetString_(GL_VENDOR)); - printf(" GL_RENDERER %s\n", (const char *)glGetString_(GL_RENDERER)); - printf(" GL_VERSION %s\n", (const char *)glGetString_(GL_VERSION)); - printf(" GLSL %s\n", (const char *)glGetString_(GL_SHADING_LANGUAGE_VERSION)); - printf(" version %d.%d profile_mask 0x%x %s\n", major, minor, profile, - (profile & 1) ? "(core)" : "(NOT CORE)"); + printf(" GL_VENDOR %s\n", (const char*)glGetString_(GL_VENDOR)); + printf(" GL_RENDERER %s\n", (const char*)glGetString_(GL_RENDERER)); + printf(" GL_VERSION %s\n", (const char*)glGetString_(GL_VERSION)); + printf(" GLSL %s\n", (const char*)glGetString_(GL_SHADING_LANGUAGE_VERSION)); + printf(" version %d.%d profile_mask 0x%x %s\n", major, minor, profile, (profile & 1) ? "(core)" : "(NOT CORE)"); unsigned char px[4]; @@ -294,8 +311,7 @@ int main(int argc, char **argv) { memset(px, 0, sizeof px); glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); int defOk = near8(px[0], 64, 10) && near8(px[1], 128, 10) && near8(px[2], 191, 10); - printf(" default-FB readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], - defOk ? "ok" : "BROKEN"); + printf(" default-FB readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], defOk ? "ok" : "BROKEN"); /* User FBO - this is what dEQP uses with --deqp-surface-type=fbo. */ unsigned int tex = 0, fbo = 0; @@ -317,8 +333,7 @@ int main(int argc, char **argv) { memset(px, 0, sizeof px); glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); fboOk = near8(px[0], 230, 10) && near8(px[1], 51, 10) && near8(px[2], 102, 10); - printf(" user-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], - fboOk ? "ok" : "BROKEN"); + printf(" user-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], fboOk ? "ok" : "BROKEN"); } else { printf(" user-FBO incomplete status=0x%x\n", fbst); } @@ -346,8 +361,7 @@ int main(int argc, char **argv) { memset(px, 0, sizeof px); glReadPixels_(DIM / 2, DIM / 2, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, px); rboOk = near8(px[0], 26, 10) && near8(px[1], 179, 10) && near8(px[2], 77, 10); - printf(" rbo-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], - rboOk ? "ok" : "BROKEN"); + printf(" rbo-FBO readback (%u,%u,%u,%u) %s\n", px[0], px[1], px[2], px[3], rboOk ? "ok" : "BROKEN"); } else { printf(" rbo-FBO incomplete status=0x%x\n", rst); } @@ -357,9 +371,8 @@ int main(int argc, char **argv) { unsigned glerr = glGetError_ ? glGetError_() : 0; int ok = fboOk && rboOk && (major > 3 || (major == 3 && minor >= 3)) && (profile & 1) && glerr == 0; - printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s rbo_fbo=%s glerr=0x%x\n", - ok ? "PASS" : "FAIL", backend, surface, defOk ? "ok" : "broken", - fboOk ? "ok" : "broken", rboOk ? "ok" : "broken", glerr); + printf("%s backend=%s surface=%s default_fb=%s user_fbo=%s rbo_fbo=%s glerr=0x%x\n", ok ? "PASS" : "FAIL", backend, + surface, defOk ? "ok" : "broken", fboOk ? "ok" : "broken", rboOk ? "ok" : "broken", glerr); fflush(stdout); /* MobileGL aborts in static teardown; leave before that runs. */ diff --git a/tools/device_bench/disagg/cpusampler.c b/tools/device_bench/disagg/cpusampler.c index ec546f7c..7371b612 100644 --- a/tools/device_bench/disagg/cpusampler.c +++ b/tools/device_bench/disagg/cpusampler.c @@ -114,8 +114,10 @@ static int ScanPids(const char* pkg, int* pids) { static void Push(long long t, int index, long long cpu) { struct Sample* s = &ring[(ringHead + ringSize) % ringCap]; - if (ringSize == ringCap) ringHead = (ringHead + 1) % ringCap; - else ++ringSize; + if (ringSize == ringCap) + ringHead = (ringHead + 1) % ringCap; + else + ++ringSize; s->t = t; s->index = index; s->cpu = cpu; diff --git a/tools/device_bench/disagg/sampler.c b/tools/device_bench/disagg/sampler.c index 74a8a786..d67759e5 100644 --- a/tools/device_bench/disagg/sampler.c +++ b/tools/device_bench/disagg/sampler.c @@ -21,7 +21,10 @@ #include static volatile sig_atomic_t g_stop = 0; -static void onsig(int s) { (void)s; g_stop = 1; } +static void onsig(int s) { + (void)s; + g_stop = 1; +} #define MAXT 128 #define MAXSY 400 @@ -29,14 +32,14 @@ static void onsig(int s) { (void)s; g_stop = 1; } struct T { int tid; char name[32]; - long long cpu0; // utime+stime at start (ticks) - long long vcs0; // nr_voluntary_switches at start - long long run; // samples with syscall "running" - long long sys[MAXSY]; // samples while in syscall nr + long long cpu0; // utime+stime at start (ticks) + long long vcs0; // nr_voluntary_switches at start + long long run; // samples with syscall "running" + long long sys[MAXSY]; // samples while in syscall nr long long openerr; }; -static long long readfile(const char *p, char *buf, int n) { +static long long readfile(const char* p, char* buf, int n) { int fd = open(p, O_RDONLY); if (fd < 0) return -1; long long r = read(fd, buf, n - 1); @@ -45,12 +48,15 @@ static long long readfile(const char *p, char *buf, int n) { return r; } -int main(int argc, char **argv) { - if (argc < 4) { fprintf(stderr, "usage: sampler pid dur_s interval_ms out\n"); return 2; } +int main(int argc, char** argv) { + if (argc < 4) { + fprintf(stderr, "usage: sampler pid dur_s interval_ms out\n"); + return 2; + } int pid = atoi(argv[1]); int durMs = atoi(argv[2]) * 1000; int intMs = argc > 4 ? atoi(argv[3]) : 2; - const char *out = argc > 4 ? argv[4] : "sampler.csv"; + const char* out = argc > 4 ? argv[4] : "sampler.csv"; struct T ts[MAXT]; int n = 0; @@ -59,35 +65,45 @@ int main(int argc, char **argv) { // initial scan for (int pass = 0; pass < 2; pass++) { snprintf(path, sizeof path, "/proc/%d/task", pid); - DIR *d = opendir(path); - if (!d) { perror("opendir task"); return 1; } - struct dirent *e; + DIR* d = opendir(path); + if (!d) { + perror("opendir task"); + return 1; + } + struct dirent* e; n = 0; while ((e = readdir(d)) && n < MAXT) { if (e->d_name[0] == '.') continue; int tid = atoi(e->d_name); if (tid <= 0) continue; - struct T *t = &ts[n++]; + struct T* t = &ts[n++]; memset(t, 0, sizeof *t); t->tid = tid; snprintf(path, sizeof path, "/proc/%d/task/%d/comm", pid, tid); if (readfile(path, buf, sizeof buf) > 0) { strncpy(t->name, buf, sizeof t->name - 1); - char *nl = strchr(t->name, '\n'); if (nl) *nl = 0; + char* nl = strchr(t->name, '\n'); + if (nl) *nl = 0; } snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, tid); long long ut = 0, st = 0; if (readfile(path, buf, sizeof buf) > 0) { // fields: 1 pid, 2 comm, 3 state ... 14 utime, 15 stime. // tokens after ')' start at field 3, so utime is f==11, stime f==12. - char *r = strrchr(buf, ')'); + char* r = strrchr(buf, ')'); if (r) { - int f = 0; char *sp = r + 1; + int f = 0; + char* sp = r + 1; while (*sp) { if (f == 11) ut = atoll(sp); - if (f == 12) { st = atoll(sp); break; } - while (*sp && *sp != ' ') sp++; - while (*sp == ' ') sp++; + if (f == 12) { + st = atoll(sp); + break; + } + while (*sp && *sp != ' ') + sp++; + while (*sp == ' ') + sp++; f++; } } @@ -96,15 +112,15 @@ int main(int argc, char **argv) { snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, tid); t->vcs0 = -1; if (readfile(path, buf, sizeof buf) > 0) { - char *v = strstr(buf, "nr_voluntary_switches"); + char* v = strstr(buf, "nr_voluntary_switches"); if (v) { - char *col = strchr(v, ':'); + char* col = strchr(v, ':'); if (col) t->vcs0 = atoll(col + 1); } } } closedir(d); - if (n > 1) break; // first pass may catch a dying thread set + if (n > 1) break; // first pass may catch a dying thread set usleep(50000); } @@ -118,11 +134,20 @@ int main(int argc, char **argv) { for (int i = 0; i < n; i++) { snprintf(path, sizeof path, "/proc/%d/task/%d/syscall", pid, ts[i].tid); long long r = readfile(path, buf, sizeof buf); - if (r <= 0) { ts[i].openerr++; continue; } - if (!strncmp(buf, "running", 7)) { ts[i].run++; continue; } + if (r <= 0) { + ts[i].openerr++; + continue; + } + if (!strncmp(buf, "running", 7)) { + ts[i].run++; + continue; + } long nr = -2; if (buf[0] != '-') nr = strtol(buf, NULL, 10); - if (nr < 0) { ts[i].run++; continue; } // -1 means running + if (nr < 0) { + ts[i].run++; + continue; + } // -1 means running if (nr >= MAXSY) nr = MAXSY - 1; ts[i].sys[nr]++; } @@ -131,24 +156,32 @@ int main(int argc, char **argv) { elapsed = (now.tv_sec - t0.tv_sec) * 1000LL + (now.tv_nsec - t0.tv_nsec) / 1000000LL; } - FILE *f = fopen(out, "w"); - if (!f) { perror("fopen out"); return 1; } - fprintf(f, "# pid=%d dur_ms=%lld interval_ms=%d samples_per_thread~=%lld\n", - pid, elapsed, intMs, elapsed / intMs); + FILE* f = fopen(out, "w"); + if (!f) { + perror("fopen out"); + return 1; + } + fprintf(f, "# pid=%d dur_ms=%lld interval_ms=%d samples_per_thread~=%lld\n", pid, elapsed, intMs, elapsed / intMs); fprintf(f, "tid,name,cpu_ticks_delta,vcs_delta,run_samples,open_err,sys_hist\n"); for (int i = 0; i < n; i++) { - struct T *t = &ts[i]; + struct T* t = &ts[i]; long long ut = 0, st = 0; snprintf(path, sizeof path, "/proc/%d/task/%d/stat", pid, t->tid); if (readfile(path, buf, sizeof buf) > 0) { - char *r = strrchr(buf, ')'); + char* r = strrchr(buf, ')'); if (r) { - int f2 = 0; char *sp = r + 1; + int f2 = 0; + char* sp = r + 1; while (*sp) { if (f2 == 11) ut = atoll(sp); - if (f2 == 12) { st = atoll(sp); break; } - while (*sp && *sp != ' ') sp++; - while (*sp == ' ') sp++; + if (f2 == 12) { + st = atoll(sp); + break; + } + while (*sp && *sp != ' ') + sp++; + while (*sp == ' ') + sp++; f2++; } } @@ -156,15 +189,14 @@ int main(int argc, char **argv) { long long vcs = -1; snprintf(path, sizeof path, "/proc/%d/task/%d/sched", pid, t->tid); if (readfile(path, buf, sizeof buf) > 0) { - char *v = strstr(buf, "nr_voluntary_switches"); + char* v = strstr(buf, "nr_voluntary_switches"); if (v) { - char *col = strchr(v, ':'); + char* col = strchr(v, ':'); if (col) vcs = atoll(col + 1); } } - fprintf(f, "%d,%s,%lld,%lld,%lld,%lld,\"", t->tid, t->name, - (ut + st) - t->cpu0, (vcs >= 0 && t->vcs0 >= 0) ? vcs - t->vcs0 : -1, - t->run, t->openerr); + fprintf(f, "%d,%s,%lld,%lld,%lld,%lld,\"", t->tid, t->name, (ut + st) - t->cpu0, + (vcs >= 0 && t->vcs0 >= 0) ? vcs - t->vcs0 : -1, t->run, t->openerr); for (int s = 0; s < MAXSY; s++) if (t->sys[s]) fprintf(f, "%d:%lld;", s, t->sys[s]); fprintf(f, "\"\n"); diff --git a/tools/device_bench/p5f/P5fPublicMipmapScenario.cpp b/tools/device_bench/p5f/P5fPublicMipmapScenario.cpp index 5b9aad3b..583df4f5 100644 --- a/tools/device_bench/p5f/P5fPublicMipmapScenario.cpp +++ b/tools/device_bench/p5f/P5fPublicMipmapScenario.cpp @@ -18,53 +18,57 @@ #undef GL_GLEXT_PROTOTYPES namespace MGITest { -class P5fPublicMipmapScenario : public ScenarioTest { -protected: - void Check(bool gpuWritten) { - if (!Ready()) return; - GLuint texture = 0, fbo = 0; - glGenTextures(1, &texture); - glBindTexture(GL_TEXTURE_2D, texture); - std::array red{}; - for (std::size_t i = 0; i < red.size(); i += 4) { - red[i] = 255; - red[i + 3] = 255; + class P5fPublicMipmapScenario : public ScenarioTest { + protected: + void Check(bool gpuWritten) { + if (!Ready()) return; + GLuint texture = 0, fbo = 0; + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + std::array red{}; + for (std::size_t i = 0; i < red.size(); i += 4) { + red[i] = 255; + red[i + 3] = 255; + } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + if (gpuWritten) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}); + glDisable(GL_SCISSOR_TEST); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + glGenerateMipmap(GL_TEXTURE_2D); + for (GLint level = 0; level <= 2; ++level) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level); + ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}) << level; + std::array pixel{71, 72, 73, 74}; + glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); + EXPECT_EQ(pixel[0], gpuWritten ? 0 : 255) << "mip=" << level; + EXPECT_EQ(pixel[1], gpuWritten ? 255 : 0) << "mip=" << level; + EXPECT_EQ(pixel[2], 0) << "mip=" << level; + EXPECT_EQ(pixel[3], 255) << "mip=" << level; + } + EXPECT_EQ(glGetError(), GLenum{GL_NO_ERROR}); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &texture); + Gl().EndFrame(); } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, red.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2); - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - if (gpuWritten) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}); - glDisable(GL_SCISSOR_TEST); - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - glClearColor(0.0f, 1.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - } - glGenerateMipmap(GL_TEXTURE_2D); - for (GLint level = 0; level <= 2; ++level) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, level); - ASSERT_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GLenum{GL_FRAMEBUFFER_COMPLETE}) << level; - std::array pixel{71, 72, 73, 74}; - glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel.data()); - EXPECT_EQ(pixel[0], gpuWritten ? 0 : 255) << "mip=" << level; - EXPECT_EQ(pixel[1], gpuWritten ? 255 : 0) << "mip=" << level; - EXPECT_EQ(pixel[2], 0) << "mip=" << level; - EXPECT_EQ(pixel[3], 255) << "mip=" << level; - } - EXPECT_EQ(glGetError(), GLenum{GL_NO_ERROR}); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glBindTexture(GL_TEXTURE_2D, 0); - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &texture); - Gl().EndFrame(); - } -}; + }; -TEST_F(P5fPublicMipmapScenario, UploadedRgba8BaseGeneratesRedMipChain) { Check(false); } -TEST_F(P5fPublicMipmapScenario, GpuWrittenBaseGeneratesGreenMipChainWithoutStaleCpuUpload) { Check(true); } + TEST_F(P5fPublicMipmapScenario, UploadedRgba8BaseGeneratesRedMipChain) { + Check(false); + } + TEST_F(P5fPublicMipmapScenario, GpuWrittenBaseGeneratesGreenMipChainWithoutStaleCpuUpload) { + Check(true); + } } // namespace MGITest diff --git a/tools/spikes/extmem_probe/extmem_probe.cpp b/tools/spikes/extmem_probe/extmem_probe.cpp index b7fa9786..1d1b762c 100644 --- a/tools/spikes/extmem_probe/extmem_probe.cpp +++ b/tools/spikes/extmem_probe/extmem_probe.cpp @@ -54,15 +54,15 @@ // survive fork, and both routes need live Vulkan on both sides. #ifdef __ANDROID__ -# define VK_USE_PLATFORM_ANDROID_KHR 1 -# define PROBE_HAVE_AHB 1 +#define VK_USE_PLATFORM_ANDROID_KHR 1 +#define PROBE_HAVE_AHB 1 #else // The probe is an Android deliverable; the host build exists only so the // T1/T1-gles/T3 harness itself can be validated against a driver that is known // to implement those routes (lavapipe/llvmpipe), which is what makes a // device-side FAIL attributable to the driver rather than to this program. // T0 is Android-only by nature. -# define PROBE_HAVE_AHB 0 +#define PROBE_HAVE_AHB 0 #endif #include @@ -73,10 +73,10 @@ #include #if PROBE_HAVE_AHB -# include -# include +#include +#include #else -# define PROP_VALUE_MAX 92 +#define PROP_VALUE_MAX 92 #endif #include @@ -103,7 +103,7 @@ // bionic declares memfd_create only from API 30; the trace APK (MOBILEGL_BUILD_EXTMEM_PROBE) // builds at the project's API 26, where the syscall itself exists. #if defined(__ANDROID__) && __ANDROID_API__ < 30 -# include +#include static int memfd_create(const char* name, unsigned int flags) { return (int)syscall(__NR_memfd_create, name, flags); } @@ -114,8 +114,8 @@ static int memfd_create(const char* name, unsigned int flags) { static VkInstance gVk11ShimInstance = VK_NULL_HANDLE; #if defined(__ANDROID__) && __ANDROID_API__ < 28 static void probeGetPhysicalDeviceProperties2(VkPhysicalDevice pd, VkPhysicalDeviceProperties2* out) { - auto fn = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(gVk11ShimInstance, - "vkGetPhysicalDeviceProperties2"); + auto fn = + (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(gVk11ShimInstance, "vkGetPhysicalDeviceProperties2"); if (fn) fn(pd, out); } static void probeGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice pd, @@ -125,8 +125,8 @@ static void probeGetPhysicalDeviceExternalBufferProperties(VkPhysicalDevice pd, gVk11ShimInstance, "vkGetPhysicalDeviceExternalBufferProperties"); if (fn) fn(pd, info, out); } -# define vkGetPhysicalDeviceProperties2 probeGetPhysicalDeviceProperties2 -# define vkGetPhysicalDeviceExternalBufferProperties probeGetPhysicalDeviceExternalBufferProperties +#define vkGetPhysicalDeviceProperties2 probeGetPhysicalDeviceProperties2 +#define vkGetPhysicalDeviceExternalBufferProperties probeGetPhysicalDeviceExternalBufferProperties #endif // --------------------------------------------------------------------------- @@ -159,7 +159,7 @@ static void pr(const char* fmt, ...) { struct RouteResult { std::string route; - std::string status; // OK / PARTIAL / UNSUPPORTED / FAIL / SKIP + std::string status; // OK / PARTIAL / UNSUPPORTED / FAIL / SKIP std::string detail; }; static std::vector gResults; @@ -184,40 +184,69 @@ static std::string fmt(const char* f, ...) { // last one. static std::string vkStr(VkResult r) { switch (r) { - case VK_SUCCESS: return "VK_SUCCESS"; - case VK_NOT_READY: return "VK_NOT_READY"; - case VK_TIMEOUT: return "VK_TIMEOUT"; - case VK_INCOMPLETE: return "VK_INCOMPLETE"; - case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY"; - case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; - case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED"; - case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; - case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED"; - case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT"; - case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT"; - case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT"; - case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER"; - case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS"; - case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED"; - case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL"; - case VK_ERROR_UNKNOWN: return "VK_ERROR_UNKNOWN"; - case VK_ERROR_OUT_OF_POOL_MEMORY: return "VK_ERROR_OUT_OF_POOL_MEMORY"; - case VK_ERROR_INVALID_EXTERNAL_HANDLE: return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; - case VK_ERROR_FRAGMENTATION: return "VK_ERROR_FRAGMENTATION"; - case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; - default: return fmt("VkResult(%d)", (int)r); + case VK_SUCCESS: + return "VK_SUCCESS"; + case VK_NOT_READY: + return "VK_NOT_READY"; + case VK_TIMEOUT: + return "VK_TIMEOUT"; + case VK_INCOMPLETE: + return "VK_INCOMPLETE"; + case VK_ERROR_OUT_OF_HOST_MEMORY: + return "VK_ERROR_OUT_OF_HOST_MEMORY"; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + return "VK_ERROR_OUT_OF_DEVICE_MEMORY"; + case VK_ERROR_INITIALIZATION_FAILED: + return "VK_ERROR_INITIALIZATION_FAILED"; + case VK_ERROR_DEVICE_LOST: + return "VK_ERROR_DEVICE_LOST"; + case VK_ERROR_MEMORY_MAP_FAILED: + return "VK_ERROR_MEMORY_MAP_FAILED"; + case VK_ERROR_LAYER_NOT_PRESENT: + return "VK_ERROR_LAYER_NOT_PRESENT"; + case VK_ERROR_EXTENSION_NOT_PRESENT: + return "VK_ERROR_EXTENSION_NOT_PRESENT"; + case VK_ERROR_FEATURE_NOT_PRESENT: + return "VK_ERROR_FEATURE_NOT_PRESENT"; + case VK_ERROR_INCOMPATIBLE_DRIVER: + return "VK_ERROR_INCOMPATIBLE_DRIVER"; + case VK_ERROR_TOO_MANY_OBJECTS: + return "VK_ERROR_TOO_MANY_OBJECTS"; + case VK_ERROR_FORMAT_NOT_SUPPORTED: + return "VK_ERROR_FORMAT_NOT_SUPPORTED"; + case VK_ERROR_FRAGMENTED_POOL: + return "VK_ERROR_FRAGMENTED_POOL"; + case VK_ERROR_UNKNOWN: + return "VK_ERROR_UNKNOWN"; + case VK_ERROR_OUT_OF_POOL_MEMORY: + return "VK_ERROR_OUT_OF_POOL_MEMORY"; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: + return "VK_ERROR_INVALID_EXTERNAL_HANDLE"; + case VK_ERROR_FRAGMENTATION: + return "VK_ERROR_FRAGMENTATION"; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: + return "VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS"; + default: + return fmt("VkResult(%d)", (int)r); } } static std::string glErrStr(GLenum e) { switch (e) { - case GL_NO_ERROR: return "GL_NO_ERROR"; - case GL_INVALID_ENUM: return "GL_INVALID_ENUM"; - case GL_INVALID_VALUE: return "GL_INVALID_VALUE"; - case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; - case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; - case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION"; - default: return fmt("GL(0x%04x)", (unsigned)e); + case GL_NO_ERROR: + return "GL_NO_ERROR"; + case GL_INVALID_ENUM: + return "GL_INVALID_ENUM"; + case GL_INVALID_VALUE: + return "GL_INVALID_VALUE"; + case GL_INVALID_OPERATION: + return "GL_INVALID_OPERATION"; + case GL_OUT_OF_MEMORY: + return "GL_OUT_OF_MEMORY"; + case GL_INVALID_FRAMEBUFFER_OPERATION: + return "GL_INVALID_FRAMEBUFFER_OPERATION"; + default: + return fmt("GL(0x%04x)", (unsigned)e); } } @@ -225,7 +254,8 @@ static std::string glErrStr(GLenum e) { // previous one static GLenum glDrain() { GLenum last = GL_NO_ERROR, e; - while ((e = glGetError()) != GL_NO_ERROR) last = e; + while ((e = glGetError()) != GL_NO_ERROR) + last = e; return last; } @@ -237,7 +267,8 @@ static std::string readSmallFile(const char* path) { close(fd); if (n <= 0) return ""; buf[n] = 0; - while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == 0)) buf[--n] = 0; + while (n > 0 && (buf[n - 1] == '\n' || buf[n - 1] == 0)) + buf[--n] = 0; return std::string(buf); } @@ -245,18 +276,18 @@ static std::string readSmallFile(const char* path) { // payload patterns // --------------------------------------------------------------------------- -static const uint64_t kRegion = 4096; // bytes per verification region -static const uint64_t kRegionCount = 8; // A..F plus slack +static const uint64_t kRegion = 4096; // bytes per verification region +static const uint64_t kRegionCount = 8; // A..F plus slack static const uint64_t kDefaultSize = 65536; // region indices inside the shared allocation enum { - REG_A = 0, // first writer's payload (CPU, allocating side) - REG_B = 1, // importer write through the plain host mapping (mmap / AHB lock / vk map) - REG_C = 2, // importer write through the imported Vulkan mapping - REG_D = 3, // importer write through the imported GL mapping (cross-process) - REG_E = 4, // GPU write (vkCmdFillBuffer) - REG_F = 5, // in-process GL-import write + REG_A = 0, // first writer's payload (CPU, allocating side) + REG_B = 1, // importer write through the plain host mapping (mmap / AHB lock / vk map) + REG_C = 2, // importer write through the imported Vulkan mapping + REG_D = 3, // importer write through the imported GL mapping (cross-process) + REG_E = 4, // GPU write (vkCmdFillBuffer) + REG_F = 5, // in-process GL-import write }; static void fillPattern(void* p, uint64_t bytes, uint32_t seed) { @@ -285,7 +316,9 @@ static uint64_t gRegionBase = 0; // the last round, while the parent still holds its Vulkan and GL imports, so an outside sampler // can read what a held, imported AHB of --size bytes costs in each accounting. static uint32_t gHoldMs = 0; -static uint64_t regOff(int region) { return gRegionBase + (uint64_t)region * kRegion; } +static uint64_t regOff(int region) { + return gRegionBase + (uint64_t)region * kRegion; +} static void writeRegion(void* base, int region, uint32_t seed) { fillPattern((uint8_t*)base + regOff(region), kRegion, seed); @@ -309,11 +342,11 @@ static int64_t checkFillWord(const void* base, int region, uint32_t word) { struct Leg { std::string name; - bool decisive = false; // counted by the verdict; informational legs are not + bool decisive = false; // counted by the verdict; informational legs are not bool attempted = false; - bool readOk = false; // the allocating side's bytes were visible to the other side - bool writeOk = false; // the other side's bytes came back - std::string fail; // failing step + driver error code + bool readOk = false; // the allocating side's bytes were visible to the other side + bool writeOk = false; // the other side's bytes came back + std::string fail; // failing step + driver error code }; static const char* legVerdict(const std::vector& legs, std::string* why) { @@ -341,7 +374,7 @@ static std::string legTrace(const std::vector& legs) { std::string s; for (const Leg& l : legs) { if (!s.empty()) s += " "; - const char* v = !l.attempted ? "notrun" + const char* v = !l.attempted ? "notrun" : (l.readOk && l.writeOk) ? "rt" : l.readOk ? "read-only" : l.writeOk ? "write-only" @@ -446,7 +479,7 @@ static bool sendMsg(int sock, uint32_t tag, const void* payload, size_t len, int if ((size_t)s == total) return true; // partial: finish the tail with plain writes (control data already delivered) size_t done = (size_t)s; - if (done < sizeof(h)) return false; // should not happen for such small headers + if (done < sizeof(h)) return false; // should not happen for such small headers return writeAll(sock, (const uint8_t*)payload + (done - sizeof(h)), total - done); } @@ -521,8 +554,11 @@ static std::string describeFd(int fd) { } struct stat st; if (fstat(fd, &st) == 0) { - const char* kind = S_ISREG(st.st_mode) ? "reg" : S_ISCHR(st.st_mode) ? "chr" - : S_ISFIFO(st.st_mode) ? "fifo" : S_ISSOCK(st.st_mode) ? "sock" : "other"; + const char* kind = S_ISREG(st.st_mode) ? "reg" + : S_ISCHR(st.st_mode) ? "chr" + : S_ISFIFO(st.st_mode) ? "fifo" + : S_ISSOCK(st.st_mode) ? "sock" + : "other"; desc += fmt(" kind=%s stsize=%lld", kind, (long long)st.st_size); } return desc; @@ -630,7 +666,8 @@ static bool vkCtxInit(VkCtx& c, bool verbose) { vkEnumerateDeviceExtensionProperties(c.phys, nullptr, &extCount, nullptr); std::vector exts(extCount); if (extCount) vkEnumerateDeviceExtensionProperties(c.phys, nullptr, &extCount, exts.data()); - for (auto& e : exts) c.deviceExts.push_back(e.extensionName); + for (auto& e : exts) + c.deviceExts.push_back(e.extensionName); c.hasExtMemFd = c.hasExt(VK_KHR_EXTERNAL_MEMORY_FD_EXTENSION_NAME); c.hasDmaBuf = c.hasExt(VK_EXT_EXTERNAL_MEMORY_DMA_BUF_EXTENSION_NAME); @@ -664,10 +701,8 @@ static bool vkCtxInit(VkCtx& c, bool verbose) { devExts.push_back(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); if (c.hasExt(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME)) devExts.push_back(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); - if (c.hasExt(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME)) - devExts.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME); - if (c.hasExt(VK_KHR_MAINTENANCE_1_EXTENSION_NAME)) - devExts.push_back(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); + if (c.hasExt(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME)) devExts.push_back(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME); + if (c.hasExt(VK_KHR_MAINTENANCE_1_EXTENSION_NAME)) devExts.push_back(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); } float prio = 1.0f; @@ -708,15 +743,16 @@ static bool vkCtxInit(VkCtx& c, bool verbose) { c.pGetAhbProps = (PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkGetDeviceProcAddr( c.device, "vkGetAndroidHardwareBufferPropertiesANDROID"); #endif - c.pGetHostPtrProps = (PFN_vkGetMemoryHostPointerPropertiesEXT)vkGetDeviceProcAddr( - c.device, "vkGetMemoryHostPointerPropertiesEXT"); + c.pGetHostPtrProps = + (PFN_vkGetMemoryHostPointerPropertiesEXT)vkGetDeviceProcAddr(c.device, "vkGetMemoryHostPointerPropertiesEXT"); if (verbose) { pr("vulkan device: %s api=%u.%u.%u driverVersion=0x%08x vendor=0x%04x", c.props.deviceName, VK_VERSION_MAJOR(c.props.apiVersion), VK_VERSION_MINOR(c.props.apiVersion), VK_VERSION_PATCH(c.props.apiVersion), c.props.driverVersion, c.props.vendorID); char uuid[64] = {0}; - for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) snprintf(uuid + i * 2, 3, "%02x", c.deviceUUID[i]); + for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) + snprintf(uuid + i * 2, 3, "%02x", c.deviceUUID[i]); pr("deviceUUID=%s minImportedHostPointerAlignment=%llu queueFamily=%u", uuid, (unsigned long long)c.minImportedHostPointerAlignment, c.queueFamily); } @@ -742,9 +778,8 @@ static int pickMemType(const VkPhysicalDeviceMemoryProperties& mp, uint32_t bits } static const VkBufferUsageFlags kProbeBufferUsage = - VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | - VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; // --------------------------------------------------------------------------- // GPU touch: prove the shared allocation survives a real GPU access @@ -759,12 +794,11 @@ static const VkBufferUsageFlags kProbeBufferUsage = struct GpuTouch { bool ran = false; VkResult submitResult = VK_NOT_READY; - int64_t readMismatch = -3; // -1 match, -3 never ran + int64_t readMismatch = -3; // -1 match, -3 never ran std::string fail; }; -static GpuTouch gpuTouch(VkCtx& c, VkBuffer buf, int readRegion, uint32_t readSeed, int fillRegion, - uint32_t fillWord) { +static GpuTouch gpuTouch(VkCtx& c, VkBuffer buf, int readRegion, uint32_t readSeed, int fillRegion, uint32_t fillWord) { GpuTouch g; if (buf == VK_NULL_HANDLE || c.cmdPool == VK_NULL_HANDLE || c.queue == VK_NULL_HANDLE) { g.fail = "no buffer/queue/command pool for the GPU touch"; @@ -866,8 +900,8 @@ static GpuTouch gpuTouch(VkCtx& c, VkBuffer buf, int readRegion, uint32_t readSe vkUnmapMemory(c.device, smem); g.ran = true; if (g.readMismatch != -1) - g.fail = fmt("GPU copy out of the shared allocation mismatched at byte %lld", - (long long)g.readMismatch); + g.fail = + fmt("GPU copy out of the shared allocation mismatched at byte %lld", (long long)g.readMismatch); } else { g.fail = "staging vkMapMemory=" + vkStr(mr); } @@ -899,7 +933,7 @@ struct ExportAlloc { bool advertisedExportable = false; bool advertisedImportable = false; VkResult bindResult = VK_SUCCESS; - std::string fail; // empty on success + std::string fail; // empty on success }; // A failure on a handle type the driver advertised as EXPORTABLE is a driver @@ -976,8 +1010,8 @@ static bool exportHostVisible(VkCtx& c, VkExternalMemoryHandleTypeFlagBits ht, u r = vkAllocateMemory(c.device, &mai, nullptr, &a.mem); if (r != VK_SUCCESS) { a.mem = VK_NULL_HANDLE; - a.fail = fmt("vkAllocateMemory(export)=%s (advertisedExportable=%d)", vkStr(r).c_str(), - (int)a.advertisedExportable); + a.fail = + fmt("vkAllocateMemory(export)=%s (advertisedExportable=%d)", vkStr(r).c_str(), (int)a.advertisedExportable); return false; } a.bindResult = vkBindBufferMemory(c.device, a.buf, a.mem, 0); @@ -1037,7 +1071,7 @@ struct GlCtx { PFNGLBUFFERSTORAGEMEMEXTPROC pBufferStorageMem = nullptr; PFNGLIMPORTMEMORYFDEXTPROC pImportMemoryFd = nullptr; PFNGLGETUNSIGNEDBYTEI_VEXTPROC pGetUnsignedBytei_v = nullptr; - void (GL_APIENTRYP pMemoryBarrier)(GLbitfield) = nullptr; + void(GL_APIENTRYP pMemoryBarrier)(GLbitfield) = nullptr; bool hasGl(const char* n) const { for (auto& s : glExts) @@ -1093,9 +1127,18 @@ static bool glCtxInit(GlCtx& g) { const char* clientExts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); splitExts(clientExts, g.eglExts); - const EGLint cfgAttr[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, - EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, + const EGLint cfgAttr[] = {EGL_SURFACE_TYPE, + EGL_PBUFFER_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES3_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, EGL_NONE}; EGLConfig cfg = nullptr; EGLint numCfg = 0; @@ -1142,12 +1185,10 @@ static bool glCtxInit(GlCtx& g) { g.pGetNativeClientBuffer = (PFNEGLGETNATIVECLIENTBUFFERANDROIDPROC)eglGetProcAddress("eglGetNativeClientBufferANDROID"); - g.pBufferStorageExternal = - (PFNGLBUFFERSTORAGEEXTERNALEXTPROC)eglGetProcAddress("glBufferStorageExternalEXT"); + g.pBufferStorageExternal = (PFNGLBUFFERSTORAGEEXTERNALEXTPROC)eglGetProcAddress("glBufferStorageExternalEXT"); g.pCreateMemoryObjects = (PFNGLCREATEMEMORYOBJECTSEXTPROC)eglGetProcAddress("glCreateMemoryObjectsEXT"); g.pDeleteMemoryObjects = (PFNGLDELETEMEMORYOBJECTSEXTPROC)eglGetProcAddress("glDeleteMemoryObjectsEXT"); - g.pMemoryObjectParameteriv = - (PFNGLMEMORYOBJECTPARAMETERIVEXTPROC)eglGetProcAddress("glMemoryObjectParameterivEXT"); + g.pMemoryObjectParameteriv = (PFNGLMEMORYOBJECTPARAMETERIVEXTPROC)eglGetProcAddress("glMemoryObjectParameterivEXT"); g.pBufferStorageMem = (PFNGLBUFFERSTORAGEMEMEXTPROC)eglGetProcAddress("glBufferStorageMemEXT"); g.pImportMemoryFd = (PFNGLIMPORTMEMORYFDEXTPROC)eglGetProcAddress("glImportMemoryFdEXT"); g.pGetUnsignedBytei_v = (PFNGLGETUNSIGNEDBYTEI_VEXTPROC)eglGetProcAddress("glGetUnsignedBytei_vEXT"); @@ -1180,7 +1221,8 @@ static std::string glDeviceUuidReport(GlCtx& g, const uint8_t* vkUuid, bool* mat GLubyte uuid[GL_UUID_SIZE_EXT] = {0}; g.pGetUnsignedBytei_v(GL_DEVICE_UUID_EXT, (GLuint)i, uuid); char hex[2 * GL_UUID_SIZE_EXT + 1] = {0}; - for (int k = 0; k < GL_UUID_SIZE_EXT; ++k) snprintf(hex + k * 2, 3, "%02x", uuid[k]); + for (int k = 0; k < GL_UUID_SIZE_EXT; ++k) + snprintf(hex + k * 2, 3, "%02x", uuid[k]); if (!out.empty()) out += ","; out += hex; if (!memcmp(uuid, vkUuid, GL_UUID_SIZE_EXT) && matched) *matched = true; @@ -1196,17 +1238,17 @@ struct GlImport { bool memObjOk = false; bool storageOk = false; bool mapOk = false; - bool persistentCoherent = false; // the PERSISTENT|COHERENT map is what AcquirePersistentMap needs + bool persistentCoherent = false; // the PERSISTENT|COHERENT map is what AcquirePersistentMap needs GLenum errImport = GL_NO_ERROR; GLenum errStorage = GL_NO_ERROR; - GLenum errMap = GL_NO_ERROR; // error of the PERSISTENT|COHERENT attempt - GLenum errMap2 = GL_NO_ERROR; // error of the plain MAP_READ|MAP_WRITE fallback + GLenum errMap = GL_NO_ERROR; // error of the PERSISTENT|COHERENT attempt + GLenum errMap2 = GL_NO_ERROR; // error of the plain MAP_READ|MAP_WRITE fallback GLuint memObj = 0; GLuint buf = 0; void* ptr = nullptr; uint64_t mappedSize = 0; - std::string variant; // which phrasing the driver accepted - std::string ladder; // every phrasing tried, with its error + std::string variant; // which phrasing the driver accepted + std::string ladder; // every phrasing tried, with its error std::string fail; }; @@ -1305,8 +1347,7 @@ static void glImportFdBuffer(GlCtx& g, int fd, uint64_t allocationSize, uint64_t bufferSize = o.mappedSize; o.ptr = glMapBufferRange(GL_ARRAY_BUFFER, 0, (GLsizeiptr)bufferSize, - GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | - GL_MAP_COHERENT_BIT_EXT); + GL_MAP_READ_BIT | GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT_EXT | GL_MAP_COHERENT_BIT_EXT); o.errMap = glDrain(); if (o.ptr) { o.mapOk = true; @@ -1426,31 +1467,31 @@ static std::string reapChild(pid_t pid) { struct T1Offer { uint64_t allocationSize; uint64_t bufferSize; - uint32_t handleType; // VkExternalMemoryHandleTypeFlagBits used to export - uint32_t seedA; // pattern the parent wrote in REG_A - uint32_t seedB; // pattern the child must write in REG_B (via mmap) - uint32_t seedC; // pattern the child must write in REG_C (via imported vkMapMemory) + uint32_t handleType; // VkExternalMemoryHandleTypeFlagBits used to export + uint32_t seedA; // pattern the parent wrote in REG_A + uint32_t seedB; // pattern the child must write in REG_B (via mmap) + uint32_t seedC; // pattern the child must write in REG_C (via imported vkMapMemory) uint32_t memoryTypeIndex; uint32_t memoryTypeBits; - uint32_t gpuWord; // word the GPU filled REG_E with - uint32_t gpuRan; // 0 -> REG_E carries nothing, do not check it + uint32_t gpuWord; // word the GPU filled REG_E with + uint32_t gpuRan; // 0 -> REG_E carries nothing, do not check it }; struct T1Result { int32_t gotFd; int32_t mmapOk; int32_t mmapErrno; - int64_t mmapMismatch; // -1 == data matched - int64_t mmapPatternOffset; // where the exporter's payload really starts in the mapping, -1 = not found - int64_t mmapGpuMismatch; // REG_E through the plain mapping (-2 = not checked) + int64_t mmapMismatch; // -1 == data matched + int64_t mmapPatternOffset; // where the exporter's payload really starts in the mapping, -1 = not found + int64_t mmapGpuMismatch; // REG_E through the plain mapping (-2 = not checked) int32_t vkInitOk; - int32_t fdPropsResult; // VkResult of vkGetMemoryFdPropertiesKHR + int32_t fdPropsResult; // VkResult of vkGetMemoryFdPropertiesKHR uint32_t fdMemoryTypeBits; - int32_t importResult; // VkResult of vkAllocateMemory with the import struct + int32_t importResult; // VkResult of vkAllocateMemory with the import struct int32_t bindResult; int32_t mapResult; - int64_t vkMismatch; // -1 == data matched - int64_t vkGpuMismatch; // REG_E through the imported mapping (-2 = not checked) + int64_t vkMismatch; // -1 == data matched + int64_t vkGpuMismatch; // REG_E through the imported mapping (-2 = not checked) int32_t wroteB; int32_t wroteC; char note[384]; @@ -1460,7 +1501,7 @@ struct T1GlOffer { uint64_t allocationSize; uint64_t bufferSize; uint32_t seedA; - uint32_t seedD; // the child writes REG_D through the imported GL mapping + uint32_t seedD; // the child writes REG_D through the imported GL mapping uint32_t gpuWord; uint32_t gpuRan; uint32_t dedicated; @@ -1482,7 +1523,7 @@ struct T1GlResult { struct T0Request { uint64_t size; - uint32_t seedA; // pattern the child writes through AHardwareBuffer_lock + uint32_t seedA; // pattern the child writes through AHardwareBuffer_lock }; struct T0Alloc { @@ -1494,11 +1535,11 @@ struct T0Alloc { }; struct T0Verify { - uint32_t seedB; // parent wrote REG_B through the imported vkMapMemory - uint32_t seedC; // parent wrote REG_C through the imported GL mapping - uint32_t seedD; // parent wrote REG_D through AHardwareBuffer_lock - uint32_t gpuWord; // the GPU filled REG_E with this - uint32_t writtenMask; // bit0=B bit1=C bit2=D bit3=E(gpu) + uint32_t seedB; // parent wrote REG_B through the imported vkMapMemory + uint32_t seedC; // parent wrote REG_C through the imported GL mapping + uint32_t seedD; // parent wrote REG_D through AHardwareBuffer_lock + uint32_t gpuWord; // the GPU filled REG_E with this + uint32_t writtenMask; // bit0=B bit1=C bit2=D bit3=E(gpu) }; struct T0Result { @@ -1521,20 +1562,20 @@ struct T0SRequest { struct T0SRound { uint32_t round; - uint32_t seedA; // the client CPU wrote REG_A with this pattern through its held lock + uint32_t seedA; // the client CPU wrote REG_A with this pattern through its held lock }; struct T0SGpuDone { uint32_t round; - uint32_t vkWord; // the server's Vulkan GPU filled REG_E with this word - uint32_t glWord; // the server's GL compute dispatch filled REG_F with this word - uint32_t mask; // bit0 = vk fill ran, bit1 = gl fill ran + uint32_t vkWord; // the server's Vulkan GPU filled REG_E with this word + uint32_t glWord; // the server's GL compute dispatch filled REG_F with this word + uint32_t mask; // bit0 = vk fill ran, bit1 = gl fill ran }; struct T0SCheck { uint32_t round; int32_t pad; - int64_t mismatchE; // through the client's still-held lock pointer, -2 = not checked + int64_t mismatchE; // through the client's still-held lock pointer, -2 = not checked int64_t mismatchF; }; @@ -1543,7 +1584,7 @@ struct T0SDone { int32_t unlockRc; int32_t relockRc; int32_t pad; - int64_t relockMismatchE; // after unlock + a fresh lock: the last GPU writes, -2 = not checked + int64_t relockMismatchE; // after unlock + a fresh lock: the last GPU writes, -2 = not checked int64_t relockMismatchF; char note[160]; }; @@ -1567,8 +1608,8 @@ struct T3Result { // T3 in the direction that makes it a tier: the CLIENT allocates, the SERVER // imports the client's host pointer. struct T3cRequest { - uint64_t size; // must be a multiple of minImportedHostPointerAlignment - uint32_t seedA; // the child writes REG_A + uint64_t size; // must be a multiple of minImportedHostPointerAlignment + uint32_t seedA; // the child writes REG_A }; struct T3cReady { @@ -1579,9 +1620,9 @@ struct T3cReady { }; struct T3cVerify { - uint32_t seedB; // the parent wrote REG_B through the imported VkDeviceMemory - uint32_t gpuWord; // the parent's GPU filled REG_E - uint32_t mask; // bit0=B bit1=E + uint32_t seedB; // the parent wrote REG_B through the imported VkDeviceMemory + uint32_t gpuWord; // the parent's GPU filled REG_E + uint32_t mask; // bit0=B bit1=E }; struct T3cResult { @@ -1651,8 +1692,8 @@ static void phaseEnumerate(VkCtx& c, GlCtx& g, bool glOk) { pr("device: model=%s device=%s android=%s", model, dev, rel); pr("vulkan: %s (api %u.%u.%u, driver 0x%08x, vendor 0x%04x)", c.props.deviceName, - VK_VERSION_MAJOR(c.props.apiVersion), VK_VERSION_MINOR(c.props.apiVersion), - VK_VERSION_PATCH(c.props.apiVersion), c.props.driverVersion, c.props.vendorID); + VK_VERSION_MAJOR(c.props.apiVersion), VK_VERSION_MINOR(c.props.apiVersion), VK_VERSION_PATCH(c.props.apiVersion), + c.props.driverVersion, c.props.vendorID); struct { const char* name; @@ -1666,14 +1707,14 @@ static void phaseEnumerate(VkCtx& c, GlCtx& g, bool glOk) { {VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, c.hasQueueFamilyForeign}, {VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, c.hasExt(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME)}, }; - for (auto& e : probe) pr(" VK ext %-58s %s", e.name, e.present ? "YES" : "no"); + for (auto& e : probe) + pr(" VK ext %-58s %s", e.name, e.present ? "YES" : "no"); pr("memory types (%u):", c.memProps.memoryTypeCount); for (uint32_t i = 0; i < c.memProps.memoryTypeCount; ++i) { const VkMemoryType& mt = c.memProps.memoryTypes[i]; pr(" [%u] heap=%u size=%lluMiB flags=%s", i, mt.heapIndex, - (unsigned long long)(c.memProps.memoryHeaps[mt.heapIndex].size >> 20), - memFlagStr(mt.propertyFlags).c_str()); + (unsigned long long)(c.memProps.memoryHeaps[mt.heapIndex].size >> 20), memFlagStr(mt.propertyFlags).c_str()); } // Only query handle types the driver actually claims: a handle type whose @@ -1693,16 +1734,23 @@ static void phaseEnumerate(VkCtx& c, GlCtx& g, bool glOk) { } pr("gles: vendor=%s renderer=%s version=%s", g.vendor.c_str(), g.renderer.c_str(), g.version.c_str()); const char* glWanted[] = { - "GL_EXT_memory_object", "GL_EXT_memory_object_fd", "GL_EXT_external_buffer", - "GL_EXT_buffer_storage", "GL_OES_EGL_image", "GL_OES_EGL_image_external", - "GL_OES_EGL_image_external_essl3", "GL_EXT_memory_object_win32", + "GL_EXT_memory_object", + "GL_EXT_memory_object_fd", + "GL_EXT_external_buffer", + "GL_EXT_buffer_storage", + "GL_OES_EGL_image", + "GL_OES_EGL_image_external", + "GL_OES_EGL_image_external_essl3", + "GL_EXT_memory_object_win32", }; - for (const char* n : glWanted) pr(" GL ext %-40s %s", n, g.hasGl(n) ? "YES" : "no"); + for (const char* n : glWanted) + pr(" GL ext %-40s %s", n, g.hasGl(n) ? "YES" : "no"); const char* eglWanted[] = { - "EGL_ANDROID_get_native_client_buffer", "EGL_KHR_image_base", "EGL_ANDROID_image_native_buffer", + "EGL_ANDROID_get_native_client_buffer", "EGL_KHR_image_base", "EGL_ANDROID_image_native_buffer", "EGL_EXT_image_dma_buf_import", "EGL_KHR_gl_texture_2D_image", }; - for (const char* n : eglWanted) pr(" EGL ext %-40s %s", n, g.hasEgl(n) ? "YES" : "no"); + for (const char* n : eglWanted) + pr(" EGL ext %-40s %s", n, g.hasEgl(n) ? "YES" : "no"); pr(" eglGetNativeClientBufferANDROID=%p glBufferStorageExternalEXT=%p", (void*)g.pGetNativeClientBuffer, (void*)g.pBufferStorageExternal); pr(" glCreateMemoryObjectsEXT=%p glImportMemoryFdEXT=%p glBufferStorageMemEXT=%p glMemoryObjectParameterivEXT=%p", @@ -1712,7 +1760,8 @@ static void phaseEnumerate(VkCtx& c, GlCtx& g, bool glOk) { bool uuidMatch = false; std::string glUuid = glDeviceUuidReport(g, c.deviceUUID, &uuidMatch); char vkUuid[2 * VK_UUID_SIZE + 1] = {0}; - for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) snprintf(vkUuid + i * 2, 3, "%02x", c.deviceUUID[i]); + for (uint32_t i = 0; i < VK_UUID_SIZE; ++i) + snprintf(vkUuid + i * 2, 3, "%02x", c.deviceUUID[i]); pr(" GL_DEVICE_UUID_EXT=%s vkDeviceUUID=%s match=%d", glUuid.c_str(), vkUuid, (int)uuidMatch); } @@ -1720,8 +1769,7 @@ static void phaseEnumerate(VkCtx& c, GlCtx& g, bool glOk) { // T1 parent: server exports its own allocation // --------------------------------------------------------------------------- -static void runT1Parent(VkCtx& c, VkExternalMemoryHandleTypeFlagBits handleType, const char* routeName, - uint64_t size) { +static void runT1Parent(VkCtx& c, VkExternalMemoryHandleTypeFlagBits handleType, const char* routeName, uint64_t size) { if (!c.hasExtMemFd || !c.pGetMemoryFdKHR) { record(routeName, "UNSUPPORTED", "VK_KHR_external_memory_fd absent"); return; @@ -1822,8 +1870,7 @@ static void runT1Parent(VkCtx& c, VkExternalMemoryHandleTypeFlagBits handleType, l.fail = fmt("mmap failed errno=%d(%s)", res.mmapErrno, strerror(res.mmapErrno)); else if (!l.readOk) l.fail = fmt("exporter payload not at offset 0 (cmp=%lld payloadAt=%lld gpuCmp=%lld)", - (long long)res.mmapMismatch, (long long)res.mmapPatternOffset, - (long long)res.mmapGpuMismatch); + (long long)res.mmapMismatch, (long long)res.mmapPatternOffset, (long long)res.mmapGpuMismatch); else if (!l.writeOk) l.fail = fmt("importer write not visible to exporter (back=%lld)", (long long)backB); legs.push_back(l); @@ -1843,8 +1890,8 @@ static void runT1Parent(VkCtx& c, VkExternalMemoryHandleTypeFlagBits handleType, else if (res.mapResult != VK_SUCCESS) l.fail = "importer vkMapMemory=" + vkStr((VkResult)res.mapResult); else if (!l.readOk) - l.fail = fmt("payload mismatch cmp=%lld gpuCmp=%lld", (long long)res.vkMismatch, - (long long)res.vkGpuMismatch); + l.fail = + fmt("payload mismatch cmp=%lld gpuCmp=%lld", (long long)res.vkMismatch, (long long)res.vkGpuMismatch); else if (!l.writeOk) l.fail = fmt("importer write not visible to exporter (back=%lld)", (long long)backC); legs.push_back(l); @@ -1867,18 +1914,17 @@ static void runT1Parent(VkCtx& c, VkExternalMemoryHandleTypeFlagBits handleType, std::string why; status = legVerdict(legs, &why); - detail = fmt( - "%s | mmap=%s(errno=%d,cmp=%lld,payloadAt=%lld,gpu=%lld,back=%lld) vkimport=%s(fdProps=%s bits=0x%x " - "bind=%s map=%s cmp=%lld gpu=%lld back=%lld) gpuTouch(submit=%s read=%lld fill=%lld) %s%s%s", - legTrace(legs).c_str(), res.mmapOk ? (res.mmapOk == 2 ? "ok-buffersize" : "ok") : "fail", res.mmapErrno, - (long long)res.mmapMismatch, (long long)res.mmapPatternOffset, (long long)res.mmapGpuMismatch, - (long long)backB, res.importResult == VK_SUCCESS ? "ok" : vkStr((VkResult)res.importResult).c_str(), - vkStr((VkResult)res.fdPropsResult).c_str(), res.fdMemoryTypeBits, vkStr((VkResult)res.bindResult).c_str(), - vkStr((VkResult)res.mapResult).c_str(), (long long)res.vkMismatch, (long long)res.vkGpuMismatch, - (long long)backC, vkStr(gt.submitResult).c_str(), (long long)gt.readMismatch, (long long)gpuFillSeenHere, - res.note, why.empty() ? "" : " | why: ", why.c_str()); - if (!mmapDecisive) - detail += " | rawmmap informational: an opaque fd is not required to be mmap-able"; + detail = + fmt("%s | mmap=%s(errno=%d,cmp=%lld,payloadAt=%lld,gpu=%lld,back=%lld) vkimport=%s(fdProps=%s bits=0x%x " + "bind=%s map=%s cmp=%lld gpu=%lld back=%lld) gpuTouch(submit=%s read=%lld fill=%lld) %s%s%s", + legTrace(legs).c_str(), res.mmapOk ? (res.mmapOk == 2 ? "ok-buffersize" : "ok") : "fail", res.mmapErrno, + (long long)res.mmapMismatch, (long long)res.mmapPatternOffset, (long long)res.mmapGpuMismatch, + (long long)backB, res.importResult == VK_SUCCESS ? "ok" : vkStr((VkResult)res.importResult).c_str(), + vkStr((VkResult)res.fdPropsResult).c_str(), res.fdMemoryTypeBits, vkStr((VkResult)res.bindResult).c_str(), + vkStr((VkResult)res.mapResult).c_str(), (long long)res.vkMismatch, (long long)res.vkGpuMismatch, + (long long)backC, vkStr(gt.submitResult).c_str(), (long long)gt.readMismatch, (long long)gpuFillSeenHere, + res.note, why.empty() ? "" : " | why: ", why.c_str()); + if (!mmapDecisive) detail += " | rawmmap informational: an opaque fd is not required to be mmap-able"; sendMsg(sock, MSG_BYE, nullptr, 0, -1); detail += " "; @@ -1930,7 +1976,7 @@ static int childT1(int sock) { p = mmap(nullptr, mappedLen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p != MAP_FAILED) { res.mmapOk = 2; - res.mmapErrno = 0; // the mapping succeeded; the first errno is history, not the verdict + res.mmapErrno = 0; // the mapping succeeded; the first errno is history, not the verdict note += fmt(" [mmap needed bufferSize not allocationSize; allocationSize errno=%d]", firstErrno); } else { res.mmapErrno = errno; @@ -2023,9 +2069,9 @@ static int childT1(int sock) { if (ht == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT) { typeIdx = (int)offer.memoryTypeIndex; } else { - typeIdx = pickMemType(c.memProps, bits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - if (typeIdx < 0) typeIdx = (int)offer.memoryTypeIndex; // fall back to the exporter's choice + typeIdx = + pickMemType(c.memProps, bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + if (typeIdx < 0) typeIdx = (int)offer.memoryTypeIndex; // fall back to the exporter's choice } // the import consumes the fd on success, so hand over a duplicate @@ -2263,10 +2309,10 @@ static void runT1GlesParent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size) { fmt("%s | memObj=%d(%s) storage=%d(%s) accepted=%s map=%d persistentCoherent=%d(%s/%s) cmpA=%lld " "cmpGpu=%lld backF=%lld | ladder: %s| %s | %s", legTrace(legs).c_str(), (int)imp.memObjOk, glErrStr(imp.errImport).c_str(), (int)imp.storageOk, - glErrStr(imp.errStorage).c_str(), imp.variant.empty() ? "none" : imp.variant.c_str(), - (int)imp.mapOk, (int)imp.persistentCoherent, glErrStr(imp.errMap).c_str(), - glErrStr(imp.errMap2).c_str(), (long long)cmpA, (long long)cmpGpu, (long long)backF, - imp.ladder.c_str(), uuidNote.c_str(), why.c_str())); + glErrStr(imp.errStorage).c_str(), imp.variant.empty() ? "none" : imp.variant.c_str(), (int)imp.mapOk, + (int)imp.persistentCoherent, glErrStr(imp.errMap).c_str(), glErrStr(imp.errMap2).c_str(), + (long long)cmpA, (long long)cmpGpu, (long long)backF, imp.ladder.c_str(), uuidNote.c_str(), + why.c_str())); glImportRelease(g, imp); } @@ -2509,8 +2555,8 @@ static void runT0Parent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size) { // (a) CPU path: AHardwareBuffer_lock on the receiving side { void* p = nullptr; - rc = AHardwareBuffer_lock(ahb, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, - -1, nullptr, &p); + rc = AHardwareBuffer_lock(ahb, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN | AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, + nullptr, &p); if (rc != 0 || !p) { cpuFail = fmt("AHardwareBuffer_lock rc=%d errno=%d", rc, errno); record("T0-ahb-cpu-lock", "FAIL", cpuFail); @@ -2765,7 +2811,7 @@ static void runT0Parent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size) { { Leg l; l.name = "cpu-lock"; - l.decisive = false; // informational: proves the handle, not the tier + l.decisive = false; // informational: proves the handle, not the tier l.attempted = cpuCmp != -3; l.readOk = cpuCmp == -1; l.writeOk = gotVerify && (writtenMask & 4) && res.mismatchD == -1; @@ -2801,8 +2847,7 @@ static void runT0Parent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size) { // mapping kept stale lines". // --------------------------------------------------------------------------- -static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, uint32_t rounds, - uint32_t seedBase); +static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, uint32_t rounds, uint32_t seedBase); static int childT0Sustained(int sock) { setRecvTimeout(sock, 60); @@ -2858,8 +2903,7 @@ static int childT0Sustained(int sock) { // The client half of the T0S rounds, after the handle has been sent: per round the CPU writes // REG_A through the held lock, the server's GPU reads it and fills REG_E / REG_F, and the client // checks both fills through the still-held pointer; then unlock + relock. The caller releases. -static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, uint32_t rounds, - uint32_t seedBase) { +static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, uint32_t rounds, uint32_t seedBase) { uint32_t tag = 0; size_t got = 0; uint32_t lastVk = 0, lastGl = 0, lastMask = 0; @@ -2867,7 +2911,7 @@ static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, T0SRound r{}; r.round = i; r.seedA = seedBase + i * 0x01010101u; - writeRegion(p, REG_A, r.seedA); // through the held lock, no unlock + writeRegion(p, REG_A, r.seedA); // through the held lock, no unlock if (!sendMsg(sock, MSG_T0S_ROUND, &r, sizeof(r), -1)) return 7; T0SGpuDone gd{}; if (!recvMsg(sock, &tag, &gd, sizeof(gd), &got, nullptr) || tag != MSG_T0S_GPUDONE) { @@ -2900,25 +2944,24 @@ static int t0sClientRounds(int sock, AHardwareBuffer* ahb, void* p, int lockRc, } snprintf(dn.note, sizeof(dn.note), "held ptr=%p relock ptr=%p", p, q); sendMsg(sock, MSG_T0S_DONE, &dn, sizeof(dn), -1); - recvMsg(sock, &tag, nullptr, 0, &got, nullptr); // BYE + recvMsg(sock, &tag, nullptr, 0, &got, nullptr); // BYE return 0; } // A GL compute pass over the imported buffer: copies `readWord`.. (1024 words) into // `out` and fills `fillWord`.. with `fill`. Word offsets are relative to the bound range. -static const char* kT0SComputeSrc = - "#version 310 es\n" - "layout(local_size_x = 64) in;\n" - "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" - "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" - "uniform uint uRead;\n" - "uniform uint uFillAt;\n" - "uniform uint uFill;\n" - "void main() {\n" - " uint i = gl_GlobalInvocationID.x;\n" - " outb.w[i] = ext.w[uRead + i];\n" - " ext.w[uFillAt + i] = uFill;\n" - "}\n"; +static const char* kT0SComputeSrc = "#version 310 es\n" + "layout(local_size_x = 64) in;\n" + "layout(std430, binding = 0) buffer Ext { uint w[]; } ext;\n" + "layout(std430, binding = 1) buffer Out { uint w[]; } outb;\n" + "uniform uint uRead;\n" + "uniform uint uFillAt;\n" + "uniform uint uFill;\n" + "void main() {\n" + " uint i = gl_GlobalInvocationID.x;\n" + " outb.w[i] = ext.w[uRead + i];\n" + " ext.w[uFillAt + i] = uFill;\n" + "}\n"; static GLuint buildT0SCompute(std::string* fail) { GLuint sh = glCreateShader(GL_COMPUTE_SHADER); @@ -2948,8 +2991,8 @@ static GLuint buildT0SCompute(std::string* fail) { return prog; } -static void t0sServe(VkCtx& c, GlCtx& g, bool glOk, int sock, AHardwareBuffer* ahb, uint64_t size, - uint32_t rounds, const char* rowPrefix, pid_t pid); +static void t0sServe(VkCtx& c, GlCtx& g, bool glOk, int sock, AHardwareBuffer* ahb, uint64_t size, uint32_t rounds, + const char* rowPrefix, pid_t pid); static void printSummary(); static void runT0SustainedParent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size, uint32_t rounds) { @@ -2994,8 +3037,8 @@ static void runT0SustainedParent(VkCtx& c, GlCtx& g, bool glOk, uint64_t size, u // Vulkan and GL, run `rounds` rounds against the client on `sock`, take its DONE, send BYE, and // record -vulkan / -gles / . `pid` >= 0 is a spawned child to reap. The // caller releases the AHB and closes `sock`. -static void t0sServe(VkCtx& c, GlCtx& g, bool glOk, int sock, AHardwareBuffer* ahb, uint64_t size, - uint32_t rounds, const char* rowPrefix, pid_t pid) { +static void t0sServe(VkCtx& c, GlCtx& g, bool glOk, int sock, AHardwareBuffer* ahb, uint64_t size, uint32_t rounds, + const char* rowPrefix, pid_t pid) { uint32_t tag = 0; size_t got = 0; // --- Vulkan import of the (still client-locked) AHB --- @@ -3245,10 +3288,14 @@ static void t0sServe(VkCtx& c, GlCtx& g, bool glOk, int sock, AHardwareBuffer* a // client checks through the same held pointer. Rows: ROUTE--{,-vulkan,-gles}. // --------------------------------------------------------------------------- -enum : uint32_t { MSG_RT_HELLO = 40, MSG_RT_HOP = 41, MSG_RT_SERVER = 42 }; +enum : uint32_t { + MSG_RT_HELLO = 40, + MSG_RT_HOP = 41, + MSG_RT_SERVER = 42 +}; struct RtHello { - uint32_t variant; // 0 direct, 1 hop + uint32_t variant; // 0 direct, 1 hop uint32_t rounds; uint64_t size; uint64_t regionBase; @@ -3259,7 +3306,7 @@ struct RtHello { }; struct RtServer { - int32_t received; // 1 = the handle arrived and describes as a BLOB of `size` + int32_t received; // 1 = the handle arrived and describes as a BLOB of `size` int32_t pad; char server[192]; char note[192]; @@ -3267,7 +3314,8 @@ struct RtServer { static std::string selfContext() { std::string sec = readSmallFile("/proc/self/attr/current"); - while (!sec.empty() && (sec.back() == '\n' || sec.back() == '\0')) sec.pop_back(); + while (!sec.empty() && (sec.back() == '\n' || sec.back() == '\0')) + sec.pop_back(); return fmt("%s uid=%d pid=%d", sec.c_str(), (int)getuid(), (int)getpid()); } @@ -3363,8 +3411,8 @@ static void rtServePair(VkCtx& c, GlCtx& g, bool glOk, int ctl, int aux) { h.note[sizeof(h.note) - 1] = 0; gRegionBase = h.regionBase; const char* vname = h.variant ? "hop" : "direct"; - pr("route server: client [%s] variant=%s size=%llu rounds=%u (%s)", h.client, vname, - (unsigned long long)h.size, h.rounds, h.note); + pr("route server: client [%s] variant=%s size=%llu rounds=%u (%s)", h.client, vname, (unsigned long long)h.size, + h.rounds, h.note); AHardwareBuffer* ahb = nullptr; int rc = -1; std::string how; @@ -3378,8 +3426,7 @@ static void rtServePair(VkCtx& c, GlCtx& g, bool glOk, int ctl, int aux) { how = fmt("hop socket did not arrive over aux (tag=%u fd=%d errno=%d)", t2, hopFd, errno); } else { rc = AHardwareBuffer_recvHandleFromUnixSocket(hopFd, &ahb); - how = fmt("hop %s; recvHandleFromUnixSocket(hop) rc=%d errno=%d", describeFd(hopFd).c_str(), rc, - errno); + how = fmt("hop %s; recvHandleFromUnixSocket(hop) rc=%d errno=%d", describeFd(hopFd).c_str(), rc, errno); close(hopFd); } } @@ -3389,8 +3436,8 @@ static void rtServePair(VkCtx& c, GlCtx& g, bool glOk, int ctl, int aux) { AHardwareBuffer_Desc d{}; AHardwareBuffer_describe(ahb, &d); sv.received = (d.format == AHARDWAREBUFFER_FORMAT_BLOB && d.width == h.size) ? 1 : 0; - snprintf(sv.note, sizeof(sv.note), "%s | desc w=%u fmt=0x%x usage=0x%llx", how.c_str(), d.width, - d.format, (unsigned long long)d.usage); + snprintf(sv.note, sizeof(sv.note), "%s | desc w=%u fmt=0x%x usage=0x%llx", how.c_str(), d.width, d.format, + (unsigned long long)d.usage); } else { snprintf(sv.note, sizeof(sv.note), "%s", how.c_str()); } @@ -3512,13 +3559,13 @@ static int rtClientVariant(int ctl, int aux, uint32_t variant, uint64_t size, ui int rrc = 10; if (sendRc != 0 || !haveServer || !sv.received) { record(row.c_str(), "FAIL", - fmt("sendRc=%d serverAnswered=%d received=%d server [%s]: %s", sendRc, (int)haveServer, - (int)sv.received, sv.server, sv.note)); + fmt("sendRc=%d serverAnswered=%d received=%d server [%s]: %s", sendRc, (int)haveServer, (int)sv.received, + sv.server, sv.note)); } else { rrc = t0sClientRounds(ctl, ahb, p, lockRc, rounds, h.seedBase); record(row.c_str(), rrc == 0 ? "OK" : "FAIL", - fmt("client [%s] -> server [%s] | %s | rounds rc=%d (the server's rows carry the GPU verdict)", - h.client, sv.server, sv.note, rrc)); + fmt("client [%s] -> server [%s] | %s | rounds rc=%d (the server's rows carry the GPU verdict)", h.client, + sv.server, sv.note, rrc)); } AHardwareBuffer_release(ahb); return rrc; @@ -3567,7 +3614,7 @@ static int routeClient(const char* endpoint, uint64_t size, uint32_t rounds, boo return worst; } -#else // !PROBE_HAVE_AHB +#else // !PROBE_HAVE_AHB static int childT0(int) { pr("T0 is Android-only"); @@ -3592,7 +3639,7 @@ static int routeClient(const char*, uint64_t, uint32_t, bool) { return 1; } -#endif // PROBE_HAVE_AHB +#endif // PROBE_HAVE_AHB // --------------------------------------------------------------------------- // T3: VK_EXT_external_memory_host over a memfd-backed mapping @@ -3659,8 +3706,8 @@ static bool importHostPointer(VkCtx& c, void* host, uint64_t mapSize, HostImport req.memoryTypeBits = 0xFFFFFFFFu; } o.bits = hp.memoryTypeBits & req.memoryTypeBits; - o.typeIdx = pickMemType(c.memProps, o.bits, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + o.typeIdx = + pickMemType(c.memProps, o.bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if (o.typeIdx < 0) o.typeIdx = pickMemType(c.memProps, o.bits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); if (o.typeIdx < 0) { o.fail = fmt("no host-visible memory type in hostPtrBits=0x%x & reqBits=0x%x", hp.memoryTypeBits, @@ -3856,8 +3903,8 @@ static void runT3ClientAllocParent(VkCtx& c, uint64_t size) { int64_t cmpA = -3, gpuFillSeen = -3; GpuTouch gt; if (imported) { - cmpA = checkRegion(hi.mapped, REG_A, seedA); // server reads what the client wrote - writeRegion(hi.mapped, REG_B, seedB); // server writes back + cmpA = checkRegion(hi.mapped, REG_A, seedA); // server reads what the client wrote + writeRegion(hi.mapped, REG_B, seedB); // server writes back gt = gpuTouch(c, hi.buf, REG_A, seedA, REG_E, gpuWord); gpuFillSeen = gt.ran ? checkFillWord(hi.mapped, REG_E, gpuWord) : -3; } @@ -3869,8 +3916,8 @@ static void runT3ClientAllocParent(VkCtx& c, uint64_t size) { T3cResult res{}; res.mismatchB = res.mismatchE = -3; bool gotVerify = false; - if (sendMsg(sock, MSG_T3C_VERIFY, &ver, sizeof(ver), -1) && - recvMsg(sock, &tag, &res, sizeof(res), &got, nullptr) && tag == MSG_T3C_RESULT) { + if (sendMsg(sock, MSG_T3C_VERIFY, &ver, sizeof(ver), -1) && recvMsg(sock, &tag, &res, sizeof(res), &got, nullptr) && + tag == MSG_T3C_RESULT) { gotVerify = true; } @@ -3902,21 +3949,20 @@ static void runT3ClientAllocParent(VkCtx& c, uint64_t size) { else if (!l.readOk) l.fail = fmt("GPU read of the client's memory mismatched at %lld", (long long)gt.readMismatch); else if (!l.writeOk) - l.fail = fmt("GPU write not visible to the client (serverMap=%lld clientMap=%lld)", - (long long)gpuFillSeen, (long long)res.mismatchE); + l.fail = fmt("GPU write not visible to the client (serverMap=%lld clientMap=%lld)", (long long)gpuFillSeen, + (long long)res.mismatchE); legs.push_back(l); } std::string why; const char* status = legVerdict(legs, &why); - std::string detail = - fmt("%s | client allocates, server imports: align=%llu size=%llu hostPtrProps=%s create=%s alloc=%s bind=%s " - "map=%s type=%d bits=0x%x | serverReadOfClient=%lld clientReadOfServer=%lld gpuRead=%lld " - "gpuFill(server=%lld,client=%lld) | %s [%s] ", - legTrace(legs).c_str(), (unsigned long long)align, (unsigned long long)mapSize, - vkStr(hi.hostPtrProps).c_str(), vkStr(hi.createResult).c_str(), vkStr(hi.allocResult).c_str(), - vkStr(hi.bindResult).c_str(), vkStr(hi.mapResult).c_str(), hi.typeIdx, hi.bits, (long long)cmpA, - (long long)res.mismatchB, (long long)gt.readMismatch, (long long)gpuFillSeen, (long long)res.mismatchE, - why.c_str(), ready.note); + std::string detail = fmt( + "%s | client allocates, server imports: align=%llu size=%llu hostPtrProps=%s create=%s alloc=%s bind=%s " + "map=%s type=%d bits=0x%x | serverReadOfClient=%lld clientReadOfServer=%lld gpuRead=%lld " + "gpuFill(server=%lld,client=%lld) | %s [%s] ", + legTrace(legs).c_str(), (unsigned long long)align, (unsigned long long)mapSize, vkStr(hi.hostPtrProps).c_str(), + vkStr(hi.createResult).c_str(), vkStr(hi.allocResult).c_str(), vkStr(hi.bindResult).c_str(), + vkStr(hi.mapResult).c_str(), hi.typeIdx, hi.bits, (long long)cmpA, (long long)res.mismatchB, + (long long)gt.readMismatch, (long long)gpuFillSeen, (long long)res.mismatchE, why.c_str(), ready.note); releaseHostImport(c, hi); munmap(host, (size_t)mapSize); @@ -3999,8 +4045,8 @@ static void runT3Parent(VkCtx& c, uint64_t size) { "gpuRead=%lld gpuFill=%lld %s", legTrace(legs).c_str(), (unsigned long long)align, hi.typeIdx, hi.bits, vkStr(hi.hostPtrProps).c_str(), vkStr(hi.allocResult).c_str(), vkStr(hi.bindResult).c_str(), - vkStr(hi.mapResult).c_str(), (long long)cmpA, (long long)gt.readMismatch, - (long long)gpuFillSeen, why.c_str())); + vkStr(hi.mapResult).c_str(), (long long)cmpA, (long long)gt.readMismatch, (long long)gpuFillSeen, + why.c_str())); } // the same memfd handed to another process @@ -4084,7 +4130,8 @@ static std::vector readArgsFile(const std::string& path) { FILE* f = fopen(path.c_str(), "r"); if (!f) return out; char tok[256]; - while (fscanf(f, "%255s", tok) == 1) out.push_back(tok); + while (fscanf(f, "%255s", tok) == 1) + out.push_back(tok); fclose(f); return out; } @@ -4116,7 +4163,7 @@ int main(int argc, char** argv) { for (const std::string& s : args) { const char* a = s.c_str(); if (!strncmp(a, "--child=", 8)) { - static std::string route; // outlives `args` + static std::string route; // outlives `args` route = a + 8; childRoute = route.c_str(); } else if (!strcmp(a, "--route-client")) { @@ -4151,7 +4198,8 @@ int main(int argc, char** argv) { doGles = false; } else if (!strcmp(a, "--help")) { printf("usage: extmem_probe [markerPath] [--size=BYTES] [--regions-at-end] [--sustained-lock[=ROUNDS]]\n" - " extmem_probe --route-client[=@endpoint] [--size=BYTES] [--sustained-lock=ROUNDS] [--regions-at-end]\n" + " extmem_probe --route-client[=@endpoint] [--size=BYTES] [--sustained-lock=ROUNDS] " + "[--regions-at-end]\n" " extmem_probe --serve (packaged as libMobileGLServer.so)\n" " [--only-t0|--only-t1|--only-t3|--only-gles|--only-sustained] [--no-gles]\n" " markerPath: exec-hook mode; options are also read from .args\n"); @@ -4183,7 +4231,8 @@ int main(int argc, char** argv) { } std::string joined; - for (const std::string& s : args) joined += " " + s; + for (const std::string& s : args) + joined += " " + s; pr("extmem_probe: MobileGL disaggregation spike B, size=%llu bytes regionBase=%llu sustainedRounds=%u " "marker=%s args=[%s ]", (unsigned long long)size, (unsigned long long)gRegionBase, sustainedRounds, @@ -4225,17 +4274,21 @@ int main(int argc, char** argv) { if (!markerPath.empty()) { int ok = 0, partial = 0, fail = 0, unsup = 0, skip = 0; for (const RouteResult& r : gResults) { - if (r.status == "OK") ++ok; - else if (r.status == "PARTIAL") ++partial; - else if (r.status == "FAIL") ++fail; - else if (r.status == "UNSUPPORTED") ++unsup; - else ++skip; + if (r.status == "OK") + ++ok; + else if (r.status == "PARTIAL") + ++partial; + else if (r.status == "FAIL") + ++fail; + else if (r.status == "UNSUPPORTED") + ++unsup; + else + ++skip; } FILE* m = fopen(markerPath.c_str(), "w"); if (m) { fprintf(m, "extmem_probe done selinux=%s rows=%zu OK=%d PARTIAL=%d FAIL=%d UNSUPPORTED=%d SKIP=%d\n", - readSmallFile("/proc/self/attr/current").c_str(), gResults.size(), ok, partial, fail, unsup, - skip); + readSmallFile("/proc/self/attr/current").c_str(), gResults.size(), ok, partial, fail, unsup, skip); fclose(m); } } diff --git a/tools/spikes/server_stub/main.cpp b/tools/spikes/server_stub/main.cpp index c2a033b7..dfb0e521 100644 --- a/tools/spikes/server_stub/main.cpp +++ b/tools/spikes/server_stub/main.cpp @@ -33,31 +33,31 @@ namespace { -// The single fact that makes this spike conclusive rather than suggestive: the exec'd -// child reports the domain it is running in. `u:r:untrusted_app:s0:...` means an ordinary -// application process really did exec this file; anything else (shell, adb, a platform -// domain) means the test was run the wrong way and its verdict does not transfer. -void ReadSelinuxContext(char* out, size_t size) { - out[0] = '\0'; - FILE* file = std::fopen("/proc/self/attr/current", "r"); - if (file == nullptr) { - std::snprintf(out, size, ""); - return; - } - const size_t read = std::fread(out, 1, size - 1, file); - std::fclose(file); - out[read] = '\0'; - // The kernel returns the context NUL-terminated inside the read; trim anything after. - for (size_t index = 0; index < read; ++index) { - if (out[index] == '\n' || out[index] == '\0') { - out[index] = '\0'; - break; + // The single fact that makes this spike conclusive rather than suggestive: the exec'd + // child reports the domain it is running in. `u:r:untrusted_app:s0:...` means an ordinary + // application process really did exec this file; anything else (shell, adb, a platform + // domain) means the test was run the wrong way and its verdict does not transfer. + void ReadSelinuxContext(char* out, size_t size) { + out[0] = '\0'; + FILE* file = std::fopen("/proc/self/attr/current", "r"); + if (file == nullptr) { + std::snprintf(out, size, ""); + return; + } + const size_t read = std::fread(out, 1, size - 1, file); + std::fclose(file); + out[read] = '\0'; + // The kernel returns the context NUL-terminated inside the read; trim anything after. + for (size_t index = 0; index < read; ++index) { + if (out[index] == '\n' || out[index] == '\0') { + out[index] = '\0'; + break; + } + } + if (out[0] == '\0') { + std::snprintf(out, size, ""); } } - if (out[0] == '\0') { - std::snprintf(out, size, ""); - } -} } // namespace @@ -69,9 +69,8 @@ int main(int argc, char** argv) { std::snprintf(line, sizeof(line), "MobileGLServer-spike ok argv0=%s pid=%d ppid=%d uid=%d gid=%d argc=%d " "selinux=%s\n", - argc > 0 && argv[0] != nullptr ? argv[0] : "", - static_cast(getpid()), static_cast(getppid()), - static_cast(getuid()), static_cast(getgid()), argc, context); + argc > 0 && argv[0] != nullptr ? argv[0] : "", static_cast(getpid()), + static_cast(getppid()), static_cast(getuid()), static_cast(getgid()), argc, context); std::fputs(line, stdout); std::fflush(stdout); @@ -83,14 +82,12 @@ int main(int argc, char** argv) { FILE* marker = std::fopen(argv[1], "w"); if (marker == nullptr) { - std::fprintf(stderr, "MobileGLServer-spike: cannot open marker %s: %s\n", argv[1], - std::strerror(errno)); + std::fprintf(stderr, "MobileGLServer-spike: cannot open marker %s: %s\n", argv[1], std::strerror(errno)); return 3; } std::fputs(line, marker); if (std::fclose(marker) != 0) { - std::fprintf(stderr, "MobileGLServer-spike: cannot write marker %s: %s\n", argv[1], - std::strerror(errno)); + std::fprintf(stderr, "MobileGLServer-spike: cannot write marker %s: %s\n", argv[1], std::strerror(errno)); return 4; } return 0; diff --git a/tools/trace_replay/apitrace_fbo_dump.cpp b/tools/trace_replay/apitrace_fbo_dump.cpp index b08aa302..604648e2 100644 --- a/tools/trace_replay/apitrace_fbo_dump.cpp +++ b/tools/trace_replay/apitrace_fbo_dump.cpp @@ -33,726 +33,716 @@ // the dump calls to the -S callset so the hook is reached. namespace mobilegl_trace_dump { -namespace { + namespace { -using PfnGetIntegerv = void (*)(GLenum, GLint *); -using PfnGetError = GLenum (*)(void); + using PfnGetIntegerv = void (*)(GLenum, GLint*); + using PfnGetError = GLenum (*)(void); -constexpr const char *kDumpPointsEnv = "MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS"; -constexpr const char *kTexture2dDumpPointsEnv = "MOBILEGL_TRACE_DUMP_TEXTURE_2D"; -constexpr const char *kScanLimitEnv = "MOBILEGL_TRACE_DUMP_FBO_SCAN_LIMIT"; -constexpr unsigned kDefaultScanLimit = 1024; + constexpr const char* kDumpPointsEnv = "MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS"; + constexpr const char* kTexture2dDumpPointsEnv = "MOBILEGL_TRACE_DUMP_TEXTURE_2D"; + constexpr const char* kScanLimitEnv = "MOBILEGL_TRACE_DUMP_FBO_SCAN_LIMIT"; + constexpr unsigned kDefaultScanLimit = 1024; -struct DumpPoint { - unsigned call = 0; - std::string directory; - // Empty means "every framebuffer object the driver still knows about". - std::vector framebuffers; - bool done = false; -}; + struct DumpPoint { + unsigned call = 0; + std::string directory; + // Empty means "every framebuffer object the driver still knows about". + std::vector framebuffers; + bool done = false; + }; -// CALL,TEXTURE,LEVEL,DIR entries, separated by ';'. This deliberately does not share the -// framebuffer dump grammar: an absolute Windows directory contains ':' but not ','. -struct Texture2dDumpPoint { - unsigned call = 0; - unsigned texture = 0; - unsigned level = 0; - std::string directory; - bool done = false; -}; + // CALL,TEXTURE,LEVEL,DIR entries, separated by ';'. This deliberately does not share the + // framebuffer dump grammar: an absolute Windows directory contains ':' but not ','. + struct Texture2dDumpPoint { + unsigned call = 0; + unsigned texture = 0; + unsigned level = 0; + std::string directory; + bool done = false; + }; -struct AttachmentDesc { - GLint objectType = GL_NONE; - GLint objectName = 0; - GLint level = 0; - GLint width = 0; - GLint height = 0; - GLint internalFormat = 0; - GLint componentType = GL_NONE; -}; + struct AttachmentDesc { + GLint objectType = GL_NONE; + GLint objectName = 0; + GLint level = 0; + GLint width = 0; + GLint height = 0; + GLint internalFormat = 0; + GLint componentType = GL_NONE; + }; -std::vector gDumpPoints; -std::vector gTexture2dDumpPoints; -bool gInstalled = false; -bool gConfigured = false; -retrace::Dumper *gInnerDumper = nullptr; -PfnGetIntegerv gGetIntegerv = nullptr; -PfnGetError gGetError = nullptr; + std::vector gDumpPoints; + std::vector gTexture2dDumpPoints; + bool gInstalled = false; + bool gConfigured = false; + retrace::Dumper* gInnerDumper = nullptr; + PfnGetIntegerv gGetIntegerv = nullptr; + PfnGetError gGetError = nullptr; -// apitrace's public dispatch is interposed by apitrace_glproc_mobilegl.cpp, which pins -// glGetIntegerv(GL_READ_BUFFER) to GL_BACK and swallows glGetError. Reading real state - -// notably each framebuffer's read buffer, which has to be restored - needs the -// uninterposed entry points. -void ResolveDirectEntryPoints() { - if (gGetIntegerv == nullptr) { - gGetIntegerv = reinterpret_cast(_getPrivateProcAddress("glGetIntegerv")); - } - if (gGetError == nullptr) { - gGetError = reinterpret_cast(_getPrivateProcAddress("glGetError")); - } -} - -GLint GetInteger(GLenum pname) { - GLint value = 0; - if (gGetIntegerv != nullptr) { - gGetIntegerv(pname, &value); - } - return value; -} - -unsigned DrainErrors() { - if (gGetError == nullptr) { - return 0; - } - unsigned count = 0; - while (gGetError() != GL_NO_ERROR) { - if (++count > 64) { - break; + // apitrace's public dispatch is interposed by apitrace_glproc_mobilegl.cpp, which pins + // glGetIntegerv(GL_READ_BUFFER) to GL_BACK and swallows glGetError. Reading real state - + // notably each framebuffer's read buffer, which has to be restored - needs the + // uninterposed entry points. + void ResolveDirectEntryPoints() { + if (gGetIntegerv == nullptr) { + gGetIntegerv = reinterpret_cast(_getPrivateProcAddress("glGetIntegerv")); + } + if (gGetError == nullptr) { + gGetError = reinterpret_cast(_getPrivateProcAddress("glGetError")); + } } - } - return count; -} -bool MakeDirectories(const std::string &path) { - if (path.empty()) { - return false; - } - std::string partial; - partial.reserve(path.size()); - for (std::size_t i = 0; i < path.size(); ++i) { - partial.push_back(path[i]); - const bool last = i + 1 == path.size(); + GLint GetInteger(GLenum pname) { + GLint value = 0; + if (gGetIntegerv != nullptr) { + gGetIntegerv(pname, &value); + } + return value; + } + + unsigned DrainErrors() { + if (gGetError == nullptr) { + return 0; + } + unsigned count = 0; + while (gGetError() != GL_NO_ERROR) { + if (++count > 64) { + break; + } + } + return count; + } + + bool MakeDirectories(const std::string& path) { + if (path.empty()) { + return false; + } + std::string partial; + partial.reserve(path.size()); + for (std::size_t i = 0; i < path.size(); ++i) { + partial.push_back(path[i]); + const bool last = i + 1 == path.size(); #if defined(_WIN32) - const bool separator = path[i] == '/' || path[i] == '\\'; + const bool separator = path[i] == '/' || path[i] == '\\'; #else - const bool separator = path[i] == '/'; + const bool separator = path[i] == '/'; #endif - if (!separator && !last) { - continue; - } - if (partial == "/") { - continue; - } + if (!separator && !last) { + continue; + } + if (partial == "/") { + continue; + } #if defined(_WIN32) - if (separator && partial.size() == 3 && partial[1] == ':') { - continue; - } + if (separator && partial.size() == 3 && partial[1] == ':') { + continue; + } #endif #if defined(_WIN32) - if (_mkdir(partial.c_str()) != 0 && errno != EEXIST) { + if (_mkdir(partial.c_str()) != 0 && errno != EEXIST) { #else - if (mkdir(partial.c_str(), 0755) != 0 && errno != EEXIST) { + if (mkdir(partial.c_str(), 0755) != 0 && errno != EEXIST) { #endif - return false; + return false; + } + } + return true; } - } - return true; -} -std::vector Split(const std::string &value, char separator) { - std::vector parts; - std::string current; - for (const char c : value) { - if (c == separator) { + std::vector Split(const std::string& value, char separator) { + std::vector parts; + std::string current; + for (const char c : value) { + if (c == separator) { + parts.push_back(current); + current.clear(); + } else { + current.push_back(c); + } + } parts.push_back(current); - current.clear(); - } else { - current.push_back(c); - } - } - parts.push_back(current); - return parts; -} - -// CALL:DIR[:FBO,FBO,...] entries, separated by ';'. An omitted or `all` framebuffer list -// dumps every live framebuffer object. -void ParseDumpPoints(const char *spec) { - for (const std::string &entry : Split(spec, ';')) { - if (entry.empty()) { - continue; - } - const std::vector fields = Split(entry, ':'); - if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) { - std::cerr << "warning: ignoring malformed " << kDumpPointsEnv << " entry: " << entry << "\n"; - continue; + return parts; } - DumpPoint point; - point.call = static_cast(std::strtoul(fields[0].c_str(), nullptr, 10)); - point.directory = fields[1]; - if (fields.size() >= 3 && !fields[2].empty() && fields[2] != "all") { - for (const std::string &name : Split(fields[2], ',')) { - if (!name.empty()) { - point.framebuffers.push_back( - static_cast(std::strtoul(name.c_str(), nullptr, 10))); + // CALL:DIR[:FBO,FBO,...] entries, separated by ';'. An omitted or `all` framebuffer list + // dumps every live framebuffer object. + void ParseDumpPoints(const char* spec) { + for (const std::string& entry : Split(spec, ';')) { + if (entry.empty()) { + continue; + } + const std::vector fields = Split(entry, ':'); + if (fields.size() < 2 || fields[0].empty() || fields[1].empty()) { + std::cerr << "warning: ignoring malformed " << kDumpPointsEnv << " entry: " << entry << "\n"; + continue; + } + + DumpPoint point; + point.call = static_cast(std::strtoul(fields[0].c_str(), nullptr, 10)); + point.directory = fields[1]; + if (fields.size() >= 3 && !fields[2].empty() && fields[2] != "all") { + for (const std::string& name : Split(fields[2], ',')) { + if (!name.empty()) { + point.framebuffers.push_back( + static_cast(std::strtoul(name.c_str(), nullptr, 10))); + } + } + } + gDumpPoints.push_back(point); + } + } + + bool IsDecimal(const std::string& value) { + return !value.empty() && value.find_first_not_of("0123456789") == std::string::npos; + } + + void ParseTexture2dDumpPoints(const char* spec) { + for (const std::string& entry : Split(spec, ';')) { + if (entry.empty()) { + continue; + } + const std::vector fields = Split(entry, ','); + if (fields.size() != 4 || !IsDecimal(fields[0]) || !IsDecimal(fields[1]) || !IsDecimal(fields[2]) || + fields[3].empty()) { + std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv << " entry: " << entry + << "\n"; + continue; + } + + char* end = nullptr; + const unsigned long call = std::strtoul(fields[0].c_str(), &end, 10); + if (*end != '\0' || call > std::numeric_limits::max()) { + std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv << " call: " << entry + << "\n"; + continue; + } + const unsigned long texture = std::strtoul(fields[1].c_str(), &end, 10); + if (*end != '\0' || texture == 0 || texture > std::numeric_limits::max()) { + std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv << " texture: " << entry + << "\n"; + continue; + } + const unsigned long level = std::strtoul(fields[2].c_str(), &end, 10); + if (*end != '\0' || level > static_cast(std::numeric_limits::max())) { + std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv << " level: " << entry + << "\n"; + continue; + } + + Texture2dDumpPoint point; + point.call = static_cast(call); + point.texture = static_cast(texture); + point.level = static_cast(level); + point.directory = fields[3]; + gTexture2dDumpPoints.push_back(point); + } + } + + unsigned ScanLimit() { + const char* value = std::getenv(kScanLimitEnv); + if (value == nullptr || value[0] == '\0') { + return kDefaultScanLimit; + } + const unsigned limit = static_cast(std::strtoul(value, nullptr, 10)); + return limit == 0 ? kDefaultScanLimit : limit; + } + + const char* ComponentTypeName(GLint componentType) { + switch (componentType) { + case GL_FLOAT: + return "float"; + case GL_INT: + return "int"; + case GL_UNSIGNED_INT: + return "uint"; + case GL_SIGNED_NORMALIZED: + return "snorm"; + case GL_UNSIGNED_NORMALIZED: + return "unorm"; + case GL_NONE: + return "none"; + default: + return "unknown"; + } + } + + bool DescribeAttachment(GLenum attachment, AttachmentDesc& desc) { + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &desc.objectType); + if (DrainErrors() != 0 || desc.objectType == GL_NONE) { + return false; + } + + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, + GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &desc.objectName); + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, + GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &desc.componentType); + DrainErrors(); + + if (desc.objectType == GL_RENDERBUFFER) { + const GLint boundRenderbuffer = GetInteger(GL_RENDERBUFFER_BINDING); + glBindRenderbuffer(GL_RENDERBUFFER, static_cast(desc.objectName)); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &desc.width); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &desc.height); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &desc.internalFormat); + glBindRenderbuffer(GL_RENDERBUFFER, static_cast(boundRenderbuffer)); + } else if (desc.objectType == GL_TEXTURE) { + glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, + GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &desc.level); + glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, GL_TEXTURE_WIDTH, + &desc.width); + glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, GL_TEXTURE_HEIGHT, + &desc.height); + glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, + GL_TEXTURE_INTERNAL_FORMAT, &desc.internalFormat); + if (DrainErrors() != 0 || desc.width <= 0 || desc.height <= 0) { + // No direct-state-access level query: fall back to the classic bound query, + // which only covers GL_TEXTURE_2D but is what render targets normally are. + const GLint boundTexture = GetInteger(GL_TEXTURE_BINDING_2D); + glBindTexture(GL_TEXTURE_2D, static_cast(desc.objectName)); + glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_WIDTH, &desc.width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_HEIGHT, &desc.height); + glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_INTERNAL_FORMAT, + &desc.internalFormat); + glBindTexture(GL_TEXTURE_2D, static_cast(boundTexture)); + } + } + + DrainErrors(); + return desc.width > 0 && desc.height > 0; + } + + bool DescribeTexture2D(GLuint texture, GLint level, AttachmentDesc& desc) { + const GLint savedTexture = GetInteger(GL_TEXTURE_BINDING_2D); + glBindTexture(GL_TEXTURE_2D, texture); + desc.objectType = GL_TEXTURE; + desc.objectName = static_cast(texture); + desc.level = level; + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &desc.width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &desc.height); + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_INTERNAL_FORMAT, &desc.internalFormat); + glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_RED_TYPE, &desc.componentType); + glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); + return DrainErrors() == 0 && desc.width > 0 && desc.height > 0; + } + + bool ReadTexture2DFloats(const AttachmentDesc& desc, std::vector& pixels) { + const std::size_t count = static_cast(desc.width) * desc.height * 4; + pixels.assign(count, 0.0f); + const GLint savedTexture = GetInteger(GL_TEXTURE_BINDING_2D); + glBindTexture(GL_TEXTURE_2D, static_cast(desc.objectName)); + if (desc.componentType == GL_INT || desc.componentType == GL_UNSIGNED_INT) { + std::vector raw(count, 0); + const GLenum type = desc.componentType == GL_INT ? GL_INT : GL_UNSIGNED_INT; + glGetTexImage(GL_TEXTURE_2D, desc.level, GL_RGBA_INTEGER, type, raw.data()); + glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); + if (DrainErrors() != 0) { + return false; + } + for (std::size_t i = 0; i < count; ++i) { + pixels[i] = desc.componentType == GL_INT ? static_cast(raw[i]) + : static_cast(static_cast(raw[i])); + } + return true; + } + glGetTexImage(GL_TEXTURE_2D, desc.level, GL_RGBA, GL_FLOAT, pixels.data()); + glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); + return DrainErrors() == 0; + } + + // Reads the attachment as floats regardless of its storage: normalised and float targets + // convert on the way out, integer targets are read as integers and widened. The float view + // keeps out-of-[0,1] accumulation buffers legible in the statistics even though the PNG + // itself has to clamp. + bool ReadAttachmentFloats(const AttachmentDesc& desc, bool depth, unsigned channels, + std::vector& pixels) { + const std::size_t count = static_cast(desc.width) * desc.height * channels; + pixels.assign(count, 0.0f); + + if (depth) { + glReadPixels(0, 0, desc.width, desc.height, GL_DEPTH_COMPONENT, GL_FLOAT, pixels.data()); + return DrainErrors() == 0; + } + + if (desc.componentType == GL_INT || desc.componentType == GL_UNSIGNED_INT) { + std::vector raw(count, 0); + const GLenum type = desc.componentType == GL_INT ? GL_INT : GL_UNSIGNED_INT; + glReadPixels(0, 0, desc.width, desc.height, GL_RGBA_INTEGER, type, raw.data()); + if (DrainErrors() != 0) { + return false; + } + for (std::size_t i = 0; i < count; ++i) { + pixels[i] = desc.componentType == GL_INT ? static_cast(raw[i]) + : static_cast(static_cast(raw[i])); + } + return true; + } + + glReadPixels(0, 0, desc.width, desc.height, GL_RGBA, GL_FLOAT, pixels.data()); + return DrainErrors() == 0; + } + + std::string FormatStatistics(const std::vector& pixels, unsigned channels) { + float minimum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + float maximum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + double total[4] = {0.0, 0.0, 0.0, 0.0}; + bool seeded[4] = {false, false, false, false}; + std::uint64_t hash = 1469598103934665603ull; + std::size_t nonFinite = 0; + + const std::size_t pixelCount = channels == 0 ? 0 : pixels.size() / channels; + for (std::size_t p = 0; p < pixelCount; ++p) { + for (unsigned c = 0; c < channels; ++c) { + const float value = pixels[p * channels + c]; + if (!std::isfinite(value)) { + ++nonFinite; + continue; + } + if (!seeded[c] || value < minimum[c]) { + minimum[c] = value; + } + if (!seeded[c] || value > maximum[c]) { + maximum[c] = value; + } + seeded[c] = true; + total[c] += value; + } + } + for (const float value : pixels) { + std::uint32_t bits = 0; + std::memcpy(&bits, &value, sizeof(bits)); + hash = (hash ^ bits) * 1099511628211ull; + } + + char buffer[512]; + std::string text; + for (unsigned c = 0; c < channels; ++c) { + const double mean = pixelCount == 0 ? 0.0 : total[c] / static_cast(pixelCount); + std::snprintf(buffer, sizeof(buffer), " c%u[min=%.6g max=%.6g mean=%.6g]", c, + static_cast(minimum[c]), static_cast(maximum[c]), mean); + text += buffer; + } + if (pixelCount > 0) { + text += " first=("; + for (unsigned c = 0; c < channels; ++c) { + if (c > 0) { + text += ","; + } + std::snprintf(buffer, sizeof(buffer), "%.9g", static_cast(pixels[c])); + text += buffer; + } + text += ")"; + } + std::snprintf(buffer, sizeof(buffer), " nonfinite=%zu hash=%016llx", nonFinite, + static_cast(hash)); + text += buffer; + return text; + } + + bool WriteFloatPng(const std::string& path, const AttachmentDesc& desc, unsigned channels, + const std::vector& pixels) { + image::Image snapshot(static_cast(desc.width), static_cast(desc.height), channels, true, + image::TYPE_FLOAT); + if (snapshot.sizeInBytes() != pixels.size() * sizeof(float)) { + return false; + } + std::memcpy(snapshot.pixels, pixels.data(), pixels.size() * sizeof(float)); + return snapshot.writePNG(path.c_str()); + } + + void DumpOneAttachment(std::ofstream& manifest, const std::string& directory, const std::string& identity, + GLenum attachment, const char* label, bool depth) { + AttachmentDesc desc; + if (!DescribeAttachment(attachment, desc)) { + return; + } + + const unsigned channels = depth ? 1u : 4u; + if (!depth) { + glReadBuffer(attachment); + if (DrainErrors() != 0) { + return; + } + } + + std::vector pixels; + const bool read = ReadAttachmentFloats(desc, depth, channels, pixels); + + const std::string path = directory + "/" + identity + "-" + label + ".png"; + const bool wrote = read && WriteFloatPng(path, desc, channels, pixels); + + char header[512]; + std::snprintf(header, sizeof(header), + "%s %s object=%s name=%d level=%d size=%dx%d internalformat=0x%04x component=%s", + identity.c_str(), label, desc.objectType == GL_RENDERBUFFER ? "renderbuffer" : "texture", + desc.objectName, desc.level, desc.width, desc.height, + static_cast(desc.internalFormat), ComponentTypeName(desc.componentType)); + manifest << header; + if (read) { + manifest << FormatStatistics(pixels, channels); + } else { + manifest << " read=failed"; + } + if (!wrote) { + manifest << " png=failed"; + } + manifest << "\n"; + } + + void DumpFramebuffer(std::ofstream& manifest, const std::string& directory, unsigned framebuffer, + GLint maxColorAttachments) { + if (framebuffer == 0) { + // The default framebuffer names its attachments GL_BACK_LEFT rather than + // GL_COLOR_ATTACHMENT0, and the replay already snapshots it into actual..png. + manifest << "fbo 0 skipped=default-framebuffer\n"; + return; + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); + if (DrainErrors() != 0) { + return; + } + + const GLint savedReadBuffer = GetInteger(GL_READ_BUFFER); + const std::string identity = "fbo" + std::to_string(framebuffer); + for (GLint index = 0; index < maxColorAttachments; ++index) { + char label[32]; + std::snprintf(label, sizeof(label), "att%d", index); + DumpOneAttachment(manifest, directory, identity, static_cast(GL_COLOR_ATTACHMENT0 + index), + label, false); + } + DumpOneAttachment(manifest, directory, identity, GL_DEPTH_ATTACHMENT, "depth", true); + + // The read buffer is per-framebuffer state the trace goes on using; put it back. + if (framebuffer != 0 && savedReadBuffer != 0) { + glReadBuffer(static_cast(savedReadBuffer)); + DrainErrors(); + } + } + + void RunDumpPoint(DumpPoint& point) { + if (!MakeDirectories(point.directory)) { + std::cerr << "warning: failed to create FBO dump directory " << point.directory << "\n"; + point.done = true; + return; + } + + // Start from a clean error state so a failure reported below is one we caused. + DrainErrors(); + + // Everything below perturbs read-side and pack state; snapshot it so the replay + // continues from where it was. + const GLint savedReadFramebuffer = GetInteger(GL_READ_FRAMEBUFFER_BINDING); + const GLint savedPackBuffer = GetInteger(GL_PIXEL_PACK_BUFFER_BINDING); + const GLint savedPackAlignment = GetInteger(GL_PACK_ALIGNMENT); + const GLint savedPackRowLength = GetInteger(GL_PACK_ROW_LENGTH); + const GLint savedPackSkipPixels = GetInteger(GL_PACK_SKIP_PIXELS); + const GLint savedPackSkipRows = GetInteger(GL_PACK_SKIP_ROWS); + const GLint savedPackImageHeight = GetInteger(GL_PACK_IMAGE_HEIGHT); + const GLint savedPackSkipImages = GetInteger(GL_PACK_SKIP_IMAGES); + const GLint savedPackSwapBytes = GetInteger(GL_PACK_SWAP_BYTES); + DrainErrors(); + + if (savedPackBuffer != 0) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); + glPixelStorei(GL_PACK_SKIP_IMAGES, 0); + glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE); + DrainErrors(); + + const GLint maxColorAttachments = GetInteger(GL_MAX_COLOR_ATTACHMENTS); + DrainErrors(); + + std::vector framebuffers = point.framebuffers; + if (framebuffers.empty()) { + const unsigned limit = ScanLimit(); + for (unsigned name = 1; name <= limit; ++name) { + if (glIsFramebuffer(name) == GL_TRUE) { + framebuffers.push_back(name); + } + } + DrainErrors(); + } + + const std::string manifestPath = point.directory + "/manifest.txt"; + std::ofstream manifest(manifestPath, std::ios::trunc); + manifest << "call " << retrace::callNo << " framebuffers " << framebuffers.size() << " maxcolorattachments " + << maxColorAttachments << "\n"; + for (const unsigned framebuffer : framebuffers) { + DumpFramebuffer(manifest, point.directory, framebuffer, maxColorAttachments); + } + manifest.flush(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(savedReadFramebuffer)); + if (savedPackBuffer != 0) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(savedPackBuffer)); + } + glPixelStorei(GL_PACK_ALIGNMENT, savedPackAlignment); + glPixelStorei(GL_PACK_ROW_LENGTH, savedPackRowLength); + glPixelStorei(GL_PACK_SKIP_PIXELS, savedPackSkipPixels); + glPixelStorei(GL_PACK_SKIP_ROWS, savedPackSkipRows); + glPixelStorei(GL_PACK_IMAGE_HEIGHT, savedPackImageHeight); + glPixelStorei(GL_PACK_SKIP_IMAGES, savedPackSkipImages); + glPixelStorei(GL_PACK_SWAP_BYTES, savedPackSwapBytes); + DrainErrors(); + + std::cerr << "MOBILEGL_TRACE_FBO_DUMP: call " << retrace::callNo << " -> " << manifestPath << " (" + << framebuffers.size() << " framebuffers)\n"; + point.done = true; + } + + void RunTexture2dDumpPoint(Texture2dDumpPoint& point) { + if (!MakeDirectories(point.directory)) { + std::cerr << "warning: failed to create texture dump directory " << point.directory << "\n"; + point.done = true; + return; + } + + // glGetError is destructive. This debug-only snapshot hook deliberately starts from a + // clean error state so diagnostics below identify the dump rather than an earlier trace call. + DrainErrors(); + const GLint savedReadFramebuffer = GetInteger(GL_READ_FRAMEBUFFER_BINDING); + const GLint savedPackBuffer = GetInteger(GL_PIXEL_PACK_BUFFER_BINDING); + const GLint savedPackAlignment = GetInteger(GL_PACK_ALIGNMENT); + const GLint savedPackRowLength = GetInteger(GL_PACK_ROW_LENGTH); + const GLint savedPackSkipPixels = GetInteger(GL_PACK_SKIP_PIXELS); + const GLint savedPackSkipRows = GetInteger(GL_PACK_SKIP_ROWS); + const GLint savedPackImageHeight = GetInteger(GL_PACK_IMAGE_HEIGHT); + const GLint savedPackSkipImages = GetInteger(GL_PACK_SKIP_IMAGES); + const GLint savedPackSwapBytes = GetInteger(GL_PACK_SWAP_BYTES); + DrainErrors(); + + if (savedPackBuffer != 0) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); + glPixelStorei(GL_PACK_SKIP_PIXELS, 0); + glPixelStorei(GL_PACK_SKIP_ROWS, 0); + glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); + glPixelStorei(GL_PACK_SKIP_IMAGES, 0); + glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE); + DrainErrors(); + + const std::string identity = + "texture" + std::to_string(point.texture) + "-level" + std::to_string(point.level); + const std::string manifestPath = point.directory + "/manifest.txt"; + std::ofstream manifest(manifestPath, std::ios::trunc); + manifest << "call " << retrace::callNo << " texture " << point.texture << " level " << point.level << "\n"; + AttachmentDesc desc; + if (glIsTexture(point.texture) == GL_FALSE || + !DescribeTexture2D(point.texture, static_cast(point.level), desc)) { + manifest << identity << " skipped=not-live-2d-texture\n"; + } else { + std::vector pixels; + const bool read = ReadTexture2DFloats(desc, pixels); + const bool wrote = read && WriteFloatPng(point.directory + "/" + identity + ".png", desc, 4, pixels); + manifest << identity << " object=texture name=" << desc.objectName << " level=" << desc.level + << " size=" << desc.width << "x" << desc.height << " internalformat=0x" << std::hex + << static_cast(desc.internalFormat) << std::dec + << " component=" << ComponentTypeName(desc.componentType); + if (read) { + manifest << FormatStatistics(pixels, 4); + } else { + manifest << " read=failed"; + } + if (!wrote) { + manifest << " png=failed"; + } + manifest << "\n"; + } + manifest.flush(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(savedReadFramebuffer)); + if (savedPackBuffer != 0) { + glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(savedPackBuffer)); + } + glPixelStorei(GL_PACK_ALIGNMENT, savedPackAlignment); + glPixelStorei(GL_PACK_ROW_LENGTH, savedPackRowLength); + glPixelStorei(GL_PACK_SKIP_PIXELS, savedPackSkipPixels); + glPixelStorei(GL_PACK_SKIP_ROWS, savedPackSkipRows); + glPixelStorei(GL_PACK_IMAGE_HEIGHT, savedPackImageHeight); + glPixelStorei(GL_PACK_SKIP_IMAGES, savedPackSkipImages); + glPixelStorei(GL_PACK_SWAP_BYTES, savedPackSwapBytes); + DrainErrors(); + + std::cerr << "MOBILEGL_TRACE_TEXTURE_2D_DUMP: call " << retrace::callNo << " texture " << point.texture + << " level " << point.level << " -> " << manifestPath << "\n"; + point.done = true; + } + + void RunPendingDumps() { + for (DumpPoint& point : gDumpPoints) { + if (!point.done && point.call == retrace::callNo) { + RunDumpPoint(point); + } + } + for (Texture2dDumpPoint& point : gTexture2dDumpPoints) { + if (!point.done && point.call == retrace::callNo) { + RunTexture2dDumpPoint(point); } } } - gDumpPoints.push_back(point); - } -} -bool IsDecimal(const std::string &value) { - return !value.empty() && value.find_first_not_of("0123456789") == std::string::npos; -} - -void ParseTexture2dDumpPoints(const char *spec) { - for (const std::string &entry : Split(spec, ';')) { - if (entry.empty()) { - continue; - } - const std::vector fields = Split(entry, ','); - if (fields.size() != 4 || !IsDecimal(fields[0]) || !IsDecimal(fields[1]) || - !IsDecimal(fields[2]) || fields[3].empty()) { - std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv - << " entry: " << entry << "\n"; - continue; - } - - char *end = nullptr; - const unsigned long call = std::strtoul(fields[0].c_str(), &end, 10); - if (*end != '\0' || call > std::numeric_limits::max()) { - std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv - << " call: " << entry << "\n"; - continue; - } - const unsigned long texture = std::strtoul(fields[1].c_str(), &end, 10); - if (*end != '\0' || texture == 0 || texture > std::numeric_limits::max()) { - std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv - << " texture: " << entry << "\n"; - continue; - } - const unsigned long level = std::strtoul(fields[2].c_str(), &end, 10); - if (*end != '\0' || level > static_cast(std::numeric_limits::max())) { - std::cerr << "warning: ignoring malformed " << kTexture2dDumpPointsEnv - << " level: " << entry << "\n"; - continue; - } - - Texture2dDumpPoint point; - point.call = static_cast(call); - point.texture = static_cast(texture); - point.level = static_cast(level); - point.directory = fields[3]; - gTexture2dDumpPoints.push_back(point); - } -} - -unsigned ScanLimit() { - const char *value = std::getenv(kScanLimitEnv); - if (value == nullptr || value[0] == '\0') { - return kDefaultScanLimit; - } - const unsigned limit = static_cast(std::strtoul(value, nullptr, 10)); - return limit == 0 ? kDefaultScanLimit : limit; -} - -const char *ComponentTypeName(GLint componentType) { - switch (componentType) { - case GL_FLOAT: - return "float"; - case GL_INT: - return "int"; - case GL_UNSIGNED_INT: - return "uint"; - case GL_SIGNED_NORMALIZED: - return "snorm"; - case GL_UNSIGNED_NORMALIZED: - return "unorm"; - case GL_NONE: - return "none"; - default: - return "unknown"; - } -} - -bool DescribeAttachment(GLenum attachment, AttachmentDesc &desc) { - glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &desc.objectType); - if (DrainErrors() != 0 || desc.objectType == GL_NONE) { - return false; - } - - glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, - GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &desc.objectName); - glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, - GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE, &desc.componentType); - DrainErrors(); - - if (desc.objectType == GL_RENDERBUFFER) { - const GLint boundRenderbuffer = GetInteger(GL_RENDERBUFFER_BINDING); - glBindRenderbuffer(GL_RENDERBUFFER, static_cast(desc.objectName)); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &desc.width); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &desc.height); - glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &desc.internalFormat); - glBindRenderbuffer(GL_RENDERBUFFER, static_cast(boundRenderbuffer)); - } else if (desc.objectType == GL_TEXTURE) { - glGetFramebufferAttachmentParameteriv(GL_READ_FRAMEBUFFER, attachment, - GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL, &desc.level); - glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, - GL_TEXTURE_WIDTH, &desc.width); - glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, - GL_TEXTURE_HEIGHT, &desc.height); - glGetTextureLevelParameteriv(static_cast(desc.objectName), desc.level, - GL_TEXTURE_INTERNAL_FORMAT, &desc.internalFormat); - if (DrainErrors() != 0 || desc.width <= 0 || desc.height <= 0) { - // No direct-state-access level query: fall back to the classic bound query, - // which only covers GL_TEXTURE_2D but is what render targets normally are. - const GLint boundTexture = GetInteger(GL_TEXTURE_BINDING_2D); - glBindTexture(GL_TEXTURE_2D, static_cast(desc.objectName)); - glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_WIDTH, &desc.width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_HEIGHT, &desc.height); - glGetTexLevelParameteriv(GL_TEXTURE_2D, desc.level, GL_TEXTURE_INTERNAL_FORMAT, - &desc.internalFormat); - glBindTexture(GL_TEXTURE_2D, static_cast(boundTexture)); - } - } - - DrainErrors(); - return desc.width > 0 && desc.height > 0; -} - -bool DescribeTexture2D(GLuint texture, GLint level, AttachmentDesc &desc) { - const GLint savedTexture = GetInteger(GL_TEXTURE_BINDING_2D); - glBindTexture(GL_TEXTURE_2D, texture); - desc.objectType = GL_TEXTURE; - desc.objectName = static_cast(texture); - desc.level = level; - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &desc.width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_HEIGHT, &desc.height); - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_INTERNAL_FORMAT, &desc.internalFormat); - glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_RED_TYPE, &desc.componentType); - glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); - return DrainErrors() == 0 && desc.width > 0 && desc.height > 0; -} - -bool ReadTexture2DFloats(const AttachmentDesc &desc, std::vector &pixels) { - const std::size_t count = static_cast(desc.width) * desc.height * 4; - pixels.assign(count, 0.0f); - const GLint savedTexture = GetInteger(GL_TEXTURE_BINDING_2D); - glBindTexture(GL_TEXTURE_2D, static_cast(desc.objectName)); - if (desc.componentType == GL_INT || desc.componentType == GL_UNSIGNED_INT) { - std::vector raw(count, 0); - const GLenum type = desc.componentType == GL_INT ? GL_INT : GL_UNSIGNED_INT; - glGetTexImage(GL_TEXTURE_2D, desc.level, GL_RGBA_INTEGER, type, raw.data()); - glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); - if (DrainErrors() != 0) { - return false; - } - for (std::size_t i = 0; i < count; ++i) { - pixels[i] = desc.componentType == GL_INT - ? static_cast(raw[i]) - : static_cast(static_cast(raw[i])); - } - return true; - } - glGetTexImage(GL_TEXTURE_2D, desc.level, GL_RGBA, GL_FLOAT, pixels.data()); - glBindTexture(GL_TEXTURE_2D, static_cast(savedTexture)); - return DrainErrors() == 0; -} - -// Reads the attachment as floats regardless of its storage: normalised and float targets -// convert on the way out, integer targets are read as integers and widened. The float view -// keeps out-of-[0,1] accumulation buffers legible in the statistics even though the PNG -// itself has to clamp. -bool ReadAttachmentFloats(const AttachmentDesc &desc, bool depth, unsigned channels, - std::vector &pixels) { - const std::size_t count = static_cast(desc.width) * desc.height * channels; - pixels.assign(count, 0.0f); - - if (depth) { - glReadPixels(0, 0, desc.width, desc.height, GL_DEPTH_COMPONENT, GL_FLOAT, pixels.data()); - return DrainErrors() == 0; - } - - if (desc.componentType == GL_INT || desc.componentType == GL_UNSIGNED_INT) { - std::vector raw(count, 0); - const GLenum type = desc.componentType == GL_INT ? GL_INT : GL_UNSIGNED_INT; - glReadPixels(0, 0, desc.width, desc.height, GL_RGBA_INTEGER, type, raw.data()); - if (DrainErrors() != 0) { - return false; - } - for (std::size_t i = 0; i < count; ++i) { - pixels[i] = desc.componentType == GL_INT - ? static_cast(raw[i]) - : static_cast(static_cast(raw[i])); - } - return true; - } - - glReadPixels(0, 0, desc.width, desc.height, GL_RGBA, GL_FLOAT, pixels.data()); - return DrainErrors() == 0; -} - -std::string FormatStatistics(const std::vector &pixels, unsigned channels) { - float minimum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - float maximum[4] = {0.0f, 0.0f, 0.0f, 0.0f}; - double total[4] = {0.0, 0.0, 0.0, 0.0}; - bool seeded[4] = {false, false, false, false}; - std::uint64_t hash = 1469598103934665603ull; - std::size_t nonFinite = 0; - - const std::size_t pixelCount = channels == 0 ? 0 : pixels.size() / channels; - for (std::size_t p = 0; p < pixelCount; ++p) { - for (unsigned c = 0; c < channels; ++c) { - const float value = pixels[p * channels + c]; - if (!std::isfinite(value)) { - ++nonFinite; - continue; + class DumpingDumper final : public retrace::Dumper { + public: + int getSnapshotCount(void) override { + RunPendingDumps(); + return gInnerDumper->getSnapshotCount(); } - if (!seeded[c] || value < minimum[c]) { - minimum[c] = value; + + image::Image* getSnapshot(int n, bool backBuffer) override { + return gInnerDumper->getSnapshot(n, backBuffer); } - if (!seeded[c] || value > maximum[c]) { - maximum[c] = value; - } - seeded[c] = true; - total[c] += value; - } - } - for (const float value : pixels) { - std::uint32_t bits = 0; - std::memcpy(&bits, &value, sizeof(bits)); - hash = (hash ^ bits) * 1099511628211ull; - } - char buffer[512]; - std::string text; - for (unsigned c = 0; c < channels; ++c) { - const double mean = pixelCount == 0 ? 0.0 : total[c] / static_cast(pixelCount); - std::snprintf(buffer, sizeof(buffer), " c%u[min=%.6g max=%.6g mean=%.6g]", c, - static_cast(minimum[c]), static_cast(maximum[c]), mean); - text += buffer; - } - if (pixelCount > 0) { - text += " first=("; - for (unsigned c = 0; c < channels; ++c) { - if (c > 0) { - text += ","; - } - std::snprintf(buffer, sizeof(buffer), "%.9g", static_cast(pixels[c])); - text += buffer; - } - text += ")"; - } - std::snprintf(buffer, sizeof(buffer), " nonfinite=%zu hash=%016llx", nonFinite, - static_cast(hash)); - text += buffer; - return text; -} + bool canDump(void) override { return gInnerDumper->canDump(); } -bool WriteFloatPng(const std::string &path, const AttachmentDesc &desc, unsigned channels, - const std::vector &pixels) { - image::Image snapshot(static_cast(desc.width), static_cast(desc.height), - channels, true, image::TYPE_FLOAT); - if (snapshot.sizeInBytes() != pixels.size() * sizeof(float)) { - return false; - } - std::memcpy(snapshot.pixels, pixels.data(), pixels.size() * sizeof(float)); - return snapshot.writePNG(path.c_str()); -} + void dumpState(StateWriter& writer) override { gInnerDumper->dumpState(writer); } + }; -void DumpOneAttachment(std::ofstream &manifest, const std::string &directory, - const std::string &identity, GLenum attachment, const char *label, bool depth) { - AttachmentDesc desc; - if (!DescribeAttachment(attachment, desc)) { - return; - } + DumpingDumper gDumpingDumper; - const unsigned channels = depth ? 1u : 4u; - if (!depth) { - glReadBuffer(attachment); - if (DrainErrors() != 0) { + } // namespace + + void InstallIfRequested() { + if (gInstalled) { return; } - } - - std::vector pixels; - const bool read = ReadAttachmentFloats(desc, depth, channels, pixels); - - const std::string path = directory + "/" + identity + "-" + label + ".png"; - const bool wrote = read && WriteFloatPng(path, desc, channels, pixels); - - char header[512]; - std::snprintf(header, sizeof(header), - "%s %s object=%s name=%d level=%d size=%dx%d internalformat=0x%04x component=%s", - identity.c_str(), label, - desc.objectType == GL_RENDERBUFFER ? "renderbuffer" : "texture", desc.objectName, - desc.level, desc.width, desc.height, static_cast(desc.internalFormat), - ComponentTypeName(desc.componentType)); - manifest << header; - if (read) { - manifest << FormatStatistics(pixels, channels); - } else { - manifest << " read=failed"; - } - if (!wrote) { - manifest << " png=failed"; - } - manifest << "\n"; -} - -void DumpFramebuffer(std::ofstream &manifest, const std::string &directory, unsigned framebuffer, - GLint maxColorAttachments) { - if (framebuffer == 0) { - // The default framebuffer names its attachments GL_BACK_LEFT rather than - // GL_COLOR_ATTACHMENT0, and the replay already snapshots it into actual..png. - manifest << "fbo 0 skipped=default-framebuffer\n"; - return; - } - - glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); - if (DrainErrors() != 0) { - return; - } - - const GLint savedReadBuffer = GetInteger(GL_READ_BUFFER); - const std::string identity = "fbo" + std::to_string(framebuffer); - for (GLint index = 0; index < maxColorAttachments; ++index) { - char label[32]; - std::snprintf(label, sizeof(label), "att%d", index); - DumpOneAttachment(manifest, directory, identity, - static_cast(GL_COLOR_ATTACHMENT0 + index), label, false); - } - DumpOneAttachment(manifest, directory, identity, GL_DEPTH_ATTACHMENT, "depth", true); - - // The read buffer is per-framebuffer state the trace goes on using; put it back. - if (framebuffer != 0 && savedReadBuffer != 0) { - glReadBuffer(static_cast(savedReadBuffer)); - DrainErrors(); - } -} - -void RunDumpPoint(DumpPoint &point) { - if (!MakeDirectories(point.directory)) { - std::cerr << "warning: failed to create FBO dump directory " << point.directory << "\n"; - point.done = true; - return; - } - - // Start from a clean error state so a failure reported below is one we caused. - DrainErrors(); - - // Everything below perturbs read-side and pack state; snapshot it so the replay - // continues from where it was. - const GLint savedReadFramebuffer = GetInteger(GL_READ_FRAMEBUFFER_BINDING); - const GLint savedPackBuffer = GetInteger(GL_PIXEL_PACK_BUFFER_BINDING); - const GLint savedPackAlignment = GetInteger(GL_PACK_ALIGNMENT); - const GLint savedPackRowLength = GetInteger(GL_PACK_ROW_LENGTH); - const GLint savedPackSkipPixels = GetInteger(GL_PACK_SKIP_PIXELS); - const GLint savedPackSkipRows = GetInteger(GL_PACK_SKIP_ROWS); - const GLint savedPackImageHeight = GetInteger(GL_PACK_IMAGE_HEIGHT); - const GLint savedPackSkipImages = GetInteger(GL_PACK_SKIP_IMAGES); - const GLint savedPackSwapBytes = GetInteger(GL_PACK_SWAP_BYTES); - DrainErrors(); - - if (savedPackBuffer != 0) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - } - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glPixelStorei(GL_PACK_SKIP_PIXELS, 0); - glPixelStorei(GL_PACK_SKIP_ROWS, 0); - glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); - glPixelStorei(GL_PACK_SKIP_IMAGES, 0); - glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE); - DrainErrors(); - - const GLint maxColorAttachments = GetInteger(GL_MAX_COLOR_ATTACHMENTS); - DrainErrors(); - - std::vector framebuffers = point.framebuffers; - if (framebuffers.empty()) { - const unsigned limit = ScanLimit(); - for (unsigned name = 1; name <= limit; ++name) { - if (glIsFramebuffer(name) == GL_TRUE) { - framebuffers.push_back(name); + if (!gConfigured) { + gConfigured = true; + const char* spec = std::getenv(kDumpPointsEnv); + if (spec != nullptr && spec[0] != '\0') { + ParseDumpPoints(spec); + } + const char* textureSpec = std::getenv(kTexture2dDumpPointsEnv); + if (textureSpec != nullptr && textureSpec[0] != '\0') { + ParseTexture2dDumpPoints(textureSpec); } } - DrainErrors(); - } - - const std::string manifestPath = point.directory + "/manifest.txt"; - std::ofstream manifest(manifestPath, std::ios::trunc); - manifest << "call " << retrace::callNo << " framebuffers " << framebuffers.size() - << " maxcolorattachments " << maxColorAttachments << "\n"; - for (const unsigned framebuffer : framebuffers) { - DumpFramebuffer(manifest, point.directory, framebuffer, maxColorAttachments); - } - manifest.flush(); - - glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(savedReadFramebuffer)); - if (savedPackBuffer != 0) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(savedPackBuffer)); - } - glPixelStorei(GL_PACK_ALIGNMENT, savedPackAlignment); - glPixelStorei(GL_PACK_ROW_LENGTH, savedPackRowLength); - glPixelStorei(GL_PACK_SKIP_PIXELS, savedPackSkipPixels); - glPixelStorei(GL_PACK_SKIP_ROWS, savedPackSkipRows); - glPixelStorei(GL_PACK_IMAGE_HEIGHT, savedPackImageHeight); - glPixelStorei(GL_PACK_SKIP_IMAGES, savedPackSkipImages); - glPixelStorei(GL_PACK_SWAP_BYTES, savedPackSwapBytes); - DrainErrors(); - - std::cerr << "MOBILEGL_TRACE_FBO_DUMP: call " << retrace::callNo << " -> " << manifestPath - << " (" << framebuffers.size() << " framebuffers)\n"; - point.done = true; -} - -void RunTexture2dDumpPoint(Texture2dDumpPoint &point) { - if (!MakeDirectories(point.directory)) { - std::cerr << "warning: failed to create texture dump directory " << point.directory << "\n"; - point.done = true; - return; - } - - // glGetError is destructive. This debug-only snapshot hook deliberately starts from a - // clean error state so diagnostics below identify the dump rather than an earlier trace call. - DrainErrors(); - const GLint savedReadFramebuffer = GetInteger(GL_READ_FRAMEBUFFER_BINDING); - const GLint savedPackBuffer = GetInteger(GL_PIXEL_PACK_BUFFER_BINDING); - const GLint savedPackAlignment = GetInteger(GL_PACK_ALIGNMENT); - const GLint savedPackRowLength = GetInteger(GL_PACK_ROW_LENGTH); - const GLint savedPackSkipPixels = GetInteger(GL_PACK_SKIP_PIXELS); - const GLint savedPackSkipRows = GetInteger(GL_PACK_SKIP_ROWS); - const GLint savedPackImageHeight = GetInteger(GL_PACK_IMAGE_HEIGHT); - const GLint savedPackSkipImages = GetInteger(GL_PACK_SKIP_IMAGES); - const GLint savedPackSwapBytes = GetInteger(GL_PACK_SWAP_BYTES); - DrainErrors(); - - if (savedPackBuffer != 0) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); - } - glPixelStorei(GL_PACK_ALIGNMENT, 1); - glPixelStorei(GL_PACK_ROW_LENGTH, 0); - glPixelStorei(GL_PACK_SKIP_PIXELS, 0); - glPixelStorei(GL_PACK_SKIP_ROWS, 0); - glPixelStorei(GL_PACK_IMAGE_HEIGHT, 0); - glPixelStorei(GL_PACK_SKIP_IMAGES, 0); - glPixelStorei(GL_PACK_SWAP_BYTES, GL_FALSE); - DrainErrors(); - - const std::string identity = "texture" + std::to_string(point.texture) + - "-level" + std::to_string(point.level); - const std::string manifestPath = point.directory + "/manifest.txt"; - std::ofstream manifest(manifestPath, std::ios::trunc); - manifest << "call " << retrace::callNo << " texture " << point.texture << " level " - << point.level << "\n"; - AttachmentDesc desc; - if (glIsTexture(point.texture) == GL_FALSE || - !DescribeTexture2D(point.texture, static_cast(point.level), desc)) { - manifest << identity << " skipped=not-live-2d-texture\n"; - } else { - std::vector pixels; - const bool read = ReadTexture2DFloats(desc, pixels); - const bool wrote = read && WriteFloatPng(point.directory + "/" + identity + ".png", desc, 4, pixels); - manifest << identity << " object=texture name=" << desc.objectName << " level=" << desc.level - << " size=" << desc.width << "x" << desc.height << " internalformat=0x" << std::hex - << static_cast(desc.internalFormat) << std::dec - << " component=" << ComponentTypeName(desc.componentType); - if (read) { - manifest << FormatStatistics(pixels, 4); - } else { - manifest << " read=failed"; + if (gDumpPoints.empty() && gTexture2dDumpPoints.empty()) { + gInstalled = true; + return; } - if (!wrote) { - manifest << " png=failed"; + if (retrace::dumper == nullptr || retrace::dumper == &gDumpingDumper) { + return; } - manifest << "\n"; - } - manifest.flush(); - glBindFramebuffer(GL_READ_FRAMEBUFFER, static_cast(savedReadFramebuffer)); - if (savedPackBuffer != 0) { - glBindBuffer(GL_PIXEL_PACK_BUFFER, static_cast(savedPackBuffer)); - } - glPixelStorei(GL_PACK_ALIGNMENT, savedPackAlignment); - glPixelStorei(GL_PACK_ROW_LENGTH, savedPackRowLength); - glPixelStorei(GL_PACK_SKIP_PIXELS, savedPackSkipPixels); - glPixelStorei(GL_PACK_SKIP_ROWS, savedPackSkipRows); - glPixelStorei(GL_PACK_IMAGE_HEIGHT, savedPackImageHeight); - glPixelStorei(GL_PACK_SKIP_IMAGES, savedPackSkipImages); - glPixelStorei(GL_PACK_SWAP_BYTES, savedPackSwapBytes); - DrainErrors(); - - std::cerr << "MOBILEGL_TRACE_TEXTURE_2D_DUMP: call " << retrace::callNo << " texture " - << point.texture << " level " << point.level << " -> " << manifestPath << "\n"; - point.done = true; -} - -void RunPendingDumps() { - for (DumpPoint &point : gDumpPoints) { - if (!point.done && point.call == retrace::callNo) { - RunDumpPoint(point); - } - } - for (Texture2dDumpPoint &point : gTexture2dDumpPoints) { - if (!point.done && point.call == retrace::callNo) { - RunTexture2dDumpPoint(point); - } - } -} - -class DumpingDumper final : public retrace::Dumper { -public: - int getSnapshotCount(void) override { - RunPendingDumps(); - return gInnerDumper->getSnapshotCount(); - } - - image::Image *getSnapshot(int n, bool backBuffer) override { - return gInnerDumper->getSnapshot(n, backBuffer); - } - - bool canDump(void) override { - return gInnerDumper->canDump(); - } - - void dumpState(StateWriter &writer) override { - gInnerDumper->dumpState(writer); - } -}; - -DumpingDumper gDumpingDumper; - -} // namespace - -void InstallIfRequested() { - if (gInstalled) { - return; - } - if (!gConfigured) { - gConfigured = true; - const char *spec = std::getenv(kDumpPointsEnv); - if (spec != nullptr && spec[0] != '\0') { - ParseDumpPoints(spec); - } - const char *textureSpec = std::getenv(kTexture2dDumpPointsEnv); - if (textureSpec != nullptr && textureSpec[0] != '\0') { - ParseTexture2dDumpPoints(textureSpec); - } - } - if (gDumpPoints.empty() && gTexture2dDumpPoints.empty()) { + ResolveDirectEntryPoints(); + gInnerDumper = retrace::dumper; + retrace::dumper = &gDumpingDumper; gInstalled = true; - return; + for (const DumpPoint& point : gDumpPoints) { + std::cerr << "MOBILEGL_TRACE_FBO_DUMP: armed for call " << point.call << " -> " << point.directory << "\n"; + } + for (const Texture2dDumpPoint& point : gTexture2dDumpPoints) { + std::cerr << "MOBILEGL_TRACE_TEXTURE_2D_DUMP: armed for call " << point.call << " texture " << point.texture + << " level " << point.level << " -> " << point.directory << "\n"; + } } - if (retrace::dumper == nullptr || retrace::dumper == &gDumpingDumper) { - return; - } - - ResolveDirectEntryPoints(); - gInnerDumper = retrace::dumper; - retrace::dumper = &gDumpingDumper; - gInstalled = true; - for (const DumpPoint &point : gDumpPoints) { - std::cerr << "MOBILEGL_TRACE_FBO_DUMP: armed for call " << point.call << " -> " - << point.directory << "\n"; - } - for (const Texture2dDumpPoint &point : gTexture2dDumpPoints) { - std::cerr << "MOBILEGL_TRACE_TEXTURE_2D_DUMP: armed for call " << point.call - << " texture " << point.texture << " level " << point.level << " -> " - << point.directory << "\n"; - } -} } // namespace mobilegl_trace_dump diff --git a/tools/trace_replay/apitrace_fbo_dump.hpp b/tools/trace_replay/apitrace_fbo_dump.hpp index 5b963884..83ab59b0 100644 --- a/tools/trace_replay/apitrace_fbo_dump.hpp +++ b/tools/trace_replay/apitrace_fbo_dump.hpp @@ -2,10 +2,10 @@ namespace mobilegl_trace_dump { -// Installs the opt-in framebuffer-attachment and named-2D-texture dump hooks. The respective -// environments are MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS and MOBILEGL_TRACE_DUMP_TEXTURE_2D. -// Safe and cheap to call on every makeCurrent: the environment is consulted once and the hook is -// installed at most once. -void InstallIfRequested(); + // Installs the opt-in framebuffer-attachment and named-2D-texture dump hooks. The respective + // environments are MOBILEGL_TRACE_DUMP_FBO_ATTACHMENTS and MOBILEGL_TRACE_DUMP_TEXTURE_2D. + // Safe and cheap to call on every makeCurrent: the environment is consulted once and the hook is + // installed at most once. + void InstallIfRequested(); } // namespace mobilegl_trace_dump diff --git a/tools/trace_replay/apitrace_glproc_mobilegl.cpp b/tools/trace_replay/apitrace_glproc_mobilegl.cpp index 0d6e0e0f..d44c9206 100644 --- a/tools/trace_replay/apitrace_glproc_mobilegl.cpp +++ b/tools/trace_replay/apitrace_glproc_mobilegl.cpp @@ -16,136 +16,136 @@ #include #include -void *_libGlHandle = nullptr; +void* _libGlHandle = nullptr; namespace { -void *LookupSymbol(const char *procName); + void* LookupSymbol(const char* procName); -void *GetMobileGlHandle() { - if (_libGlHandle != nullptr) { + void* GetMobileGlHandle() { + if (_libGlHandle != nullptr) { + return _libGlHandle; + } + + const char* library = std::getenv("MOBILEGL_TRACE_LIBRARY"); + if (library != nullptr && library[0] != '\0') { + _libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + if (_libGlHandle == nullptr) { + _libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL); + } + } + if (_libGlHandle == nullptr) { + _libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + } + if (_libGlHandle == nullptr) { + _libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL); + } return _libGlHandle; } - const char *library = std::getenv("MOBILEGL_TRACE_LIBRARY"); - if (library != nullptr && library[0] != '\0') { - _libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); - if (_libGlHandle == nullptr) { - _libGlHandle = dlopen(library, RTLD_NOW | RTLD_GLOBAL); + using GlReadBuffer = void (*)(GLenum mode); + using GlReadPixels = void (*)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + void* pixels); + using GlGetIntegerv = void (*)(GLenum pname, GLint* data); + using GlGetError = GLenum (*)(); + GlReadBuffer gRealGlReadBuffer = nullptr; + GlReadPixels gRealGlReadPixels = nullptr; + GlGetIntegerv gRealGlGetIntegerv = nullptr; + GlGetError gRealGlGetError = nullptr; + int gSuppressInvalidEnumCount = 0; + + void MobileGLTraceReadBuffer(GLenum mode) { + if (mode == GL_BACK) { + ++gSuppressInvalidEnumCount; + return; } - } - if (_libGlHandle == nullptr) { - _libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); - } - if (_libGlHandle == nullptr) { - _libGlHandle = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL); - } - return _libGlHandle; -} - -using GlReadBuffer = void (*)(GLenum mode); -using GlReadPixels = void (*)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - void *pixels); -using GlGetIntegerv = void (*)(GLenum pname, GLint *data); -using GlGetError = GLenum (*)(); -GlReadBuffer gRealGlReadBuffer = nullptr; -GlReadPixels gRealGlReadPixels = nullptr; -GlGetIntegerv gRealGlGetIntegerv = nullptr; -GlGetError gRealGlGetError = nullptr; -int gSuppressInvalidEnumCount = 0; - -void MobileGLTraceReadBuffer(GLenum mode) { - if (mode == GL_BACK) { - ++gSuppressInvalidEnumCount; - return; - } - if (gRealGlReadBuffer == nullptr) { - gRealGlReadBuffer = reinterpret_cast(LookupSymbol("glReadBuffer")); - } - if (gRealGlReadBuffer != nullptr) { - gRealGlReadBuffer(mode); - } -} - -void MobileGLTraceReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, - void *pixels) { - if (gRealGlReadPixels == nullptr) { - gRealGlReadPixels = reinterpret_cast(LookupSymbol("glReadPixels")); - } - if (gRealGlReadPixels != nullptr) { - gRealGlReadPixels(x, y, width, height, format, type, pixels); - } -} - -void MobileGLTraceGetIntegerv(GLenum pname, GLint *data) { - if (pname == GL_READ_BUFFER) { - if (data != nullptr) { - *data = GL_BACK; + if (gRealGlReadBuffer == nullptr) { + gRealGlReadBuffer = reinterpret_cast(LookupSymbol("glReadBuffer")); } - ++gSuppressInvalidEnumCount; - return; - } - if (gRealGlGetIntegerv == nullptr) { - gRealGlGetIntegerv = reinterpret_cast(LookupSymbol("glGetIntegerv")); - } - if (gRealGlGetIntegerv != nullptr) { - gRealGlGetIntegerv(pname, data); - } -} - -GLenum MobileGLTraceGetError() { - if (gRealGlGetError == nullptr) { - gRealGlGetError = reinterpret_cast(LookupSymbol("glGetError")); - } - if (gRealGlGetError == nullptr) { - return GL_NO_ERROR; - } - GLenum error = gRealGlGetError(); - if (error == GL_INVALID_ENUM && gSuppressInvalidEnumCount > 0) { - --gSuppressInvalidEnumCount; - return GL_NO_ERROR; - } - return error; -} - -void *LookupSymbol(const char *procName) { - void *mobileGl = GetMobileGlHandle(); - if (mobileGl != nullptr) { - void *proc = dlsym(mobileGl, procName); - if (proc != nullptr) { - return proc; + if (gRealGlReadBuffer != nullptr) { + gRealGlReadBuffer(mode); } } - return dlsym(RTLD_DEFAULT, procName); -} + void MobileGLTraceReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, + void* pixels) { + if (gRealGlReadPixels == nullptr) { + gRealGlReadPixels = reinterpret_cast(LookupSymbol("glReadPixels")); + } + if (gRealGlReadPixels != nullptr) { + gRealGlReadPixels(x, y, width, height, format, type, pixels); + } + } + + void MobileGLTraceGetIntegerv(GLenum pname, GLint* data) { + if (pname == GL_READ_BUFFER) { + if (data != nullptr) { + *data = GL_BACK; + } + ++gSuppressInvalidEnumCount; + return; + } + if (gRealGlGetIntegerv == nullptr) { + gRealGlGetIntegerv = reinterpret_cast(LookupSymbol("glGetIntegerv")); + } + if (gRealGlGetIntegerv != nullptr) { + gRealGlGetIntegerv(pname, data); + } + } + + GLenum MobileGLTraceGetError() { + if (gRealGlGetError == nullptr) { + gRealGlGetError = reinterpret_cast(LookupSymbol("glGetError")); + } + if (gRealGlGetError == nullptr) { + return GL_NO_ERROR; + } + GLenum error = gRealGlGetError(); + if (error == GL_INVALID_ENUM && gSuppressInvalidEnumCount > 0) { + --gSuppressInvalidEnumCount; + return GL_NO_ERROR; + } + return error; + } + + void* LookupSymbol(const char* procName) { + void* mobileGl = GetMobileGlHandle(); + if (mobileGl != nullptr) { + void* proc = dlsym(mobileGl, procName); + if (proc != nullptr) { + return proc; + } + } + + return dlsym(RTLD_DEFAULT, procName); + } } // namespace -void *_getPublicProcAddress(const char *procName) { +void* _getPublicProcAddress(const char* procName) { if (strcmp(procName, "glReadPixels") == 0) { - return reinterpret_cast(&MobileGLTraceReadPixels); + return reinterpret_cast(&MobileGLTraceReadPixels); } if (strcmp(procName, "glReadBuffer") == 0) { - return reinterpret_cast(&MobileGLTraceReadBuffer); + return reinterpret_cast(&MobileGLTraceReadBuffer); } if (strcmp(procName, "glGetIntegerv") == 0) { - return reinterpret_cast(&MobileGLTraceGetIntegerv); + return reinterpret_cast(&MobileGLTraceGetIntegerv); } if (strcmp(procName, "glGetError") == 0) { - return reinterpret_cast(&MobileGLTraceGetError); + return reinterpret_cast(&MobileGLTraceGetError); } return LookupSymbol(procName); } -void *_getPrivateProcAddress(const char *procName) { - void *proc = LookupSymbol(procName); +void* _getPrivateProcAddress(const char* procName) { + void* proc = LookupSymbol(procName); if (proc != nullptr) { return proc; } if (_eglGetProcAddress != nullptr) { - return reinterpret_cast(_eglGetProcAddress(procName)); + return reinterpret_cast(_eglGetProcAddress(procName)); } return nullptr; diff --git a/tools/trace_replay/apitrace_glws_egl.cpp b/tools/trace_replay/apitrace_glws_egl.cpp index a607d43e..24247686 100644 --- a/tools/trace_replay/apitrace_glws_egl.cpp +++ b/tools/trace_replay/apitrace_glws_egl.cpp @@ -24,671 +24,664 @@ namespace { -using PfnEglBindApi = EGLBoolean (*)(EGLenum); -using PfnEglChooseConfig = EGLBoolean (*)(EGLDisplay, const EGLint *, EGLConfig *, EGLint, EGLint *); -using PfnEglCreateContext = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint *); -using PfnEglCreatePbufferSurface = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint *); -using PfnEglCreateWindowSurface = EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint *); -using PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext); -using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface); -using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint *); -using PfnEglGetDisplay = EGLDisplay (*)(EGLNativeDisplayType); -using PfnEglGetError = EGLint (*)(); -using PfnEglInitialize = EGLBoolean (*)(EGLDisplay, EGLint *, EGLint *); -using PfnEglMakeCurrent = EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext); -using PfnEglQueryString = const char *(*)(EGLDisplay, EGLint); -using PfnEglSwapBuffers = EGLBoolean (*)(EGLDisplay, EGLSurface); -using PfnEglTerminate = EGLBoolean (*)(EGLDisplay); -using PfnGlGetString = const unsigned char *(*)(unsigned int); + using PfnEglBindApi = EGLBoolean (*)(EGLenum); + using PfnEglChooseConfig = EGLBoolean (*)(EGLDisplay, const EGLint*, EGLConfig*, EGLint, EGLint*); + using PfnEglCreateContext = EGLContext (*)(EGLDisplay, EGLConfig, EGLContext, const EGLint*); + using PfnEglCreatePbufferSurface = EGLSurface (*)(EGLDisplay, EGLConfig, const EGLint*); + using PfnEglCreateWindowSurface = EGLSurface (*)(EGLDisplay, EGLConfig, EGLNativeWindowType, const EGLint*); + using PfnEglDestroyContext = EGLBoolean (*)(EGLDisplay, EGLContext); + using PfnEglDestroySurface = EGLBoolean (*)(EGLDisplay, EGLSurface); + using PfnEglGetConfigAttrib = EGLBoolean (*)(EGLDisplay, EGLConfig, EGLint, EGLint*); + using PfnEglGetDisplay = EGLDisplay (*)(EGLNativeDisplayType); + using PfnEglGetError = EGLint (*)(); + using PfnEglInitialize = EGLBoolean (*)(EGLDisplay, EGLint*, EGLint*); + using PfnEglMakeCurrent = EGLBoolean (*)(EGLDisplay, EGLSurface, EGLSurface, EGLContext); + using PfnEglQueryString = const char* (*)(EGLDisplay, EGLint); + using PfnEglSwapBuffers = EGLBoolean (*)(EGLDisplay, EGLSurface); + using PfnEglTerminate = EGLBoolean (*)(EGLDisplay); + using PfnGlGetString = const unsigned char* (*)(unsigned int); -struct EglFns { - PfnEglBindApi bindApi = nullptr; - PfnEglChooseConfig chooseConfig = nullptr; - PfnEglCreateContext createContext = nullptr; - PfnEglCreatePbufferSurface createPbufferSurface = nullptr; - PfnEglCreateWindowSurface createWindowSurface = nullptr; - PfnEglDestroyContext destroyContext = nullptr; - PfnEglDestroySurface destroySurface = nullptr; - PfnEglGetConfigAttrib getConfigAttrib = nullptr; - PfnEglGetDisplay getDisplay = nullptr; - PfnEglGetError getError = nullptr; - PfnEglInitialize initialize = nullptr; - PfnEglMakeCurrent makeCurrent = nullptr; - PfnEglQueryString queryString = nullptr; - PfnEglSwapBuffers swapBuffers = nullptr; - PfnEglTerminate terminate = nullptr; -}; + struct EglFns { + PfnEglBindApi bindApi = nullptr; + PfnEglChooseConfig chooseConfig = nullptr; + PfnEglCreateContext createContext = nullptr; + PfnEglCreatePbufferSurface createPbufferSurface = nullptr; + PfnEglCreateWindowSurface createWindowSurface = nullptr; + PfnEglDestroyContext destroyContext = nullptr; + PfnEglDestroySurface destroySurface = nullptr; + PfnEglGetConfigAttrib getConfigAttrib = nullptr; + PfnEglGetDisplay getDisplay = nullptr; + PfnEglGetError getError = nullptr; + PfnEglInitialize initialize = nullptr; + PfnEglMakeCurrent makeCurrent = nullptr; + PfnEglQueryString queryString = nullptr; + PfnEglSwapBuffers swapBuffers = nullptr; + PfnEglTerminate terminate = nullptr; + }; -EglFns gEgl; -EGLDisplay gDisplay = EGL_NO_DISPLAY; -void *gMobileGl = nullptr; -const glws::Drawable *gCurrentDrawable = nullptr; -EGLContext gCurrentContext = EGL_NO_CONTEXT; -int gRequestedWidth = 0; -int gRequestedHeight = 0; -bool gPrintedGlIdentity = false; + EglFns gEgl; + EGLDisplay gDisplay = EGL_NO_DISPLAY; + void* gMobileGl = nullptr; + const glws::Drawable* gCurrentDrawable = nullptr; + EGLContext gCurrentContext = EGL_NO_CONTEXT; + int gRequestedWidth = 0; + int gRequestedHeight = 0; + bool gPrintedGlIdentity = false; #if defined(__APPLE__) -constexpr unsigned long kNSWindowStyleMaskTitled = 1ul << 0; -constexpr unsigned long kNSWindowStyleMaskClosable = 1ul << 1; -constexpr unsigned long kNSWindowStyleMaskMiniaturizable = 1ul << 2; -constexpr unsigned long kNSWindowStyleMaskResizable = 1ul << 3; -constexpr unsigned long kNSBackingStoreBuffered = 2; -constexpr long kNSApplicationActivationPolicyRegular = 0; -constexpr unsigned long long kNSEventMaskAny = ~0ull; + constexpr unsigned long kNSWindowStyleMaskTitled = 1ul << 0; + constexpr unsigned long kNSWindowStyleMaskClosable = 1ul << 1; + constexpr unsigned long kNSWindowStyleMaskMiniaturizable = 1ul << 2; + constexpr unsigned long kNSWindowStyleMaskResizable = 1ul << 3; + constexpr unsigned long kNSBackingStoreBuffered = 2; + constexpr long kNSApplicationActivationPolicyRegular = 0; + constexpr unsigned long long kNSEventMaskAny = ~0ull; -template -Fn ObjcMsgSend() { - return reinterpret_cast(objc_msgSend); -} - -id SendId(id receiver, const char *selector) { - return ObjcMsgSend()(receiver, sel_registerName(selector)); -} - -id NSStringFromUtf8(const char *value) { - id stringClass = reinterpret_cast(objc_getClass("NSString")); - return ObjcMsgSend()( - stringClass, sel_registerName("stringWithUTF8String:"), value); -} - -void SendVoid(id receiver, const char *selector) { - ObjcMsgSend()(receiver, sel_registerName(selector)); -} - -void SendVoidId(id receiver, const char *selector, id value) { - ObjcMsgSend()(receiver, sel_registerName(selector), value); -} - -void SendVoidBool(id receiver, const char *selector, bool value) { - ObjcMsgSend()(receiver, sel_registerName(selector), value); -} - -void SendVoidLong(id receiver, const char *selector, long value) { - ObjcMsgSend()(receiver, sel_registerName(selector), value); -} - -void SendVoidCGRect(id receiver, const char *selector, CGRect value) { - ObjcMsgSend()(receiver, sel_registerName(selector), value); -} - -void SendVoidCGSize(id receiver, const char *selector, CGSize value) { - ObjcMsgSend()(receiver, sel_registerName(selector), value); -} - -id Retain(id object) { - return object ? SendId(object, "retain") : nil; -} - -void Release(id object) { - if (object) { - SendVoid(object, "release"); + template + Fn ObjcMsgSend() { + return reinterpret_cast(objc_msgSend); } -} -id SharedApplication() { - id appClass = reinterpret_cast(objc_getClass("NSApplication")); - return appClass ? SendId(appClass, "sharedApplication") : nil; -} - -void PumpMacOSEvents() { - id app = SharedApplication(); - if (!app) { - return; + id SendId(id receiver, const char* selector) { + return ObjcMsgSend()(receiver, sel_registerName(selector)); } - id distantPast = SendId(reinterpret_cast(objc_getClass("NSDate")), "distantPast"); - id stringClass = reinterpret_cast(objc_getClass("NSString")); - id defaultRunLoopMode = ObjcMsgSend()( - stringClass, sel_registerName("stringWithUTF8String:"), "kCFRunLoopDefaultMode"); - while (true) { - id event = ObjcMsgSend()( - app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), - kNSEventMaskAny, distantPast, defaultRunLoopMode, true); - if (!event) { - break; + + id NSStringFromUtf8(const char* value) { + id stringClass = reinterpret_cast(objc_getClass("NSString")); + return ObjcMsgSend()(stringClass, sel_registerName("stringWithUTF8String:"), + value); + } + + void SendVoid(id receiver, const char* selector) { + ObjcMsgSend()(receiver, sel_registerName(selector)); + } + + void SendVoidId(id receiver, const char* selector, id value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidBool(id receiver, const char* selector, bool value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidLong(id receiver, const char* selector, long value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidCGRect(id receiver, const char* selector, CGRect value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + void SendVoidCGSize(id receiver, const char* selector, CGSize value) { + ObjcMsgSend()(receiver, sel_registerName(selector), value); + } + + id Retain(id object) { + return object ? SendId(object, "retain") : nil; + } + + void Release(id object) { + if (object) { + SendVoid(object, "release"); } - SendVoidId(app, "sendEvent:", event); - } - SendVoid(app, "updateWindows"); -} - -extern "C" void mobilegl_trace_pump_events() { - PumpMacOSEvents(); -} - -void EnsureApplicationActive() { - id app = SharedApplication(); - if (!app) { - return; - } - SendVoid(app, "finishLaunching"); - SendVoidLong(app, "setActivationPolicy:", kNSApplicationActivationPolicyRegular); - SendVoidId(app, "unhide:", nil); - SendVoidBool(app, "activateIgnoringOtherApps:", true); -} - -id CreateMetalWindow(int width, int height, id *outLayer) { - EnsureApplicationActive(); - id windowClass = reinterpret_cast(objc_getClass("NSWindow")); - id metalLayerClass = reinterpret_cast(objc_getClass("CAMetalLayer")); - if (!windowClass || !metalLayerClass) { - std::cerr << "error: failed to resolve NSWindow/CAMetalLayer\n"; - return nil; } - const auto surfaceWidth = static_cast(std::max(width, 1)); - const auto surfaceHeight = static_cast(std::max(height, 1)); - const CGRect frame = {{80.0, 80.0}, {surfaceWidth, surfaceHeight}}; - constexpr unsigned long style = kNSWindowStyleMaskTitled | kNSWindowStyleMaskClosable | - kNSWindowStyleMaskMiniaturizable | kNSWindowStyleMaskResizable; - id window = SendId(windowClass, "alloc"); - window = ObjcMsgSend()( - window, sel_registerName("initWithContentRect:styleMask:backing:defer:"), - frame, style, kNSBackingStoreBuffered, false); - if (!window) { - std::cerr << "error: failed to create NSWindow\n"; - return nil; + id SharedApplication() { + id appClass = reinterpret_cast(objc_getClass("NSApplication")); + return appClass ? SendId(appClass, "sharedApplication") : nil; } - SendVoidId(window, "setTitle:", NSStringFromUtf8("MobileGL Trace Replay")); - SendVoidBool(window, "setReleasedWhenClosed:", false); - id contentView = SendId(window, "contentView"); - id layer = SendId(metalLayerClass, "layer"); - if (!contentView || !layer) { - Release(window); - std::cerr << "error: failed to create CAMetalLayer\n"; - return nil; + void PumpMacOSEvents() { + id app = SharedApplication(); + if (!app) { + return; + } + id distantPast = SendId(reinterpret_cast(objc_getClass("NSDate")), "distantPast"); + id stringClass = reinterpret_cast(objc_getClass("NSString")); + id defaultRunLoopMode = ObjcMsgSend()( + stringClass, sel_registerName("stringWithUTF8String:"), "kCFRunLoopDefaultMode"); + while (true) { + id event = ObjcMsgSend()( + app, sel_registerName("nextEventMatchingMask:untilDate:inMode:dequeue:"), kNSEventMaskAny, distantPast, + defaultRunLoopMode, true); + if (!event) { + break; + } + SendVoidId(app, "sendEvent:", event); + } + SendVoid(app, "updateWindows"); } - Retain(layer); - SendVoidBool(contentView, "setWantsLayer:", true); - SendVoidCGRect(layer, "setFrame:", {{0.0, 0.0}, {surfaceWidth, surfaceHeight}}); - SendVoidCGSize(layer, "setDrawableSize:", {surfaceWidth, surfaceHeight}); - SendVoidId(contentView, "setLayer:", layer); - *outLayer = layer; - return window; -} + extern "C" void mobilegl_trace_pump_events() { + PumpMacOSEvents(); + } -void ShowMetalWindow(id window) { - if (!window) { - return; + void EnsureApplicationActive() { + id app = SharedApplication(); + if (!app) { + return; + } + SendVoid(app, "finishLaunching"); + SendVoidLong(app, "setActivationPolicy:", kNSApplicationActivationPolicyRegular); + SendVoidId(app, "unhide:", nil); + SendVoidBool(app, "activateIgnoringOtherApps:", true); + } + + id CreateMetalWindow(int width, int height, id* outLayer) { + EnsureApplicationActive(); + id windowClass = reinterpret_cast(objc_getClass("NSWindow")); + id metalLayerClass = reinterpret_cast(objc_getClass("CAMetalLayer")); + if (!windowClass || !metalLayerClass) { + std::cerr << "error: failed to resolve NSWindow/CAMetalLayer\n"; + return nil; + } + + const auto surfaceWidth = static_cast(std::max(width, 1)); + const auto surfaceHeight = static_cast(std::max(height, 1)); + const CGRect frame = {{80.0, 80.0}, {surfaceWidth, surfaceHeight}}; + constexpr unsigned long style = kNSWindowStyleMaskTitled | kNSWindowStyleMaskClosable | + kNSWindowStyleMaskMiniaturizable | kNSWindowStyleMaskResizable; + id window = SendId(windowClass, "alloc"); + window = ObjcMsgSend()( + window, sel_registerName("initWithContentRect:styleMask:backing:defer:"), frame, style, + kNSBackingStoreBuffered, false); + if (!window) { + std::cerr << "error: failed to create NSWindow\n"; + return nil; + } + SendVoidId(window, "setTitle:", NSStringFromUtf8("MobileGL Trace Replay")); + SendVoidBool(window, "setReleasedWhenClosed:", false); + + id contentView = SendId(window, "contentView"); + id layer = SendId(metalLayerClass, "layer"); + if (!contentView || !layer) { + Release(window); + std::cerr << "error: failed to create CAMetalLayer\n"; + return nil; + } + Retain(layer); + SendVoidBool(contentView, "setWantsLayer:", true); + SendVoidCGRect(layer, "setFrame:", {{0.0, 0.0}, {surfaceWidth, surfaceHeight}}); + SendVoidCGSize(layer, "setDrawableSize:", {surfaceWidth, surfaceHeight}); + SendVoidId(contentView, "setLayer:", layer); + + *outLayer = layer; + return window; + } + + void ShowMetalWindow(id window) { + if (!window) { + return; + } + EnsureApplicationActive(); + ObjcMsgSend()(window, sel_registerName("makeKeyAndOrderFront:"), nil); + SendVoid(window, "orderFrontRegardless"); + PumpMacOSEvents(); } - EnsureApplicationActive(); - ObjcMsgSend()(window, sel_registerName("makeKeyAndOrderFront:"), nil); - SendVoid(window, "orderFrontRegardless"); - PumpMacOSEvents(); -} #endif -void HoldAfterTargetPresent(const char *callNo) { - const char *holdMsText = std::getenv("MOBILEGL_TRACE_HOLD_MS"); - if (holdMsText == nullptr || holdMsText[0] == '\0') { - return; - } - const int holdMs = std::atoi(holdMsText); - if (holdMs <= 0) { - return; - } - const char *holdDone = std::getenv("MOBILEGL_TRACE_HOLD_DONE"); - if (holdDone != nullptr && std::strcmp(holdDone, "1") == 0) { - return; - } - const char *holdCall = std::getenv("MOBILEGL_TRACE_HOLD_CALL"); - if (holdCall != nullptr && holdCall[0] != '\0' && std::strcmp(holdCall, callNo) != 0) { - return; - } + void HoldAfterTargetPresent(const char* callNo) { + const char* holdMsText = std::getenv("MOBILEGL_TRACE_HOLD_MS"); + if (holdMsText == nullptr || holdMsText[0] == '\0') { + return; + } + const int holdMs = std::atoi(holdMsText); + if (holdMs <= 0) { + return; + } + const char* holdDone = std::getenv("MOBILEGL_TRACE_HOLD_DONE"); + if (holdDone != nullptr && std::strcmp(holdDone, "1") == 0) { + return; + } + const char* holdCall = std::getenv("MOBILEGL_TRACE_HOLD_CALL"); + if (holdCall != nullptr && holdCall[0] != '\0' && std::strcmp(holdCall, callNo) != 0) { + return; + } - const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(holdMs); - while (std::chrono::steady_clock::now() < deadline) { + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(holdMs); + while (std::chrono::steady_clock::now() < deadline) { +#if defined(__APPLE__) + PumpMacOSEvents(); +#endif + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } #if defined(__APPLE__) PumpMacOSEvents(); #endif - std::this_thread::sleep_for(std::chrono::milliseconds(16)); + setenv("MOBILEGL_TRACE_HOLD_DONE", "1", 1); } -#if defined(__APPLE__) - PumpMacOSEvents(); -#endif - setenv("MOBILEGL_TRACE_HOLD_DONE", "1", 1); -} -int ResolveWidth(int width) { - if (gRequestedWidth > 0) { - return gRequestedWidth; + int ResolveWidth(int width) { + if (gRequestedWidth > 0) { + return gRequestedWidth; + } + return width > 0 ? width : 1; } - return width > 0 ? width : 1; -} -int ResolveHeight(int height) { - if (gRequestedHeight > 0) { - return gRequestedHeight; + int ResolveHeight(int height) { + if (gRequestedHeight > 0) { + return gRequestedHeight; + } + return height > 0 ? height : 1; } - return height > 0 ? height : 1; -} -void *Lookup(const char *name) { - if (gMobileGl == nullptr) { - const char *library = std::getenv("MOBILEGL_TRACE_LIBRARY"); - if (library != nullptr && library[0] != '\0') { - gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + void* Lookup(const char* name) { + if (gMobileGl == nullptr) { + const char* library = std::getenv("MOBILEGL_TRACE_LIBRARY"); + if (library != nullptr && library[0] != '\0') { + gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + if (gMobileGl == nullptr) { + gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL); + } + } if (gMobileGl == nullptr) { - gMobileGl = dlopen(library, RTLD_NOW | RTLD_GLOBAL); + gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + } + if (gMobileGl == nullptr) { + gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL); } } - if (gMobileGl == nullptr) { - gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL | RTLD_NOLOAD); + if (gMobileGl != nullptr) { + void* symbol = dlsym(gMobileGl, name); + if (symbol != nullptr) { + return symbol; + } } - if (gMobileGl == nullptr) { - gMobileGl = dlopen("libMobileGL.so", RTLD_NOW | RTLD_GLOBAL); - } - } - if (gMobileGl != nullptr) { - void *symbol = dlsym(gMobileGl, name); - if (symbol != nullptr) { - return symbol; - } - } - return dlsym(RTLD_DEFAULT, name); -} - -template -bool Load(T &slot, const char *name) { - slot = reinterpret_cast(Lookup(name)); - if (slot == nullptr) { - std::cerr << "error: failed to resolve " << name << " from MobileGL\n"; - return false; - } - return true; -} - -bool LoadEgl() { - return Load(gEgl.bindApi, "eglBindAPI") && - Load(gEgl.chooseConfig, "eglChooseConfig") && - Load(gEgl.createContext, "eglCreateContext") && - Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") && - Load(gEgl.createWindowSurface, "eglCreateWindowSurface") && - Load(gEgl.destroyContext, "eglDestroyContext") && - Load(gEgl.destroySurface, "eglDestroySurface") && - Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") && - Load(gEgl.getDisplay, "eglGetDisplay") && - Load(gEgl.getError, "eglGetError") && - Load(gEgl.initialize, "eglInitialize") && - Load(gEgl.makeCurrent, "eglMakeCurrent") && - Load(gEgl.queryString, "eglQueryString") && - Load(gEgl.swapBuffers, "eglSwapBuffers") && - Load(gEgl.terminate, "eglTerminate"); -} - -bool TraceReplayWantsWindowSurface() { - const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE"); - return mode != nullptr && std::strcmp(mode, "window") == 0; -} - -void PrintGlIdentityOnce() { - if (gPrintedGlIdentity) { - return; - } - auto glGetString = reinterpret_cast(Lookup("glGetString")); - if (glGetString == nullptr) { - std::cerr << "MOBILEGL_TRACE_GL_INFO unavailable: glGetString was not resolved\n"; - gPrintedGlIdentity = true; - return; - } - constexpr unsigned int GL_VENDOR_ENUM = 0x1F00; - constexpr unsigned int GL_RENDERER_ENUM = 0x1F01; - constexpr unsigned int GL_VERSION_ENUM = 0x1F02; - constexpr unsigned int GL_SHADING_LANGUAGE_VERSION_ENUM = 0x8B8C; - auto stringOrUnknown = [glGetString](unsigned int name) { - const auto *value = glGetString(name); - return value == nullptr ? reinterpret_cast("") : value; - }; - std::cerr << "MOBILEGL_TRACE_GL_VENDOR: " - << reinterpret_cast(stringOrUnknown(GL_VENDOR_ENUM)) << "\n" - << "MOBILEGL_TRACE_GL_RENDERER: " - << reinterpret_cast(stringOrUnknown(GL_RENDERER_ENUM)) << "\n" - << "MOBILEGL_TRACE_GL_VERSION: " - << reinterpret_cast(stringOrUnknown(GL_VERSION_ENUM)) << "\n" - << "MOBILEGL_TRACE_GL_SHADING_LANGUAGE_VERSION: " - << reinterpret_cast(stringOrUnknown(GL_SHADING_LANGUAGE_VERSION_ENUM)) << "\n"; - gPrintedGlIdentity = true; -} - -class EglVisual final : public glws::Visual { -public: - EGLConfig config = nullptr; - EGLenum api = EGL_OPENGL_API; - - explicit EglVisual(glws::Profile prof) : Visual(prof) {} -}; - -class EglDrawable final : public glws::Drawable { -public: - EGLSurface surface = EGL_NO_SURFACE; -#if defined(__APPLE__) - id window = nil; - id metalLayer = nil; - bool windowShown = false; -#endif - - EglDrawable(const EglVisual *visual, int width, int height, bool pbuffer) - : Drawable(visual, width, height, pbuffer) { - createSurface(); + return dlsym(RTLD_DEFAULT, name); } - ~EglDrawable() override { - destroySurface(); -#if defined(__APPLE__) - Release(metalLayer); - metalLayer = nil; - Release(window); - window = nil; -#endif - } - - void resize(int w, int h) override { - w = ResolveWidth(w); - h = ResolveHeight(h); - if (w == width && h == height) { - return; - } - destroySurface(); - width = w; - height = h; - createSurface(); - if (gCurrentDrawable == this && gCurrentContext != EGL_NO_CONTEXT) { - gEgl.makeCurrent(gDisplay, surface, surface, gCurrentContext); - } - } - - void show() override { - visible = true; -#if defined(__APPLE__) - if (windowShown) { - ShowMetalWindow(window); - } -#endif - } - - void swapBuffers() override { - if (surface == EGL_NO_SURFACE) { - return; - } - char callNo[32]; - snprintf(callNo, sizeof(callNo), "%u", retrace::callNo); - gEgl.swapBuffers(gDisplay, surface); - // Frame boundary: retrace_eglSwapBuffers() has already run frame_complete() and - // handed the frame to us. No-op unless benchmark mode armed the timer. - mobilegl_trace::benchmark::OnFrameBoundary(); -#if defined(__APPLE__) - PumpMacOSEvents(); - if (window && !windowShown) { - ShowMetalWindow(window); - windowShown = true; - } -#endif - HoldAfterTargetPresent(callNo); - } - -private: - bool shouldCreateWindowSurface() const { - if (pbuffer) { + template + bool Load(T& slot, const char* name) { + slot = reinterpret_cast(Lookup(name)); + if (slot == nullptr) { + std::cerr << "error: failed to resolve " << name << " from MobileGL\n"; return false; } - const char *mode = std::getenv("MOBILEGL_TRACE_SURFACE"); + return true; + } + + bool LoadEgl() { + return Load(gEgl.bindApi, "eglBindAPI") && Load(gEgl.chooseConfig, "eglChooseConfig") && + Load(gEgl.createContext, "eglCreateContext") && + Load(gEgl.createPbufferSurface, "eglCreatePbufferSurface") && + Load(gEgl.createWindowSurface, "eglCreateWindowSurface") && + Load(gEgl.destroyContext, "eglDestroyContext") && Load(gEgl.destroySurface, "eglDestroySurface") && + Load(gEgl.getConfigAttrib, "eglGetConfigAttrib") && Load(gEgl.getDisplay, "eglGetDisplay") && + Load(gEgl.getError, "eglGetError") && Load(gEgl.initialize, "eglInitialize") && + Load(gEgl.makeCurrent, "eglMakeCurrent") && Load(gEgl.queryString, "eglQueryString") && + Load(gEgl.swapBuffers, "eglSwapBuffers") && Load(gEgl.terminate, "eglTerminate"); + } + + bool TraceReplayWantsWindowSurface() { + const char* mode = std::getenv("MOBILEGL_TRACE_SURFACE"); return mode != nullptr && std::strcmp(mode, "window") == 0; } - void createSurface() { - const int surfaceWidth = ResolveWidth(width); - const int surfaceHeight = ResolveHeight(height); -#if defined(__APPLE__) - if (shouldCreateWindowSurface()) { - if (!window) { - window = CreateMetalWindow(surfaceWidth, surfaceHeight, &metalLayer); - } else if (metalLayer) { - const auto w = static_cast(std::max(surfaceWidth, 1)); - const auto h = static_cast(std::max(surfaceHeight, 1)); - SendVoidCGSize(metalLayer, "setDrawableSize:", {w, h}); - SendVoidCGRect(metalLayer, "setFrame:", {{0.0, 0.0}, {w, h}}); - } - if (metalLayer) { - surface = gEgl.createWindowSurface( - gDisplay, - static_cast(visual)->config, - reinterpret_cast(metalLayer), - nullptr); - } - if (surface == EGL_NO_SURFACE) { - std::cerr << "error: EGL window surface creation failed: 0x" << std::hex - << gEgl.getError() << std::dec << "\n"; - } + void PrintGlIdentityOnce() { + if (gPrintedGlIdentity) { return; } -#else - if (shouldCreateWindowSurface()) { - // P12 (on-screen server window), D9. A LINUX REPLAY HAS NO WINDOW OF ITS OWN: the window - // surface is the MobileGL SERVER's (MOBILEGL_IPC_SURFACE=server, WindowKind::ServerOwned), - // so the native window is none and the size the trace wants rides EGL_WIDTH/EGL_HEIGHT. - // Without that knob MobileGL refuses a null window (EGL_BAD_NATIVE_WINDOW) and this says - // why. resize() re-creates the surface at the new size, as it does the pbuffer. - const EGLint windowAttribs[] = { - EGL_WIDTH, surfaceWidth, - EGL_HEIGHT, surfaceHeight, - EGL_NONE, - }; - surface = gEgl.createWindowSurface(gDisplay, static_cast(visual)->config, - EGLNativeWindowType{}, windowAttribs); - if (surface == EGL_NO_SURFACE) { - std::cerr << "error: EGL window surface creation failed: 0x" << std::hex << gEgl.getError() - << std::dec - << " - a Linux replay has no window of its own; --window-surface renders on the " - "MobileGL server's window and needs MOBILEGL_IPC_SURFACE=server against a server " - "that owns a display\n"; - } + auto glGetString = reinterpret_cast(Lookup("glGetString")); + if (glGetString == nullptr) { + std::cerr << "MOBILEGL_TRACE_GL_INFO unavailable: glGetString was not resolved\n"; + gPrintedGlIdentity = true; return; } -#endif - const EGLint attribs[] = { - EGL_WIDTH, surfaceWidth, - EGL_HEIGHT, surfaceHeight, - EGL_NONE, + constexpr unsigned int GL_VENDOR_ENUM = 0x1F00; + constexpr unsigned int GL_RENDERER_ENUM = 0x1F01; + constexpr unsigned int GL_VERSION_ENUM = 0x1F02; + constexpr unsigned int GL_SHADING_LANGUAGE_VERSION_ENUM = 0x8B8C; + auto stringOrUnknown = [glGetString](unsigned int name) { + const auto* value = glGetString(name); + return value == nullptr ? reinterpret_cast("") : value; }; - surface = gEgl.createPbufferSurface(gDisplay, static_cast(visual)->config, attribs); - if (surface == EGL_NO_SURFACE) { - std::cerr << "error: EGL pbuffer creation failed: 0x" << std::hex - << gEgl.getError() << std::dec << "\n"; - } + std::cerr << "MOBILEGL_TRACE_GL_VENDOR: " << reinterpret_cast(stringOrUnknown(GL_VENDOR_ENUM)) + << "\n" + << "MOBILEGL_TRACE_GL_RENDERER: " << reinterpret_cast(stringOrUnknown(GL_RENDERER_ENUM)) + << "\n" + << "MOBILEGL_TRACE_GL_VERSION: " << reinterpret_cast(stringOrUnknown(GL_VERSION_ENUM)) + << "\n" + << "MOBILEGL_TRACE_GL_SHADING_LANGUAGE_VERSION: " + << reinterpret_cast(stringOrUnknown(GL_SHADING_LANGUAGE_VERSION_ENUM)) << "\n"; + gPrintedGlIdentity = true; } - void destroySurface() { - if (surface != EGL_NO_SURFACE) { - gEgl.destroySurface(gDisplay, surface); - surface = EGL_NO_SURFACE; + class EglVisual final : public glws::Visual { + public: + EGLConfig config = nullptr; + EGLenum api = EGL_OPENGL_API; + + explicit EglVisual(glws::Profile prof) : Visual(prof) {} + }; + + class EglDrawable final : public glws::Drawable { + public: + EGLSurface surface = EGL_NO_SURFACE; +#if defined(__APPLE__) + id window = nil; + id metalLayer = nil; + bool windowShown = false; +#endif + + EglDrawable(const EglVisual* visual, int width, int height, bool pbuffer) + : Drawable(visual, width, height, pbuffer) { + createSurface(); } + + ~EglDrawable() override { + destroySurface(); #if defined(__APPLE__) - if (!shouldCreateWindowSurface()) { Release(metalLayer); metalLayer = nil; Release(window); window = nil; - } #endif - } -}; - -class EglContext final : public glws::Context { -public: - EGLContext context = EGL_NO_CONTEXT; - - EglContext(const EglVisual *visual, glws::Context *shareContext) - : Context(visual) { - const EGLContext share = shareContext == nullptr ? EGL_NO_CONTEXT : static_cast(shareContext)->context; - if (!gEgl.bindApi(visual->api)) { - std::cerr << "error: eglBindAPI failed: 0x" << std::hex << gEgl.getError() - << std::dec << "\n"; } - EGLint attribs[9]; - int index = 0; - if (visual->api == EGL_OPENGL_ES_API) { - attribs[index++] = EGL_CONTEXT_CLIENT_VERSION; - attribs[index++] = static_cast(std::max(profile.major, 2)); - } else { - attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR; - attribs[index++] = profile.major >= 3 ? profile.major : 3; - attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR; - attribs[index++] = profile.major >= 3 ? profile.minor : 3; - attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; - attribs[index++] = profile.core ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR - : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + void resize(int w, int h) override { + w = ResolveWidth(w); + h = ResolveHeight(h); + if (w == width && h == height) { + return; + } + destroySurface(); + width = w; + height = h; + createSurface(); + if (gCurrentDrawable == this && gCurrentContext != EGL_NO_CONTEXT) { + gEgl.makeCurrent(gDisplay, surface, surface, gCurrentContext); + } } - attribs[index++] = EGL_NONE; - context = gEgl.createContext(gDisplay, visual->config, share, attribs); - if (context == EGL_NO_CONTEXT && visual->api == EGL_OPENGL_API) { - const EGLint fallbackAttribs[] = {EGL_NONE}; - context = gEgl.createContext(gDisplay, visual->config, share, fallbackAttribs); + void show() override { + visible = true; +#if defined(__APPLE__) + if (windowShown) { + ShowMetalWindow(window); + } +#endif } - if (context == EGL_NO_CONTEXT) { - std::cerr << "error: eglCreateContext failed: 0x" << std::hex << gEgl.getError() - << std::dec << "\n"; + + void swapBuffers() override { + if (surface == EGL_NO_SURFACE) { + return; + } + char callNo[32]; + snprintf(callNo, sizeof(callNo), "%u", retrace::callNo); + gEgl.swapBuffers(gDisplay, surface); + // Frame boundary: retrace_eglSwapBuffers() has already run frame_complete() and + // handed the frame to us. No-op unless benchmark mode armed the timer. + mobilegl_trace::benchmark::OnFrameBoundary(); +#if defined(__APPLE__) + PumpMacOSEvents(); + if (window && !windowShown) { + ShowMetalWindow(window); + windowShown = true; + } +#endif + HoldAfterTargetPresent(callNo); } + + private: + bool shouldCreateWindowSurface() const { + if (pbuffer) { + return false; + } + const char* mode = std::getenv("MOBILEGL_TRACE_SURFACE"); + return mode != nullptr && std::strcmp(mode, "window") == 0; + } + + void createSurface() { + const int surfaceWidth = ResolveWidth(width); + const int surfaceHeight = ResolveHeight(height); +#if defined(__APPLE__) + if (shouldCreateWindowSurface()) { + if (!window) { + window = CreateMetalWindow(surfaceWidth, surfaceHeight, &metalLayer); + } else if (metalLayer) { + const auto w = static_cast(std::max(surfaceWidth, 1)); + const auto h = static_cast(std::max(surfaceHeight, 1)); + SendVoidCGSize(metalLayer, "setDrawableSize:", {w, h}); + SendVoidCGRect(metalLayer, "setFrame:", {{0.0, 0.0}, { w, h }}); + } + if (metalLayer) { + surface = gEgl.createWindowSurface(gDisplay, static_cast(visual)->config, + reinterpret_cast(metalLayer), nullptr); + } + if (surface == EGL_NO_SURFACE) { + std::cerr << "error: EGL window surface creation failed: 0x" << std::hex << gEgl.getError() + << std::dec << "\n"; + } + return; + } +#else + if (shouldCreateWindowSurface()) { + // P12 (on-screen server window), D9. A LINUX REPLAY HAS NO WINDOW OF ITS OWN: the window + // surface is the MobileGL SERVER's (MOBILEGL_IPC_SURFACE=server, WindowKind::ServerOwned), + // so the native window is none and the size the trace wants rides EGL_WIDTH/EGL_HEIGHT. + // Without that knob MobileGL refuses a null window (EGL_BAD_NATIVE_WINDOW) and this says + // why. resize() re-creates the surface at the new size, as it does the pbuffer. + const EGLint windowAttribs[] = { + EGL_WIDTH, surfaceWidth, EGL_HEIGHT, surfaceHeight, EGL_NONE, + }; + surface = gEgl.createWindowSurface(gDisplay, static_cast(visual)->config, + EGLNativeWindowType{}, windowAttribs); + if (surface == EGL_NO_SURFACE) { + std::cerr << "error: EGL window surface creation failed: 0x" << std::hex << gEgl.getError() + << std::dec + << " - a Linux replay has no window of its own; --window-surface renders on the " + "MobileGL server's window and needs MOBILEGL_IPC_SURFACE=server against a server " + "that owns a display\n"; + } + return; + } +#endif + const EGLint attribs[] = { + EGL_WIDTH, surfaceWidth, EGL_HEIGHT, surfaceHeight, EGL_NONE, + }; + surface = gEgl.createPbufferSurface(gDisplay, static_cast(visual)->config, attribs); + if (surface == EGL_NO_SURFACE) { + std::cerr << "error: EGL pbuffer creation failed: 0x" << std::hex << gEgl.getError() << std::dec + << "\n"; + } + } + + void destroySurface() { + if (surface != EGL_NO_SURFACE) { + gEgl.destroySurface(gDisplay, surface); + surface = EGL_NO_SURFACE; + } +#if defined(__APPLE__) + if (!shouldCreateWindowSurface()) { + Release(metalLayer); + metalLayer = nil; + Release(window); + window = nil; + } +#endif + } + }; + + class EglContext final : public glws::Context { + public: + EGLContext context = EGL_NO_CONTEXT; + + EglContext(const EglVisual* visual, glws::Context* shareContext) : Context(visual) { + const EGLContext share = + shareContext == nullptr ? EGL_NO_CONTEXT : static_cast(shareContext)->context; + if (!gEgl.bindApi(visual->api)) { + std::cerr << "error: eglBindAPI failed: 0x" << std::hex << gEgl.getError() << std::dec << "\n"; + } + + EGLint attribs[9]; + int index = 0; + if (visual->api == EGL_OPENGL_ES_API) { + attribs[index++] = EGL_CONTEXT_CLIENT_VERSION; + attribs[index++] = static_cast(std::max(profile.major, 2)); + } else { + attribs[index++] = EGL_CONTEXT_MAJOR_VERSION_KHR; + attribs[index++] = profile.major >= 3 ? profile.major : 3; + attribs[index++] = EGL_CONTEXT_MINOR_VERSION_KHR; + attribs[index++] = profile.major >= 3 ? profile.minor : 3; + attribs[index++] = EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR; + attribs[index++] = profile.core ? EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR + : EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR; + } + attribs[index++] = EGL_NONE; + + context = gEgl.createContext(gDisplay, visual->config, share, attribs); + if (context == EGL_NO_CONTEXT && visual->api == EGL_OPENGL_API) { + const EGLint fallbackAttribs[] = {EGL_NONE}; + context = gEgl.createContext(gDisplay, visual->config, share, fallbackAttribs); + } + if (context == EGL_NO_CONTEXT) { + std::cerr << "error: eglCreateContext failed: 0x" << std::hex << gEgl.getError() << std::dec << "\n"; + } + } + + ~EglContext() override { + if (context != EGL_NO_CONTEXT) { + gEgl.destroyContext(gDisplay, context); + } + } + }; + + const EglDrawable* AsEglDrawable(const glws::Drawable* drawable) { + return static_cast(drawable); } - ~EglContext() override { - if (context != EGL_NO_CONTEXT) { - gEgl.destroyContext(gDisplay, context); - } + const EglContext* AsEglContext(const glws::Context* context) { + return static_cast(context); } -}; - -const EglDrawable *AsEglDrawable(const glws::Drawable *drawable) { - return static_cast(drawable); -} - -const EglContext *AsEglContext(const glws::Context *context) { - return static_cast(context); -} } // namespace namespace glws { -void init() { - if (gDisplay != EGL_NO_DISPLAY) { - return; - } - if (!LoadEgl()) { - return; - } - gDisplay = gEgl.getDisplay(EGL_DEFAULT_DISPLAY); - if (gDisplay == EGL_NO_DISPLAY) { - std::cerr << "error: eglGetDisplay failed: 0x" << std::hex << gEgl.getError() - << std::dec << "\n"; - return; - } - EGLint major = 0; - EGLint minor = 0; - if (!gEgl.initialize(gDisplay, &major, &minor)) { - std::cerr << "error: eglInitialize failed: 0x" << std::hex << gEgl.getError() - << std::dec << "\n"; - } -} - -void cleanup() { - if (gDisplay != EGL_NO_DISPLAY) { - gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - gEgl.terminate(gDisplay); - gDisplay = EGL_NO_DISPLAY; - } -} - -Visual *createVisual(bool doubleBuffer, unsigned samples, Profile profile) { - auto *visual = new EglVisual(profile); - visual->doubleBuffer = doubleBuffer; - visual->api = profile.api == glfeatures::API_GLES ? EGL_OPENGL_ES_API : EGL_OPENGL_API; - - if (!gEgl.bindApi(visual->api)) { - delete visual; - return nullptr; + void init() { + if (gDisplay != EGL_NO_DISPLAY) { + return; + } + if (!LoadEgl()) { + return; + } + gDisplay = gEgl.getDisplay(EGL_DEFAULT_DISPLAY); + if (gDisplay == EGL_NO_DISPLAY) { + std::cerr << "error: eglGetDisplay failed: 0x" << std::hex << gEgl.getError() << std::dec << "\n"; + return; + } + EGLint major = 0; + EGLint minor = 0; + if (!gEgl.initialize(gDisplay, &major, &minor)) { + std::cerr << "error: eglInitialize failed: 0x" << std::hex << gEgl.getError() << std::dec << "\n"; + } } - const EGLint surfaceType = TraceReplayWantsWindowSurface() ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT; - const EGLint attribs[] = { - EGL_SURFACE_TYPE, surfaceType, - EGL_RENDERABLE_TYPE, visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_DEPTH_SIZE, 24, - EGL_STENCIL_SIZE, 8, - EGL_SAMPLE_BUFFERS, samples > 1 ? 1 : 0, - EGL_SAMPLES, samples > 1 ? static_cast(samples) : 0, + void cleanup() { + if (gDisplay != EGL_NO_DISPLAY) { + gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + gEgl.terminate(gDisplay); + gDisplay = EGL_NO_DISPLAY; + } + } + + Visual* createVisual(bool doubleBuffer, unsigned samples, Profile profile) { + auto* visual = new EglVisual(profile); + visual->doubleBuffer = doubleBuffer; + visual->api = profile.api == glfeatures::API_GLES ? EGL_OPENGL_ES_API : EGL_OPENGL_API; + + if (!gEgl.bindApi(visual->api)) { + delete visual; + return nullptr; + } + + const EGLint surfaceType = TraceReplayWantsWindowSurface() ? EGL_WINDOW_BIT : EGL_PBUFFER_BIT; + const EGLint attribs[] = { + EGL_SURFACE_TYPE, + surfaceType, + EGL_RENDERABLE_TYPE, + visual->api == EGL_OPENGL_ES_API ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_DEPTH_SIZE, + 24, + EGL_STENCIL_SIZE, + 8, + EGL_SAMPLE_BUFFERS, + samples > 1 ? 1 : 0, + EGL_SAMPLES, + samples > 1 ? static_cast(samples) : 0, EGL_NONE, - }; - EGLint count = 0; - if (!gEgl.chooseConfig(gDisplay, attribs, &visual->config, 1, &count) || count < 1) { - delete visual; - return nullptr; + }; + EGLint count = 0; + if (!gEgl.chooseConfig(gDisplay, attribs, &visual->config, 1, &count) || count < 1) { + delete visual; + return nullptr; + } + return visual; } - return visual; -} -Drawable *createDrawable(const Visual *visual, int width, int height, const pbuffer_info *pbInfo) { - (void)pbInfo; - const bool usePbuffer = !TraceReplayWantsWindowSurface(); - return new EglDrawable(static_cast(visual), ResolveWidth(width), - ResolveHeight(height), usePbuffer); -} + Drawable* createDrawable(const Visual* visual, int width, int height, const pbuffer_info* pbInfo) { + (void)pbInfo; + const bool usePbuffer = !TraceReplayWantsWindowSurface(); + return new EglDrawable(static_cast(visual), ResolveWidth(width), ResolveHeight(height), + usePbuffer); + } -Context *createContext(const Visual *visual, Context *shareContext, bool) { - return new EglContext(static_cast(visual), shareContext); -} + Context* createContext(const Visual* visual, Context* shareContext, bool) { + return new EglContext(static_cast(visual), shareContext); + } -bool makeCurrentInternal(Drawable *drawable, Drawable *readable, Context *context) { - EGLSurface drawSurface = drawable == nullptr ? EGL_NO_SURFACE : AsEglDrawable(drawable)->surface; - EGLSurface readSurface = readable == nullptr ? drawSurface : AsEglDrawable(readable)->surface; - EGLContext eglContext = context == nullptr ? EGL_NO_CONTEXT : AsEglContext(context)->context; - if (drawSurface == EGL_NO_SURFACE && readSurface == EGL_NO_SURFACE && eglContext == EGL_NO_CONTEXT) { - gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - gCurrentDrawable = nullptr; - gCurrentContext = EGL_NO_CONTEXT; + bool makeCurrentInternal(Drawable* drawable, Drawable* readable, Context* context) { + EGLSurface drawSurface = drawable == nullptr ? EGL_NO_SURFACE : AsEglDrawable(drawable)->surface; + EGLSurface readSurface = readable == nullptr ? drawSurface : AsEglDrawable(readable)->surface; + EGLContext eglContext = context == nullptr ? EGL_NO_CONTEXT : AsEglContext(context)->context; + if (drawSurface == EGL_NO_SURFACE && readSurface == EGL_NO_SURFACE && eglContext == EGL_NO_CONTEXT) { + gEgl.makeCurrent(gDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + gCurrentDrawable = nullptr; + gCurrentContext = EGL_NO_CONTEXT; + return true; + } + if (gEgl.makeCurrent(gDisplay, drawSurface, readSurface, eglContext) != EGL_TRUE) { + return false; + } + gCurrentDrawable = drawable; + gCurrentContext = eglContext; + PrintGlIdentityOnce(); + // retrace::setUp() installs the GL dumper after glws::init(), so the earliest point at + // which the dump hook can wrap it is the first time a context becomes current. + mobilegl_trace_dump::InstallIfRequested(); return true; } - if (gEgl.makeCurrent(gDisplay, drawSurface, readSurface, eglContext) != EGL_TRUE) { + + bool processEvents() { +#if defined(__APPLE__) + PumpMacOSEvents(); +#endif return false; } - gCurrentDrawable = drawable; - gCurrentContext = eglContext; - PrintGlIdentityOnce(); - // retrace::setUp() installs the GL dumper after glws::init(), so the earliest point at - // which the dump hook can wrap it is the first time a context becomes current. - mobilegl_trace_dump::InstallIfRequested(); - return true; -} -bool processEvents() { -#if defined(__APPLE__) - PumpMacOSEvents(); -#endif - return false; -} + bool bindTexImage(Drawable*, int) { + return false; + } -bool bindTexImage(Drawable *, int) { - return false; -} + bool releaseTexImage(Drawable*, int) { + return false; + } -bool releaseTexImage(Drawable *, int) { - return false; -} - -bool setPbufferAttrib(Drawable *, const int *) { - return false; -} + bool setPbufferAttrib(Drawable*, const int*) { + return false; + } } // namespace glws -extern "C" bool mobilegl_trace_get_drawable_bounds(int *width, int *height) { +extern "C" bool mobilegl_trace_get_drawable_bounds(int* width, int* height) { if (gCurrentDrawable == nullptr || width == nullptr || height == nullptr) { return false; } diff --git a/tools/trace_replay/fixture_tools/realign_buffer_ranges.cpp b/tools/trace_replay/fixture_tools/realign_buffer_ranges.cpp index 7600d455..19ad6858 100644 --- a/tools/trace_replay/fixture_tools/realign_buffer_ranges.cpp +++ b/tools/trace_replay/fixture_tools/realign_buffer_ranges.cpp @@ -46,383 +46,401 @@ namespace { -constexpr unsigned kUniformBuffer = 0x8A11; -constexpr unsigned kShaderStorageBuffer = 0x90D2; -constexpr unsigned kDynamicStorageBit = 0x0100; -constexpr unsigned kRelocBufferName = 1000000; + constexpr unsigned kUniformBuffer = 0x8A11; + constexpr unsigned kShaderStorageBuffer = 0x90D2; + constexpr unsigned kDynamicStorageBit = 0x0100; + constexpr unsigned kRelocBufferName = 1000000; -[[noreturn]] void Fail(const trace::Call *call, const std::string &what) { - std::fprintf(stderr, "error: call %u %s: %s\n", call ? call->no : 0u, call ? call->name() : "-", what.c_str()); - std::exit(1); -} - -bool Is(const trace::Call *call, const char *name) { return std::strcmp(call->name(), name) == 0; } - -bool StartsWith(const char *s, const char *prefix) { return std::strncmp(s, prefix, std::strlen(prefix)) == 0; } - -bool ReadsIndexedBuffers(const trace::Call *call) { - const char *n = call->name(); - return StartsWith(n, "glDraw") || StartsWith(n, "glMultiDraw") || StartsWith(n, "glDispatchCompute"); -} - -bool IsRangeTarget(unsigned target) { return target == kUniformBuffer || target == kShaderStorageBuffer; } - -struct MapRegion { - unsigned buffer; - uint64_t offset; - uint64_t length; - uint64_t ptr; -}; - -struct Reloc { - unsigned src = 0; - uint64_t offset = 0; - uint64_t size = 0; - bool dirty = true; - std::vector last; - const trace::EnumSig *targetSig = nullptr; -}; - -// Signatures for the calls this tool inserts; ids are allocated past the trace's own. -struct Sigs { - const char *createNames[2] = {"n", "buffers"}; - const char *storageNames[4] = {"buffer", "size", "data", "flags"}; - const char *subDataNames[4] = {"buffer", "offset", "size", "data"}; - const char *bindRangeNames[5] = {"target", "index", "buffer", "offset", "size"}; - const char *bindNames[2] = {"target", "buffer"}; - trace::FunctionSig create{}, storage{}, subData{}, bindRange{}, bind{}; - - void Init(trace::Id firstId) { - create = {firstId + 0, "glCreateBuffers", 2, createNames}; - storage = {firstId + 1, "glNamedBufferStorage", 4, storageNames}; - subData = {firstId + 2, "glNamedBufferSubData", 4, subDataNames}; - bindRange = {firstId + 3, "glBindBufferRange", 5, bindRangeNames}; - bind = {firstId + 4, "glBindBuffer", 2, bindNames}; - } -}; - -class Rewriter { -public: - uint64_t align = 1024; - std::set mapCalls; - - int Run(const char *in, const char *out) { - Scan(in); - if (relocSrc_.empty()) { - std::fprintf(stderr, "nothing to realign at %llu bytes\n", (unsigned long long)align); - return 2; - } - sigs_.Init(maxSigId_ + 1); - stride_ = (maxRelocSize_ + align - 1) / align * align; - Pass(in, nullptr); // dry run: count the slots - slotCap_ = slotsUsed_; - Pass(in, out); - std::fprintf(stderr, - "realigned %llu binds of %zu source buffer(s) to %llu bytes: %llu uploads, relocation buffer %llu " - "bytes, %llu calls inserted\n", - (unsigned long long)relocBinds_, relocSrc_.size(), (unsigned long long)align, - (unsigned long long)slotsUsed_, (unsigned long long)(slotCap_ * stride_), - (unsigned long long)inserted_); - for (const auto &m : callMap_) { - std::printf("call %u -> %llu\n", m.first, (unsigned long long)m.second); - } - return 0; + [[noreturn]] void Fail(const trace::Call* call, const std::string& what) { + std::fprintf(stderr, "error: call %u %s: %s\n", call ? call->no : 0u, call ? call->name() : "-", what.c_str()); + std::exit(1); } -private: - // Pass 0: which buffers need relocating, and the sizes involved. - void Scan(const char *in) { - trace::Parser p; - if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in); - trace::Call *call; - while ((call = p.parse_call())) { - maxSigId_ = std::max(maxSigId_, call->sig->id); - if (Is(call, "glBindBufferRange")) { - unsigned target = unsigned(call->arg(0).toSInt()); - uint64_t offset = uint64_t(call->arg(3).toSInt()); - if (IsRangeTarget(target) && offset % align != 0) { - relocSrc_.insert(unsigned(call->arg(2).toUInt())); - maxRelocSize_ = std::max(maxRelocSize_, uint64_t(call->arg(4).toSInt())); - } - } else if (Is(call, "glBindBuffersRange")) { - unsigned target = unsigned(call->arg(0).toSInt()); - const trace::Array *offsets = call->arg(4).toArray(); - if (IsRangeTarget(target) && offsets) { - for (const trace::Value *v : offsets->values) { - if (uint64_t(v->toSInt()) % align != 0) Fail(call, "misaligned glBindBuffersRange is not handled"); + bool Is(const trace::Call* call, const char* name) { + return std::strcmp(call->name(), name) == 0; + } + + bool StartsWith(const char* s, const char* prefix) { + return std::strncmp(s, prefix, std::strlen(prefix)) == 0; + } + + bool ReadsIndexedBuffers(const trace::Call* call) { + const char* n = call->name(); + return StartsWith(n, "glDraw") || StartsWith(n, "glMultiDraw") || StartsWith(n, "glDispatchCompute"); + } + + bool IsRangeTarget(unsigned target) { + return target == kUniformBuffer || target == kShaderStorageBuffer; + } + + struct MapRegion { + unsigned buffer; + uint64_t offset; + uint64_t length; + uint64_t ptr; + }; + + struct Reloc { + unsigned src = 0; + uint64_t offset = 0; + uint64_t size = 0; + bool dirty = true; + std::vector last; + const trace::EnumSig* targetSig = nullptr; + }; + + // Signatures for the calls this tool inserts; ids are allocated past the trace's own. + struct Sigs { + const char* createNames[2] = {"n", "buffers"}; + const char* storageNames[4] = {"buffer", "size", "data", "flags"}; + const char* subDataNames[4] = {"buffer", "offset", "size", "data"}; + const char* bindRangeNames[5] = {"target", "index", "buffer", "offset", "size"}; + const char* bindNames[2] = {"target", "buffer"}; + trace::FunctionSig create{}, storage{}, subData{}, bindRange{}, bind{}; + + void Init(trace::Id firstId) { + create = {firstId + 0, "glCreateBuffers", 2, createNames}; + storage = {firstId + 1, "glNamedBufferStorage", 4, storageNames}; + subData = {firstId + 2, "glNamedBufferSubData", 4, subDataNames}; + bindRange = {firstId + 3, "glBindBufferRange", 5, bindRangeNames}; + bind = {firstId + 4, "glBindBuffer", 2, bindNames}; + } + }; + + class Rewriter { + public: + uint64_t align = 1024; + std::set mapCalls; + + int Run(const char* in, const char* out) { + Scan(in); + if (relocSrc_.empty()) { + std::fprintf(stderr, "nothing to realign at %llu bytes\n", (unsigned long long)align); + return 2; + } + sigs_.Init(maxSigId_ + 1); + stride_ = (maxRelocSize_ + align - 1) / align * align; + Pass(in, nullptr); // dry run: count the slots + slotCap_ = slotsUsed_; + Pass(in, out); + std::fprintf( + stderr, + "realigned %llu binds of %zu source buffer(s) to %llu bytes: %llu uploads, relocation buffer %llu " + "bytes, %llu calls inserted\n", + (unsigned long long)relocBinds_, relocSrc_.size(), (unsigned long long)align, + (unsigned long long)slotsUsed_, (unsigned long long)(slotCap_ * stride_), + (unsigned long long)inserted_); + for (const auto& m : callMap_) { + std::printf("call %u -> %llu\n", m.first, (unsigned long long)m.second); + } + return 0; + } + + private: + // Pass 0: which buffers need relocating, and the sizes involved. + void Scan(const char* in) { + trace::Parser p; + if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in); + trace::Call* call; + while ((call = p.parse_call())) { + maxSigId_ = std::max(maxSigId_, call->sig->id); + if (Is(call, "glBindBufferRange")) { + unsigned target = unsigned(call->arg(0).toSInt()); + uint64_t offset = uint64_t(call->arg(3).toSInt()); + if (IsRangeTarget(target) && offset % align != 0) { + relocSrc_.insert(unsigned(call->arg(2).toUInt())); + maxRelocSize_ = std::max(maxRelocSize_, uint64_t(call->arg(4).toSInt())); + } + } else if (Is(call, "glBindBuffersRange")) { + unsigned target = unsigned(call->arg(0).toSInt()); + const trace::Array* offsets = call->arg(4).toArray(); + if (IsRangeTarget(target) && offsets) { + for (const trace::Value* v : offsets->values) { + if (uint64_t(v->toSInt()) % align != 0) + Fail(call, "misaligned glBindBuffersRange is not handled"); + } } } + delete call; } - delete call; } - } - void Pass(const char *in, const char *out) { - trace::Parser p; - if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in); - trace::Writer writer; - writer_ = nullptr; - if (out) { - if (!writer.open(out, p.getVersion(), p.getProperties())) Fail(nullptr, std::string("cannot create ") + out); - writer_ = &writer; + void Pass(const char* in, const char* out) { + trace::Parser p; + if (!p.open(in)) Fail(nullptr, std::string("cannot open ") + in); + trace::Writer writer; + writer_ = nullptr; + if (out) { + if (!writer.open(out, p.getVersion(), p.getProperties())) + Fail(nullptr, std::string("cannot create ") + out); + writer_ = &writer; + } + shadow_.clear(); + maps_.clear(); + generic_.clear(); + relocs_.clear(); + slotsUsed_ = 0; + relocBinds_ = 0; + inserted_ = 0; + created_ = false; + + trace::Call* call; + while ((call = p.parse_call())) { + Process(call); + delete call; + } + if (writer_) writer.close(); + writer_ = nullptr; } - shadow_.clear(); - maps_.clear(); - generic_.clear(); - relocs_.clear(); - slotsUsed_ = 0; - relocBinds_ = 0; - inserted_ = 0; - created_ = false; - trace::Call *call; - while ((call = p.parse_call())) { - Process(call); - delete call; + unsigned BufferArg(trace::Call* call, unsigned index) { return unsigned(call->arg(index).toUInt()); } + + unsigned Generic(unsigned target) { + auto it = generic_.find(target); + return it == generic_.end() ? 0 : it->second; } - if (writer_) writer.close(); - writer_ = nullptr; - } - unsigned BufferArg(trace::Call *call, unsigned index) { return unsigned(call->arg(index).toUInt()); } - - unsigned Generic(unsigned target) { - auto it = generic_.find(target); - return it == generic_.end() ? 0 : it->second; - } - - std::vector *Shadow(unsigned buffer) { - auto it = shadow_.find(buffer); - return it == shadow_.end() ? nullptr : &it->second; - } - - void Specify(trace::Call *call, unsigned buffer, uint64_t size, trace::Value *data) { - if (!relocSrc_.count(buffer)) return; - std::vector &s = shadow_[buffer]; - s.assign(size, 0); - const trace::Blob *blob = data ? data->toBlob() : nullptr; - if (blob) std::memcpy(s.data(), blob->buf, std::min(size, blob->size)); - for (auto &r : relocs_) { - if (r.second.src == buffer) r.second.dirty = true; + std::vector* Shadow(unsigned buffer) { + auto it = shadow_.find(buffer); + return it == shadow_.end() ? nullptr : &it->second; } - (void)call; - } - void Write(trace::Call *call, unsigned buffer, uint64_t offset, const char *bytes, uint64_t size) { - std::vector *s = Shadow(buffer); - if (!s) return; - if (offset + size > s->size()) Fail(call, "write past the end of a tracked buffer"); - std::memcpy(s->data() + offset, bytes, size); - } + void Specify(trace::Call* call, unsigned buffer, uint64_t size, trace::Value* data) { + if (!relocSrc_.count(buffer)) return; + std::vector& s = shadow_[buffer]; + s.assign(size, 0); + const trace::Blob* blob = data ? data->toBlob() : nullptr; + if (blob) std::memcpy(s.data(), blob->buf, std::min(size, blob->size)); + for (auto& r : relocs_) { + if (r.second.src == buffer) r.second.dirty = true; + } + (void)call; + } - void Refuse(trace::Call *call, unsigned buffer) { - if (relocSrc_.count(buffer)) Fail(call, "writes a relocated source buffer in a way the trace does not carry the bytes of"); - } + void Write(trace::Call* call, unsigned buffer, uint64_t offset, const char* bytes, uint64_t size) { + std::vector* s = Shadow(buffer); + if (!s) return; + if (offset + size > s->size()) Fail(call, "write past the end of a tracked buffer"); + std::memcpy(s->data() + offset, bytes, size); + } - void Emit(trace::Call *call) { - if (!writer_) return; - if (mapCalls.count(call->no)) callMap_[call->no] = writtenCalls_; - writer_->writeCall(call); - ++writtenCalls_; - } + void Refuse(trace::Call* call, unsigned buffer) { + if (relocSrc_.count(buffer)) + Fail(call, "writes a relocated source buffer in a way the trace does not carry the bytes of"); + } - void EmitNew(trace::Call *call) { - ++inserted_; - if (writer_) { + void Emit(trace::Call* call) { + if (!writer_) return; + if (mapCalls.count(call->no)) callMap_[call->no] = writtenCalls_; writer_->writeCall(call); ++writtenCalls_; } - delete call; - } - trace::Call *NewCall(const trace::FunctionSig *sig, unsigned thread) { - return new trace::Call(sig, trace::CallFlags(0), thread); - } - - void EmitBind(unsigned thread, const trace::EnumSig *targetSig, unsigned target, unsigned buffer) { - trace::Call *c = NewCall(&sigs_.bind, thread); - c->args[0].value = targetSig ? static_cast(new trace::Enum(targetSig, target)) - : static_cast(new trace::SInt(target)); - c->args[1].value = new trace::UInt(buffer); - EmitNew(c); - } - - void CreateRelocBuffer(unsigned thread) { - trace::Call *c = NewCall(&sigs_.create, thread); - c->args[0].value = new trace::SInt(1); - trace::Array *names = new trace::Array(1); - names->values[0] = new trace::UInt(kRelocBufferName); - c->args[1].value = names; - EmitNew(c); - - c = NewCall(&sigs_.storage, thread); - c->args[0].value = new trace::UInt(kRelocBufferName); - c->args[1].value = new trace::SInt(int64_t(slotCap_ * stride_)); - c->args[2].value = new trace::Null(); - c->args[3].value = new trace::UInt(kDynamicStorageBit); - EmitNew(c); - created_ = true; - } - - void Upload(trace::Call *before, unsigned target, unsigned index, Reloc &r) { - std::vector *s = Shadow(r.src); - if (!s) Fail(before, "a relocated range reads a buffer whose storage the trace never specified"); - if (r.offset + r.size > s->size()) Fail(before, "a relocated range runs past its buffer"); - std::vector bytes(s->begin() + r.offset, s->begin() + r.offset + r.size); - if (!r.dirty && bytes == r.last) return; - - unsigned thread = before->thread_id; - if (writer_ && slotsUsed_ >= slotCap_) Fail(before, "relocation slots exceeded the dry run"); - uint64_t slotOffset = slotsUsed_++ * stride_; - if (!created_) CreateRelocBuffer(thread); - - trace::Call *c = NewCall(&sigs_.subData, thread); - c->args[0].value = new trace::UInt(kRelocBufferName); - c->args[1].value = new trace::SInt(int64_t(slotOffset)); - c->args[2].value = new trace::SInt(int64_t(r.size)); - trace::Blob *blob = new trace::Blob(r.size); - std::memcpy(blob->buf, bytes.data(), r.size); - c->args[3].value = blob; - EmitNew(c); - - c = NewCall(&sigs_.bindRange, thread); - c->args[0].value = r.targetSig ? static_cast(new trace::Enum(r.targetSig, target)) - : static_cast(new trace::SInt(target)); - c->args[1].value = new trace::UInt(index); - c->args[2].value = new trace::UInt(kRelocBufferName); - c->args[3].value = new trace::SInt(int64_t(slotOffset)); - c->args[4].value = new trace::SInt(int64_t(r.size)); - EmitNew(c); - - // glBindBufferRange also moved the generic binding; put back the app's. - EmitBind(thread, r.targetSig, target, Generic(target)); - - r.last = std::move(bytes); - r.dirty = false; - } - - void Process(trace::Call *call) { - const char *n = call->name(); - - if (Is(call, "glBindBuffer")) { - generic_[unsigned(call->arg(0).toSInt())] = BufferArg(call, 1); - } else if (Is(call, "glBindBufferBase")) { - unsigned target = unsigned(call->arg(0).toSInt()); - generic_[target] = BufferArg(call, 2); - relocs_.erase({target, unsigned(call->arg(1).toUInt())}); - } else if (Is(call, "glBindBufferRange")) { - unsigned target = unsigned(call->arg(0).toSInt()); - unsigned index = unsigned(call->arg(1).toUInt()); - unsigned buffer = BufferArg(call, 2); - uint64_t offset = uint64_t(call->arg(3).toSInt()); - generic_[target] = buffer; - if (IsRangeTarget(target) && offset % align != 0) { - Reloc r; - r.src = buffer; - r.offset = offset; - r.size = uint64_t(call->arg(4).toSInt()); - trace::Value &t = call->arg(0); - trace::Enum *e = nullptr; - // Enum values print by name in dumps; keep the trace's own enum signature when it has one. - struct EnumProbe : trace::Visitor { - trace::Enum *found = nullptr; - void visit(trace::Enum *node) override { found = node; } - } probe; - t.visit(probe); - e = probe.found; - r.targetSig = e ? e->sig : nullptr; - relocs_[{target, index}] = std::move(r); - ++relocBinds_; - if (mapCalls.count(call->no) && writer_) callMap_[call->no] = writtenCalls_; - // Only the generic binding survives at this point; the indexed one is bound before its first read. - EmitBind(call->thread_id, e ? e->sig : nullptr, target, buffer); - --inserted_; // a replacement, not an insertion - return; + void EmitNew(trace::Call* call) { + ++inserted_; + if (writer_) { + writer_->writeCall(call); + ++writtenCalls_; } - relocs_.erase({target, index}); - } else if (Is(call, "glBindBuffersBase") || Is(call, "glBindBuffersRange")) { - unsigned target = unsigned(call->arg(0).toSInt()); - unsigned first = unsigned(call->arg(1).toUInt()); - unsigned count = unsigned(call->arg(2).toSInt()); - for (unsigned i = first; i < first + count; ++i) relocs_.erase({target, i}); - } else if (Is(call, "glNamedBufferStorage") || Is(call, "glNamedBufferData")) { - Specify(call, BufferArg(call, 0), uint64_t(call->arg(1).toSInt()), &call->arg(2)); - } else if (Is(call, "glBufferStorage") || Is(call, "glBufferData")) { - Specify(call, Generic(unsigned(call->arg(0).toSInt())), uint64_t(call->arg(1).toSInt()), &call->arg(2)); - } else if (Is(call, "glNamedBufferSubData") || Is(call, "glBufferSubData")) { - unsigned buffer = Is(call, "glNamedBufferSubData") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); - if (relocSrc_.count(buffer)) { - const trace::Blob *blob = call->arg(3).toBlob(); - if (!blob) Fail(call, "subdata without bytes"); - Write(call, buffer, uint64_t(call->arg(1).toSInt()), blob->buf, - std::min(uint64_t(call->arg(2).toSInt()), blob->size)); - } - } else if (Is(call, "glCopyNamedBufferSubData")) { - Refuse(call, BufferArg(call, 1)); - } else if (Is(call, "glCopyBufferSubData")) { - Refuse(call, Generic(unsigned(call->arg(1).toSInt()))); - } else if (StartsWith(n, "glClearNamedBuffer")) { - Refuse(call, BufferArg(call, 0)); - } else if (StartsWith(n, "glClearBuffer") && (StartsWith(n, "glClearBufferData") || StartsWith(n, "glClearBufferSubData"))) { - Refuse(call, Generic(unsigned(call->arg(0).toSInt()))); - } else if (Is(call, "glMapNamedBufferRange") || Is(call, "glMapBufferRange")) { - unsigned buffer = Is(call, "glMapNamedBufferRange") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); - if (relocSrc_.count(buffer) && call->ret) { - maps_.push_back({buffer, uint64_t(call->arg(1).toSInt()), uint64_t(call->arg(2).toSInt()), - call->ret->toUIntPtr()}); - } - } else if (Is(call, "glMapNamedBuffer") || Is(call, "glMapBuffer")) { - unsigned buffer = Is(call, "glMapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); - std::vector *s = Shadow(buffer); - if (s && call->ret) maps_.push_back({buffer, 0, s->size(), call->ret->toUIntPtr()}); - } else if (Is(call, "glUnmapNamedBuffer") || Is(call, "glUnmapBuffer")) { - unsigned buffer = Is(call, "glUnmapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); - maps_.erase(std::remove_if(maps_.begin(), maps_.end(), [&](const MapRegion &m) { return m.buffer == buffer; }), - maps_.end()); - } else if (Is(call, "memcpy")) { - uint64_t dest = call->arg(0).toUIntPtr(); - const trace::Blob *blob = call->arg(1).toBlob(); - uint64_t size = call->arg(2).toUInt(); - if (blob) { - size = std::min(size, blob->size); - for (const MapRegion &m : maps_) { - uint64_t lo = std::max(dest, m.ptr); - uint64_t hi = std::min(dest + size, m.ptr + m.length); - if (lo >= hi) continue; - Write(call, m.buffer, m.offset + (lo - m.ptr), blob->buf + (lo - dest), hi - lo); - } - } - } else if (ReadsIndexedBuffers(call)) { - for (auto &r : relocs_) Upload(call, r.first.first, r.first.second, r.second); + delete call; } - Emit(call); - } + trace::Call* NewCall(const trace::FunctionSig* sig, unsigned thread) { + return new trace::Call(sig, trace::CallFlags(0), thread); + } - Sigs sigs_; - trace::Id maxSigId_ = 0; - std::set relocSrc_; - uint64_t maxRelocSize_ = 0; - uint64_t stride_ = 0; - uint64_t slotCap_ = 0; - uint64_t slotsUsed_ = 0; - uint64_t relocBinds_ = 0; - uint64_t inserted_ = 0; - uint64_t writtenCalls_ = 0; - bool created_ = false; - trace::Writer *writer_ = nullptr; - std::unordered_map> shadow_; - std::vector maps_; - std::unordered_map generic_; - std::map, Reloc> relocs_; - std::map callMap_; -}; + void EmitBind(unsigned thread, const trace::EnumSig* targetSig, unsigned target, unsigned buffer) { + trace::Call* c = NewCall(&sigs_.bind, thread); + c->args[0].value = targetSig ? static_cast(new trace::Enum(targetSig, target)) + : static_cast(new trace::SInt(target)); + c->args[1].value = new trace::UInt(buffer); + EmitNew(c); + } -} // namespace + void CreateRelocBuffer(unsigned thread) { + trace::Call* c = NewCall(&sigs_.create, thread); + c->args[0].value = new trace::SInt(1); + trace::Array* names = new trace::Array(1); + names->values[0] = new trace::UInt(kRelocBufferName); + c->args[1].value = names; + EmitNew(c); -int main(int argc, char **argv) { + c = NewCall(&sigs_.storage, thread); + c->args[0].value = new trace::UInt(kRelocBufferName); + c->args[1].value = new trace::SInt(int64_t(slotCap_ * stride_)); + c->args[2].value = new trace::Null(); + c->args[3].value = new trace::UInt(kDynamicStorageBit); + EmitNew(c); + created_ = true; + } + + void Upload(trace::Call* before, unsigned target, unsigned index, Reloc& r) { + std::vector* s = Shadow(r.src); + if (!s) Fail(before, "a relocated range reads a buffer whose storage the trace never specified"); + if (r.offset + r.size > s->size()) Fail(before, "a relocated range runs past its buffer"); + std::vector bytes(s->begin() + r.offset, s->begin() + r.offset + r.size); + if (!r.dirty && bytes == r.last) return; + + unsigned thread = before->thread_id; + if (writer_ && slotsUsed_ >= slotCap_) Fail(before, "relocation slots exceeded the dry run"); + uint64_t slotOffset = slotsUsed_++ * stride_; + if (!created_) CreateRelocBuffer(thread); + + trace::Call* c = NewCall(&sigs_.subData, thread); + c->args[0].value = new trace::UInt(kRelocBufferName); + c->args[1].value = new trace::SInt(int64_t(slotOffset)); + c->args[2].value = new trace::SInt(int64_t(r.size)); + trace::Blob* blob = new trace::Blob(r.size); + std::memcpy(blob->buf, bytes.data(), r.size); + c->args[3].value = blob; + EmitNew(c); + + c = NewCall(&sigs_.bindRange, thread); + c->args[0].value = r.targetSig ? static_cast(new trace::Enum(r.targetSig, target)) + : static_cast(new trace::SInt(target)); + c->args[1].value = new trace::UInt(index); + c->args[2].value = new trace::UInt(kRelocBufferName); + c->args[3].value = new trace::SInt(int64_t(slotOffset)); + c->args[4].value = new trace::SInt(int64_t(r.size)); + EmitNew(c); + + // glBindBufferRange also moved the generic binding; put back the app's. + EmitBind(thread, r.targetSig, target, Generic(target)); + + r.last = std::move(bytes); + r.dirty = false; + } + + void Process(trace::Call* call) { + const char* n = call->name(); + + if (Is(call, "glBindBuffer")) { + generic_[unsigned(call->arg(0).toSInt())] = BufferArg(call, 1); + } else if (Is(call, "glBindBufferBase")) { + unsigned target = unsigned(call->arg(0).toSInt()); + generic_[target] = BufferArg(call, 2); + relocs_.erase({target, unsigned(call->arg(1).toUInt())}); + } else if (Is(call, "glBindBufferRange")) { + unsigned target = unsigned(call->arg(0).toSInt()); + unsigned index = unsigned(call->arg(1).toUInt()); + unsigned buffer = BufferArg(call, 2); + uint64_t offset = uint64_t(call->arg(3).toSInt()); + generic_[target] = buffer; + if (IsRangeTarget(target) && offset % align != 0) { + Reloc r; + r.src = buffer; + r.offset = offset; + r.size = uint64_t(call->arg(4).toSInt()); + trace::Value& t = call->arg(0); + trace::Enum* e = nullptr; + // Enum values print by name in dumps; keep the trace's own enum signature when it has one. + struct EnumProbe : trace::Visitor { + trace::Enum* found = nullptr; + void visit(trace::Enum* node) override { found = node; } + } probe; + t.visit(probe); + e = probe.found; + r.targetSig = e ? e->sig : nullptr; + relocs_[{target, index}] = std::move(r); + ++relocBinds_; + if (mapCalls.count(call->no) && writer_) callMap_[call->no] = writtenCalls_; + // Only the generic binding survives at this point; the indexed one is bound before its first read. + EmitBind(call->thread_id, e ? e->sig : nullptr, target, buffer); + --inserted_; // a replacement, not an insertion + return; + } + relocs_.erase({target, index}); + } else if (Is(call, "glBindBuffersBase") || Is(call, "glBindBuffersRange")) { + unsigned target = unsigned(call->arg(0).toSInt()); + unsigned first = unsigned(call->arg(1).toUInt()); + unsigned count = unsigned(call->arg(2).toSInt()); + for (unsigned i = first; i < first + count; ++i) + relocs_.erase({target, i}); + } else if (Is(call, "glNamedBufferStorage") || Is(call, "glNamedBufferData")) { + Specify(call, BufferArg(call, 0), uint64_t(call->arg(1).toSInt()), &call->arg(2)); + } else if (Is(call, "glBufferStorage") || Is(call, "glBufferData")) { + Specify(call, Generic(unsigned(call->arg(0).toSInt())), uint64_t(call->arg(1).toSInt()), &call->arg(2)); + } else if (Is(call, "glNamedBufferSubData") || Is(call, "glBufferSubData")) { + unsigned buffer = + Is(call, "glNamedBufferSubData") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); + if (relocSrc_.count(buffer)) { + const trace::Blob* blob = call->arg(3).toBlob(); + if (!blob) Fail(call, "subdata without bytes"); + Write(call, buffer, uint64_t(call->arg(1).toSInt()), blob->buf, + std::min(uint64_t(call->arg(2).toSInt()), blob->size)); + } + } else if (Is(call, "glCopyNamedBufferSubData")) { + Refuse(call, BufferArg(call, 1)); + } else if (Is(call, "glCopyBufferSubData")) { + Refuse(call, Generic(unsigned(call->arg(1).toSInt()))); + } else if (StartsWith(n, "glClearNamedBuffer")) { + Refuse(call, BufferArg(call, 0)); + } else if (StartsWith(n, "glClearBuffer") && + (StartsWith(n, "glClearBufferData") || StartsWith(n, "glClearBufferSubData"))) { + Refuse(call, Generic(unsigned(call->arg(0).toSInt()))); + } else if (Is(call, "glMapNamedBufferRange") || Is(call, "glMapBufferRange")) { + unsigned buffer = + Is(call, "glMapNamedBufferRange") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); + if (relocSrc_.count(buffer) && call->ret) { + maps_.push_back({buffer, uint64_t(call->arg(1).toSInt()), uint64_t(call->arg(2).toSInt()), + call->ret->toUIntPtr()}); + } + } else if (Is(call, "glMapNamedBuffer") || Is(call, "glMapBuffer")) { + unsigned buffer = + Is(call, "glMapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); + std::vector* s = Shadow(buffer); + if (s && call->ret) maps_.push_back({buffer, 0, s->size(), call->ret->toUIntPtr()}); + } else if (Is(call, "glUnmapNamedBuffer") || Is(call, "glUnmapBuffer")) { + unsigned buffer = + Is(call, "glUnmapNamedBuffer") ? BufferArg(call, 0) : Generic(unsigned(call->arg(0).toSInt())); + maps_.erase( + std::remove_if(maps_.begin(), maps_.end(), [&](const MapRegion& m) { return m.buffer == buffer; }), + maps_.end()); + } else if (Is(call, "memcpy")) { + uint64_t dest = call->arg(0).toUIntPtr(); + const trace::Blob* blob = call->arg(1).toBlob(); + uint64_t size = call->arg(2).toUInt(); + if (blob) { + size = std::min(size, blob->size); + for (const MapRegion& m : maps_) { + uint64_t lo = std::max(dest, m.ptr); + uint64_t hi = std::min(dest + size, m.ptr + m.length); + if (lo >= hi) continue; + Write(call, m.buffer, m.offset + (lo - m.ptr), blob->buf + (lo - dest), hi - lo); + } + } + } else if (ReadsIndexedBuffers(call)) { + for (auto& r : relocs_) + Upload(call, r.first.first, r.first.second, r.second); + } + + Emit(call); + } + + Sigs sigs_; + trace::Id maxSigId_ = 0; + std::set relocSrc_; + uint64_t maxRelocSize_ = 0; + uint64_t stride_ = 0; + uint64_t slotCap_ = 0; + uint64_t slotsUsed_ = 0; + uint64_t relocBinds_ = 0; + uint64_t inserted_ = 0; + uint64_t writtenCalls_ = 0; + bool created_ = false; + trace::Writer* writer_ = nullptr; + std::unordered_map> shadow_; + std::vector maps_; + std::unordered_map generic_; + std::map, Reloc> relocs_; + std::map callMap_; + }; + +} // namespace + +int main(int argc, char** argv) { Rewriter rw; - std::vector files; + std::vector files; for (int i = 1; i < argc; ++i) { if (!std::strcmp(argv[i], "--align") && i + 1 < argc) { rw.align = std::strtoull(argv[++i], nullptr, 0); diff --git a/tools/trace_replay/trace_env_overrides_test.cpp b/tools/trace_replay/trace_env_overrides_test.cpp index 0b9766eb..7b05f9d7 100644 --- a/tools/trace_replay/trace_env_overrides_test.cpp +++ b/tools/trace_replay/trace_env_overrides_test.cpp @@ -26,50 +26,47 @@ namespace { -int gFailures = 0; + int gFailures = 0; -void ExpectSplit(const std::string& input, const std::vector& expected) { - const std::vector actual = mobilegl_trace::SplitSemicolonList(input); - if (actual == expected) { - return; + void ExpectSplit(const std::string& input, const std::vector& expected) { + const std::vector actual = mobilegl_trace::SplitSemicolonList(input); + if (actual == expected) { + return; + } + ++gFailures; + std::cerr << "SplitSemicolonList(\"" << input << "\") gave " << actual.size() << " entries, expected " + << expected.size() << ":"; + for (const std::string& entry : actual) { + std::cerr << " [" << entry << "]"; + } + std::cerr << "\n"; } - ++gFailures; - std::cerr << "SplitSemicolonList(\"" << input << "\") gave " << actual.size() - << " entries, expected " << expected.size() << ":"; - for (const std::string& entry : actual) { - std::cerr << " [" << entry << "]"; - } - std::cerr << "\n"; -} -const char* ActionName(mobilegl_trace::EnvOverrideAction action) { - switch (action) { + const char* ActionName(mobilegl_trace::EnvOverrideAction action) { + switch (action) { case mobilegl_trace::EnvOverrideAction::Ignore: return "Ignore"; case mobilegl_trace::EnvOverrideAction::Set: return "Set"; case mobilegl_trace::EnvOverrideAction::Unset: return "Unset"; + } + return "?"; } - return "?"; -} -void ExpectParse(const std::string& entry, - mobilegl_trace::EnvOverrideAction expectedAction, - const std::string& expectedKey, - const std::string& expectedValue) { - std::string key = ""; - std::string value = ""; - const mobilegl_trace::EnvOverrideAction action = - mobilegl_trace::ParseEnvOverride(entry, &key, &value); - if (action == expectedAction && key == expectedKey && value == expectedValue) { - return; + void ExpectParse(const std::string& entry, mobilegl_trace::EnvOverrideAction expectedAction, + const std::string& expectedKey, const std::string& expectedValue) { + std::string key = ""; + std::string value = ""; + const mobilegl_trace::EnvOverrideAction action = mobilegl_trace::ParseEnvOverride(entry, &key, &value); + if (action == expectedAction && key == expectedKey && value == expectedValue) { + return; + } + ++gFailures; + std::cerr << "ParseEnvOverride(\"" << entry << "\") gave " << ActionName(action) << " key=[" << key + << "] value=[" << value << "], expected " << ActionName(expectedAction) << " key=[" << expectedKey + << "] value=[" << expectedValue << "]\n"; } - ++gFailures; - std::cerr << "ParseEnvOverride(\"" << entry << "\") gave " << ActionName(action) << " key=[" - << key << "] value=[" << value << "], expected " << ActionName(expectedAction) - << " key=[" << expectedKey << "] value=[" << expectedValue << "]\n"; -} } // namespace @@ -79,8 +76,7 @@ int main() { // Splitting. ExpectSplit("", {}); ExpectSplit("MOBILEGL_PIPE_PUSH=1", {"MOBILEGL_PIPE_PUSH=1"}); - ExpectSplit("MOBILEGL_PIPE_PUSH=1;MOBILEGL_PIPE_VERIFY=1", - {"MOBILEGL_PIPE_PUSH=1", "MOBILEGL_PIPE_VERIFY=1"}); + ExpectSplit("MOBILEGL_PIPE_PUSH=1;MOBILEGL_PIPE_VERIFY=1", {"MOBILEGL_PIPE_PUSH=1", "MOBILEGL_PIPE_VERIFY=1"}); // A trailing ';' is what a caller that joins a list gets for free, and an empty entry // must not be turned into an unsetenv("") - the whole passthrough would then depend on // how carefully the shell script trimmed its own string. diff --git a/tools/trace_replay/trace_replay_cli.cpp b/tools/trace_replay/trace_replay_cli.cpp index 76a00591..524e8f27 100644 --- a/tools/trace_replay/trace_replay_cli.cpp +++ b/tools/trace_replay/trace_replay_cli.cpp @@ -17,206 +17,206 @@ extern "C" void mobilegl_trace_set_requested_size(int width, int height); namespace { -void PrintUsage(const char *argv0) { - std::cerr - << "Usage: " << argv0 << " --trace PATH --target-call N --output DIR [options]\n" - << "\n" - << "Options:\n" - << " --golden PATH Golden PNG to compare against\n" - << " --alternate-golden PATH Additional acceptable golden PNG\n" - << " --diff PATH Difference PNG output path\n" - << " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n" - << " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n" - << " --width N Replay surface width override\n" - << " --height N Replay surface height override\n" - << " --window-surface Replay to a native window surface\n" - << " --pbuffer-surface Replay to an EGL pbuffer surface (default)\n" - << " --hold-ms N Keep the replay process alive after retrace (default: 0)\n" - << " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n" - << " --crop-x N Compare crop x\n" - << " --crop-y N Compare crop y\n" - << " --crop-width N Compare crop width\n" - << " --crop-height N Compare crop height\n" - << " --coherent-as-flush Set MOBILEGL_COHERENT_AS_FLUSH=1 for the replay\n" - << " --benchmark Frame-timing mode: replay the whole trace, record a\n" - << " wall-clock timestamp at every frame boundary, and skip\n" - << " the snapshot and the golden comparison. --target-call is\n" - << " not required in this mode.\n" - << " --benchmark-tail-frames=N Frames at the end of the run the mean/median/p95 are\n" - << " computed over (default: 200, clamped to the frame count)\n" - << " --benchmark-finish=0/1 glFinish at every frame boundary so a frame time covers\n" - << " GPU completion and not just CPU submission (default: 1)\n" - << " --benchmark-result=PATH Timing JSON output (default: OUTPUT/benchmark.json)\n" - << " --dump-fbo-attachments CALL:DIR[:FBO,FBO,...]\n" - << " At CALL, write every colour attachment and the depth\n" - << " attachment of every live framebuffer object into DIR as\n" - << " fbo-att.png / fbo-depth.png, plus a manifest.txt\n" - << " of formats and per-channel statistics. Repeatable.\n"; -} - -bool ReadValue(int argc, char **argv, int &index, std::string &out) { - if (index + 1 >= argc) { - std::cerr << "Missing value for " << argv[index] << "\n"; - return false; - } - out = argv[++index]; - return true; -} - -bool ReadInt(int argc, char **argv, int &index, int &out) { - std::string value; - if (!ReadValue(argc, argv, index, value)) { - return false; - } - out = std::atoi(value.c_str()); - return true; -} - -bool ReadLongLong(int argc, char **argv, int &index, long long &out) { - std::string value; - if (!ReadValue(argc, argv, index, value)) { - return false; - } - out = std::strtoll(value.c_str(), nullptr, 10); - return true; -} - -bool ReadDouble(int argc, char **argv, int &index, double &out) { - std::string value; - if (!ReadValue(argc, argv, index, value)) { - return false; - } - out = std::strtod(value.c_str(), nullptr); - return true; -} - -// The benchmark options are documented as --name=VALUE. The space-separated spelling every -// other option here uses is accepted too, so --benchmark-tail-frames=300 and -// --benchmark-tail-frames 300 both work; hasInlineValue says which one was given. -bool MatchOption(const std::string &arg, const char *name, std::string &inlineValue, bool &hasInlineValue) { - if (arg == name) { - hasInlineValue = false; - return true; - } - const std::string prefix = std::string(name) + "="; - if (arg.compare(0, prefix.size(), prefix) == 0) { - inlineValue = arg.substr(prefix.size()); - hasInlineValue = true; - return true; - } - return false; -} - -bool ParseArgs(int argc, char **argv, mobilegl_trace::Request &request) { - request.backend = "DirectGLES"; - // MOBILEGL_ITRP_FIX is a tri-state (Auto/ForceOn/ForceOff); the request only carries ForceOn. - if (const char *itrpFix = std::getenv("MOBILEGL_ITRP_FIX")) { - std::string value = itrpFix; - for (char &ch : value) ch = static_cast(std::tolower(static_cast(ch))); - request.iterationRPFix = value == "1" || value == "true" || value == "on" || value == "forceon"; + void PrintUsage(const char* argv0) { + std::cerr << "Usage: " << argv0 << " --trace PATH --target-call N --output DIR [options]\n" + << "\n" + << "Options:\n" + << " --golden PATH Golden PNG to compare against\n" + << " --alternate-golden PATH Additional acceptable golden PNG\n" + << " --diff PATH Difference PNG output path\n" + << " --backend NAME DirectGLES or DirectVulkan (default: DirectGLES)\n" + << " --mobilegl-library PATH libMobileGL.so path (default: libMobileGL.so)\n" + << " --width N Replay surface width override\n" + << " --height N Replay surface height override\n" + << " --window-surface Replay to a native window surface\n" + << " --pbuffer-surface Replay to an EGL pbuffer surface (default)\n" + << " --hold-ms N Keep the replay process alive after retrace (default: 0)\n" + << " --ssim-threshold N Minimum SSIM required to pass (default: 0.99)\n" + << " --crop-x N Compare crop x\n" + << " --crop-y N Compare crop y\n" + << " --crop-width N Compare crop width\n" + << " --crop-height N Compare crop height\n" + << " --coherent-as-flush Set MOBILEGL_COHERENT_AS_FLUSH=1 for the replay\n" + << " --benchmark Frame-timing mode: replay the whole trace, record a\n" + << " wall-clock timestamp at every frame boundary, and skip\n" + << " the snapshot and the golden comparison. --target-call is\n" + << " not required in this mode.\n" + << " --benchmark-tail-frames=N Frames at the end of the run the mean/median/p95 are\n" + << " computed over (default: 200, clamped to the frame count)\n" + << " --benchmark-finish=0/1 glFinish at every frame boundary so a frame time covers\n" + << " GPU completion and not just CPU submission (default: 1)\n" + << " --benchmark-result=PATH Timing JSON output (default: OUTPUT/benchmark.json)\n" + << " --dump-fbo-attachments CALL:DIR[:FBO,FBO,...]\n" + << " At CALL, write every colour attachment and the depth\n" + << " attachment of every live framebuffer object into DIR as\n" + << " fbo-att.png / fbo-depth.png, plus a manifest.txt\n" + << " of formats and per-channel statistics. Repeatable.\n"; } - for (int i = 1; i < argc; ++i) { - const std::string arg = argv[i]; - std::string optionValue; - bool optionHasValue = false; - if (arg == "--trace") { - if (!ReadValue(argc, argv, i, request.tracePath)) return false; - } else if (arg == "--golden") { - if (!ReadValue(argc, argv, i, request.goldenPath)) return false; - } else if (arg == "--alternate-golden") { - std::string alternateGolden; - if (!ReadValue(argc, argv, i, alternateGolden)) return false; - if (!alternateGolden.empty()) { - request.alternateGoldenPaths.push_back(alternateGolden); - } - } else if (arg == "--diff") { - if (!ReadValue(argc, argv, i, request.diffPath)) return false; - } else if (arg == "--output") { - if (!ReadValue(argc, argv, i, request.outputDir)) return false; - } else if (arg == "--backend") { - if (!ReadValue(argc, argv, i, request.backend)) return false; - } else if (arg == "--mobilegl-library") { - if (!ReadValue(argc, argv, i, request.mobileGlLibrary)) return false; - } else if (arg == "--target-frame") { - if (!ReadInt(argc, argv, i, request.targetFrame)) return false; - } else if (arg == "--target-call") { - if (!ReadLongLong(argc, argv, i, request.targetCall)) return false; - } else if (arg == "--width") { - if (!ReadInt(argc, argv, i, request.width)) return false; - } else if (arg == "--height") { - if (!ReadInt(argc, argv, i, request.height)) return false; - } else if (arg == "--window-surface") { - request.usePbuffer = false; - } else if (arg == "--pbuffer-surface") { - request.usePbuffer = true; - } else if (arg == "--hold-ms") { - if (!ReadInt(argc, argv, i, request.holdMs)) return false; - } else if (arg == "--ssim-threshold") { - if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false; - } else if (arg == "--crop-x") { - if (!ReadInt(argc, argv, i, request.cropX)) return false; - } else if (arg == "--crop-y") { - if (!ReadInt(argc, argv, i, request.cropY)) return false; - } else if (arg == "--crop-width") { - if (!ReadInt(argc, argv, i, request.cropWidth)) return false; - } else if (arg == "--crop-height") { - if (!ReadInt(argc, argv, i, request.cropHeight)) return false; - } else if (arg == "--coherent-as-flush") { - request.coherentAsFlush = true; - } else if (arg == "--benchmark") { - request.benchmark = true; - } else if (MatchOption(arg, "--benchmark-tail-frames", optionValue, optionHasValue)) { - if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; - request.benchmarkTailFrames = std::atoi(optionValue.c_str()); - } else if (MatchOption(arg, "--benchmark-finish", optionValue, optionHasValue)) { - if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; - request.benchmarkFinish = optionValue != "0"; - } else if (MatchOption(arg, "--benchmark-result", optionValue, optionHasValue)) { - if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; - request.benchmarkResultPath = optionValue; - } else if (arg == "--dump-fbo-attachments") { - std::string dumpPoint; - if (!ReadValue(argc, argv, i, dumpPoint)) return false; - if (dumpPoint.find(':') == std::string::npos) { - std::cerr << "--dump-fbo-attachments expects CALL:DIR[:FBO,FBO,...]\n"; - return false; - } - request.fboAttachmentDumps.push_back(dumpPoint); - } else if (arg == "--help" || arg == "-h") { - return false; - } else { - std::cerr << "Unknown argument: " << arg << "\n"; + bool ReadValue(int argc, char** argv, int& index, std::string& out) { + if (index + 1 >= argc) { + std::cerr << "Missing value for " << argv[index] << "\n"; return false; } + out = argv[++index]; + return true; } - if (request.tracePath.empty()) { - std::cerr << "--trace is required\n"; + bool ReadInt(int argc, char** argv, int& index, int& out) { + std::string value; + if (!ReadValue(argc, argv, index, value)) { + return false; + } + out = std::atoi(value.c_str()); + return true; + } + + bool ReadLongLong(int argc, char** argv, int& index, long long& out) { + std::string value; + if (!ReadValue(argc, argv, index, value)) { + return false; + } + out = std::strtoll(value.c_str(), nullptr, 10); + return true; + } + + bool ReadDouble(int argc, char** argv, int& index, double& out) { + std::string value; + if (!ReadValue(argc, argv, index, value)) { + return false; + } + out = std::strtod(value.c_str(), nullptr); + return true; + } + + // The benchmark options are documented as --name=VALUE. The space-separated spelling every + // other option here uses is accepted too, so --benchmark-tail-frames=300 and + // --benchmark-tail-frames 300 both work; hasInlineValue says which one was given. + bool MatchOption(const std::string& arg, const char* name, std::string& inlineValue, bool& hasInlineValue) { + if (arg == name) { + hasInlineValue = false; + return true; + } + const std::string prefix = std::string(name) + "="; + if (arg.compare(0, prefix.size(), prefix) == 0) { + inlineValue = arg.substr(prefix.size()); + hasInlineValue = true; + return true; + } return false; } - if (request.outputDir.empty()) { - std::cerr << "--output is required\n"; - return false; + + bool ParseArgs(int argc, char** argv, mobilegl_trace::Request& request) { + request.backend = "DirectGLES"; + // MOBILEGL_ITRP_FIX is a tri-state (Auto/ForceOn/ForceOff); the request only carries ForceOn. + if (const char* itrpFix = std::getenv("MOBILEGL_ITRP_FIX")) { + std::string value = itrpFix; + for (char& ch : value) + ch = static_cast(std::tolower(static_cast(ch))); + request.iterationRPFix = value == "1" || value == "true" || value == "on" || value == "forceon"; + } + + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + std::string optionValue; + bool optionHasValue = false; + if (arg == "--trace") { + if (!ReadValue(argc, argv, i, request.tracePath)) return false; + } else if (arg == "--golden") { + if (!ReadValue(argc, argv, i, request.goldenPath)) return false; + } else if (arg == "--alternate-golden") { + std::string alternateGolden; + if (!ReadValue(argc, argv, i, alternateGolden)) return false; + if (!alternateGolden.empty()) { + request.alternateGoldenPaths.push_back(alternateGolden); + } + } else if (arg == "--diff") { + if (!ReadValue(argc, argv, i, request.diffPath)) return false; + } else if (arg == "--output") { + if (!ReadValue(argc, argv, i, request.outputDir)) return false; + } else if (arg == "--backend") { + if (!ReadValue(argc, argv, i, request.backend)) return false; + } else if (arg == "--mobilegl-library") { + if (!ReadValue(argc, argv, i, request.mobileGlLibrary)) return false; + } else if (arg == "--target-frame") { + if (!ReadInt(argc, argv, i, request.targetFrame)) return false; + } else if (arg == "--target-call") { + if (!ReadLongLong(argc, argv, i, request.targetCall)) return false; + } else if (arg == "--width") { + if (!ReadInt(argc, argv, i, request.width)) return false; + } else if (arg == "--height") { + if (!ReadInt(argc, argv, i, request.height)) return false; + } else if (arg == "--window-surface") { + request.usePbuffer = false; + } else if (arg == "--pbuffer-surface") { + request.usePbuffer = true; + } else if (arg == "--hold-ms") { + if (!ReadInt(argc, argv, i, request.holdMs)) return false; + } else if (arg == "--ssim-threshold") { + if (!ReadDouble(argc, argv, i, request.ssimThreshold)) return false; + } else if (arg == "--crop-x") { + if (!ReadInt(argc, argv, i, request.cropX)) return false; + } else if (arg == "--crop-y") { + if (!ReadInt(argc, argv, i, request.cropY)) return false; + } else if (arg == "--crop-width") { + if (!ReadInt(argc, argv, i, request.cropWidth)) return false; + } else if (arg == "--crop-height") { + if (!ReadInt(argc, argv, i, request.cropHeight)) return false; + } else if (arg == "--coherent-as-flush") { + request.coherentAsFlush = true; + } else if (arg == "--benchmark") { + request.benchmark = true; + } else if (MatchOption(arg, "--benchmark-tail-frames", optionValue, optionHasValue)) { + if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; + request.benchmarkTailFrames = std::atoi(optionValue.c_str()); + } else if (MatchOption(arg, "--benchmark-finish", optionValue, optionHasValue)) { + if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; + request.benchmarkFinish = optionValue != "0"; + } else if (MatchOption(arg, "--benchmark-result", optionValue, optionHasValue)) { + if (!optionHasValue && !ReadValue(argc, argv, i, optionValue)) return false; + request.benchmarkResultPath = optionValue; + } else if (arg == "--dump-fbo-attachments") { + std::string dumpPoint; + if (!ReadValue(argc, argv, i, dumpPoint)) return false; + if (dumpPoint.find(':') == std::string::npos) { + std::cerr << "--dump-fbo-attachments expects CALL:DIR[:FBO,FBO,...]\n"; + return false; + } + request.fboAttachmentDumps.push_back(dumpPoint); + } else if (arg == "--help" || arg == "-h") { + return false; + } else { + std::cerr << "Unknown argument: " << arg << "\n"; + return false; + } + } + + if (request.tracePath.empty()) { + std::cerr << "--trace is required\n"; + return false; + } + if (request.outputDir.empty()) { + std::cerr << "--output is required\n"; + return false; + } + if (request.targetCall < 0 && !request.benchmark) { + std::cerr << "--target-call is required\n"; + return false; + } + if (request.holdMs < 0) { + std::cerr << "--hold-ms must be non-negative\n"; + return false; + } + if (request.benchmarkTailFrames <= 0) { + std::cerr << "--benchmark-tail-frames must be positive\n"; + return false; + } + return true; } - if (request.targetCall < 0 && !request.benchmark) { - std::cerr << "--target-call is required\n"; - return false; - } - if (request.holdMs < 0) { - std::cerr << "--hold-ms must be non-negative\n"; - return false; - } - if (request.benchmarkTailFrames <= 0) { - std::cerr << "--benchmark-tail-frames must be positive\n"; - return false; - } - return true; -} } // namespace -int main(int argc, char **argv) { +int main(int argc, char** argv) { mobilegl_trace::Request request; if (!ParseArgs(argc, argv, request)) { PrintUsage(argv[0]); diff --git a/tools/wgl-smoke/glfw_smoke.cpp b/tools/wgl-smoke/glfw_smoke.cpp index 5406d914..4785635a 100644 --- a/tools/wgl-smoke/glfw_smoke.cpp +++ b/tools/wgl-smoke/glfw_smoke.cpp @@ -68,8 +68,8 @@ static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) { static volatile LONG entered = 0; if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH; if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH; - HANDLE f = CreateFileA("glfw-crash-stack.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, nullptr); + HANDLE f = + CreateFileA("glfw-crash-stack.txt", GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (f == INVALID_HANDLE_VALUE) TerminateProcess(GetCurrentProcess(), 0x43); RawPrint(f, "AV at "); RawHex(f, (unsigned long long)ep->ExceptionRecord->ExceptionAddress); diff --git a/tools/wgl-smoke/wgl_smoke.cpp b/tools/wgl-smoke/wgl_smoke.cpp index a2bbb403..e509f2f7 100644 --- a/tools/wgl-smoke/wgl_smoke.cpp +++ b/tools/wgl-smoke/wgl_smoke.cpp @@ -184,9 +184,9 @@ static LONG WINAPI CrashDump(EXCEPTION_POINTERS* ep) { static volatile LONG entered = 0; if (ep->ExceptionRecord->ExceptionCode != 0xC0000005) return EXCEPTION_CONTINUE_SEARCH; if (InterlockedExchange(&entered, 1)) return EXCEPTION_CONTINUE_SEARCH; - HANDLE f = CreateFileA( - "C:\\Users\\yello\\AndroidStudioProjects\\FoldCraftLauncher\\MobileGL\\.claude\\worktrees\\wgl-host\\wgl-smoke\\crash-stack.txt", - GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + HANDLE f = CreateFileA("C:\\Users\\yello\\AndroidStudioProjects\\FoldCraftLauncher\\MobileGL\\." + "claude\\worktrees\\wgl-host\\wgl-smoke\\crash-stack.txt", + GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (f == INVALID_HANDLE_VALUE) { TerminateProcess(GetCurrentProcess(), 0x43); } @@ -290,10 +290,8 @@ int main() { eglGetError_ ? eglGetError_() : -1); } - auto wglGetExtensionsStringARB = - (PFN_wglGetExtensionsStringARB)p_wglGetProcAddress("wglGetExtensionsStringARB"); - auto wglCreateContextAttribsARB = - (PFN_wglCreateContextAttribsARB)p_wglGetProcAddress("wglCreateContextAttribsARB"); + auto wglGetExtensionsStringARB = (PFN_wglGetExtensionsStringARB)p_wglGetProcAddress("wglGetExtensionsStringARB"); + auto wglCreateContextAttribsARB = (PFN_wglCreateContextAttribsARB)p_wglGetProcAddress("wglCreateContextAttribsARB"); auto wglSwapIntervalEXT = (PFN_wglSwapIntervalEXT)p_wglGetProcAddress("wglSwapIntervalEXT"); auto wglGetPixelFormatAttribivARB = (PFN_wglGetPixelFormatAttribivARB)p_wglGetProcAddress("wglGetPixelFormatAttribivARB"); @@ -326,7 +324,7 @@ int main() { int chosenFormat = 0; for (int i = 1; i <= nativeCount; ++i) { const int attribs[] = {WGL_SUPPORT_OPENGL_ARB, WGL_DRAW_TO_WINDOW_ARB, WGL_PIXEL_TYPE_ARB, - WGL_DOUBLE_BUFFER_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB}; + WGL_DOUBLE_BUFFER_ARB, WGL_DEPTH_BITS_ARB, WGL_STENCIL_BITS_ARB}; int values[6] = {}; if (!wglGetPixelFormatAttribivARB(dc, i, 0, 6, attribs, values)) continue; if (values[0] && values[1] && values[2] == WGL_TYPE_RGBA_ARB && values[3] && values[4] >= 24 && @@ -342,11 +340,16 @@ int main() { if (!SetPixelFormat(dc, chosenFormat, &realPfd)) FAIL("SetPixelFormat (real) failed"); const int ctxAttribs[] = { - WGL_CONTEXT_OPENGL_NO_ERROR_ARB, 1, - WGL_CONTEXT_MAJOR_VERSION_ARB, 3, - WGL_CONTEXT_MINOR_VERSION_ARB, 2, - WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, - WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + WGL_CONTEXT_OPENGL_NO_ERROR_ARB, + 1, + WGL_CONTEXT_MAJOR_VERSION_ARB, + 3, + WGL_CONTEXT_MINOR_VERSION_ARB, + 2, + WGL_CONTEXT_FLAGS_ARB, + WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, + WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0, }; HGLRC ctx = wglCreateContextAttribsARB(dc, nullptr, ctxAttribs); @@ -389,8 +392,7 @@ int main() { GLint major = 0, minor = 0; glGetIntegerv_(GL_MAJOR_VERSION, &major); glGetIntegerv_(GL_MINOR_VERSION, &minor); - std::printf("real ctx: GL %d.%d | %s | %s\n", major, minor, glGetString_(GL_RENDERER), - glGetString_(GL_VERSION)); + std::printf("real ctx: GL %d.%d | %s | %s\n", major, minor, glGetString_(GL_RENDERER), glGetString_(GL_VERSION)); if (major < 3 || (major == 3 && minor < 2)) FAIL("context version below 3.2"); const char* vsSrc = "#version 150 core\n"