// MobileGL - MobileGL/MG_State/GLState/ProgramState/ProgramTranslationCache.h
// Copyright (c) 2025-2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
//   https://www.gnu.org/licenses/gpl-3.0.txt
//   https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header

#pragma once
#include <Includes.h>
#include <MG_State/GLState/ProgramState/ProgramObject.h>
#include <MG_Util/ShaderTranspiler/TranslationCache.h>

namespace MobileGL::MG_State::GLState {
    // ===================================================================================
    // L1 of the shader translation memo: THE WHOLE FRONT END of one glLinkProgram.
    //
    // A hit skips the glslang link and mapIO, GlslangToSpv, the 11-pass
    // SanitizeAndOptimizeBinary chain, buildReflection, and the global-UBO routing pass. No
    // TProgram is constructed at all - which is only possible because the GL query surface no
    // longer reads one (see ProgramObject::UniformReflection and
    // ProgramLinkTask::SnapshotGlslangReflection).
    //
    // IT DOES NOT SKIP THE PARSE, and no widening of this payload could: the parse belongs to
    // glCompileShader, a different entry point one job earlier, and it has already run by the
    // time a link looks this key up. Skipping it is L1c's job - the compile half of the memo,
    // in MG_Util/ShaderTranspiler/TranslationCache.h. The two together are what make a
    // repeated program build construct no glslang object of any kind; either one alone leaves
    // roughly half the front end on the hot path (~322 us of parse against a ~650 us
    // CTS-shaped program build, and 1.45-1.48x measured on device with L1 alone).
    //
    // WHY THE PAYLOAD IS THE WHOLE THING rather than just the SPIR-V: the frontend answers
    // glGetActiveUniform, glGetProgramResource*, glGetUniformLocation and the rest out of
    // LinkArtifacts, and glUniform*/glGetUniform* out of SpirvArtifacts. Caching only the
    // modules would have left the link on the hot path to rebuild exactly the data the
    // payload can carry.
    //
    // WHY IT LIVES HERE AND NOT IN MG_Util: the payload is a ProgramObject::LinkArtifacts
    // plus a ProgramObject::SpirvArtifacts, and MG_Util must not depend on MG_State. The
    // KEY is plain bytes and stays in MG_Util (BuildSpirvTranslationKey), so both layers
    // agree on exactly one definition of "the same front-end input".
    //
    // EVERYTHING IN THE PAYLOAD IS PLAIN OWNED DATA. `link.program` is null by construction:
    // the whole point is that a hit never has a glslang arena to point into. Both structs
    // were audited field by field - the only member that ever pointed into glslang-owned
    // memory was `program` itself, and TUniformInitializer / XfbVarying, which look like
    // glslang types, are std::string + std::vector aggregates.
    struct ProgramTranslationResult {
        // program == nullptr, always. Asserted at insert.
        ProgramObject::LinkArtifacts link;
        ProgramObject::SpirvArtifacts spirv;
    };
    using ProgramTranslationResultPtr = SharedPtr<const ProgramTranslationResult>;

    SizeT ProgramTranslationResultBytes(const ProgramTranslationResult& result);

    // Process-global, and safe to be: the FRONT-END environment fingerprint is in the key
    // (see CompileEnv::frontendFingerprint), so a program built under one context's glslang
    // limits can never be handed to a context with different ones - while two contexts on
    // DIFFERENT GPUs that agree on those limits deliberately share entries.
    //
    // Global rather than per-context because the producer runs on a ShaderCompilePool worker
    // and must not reach MG_State::pGLContext.
    MG_Util::ShaderTranspiler::BoundedTranslationCache<ProgramTranslationResult>&
    GetProgramTranslationCache();

    void ClearProgramTranslationCache();
    void LogProgramTranslationCacheStats();
} // namespace MobileGL::MG_State::GLState
