code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <atomic>
#include <fstream>
#include <functional>
#include <mutex>
#include <string>
#include <thread>

#include "common/alignment.h"
#include "common/assert.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "common/thread_worker.h"
#include "shader_recompiler/backend/glasm/emit_glasm.h"
#include "shader_recompiler/backend/glsl/emit_glsl.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/frontend/ir/program.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"
#include "shader_recompiler/frontend/maxwell/translate_program.h"
#include "shader_recompiler/profile.h"
#include "video_core/engines/draw_manager.h"
#include "video_core/engines/kepler_compute.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/shader_cache.h"
#include "video_core/shader_environment.h"
#include "video_core/shader_notify.h"

namespace OpenGL {
namespace {
using Shader::Backend::GLASM::EmitGLASM;
using Shader::Backend::GLSL::EmitGLSL;
using Shader::Backend::SPIRV::EmitSPIRV;
using Shader::Maxwell::ConvertLegacyToGeneric;
using Shader::Maxwell::GenerateGeometryPassthrough;
using Shader::Maxwell::MergeDualVertexPrograms;
using Shader::Maxwell::TranslateProgram;
using VideoCommon::ComputeEnvironment;
using VideoCommon::FileEnvironment;
using VideoCommon::GenericEnvironment;
using VideoCommon::GraphicsEnvironment;
using VideoCommon::LoadPipelines;
using VideoCommon::SerializePipeline;
using Context = ShaderContext::Context;

constexpr u32 CACHE_VERSION = 10;

template <typename Container>
auto MakeSpan(Container& container) {
    return std::span(container.data(), container.size());
}

Shader::OutputTopology MaxwellToOutputTopology(Maxwell::PrimitiveTopology topology) {
    switch (topology) {
    case Maxwell::PrimitiveTopology::Points:
        return Shader::OutputTopology::PointList;
    case Maxwell::PrimitiveTopology::LineStrip:
        return Shader::OutputTopology::LineStrip;
    default:
        return Shader::OutputTopology::TriangleStrip;
    }
}

Shader::RuntimeInfo MakeRuntimeInfo(const GraphicsPipelineKey& key,
                                    const Shader::IR::Program& program,
                                    const Shader::IR::Program* previous_program,
                                    bool glasm_use_storage_buffers, bool use_assembly_shaders) {
    Shader::RuntimeInfo info;
    if (previous_program) {
        info.previous_stage_stores = previous_program->info.stores;
        info.previous_stage_legacy_stores_mapping = previous_program->info.legacy_stores_mapping;
    } else {
        // Mark all stores as available for vertex shaders
        info.previous_stage_stores.mask.set();
    }
    switch (program.stage) {
    case Shader::Stage::VertexB:
    case Shader::Stage::Geometry:
        if (!use_assembly_shaders && key.xfb_enabled != 0) {
            auto [varyings, count] = VideoCommon::MakeTransformFeedbackVaryings(key.xfb_state);
            info.xfb_varyings = varyings;
            info.xfb_count = count;
        }
        break;
    case Shader::Stage::TessellationEval:
        // Flip the face, as OpenGL's drawing is flipped.
        info.tess_clockwise = key.tessellation_clockwise == 0;
        info.tess_primitive = [&key] {
            switch (key.tessellation_primitive) {
            case Maxwell::Tessellation::DomainType::Isolines:
                return Shader::TessPrimitive::Isolines;
            case Maxwell::Tessellation::DomainType::Triangles:
                return Shader::TessPrimitive::Triangles;
            case Maxwell::Tessellation::DomainType::Quads:
                return Shader::TessPrimitive::Quads;
            }
            ASSERT(false);
            return Shader::TessPrimitive::Triangles;
        }();
        info.tess_spacing = [&] {
            switch (key.tessellation_spacing) {
            case Maxwell::Tessellation::Spacing::Integer:
                return Shader::TessSpacing::Equal;
            case Maxwell::Tessellation::Spacing::FractionalOdd:
                return Shader::TessSpacing::FractionalOdd;
            case Maxwell::Tessellation::Spacing::FractionalEven:
                return Shader::TessSpacing::FractionalEven;
            }
            ASSERT(false);
            return Shader::TessSpacing::Equal;
        }();
        break;
    case Shader::Stage::Fragment:
        info.force_early_z = key.early_z != 0;
        break;
    default:
        break;
    }
    switch (key.gs_input_topology) {
    case Maxwell::PrimitiveTopology::Points:
        info.input_topology = Shader::InputTopology::Points;
        break;
    case Maxwell::PrimitiveTopology::Lines:
    case Maxwell::PrimitiveTopology::LineLoop:
    case Maxwell::PrimitiveTopology::LineStrip:
        info.input_topology = Shader::InputTopology::Lines;
        break;
    case Maxwell::PrimitiveTopology::Triangles:
    case Maxwell::PrimitiveTopology::TriangleStrip:
    case Maxwell::PrimitiveTopology::TriangleFan:
    case Maxwell::PrimitiveTopology::Quads:
    case Maxwell::PrimitiveTopology::QuadStrip:
    case Maxwell::PrimitiveTopology::Polygon:
    case Maxwell::PrimitiveTopology::Patches:
        info.input_topology = Shader::InputTopology::Triangles;
        break;
    case Maxwell::PrimitiveTopology::LinesAdjacency:
    case Maxwell::PrimitiveTopology::LineStripAdjacency:
        info.input_topology = Shader::InputTopology::LinesAdjacency;
        break;
    case Maxwell::PrimitiveTopology::TrianglesAdjacency:
    case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
        info.input_topology = Shader::InputTopology::TrianglesAdjacency;
        break;
    }
    info.glasm_use_storage_buffers = glasm_use_storage_buffers;
    return info;
}

void SetXfbState(VideoCommon::TransformFeedbackState& state, const Maxwell& regs) {
    std::ranges::transform(regs.transform_feedback.controls, state.layouts.begin(),
                           [](const auto& layout) {
                               return VideoCommon::TransformFeedbackState::Layout{
                                   .stream = layout.stream,
                                   .varying_count = layout.varying_count,
                                   .stride = layout.stride,
                               };
                           });
    state.varyings = regs.stream_out_layout;
}
} // Anonymous namespace

ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
                         Core::Frontend::EmuWindow& emu_window_, const Device& device_,
                         TextureCache& texture_cache_, BufferCache& buffer_cache_,
                         ProgramManager& program_manager_, StateTracker& state_tracker_,
                         VideoCore::ShaderNotify& shader_notify_)
    : VideoCommon::ShaderCache{device_memory_}, emu_window{emu_window_}, device{device_},
      texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_},
      state_tracker{state_tracker_}, shader_notify{shader_notify_},
      use_asynchronous_shaders{device.UseAsynchronousShaders()},
      strict_context_required{device.StrictContextRequired()},
      profile{
          .supported_spirv = 0x00010000,

          .unified_descriptor_binding = false,
          .support_descriptor_aliasing = false,
          .support_int8 = false,
          .support_int16 = false,
          .support_int64 = device.HasShaderInt64(),
          .support_vertex_instance_id = true,
          .support_float_controls = false,
          .support_separate_denorm_behavior = false,
          .support_separate_rounding_mode = false,
          .support_fp16_denorm_preserve = false,
          .support_fp32_denorm_preserve = false,
          .support_fp16_denorm_flush = false,
          .support_fp32_denorm_flush = false,
          .support_fp16_signed_zero_nan_preserve = false,
          .support_fp32_signed_zero_nan_preserve = false,
          .support_fp64_signed_zero_nan_preserve = false,
          .support_explicit_workgroup_layout = false,
          .support_vote = true,
          .support_viewport_index_layer_non_geometry =
              device.HasNvViewportArray2() || device.HasVertexViewportLayer(),
          .support_viewport_mask = device.HasNvViewportArray2(),
          .support_typeless_image_loads = device.HasImageLoadFormatted(),
          .support_demote_to_helper_invocation = false,
          .support_int64_atomics = false,
          .support_derivative_control = device.HasDerivativeControl(),
          .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
          .support_native_ndc = true,
          .support_gl_nv_gpu_shader_5 = device.HasNvGpuShader5(),
          .support_gl_amd_gpu_shader_half_float = device.HasAmdShaderHalfFloat(),
          .support_gl_texture_shadow_lod = device.HasTextureShadowLod(),
          .support_gl_warp_intrinsics = false,
          .support_gl_variable_aoffi = device.HasVariableAoffi(),
          .support_gl_sparse_textures = device.HasSparseTexture2(),
          .support_gl_derivative_control = device.HasDerivativeControl(),
          .support_geometry_streams = true,

          .warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyLargerThanGuest(),

          .lower_left_origin_mode = true,
          .need_declared_frag_colors = true,
          .need_fastmath_off = device.NeedsFastmathOff(),
          .need_gather_subpixel_offset = device.IsAmd() || device.IsIntel(),

          .has_broken_spirv_clamp = true,
          .has_broken_unsigned_image_offsets = true,
          .has_broken_signed_operations = true,
          .has_broken_fp16_float_controls = false,
          .has_gl_component_indexing_bug = device.HasComponentIndexingBug(),
          .has_gl_precise_bug = device.HasPreciseBug(),
          .has_gl_cbuf_ftou_bug = device.HasCbufFtouBug(),
          .has_gl_bool_ref_bug = device.HasBoolRefBug(),
          .ignore_nan_fp_comparisons = true,
          .gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
          .min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
          .max_user_clip_distances = 8,
      },
      host_info{
          .support_float64 = true,
          .support_float16 = false,
          .support_int64 = device.HasShaderInt64(),
          .needs_demote_reorder = device.IsAmd(),
          .support_snorm_render_buffer = false,
          .support_viewport_index_layer = device.HasVertexViewportLayer(),
          .min_ssbo_alignment = static_cast<u32>(device.GetShaderStorageBufferAlignment()),
          .support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
          .support_conditional_barrier = device.SupportsConditionalBarriers(),
      } {
    if (use_asynchronous_shaders) {
        workers = CreateWorkers();
    }
}

ShaderCache::~ShaderCache() = default;

void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                                    const VideoCore::DiskResourceLoadCallback& callback) {
    if (title_id == 0) {
        return;
    }
    const auto shader_dir{Common::FS::GetYuzuPath(Common::FS::YuzuPath::ShaderDir)};
    const auto base_dir{shader_dir / fmt::format("{:016x}", title_id)};
    if (!Common::FS::CreateDir(shader_dir) || !Common::FS::CreateDir(base_dir)) {
        LOG_ERROR(Common_Filesystem, "Failed to create shader cache directories");
        return;
    }
    shader_cache_filename = base_dir / "opengl.bin";

    if (!workers && !strict_context_required) {
        workers = CreateWorkers();
    }
    std::optional<Context> strict_context;
    if (strict_context_required) {
        strict_context.emplace(emu_window);
    }

    struct {
        std::mutex mutex;
        size_t total{};
        size_t built{};
        bool has_loaded{};
    } state;

    const auto queue_work{[&](Common::UniqueFunction<void, Context*>&& work) {
        if (strict_context_required) {
            work(&strict_context.value());
        } else {
            workers->QueueWork(std::move(work));
        }
    }};
    const auto load_compute{[&](std::ifstream& file, FileEnvironment env) {
        ComputePipelineKey key;
        file.read(reinterpret_cast<char*>(&key), sizeof(key));
        queue_work([this, key, env_ = std::move(env), &state, &callback](Context* ctx) mutable {
            ctx->pools.ReleaseContents();
            auto pipeline{CreateComputePipeline(ctx->pools, key, env_, true)};
            std::scoped_lock lock{state.mutex};
            if (pipeline) {
                compute_cache.emplace(key, std::move(pipeline));
            }
            ++state.built;
            if (state.has_loaded) {
                callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
            }
        });
        ++state.total;
    }};
    const auto load_graphics{[&](std::ifstream& file, std::vector<FileEnvironment> envs) {
        GraphicsPipelineKey key;
        file.read(reinterpret_cast<char*>(&key), sizeof(key));
        queue_work([this, key, envs_ = std::move(envs), &state, &callback](Context* ctx) mutable {
            boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
            for (auto& env : envs_) {
                env_ptrs.push_back(&env);
            }
            ctx->pools.ReleaseContents();
            auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false, true)};
            std::scoped_lock lock{state.mutex};
            if (pipeline) {
                graphics_cache.emplace(key, std::move(pipeline));
            }
            ++state.built;
            if (state.has_loaded) {
                callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
            }
        });
        ++state.total;
    }};
    LoadPipelines(stop_loading, shader_cache_filename, CACHE_VERSION, load_compute, load_graphics);

    LOG_INFO(Render_OpenGL, "Total Pipeline Count: {}", state.total);

    std::unique_lock lock{state.mutex};
    callback(VideoCore::LoadCallbackStage::Build, 0, state.total);
    state.has_loaded = true;
    lock.unlock();

    if (strict_context_required) {
        return;
    }
    workers->WaitForRequests(stop_loading);
    if (!use_asynchronous_shaders) {
        workers.reset();
    }
}

GraphicsPipeline* ShaderCache::CurrentGraphicsPipeline() {
    if (!RefreshStages(graphics_key.unique_hashes)) {
        current_pipeline = nullptr;
        return nullptr;
    }
    const auto& regs{maxwell3d->regs};
    graphics_key.raw = 0;
    graphics_key.early_z.Assign(regs.mandated_early_z != 0 ? 1 : 0);
    graphics_key.gs_input_topology.Assign(maxwell3d->draw_manager->GetDrawState().topology);
    graphics_key.tessellation_primitive.Assign(regs.tessellation.params.domain_type.Value());
    graphics_key.tessellation_spacing.Assign(regs.tessellation.params.spacing.Value());
    graphics_key.tessellation_clockwise.Assign(
        regs.tessellation.params.output_primitives.Value() ==
        Maxwell::Tessellation::OutputPrimitives::Triangles_CW);
    graphics_key.xfb_enabled.Assign(regs.transform_feedback_enabled != 0 ? 1 : 0);
    graphics_key.app_stage.Assign(maxwell3d->engine_state);
    if (graphics_key.xfb_enabled) {
        SetXfbState(graphics_key.xfb_state, regs);
    }
    if (current_pipeline && graphics_key == current_pipeline->Key()) {
        return BuiltPipeline(current_pipeline);
    }
    return CurrentGraphicsPipelineSlowPath();
}

GraphicsPipeline* ShaderCache::CurrentGraphicsPipelineSlowPath() {
    const auto [pair, is_new]{graphics_cache.try_emplace(graphics_key)};
    auto& pipeline{pair->second};
    if (is_new) {
        pipeline = CreateGraphicsPipeline();
    }
    if (!pipeline) {
        return nullptr;
    }
    current_pipeline = pipeline.get();
    return BuiltPipeline(current_pipeline);
}

GraphicsPipeline* ShaderCache::BuiltPipeline(GraphicsPipeline* pipeline) const noexcept {
    if (pipeline->IsBuilt()) {
        return pipeline;
    }
    if (!use_asynchronous_shaders) {
        return pipeline;
    }
    // If something is using depth, we can assume that games are not rendering anything which
    // will be used one time.
    if (maxwell3d->regs.zeta_enable) {
        return nullptr;
    }
    // If games are using a small index count, we can assume these are full screen quads.
    // Usually these shaders are only used once for building textures so we can assume they
    // can't be built async
    const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
    if (draw_state.index_buffer.count <= 6 || draw_state.vertex_buffer.count <= 6) {
        return pipeline;
    }
    return nullptr;
}

ComputePipeline* ShaderCache::CurrentComputePipeline() {
    const VideoCommon::ShaderInfo* const shader{ComputeShader()};
    if (!shader) {
        return nullptr;
    }
    const auto& qmd{kepler_compute->launch_description};
    const ComputePipelineKey key{
        .unique_hash = shader->unique_hash,
        .shared_memory_size = qmd.shared_alloc,
        .workgroup_size{qmd.block_dim_x, qmd.block_dim_y, qmd.block_dim_z},
    };
    const auto [pair, is_new]{compute_cache.try_emplace(key)};
    auto& pipeline{pair->second};
    if (!is_new) {
        return pipeline.get();
    }
    pipeline = CreateComputePipeline(key, shader);
    return pipeline.get();
}

std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline() {
    GraphicsEnvironments environments;
    GetGraphicsEnvironments(environments, graphics_key.unique_hashes);

    main_pools.ReleaseContents();
    auto pipeline{CreateGraphicsPipeline(main_pools, graphics_key, environments.Span(),
                                         use_asynchronous_shaders)};
    if (!pipeline || shader_cache_filename.empty()) {
        return pipeline;
    }
    boost::container::static_vector<const GenericEnvironment*, Maxwell::MaxShaderProgram> env_ptrs;
    for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
        if (graphics_key.unique_hashes[index] != 0) {
            env_ptrs.push_back(&environments.envs[index]);
        }
    }
    SerializePipeline(graphics_key, env_ptrs, shader_cache_filename, CACHE_VERSION);
    return pipeline;
}

std::unique_ptr<GraphicsPipeline> ShaderCache::CreateGraphicsPipeline(
    ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
    std::span<Shader::Environment* const> envs, bool use_shader_workers,
    bool force_context_flush) try {
    auto hash = key.Hash();
    LOG_INFO(Render_OpenGL, "0x{:016x}", hash);
    size_t env_index{};
    u32 total_storage_buffers{};
    std::array<Shader::IR::Program, Maxwell::MaxShaderProgram> programs;
    const bool uses_vertex_a{key.unique_hashes[0] != 0};
    const bool uses_vertex_b{key.unique_hashes[1] != 0};

    // Layer passthrough generation for devices without GL_ARB_shader_viewport_layer_array
    Shader::IR::Program* layer_source_program{};

    for (size_t index = 0; index < Maxwell::MaxShaderProgram; ++index) {
        const bool is_emulated_stage = layer_source_program != nullptr &&
                                       index == static_cast<u32>(Maxwell::ShaderType::Geometry);
        if (key.unique_hashes[index] == 0 && is_emulated_stage) {
            auto topology = MaxwellToOutputTopology(key.gs_input_topology);
            programs[index] = GenerateGeometryPassthrough(pools.inst, pools.block, host_info,
                                                          *layer_source_program, topology);
            continue;
        }
        if (key.unique_hashes[index] == 0) {
            continue;
        }
        Shader::Environment& env{*envs[env_index]};
        ++env_index;

        const u32 cfg_offset{static_cast<u32>(env.StartAddress() + sizeof(Shader::ProgramHeader))};
        Shader::Maxwell::Flow::CFG cfg(env, pools.flow_block, cfg_offset, index == 0);

        if (Settings::values.dump_shaders) {
            env.Dump(hash, key.unique_hashes[index]);
        }

        if (!uses_vertex_a || index != 1) {
            // Normal path
            programs[index] = TranslateProgram(pools.inst, pools.block, env, cfg, host_info);

            total_storage_buffers +=
                Shader::NumDescriptors(programs[index].info.storage_buffers_descriptors);
        } else {
            // VertexB path when VertexA is present.
            auto& program_va{programs[0]};
            auto program_vb{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
            total_storage_buffers +=
                Shader::NumDescriptors(program_vb.info.storage_buffers_descriptors);
            programs[index] = MergeDualVertexPrograms(program_va, program_vb, env);
        }

        if (programs[index].info.requires_layer_emulation) {
            layer_source_program = &programs[index];
        }
    }
    const u32 glasm_storage_buffer_limit{device.GetMaxGLASMStorageBufferBlocks()};
    const bool glasm_use_storage_buffers{total_storage_buffers <= glasm_storage_buffer_limit};

    std::array<const Shader::Info*, Maxwell::MaxShaderStage> infos{};

    std::array<std::string, 5> sources;
    std::array<std::vector<u32>, 5> sources_spirv;
    Shader::Backend::Bindings binding;
    Shader::IR::Program* previous_program{};
    const bool use_glasm{device.UseAssemblyShaders()};
    const size_t first_index = uses_vertex_a && uses_vertex_b ? 1 : 0;
    for (size_t index = first_index; index < Maxwell::MaxShaderProgram; ++index) {
        const bool is_emulated_stage = layer_source_program != nullptr &&
                                       index == static_cast<u32>(Maxwell::ShaderType::Geometry);
        if (key.unique_hashes[index] == 0 && !is_emulated_stage) {
            continue;
        }
        UNIMPLEMENTED_IF(index == 0);

        Shader::IR::Program& program{programs[index]};
        const size_t stage_index{index - 1};
        infos[stage_index] = &program.info;

        const auto runtime_info{
            MakeRuntimeInfo(key, program, previous_program, glasm_use_storage_buffers, use_glasm)};
        switch (device.GetShaderBackend()) {
        case Settings::ShaderBackend::Glsl:
            ConvertLegacyToGeneric(program, runtime_info);
            sources[stage_index] = EmitGLSL(profile, runtime_info, program, binding);
            break;
        case Settings::ShaderBackend::Glasm:
            sources[stage_index] = EmitGLASM(profile, runtime_info, program, binding);
            break;
        case Settings::ShaderBackend::SpirV:
            ConvertLegacyToGeneric(program, runtime_info);
            sources_spirv[stage_index] = EmitSPIRV(profile, runtime_info, program, binding);
            break;
        }
        previous_program = &program;
    }
    auto* const thread_worker{use_shader_workers ? workers.get() : nullptr};
    return std::make_unique<GraphicsPipeline>(device, texture_cache, buffer_cache, program_manager,
                                              state_tracker, thread_worker, &shader_notify, sources,
                                              sources_spirv, infos, key, force_context_flush);

} catch (Shader::Exception& exception) {
    LOG_ERROR(Render_OpenGL, "{}", exception.what());
    return nullptr;
}

std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
    const ComputePipelineKey& key, const VideoCommon::ShaderInfo* shader) {
    const GPUVAddr program_base{kepler_compute->regs.code_loc.Address()};
    const auto& qmd{kepler_compute->launch_description};
    ComputeEnvironment env{*kepler_compute, *gpu_memory, program_base, qmd.program_start};
    env.SetCachedSize(shader->size_bytes);

    main_pools.ReleaseContents();
    auto pipeline{CreateComputePipeline(main_pools, key, env)};
    if (!pipeline || shader_cache_filename.empty()) {
        return pipeline;
    }
    SerializePipeline(key, std::array<const GenericEnvironment*, 1>{&env}, shader_cache_filename,
                      CACHE_VERSION);
    return pipeline;
}

std::unique_ptr<ComputePipeline> ShaderCache::CreateComputePipeline(
    ShaderContext::ShaderPools& pools, const ComputePipelineKey& key, Shader::Environment& env,
    bool force_context_flush) try {
    auto hash = key.Hash();
    LOG_INFO(Render_OpenGL, "0x{:016x}", hash);

    Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};

    if (Settings::values.dump_shaders) {
        env.Dump(hash, key.unique_hash);
    }

    auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
    const u32 num_storage_buffers{Shader::NumDescriptors(program.info.storage_buffers_descriptors)};
    Shader::RuntimeInfo info;
    info.glasm_use_storage_buffers = num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();

    std::string code{};
    std::vector<u32> code_spirv;
    switch (device.GetShaderBackend()) {
    case Settings::ShaderBackend::Glsl:
        code = EmitGLSL(profile, program);
        break;
    case Settings::ShaderBackend::Glasm:
        code = EmitGLASM(profile, info, program);
        break;
    case Settings::ShaderBackend::SpirV:
        code_spirv = EmitSPIRV(profile, program);
        break;
    }

    return std::make_unique<ComputePipeline>(device, texture_cache, buffer_cache, program_manager,
                                             program.info, code, code_spirv, force_context_flush);
} catch (Shader::Exception& exception) {
    LOG_ERROR(Render_OpenGL, "{}", exception.what());
    return nullptr;
}

std::unique_ptr<ShaderWorker> ShaderCache::CreateWorkers() const {
    return std::make_unique<ShaderWorker>(std::max(std::thread::hardware_concurrency(), 2U) - 1,
                                          "GlShaderBuilder",
                                          [this] { return Context{emu_window}; });
}

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <filesystem>
#include <unordered_map>

#include "common/common_types.h"
#include "common/thread_worker.h"
#include "shader_recompiler/host_translate_info.h"
#include "shader_recompiler/profile.h"
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
#include "video_core/renderer_opengl/gl_graphics_pipeline.h"
#include "video_core/renderer_opengl/gl_shader_context.h"
#include "video_core/shader_cache.h"

namespace Tegra {
class MemoryManager;
} // namespace Tegra

namespace OpenGL {

class Device;
class ProgramManager;
class RasterizerOpenGL;
using ShaderWorker = Common::StatefulThreadWorker<ShaderContext::Context>;

class ShaderCache : public VideoCommon::ShaderCache {
public:
    explicit ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
                         Core::Frontend::EmuWindow& emu_window_, const Device& device_,
                         TextureCache& texture_cache_, BufferCache& buffer_cache_,
                         ProgramManager& program_manager_, StateTracker& state_tracker_,
                         VideoCore::ShaderNotify& shader_notify_);
    ~ShaderCache();

    void LoadDiskResources(u64 title_id, std::stop_token stop_loading,
                           const VideoCore::DiskResourceLoadCallback& callback);

    [[nodiscard]] GraphicsPipeline* CurrentGraphicsPipeline();

    [[nodiscard]] ComputePipeline* CurrentComputePipeline();

private:
    GraphicsPipeline* CurrentGraphicsPipelineSlowPath();

    [[nodiscard]] GraphicsPipeline* BuiltPipeline(GraphicsPipeline* pipeline) const noexcept;

    std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline();

    std::unique_ptr<GraphicsPipeline> CreateGraphicsPipeline(
        ShaderContext::ShaderPools& pools, const GraphicsPipelineKey& key,
        std::span<Shader::Environment* const> envs, bool use_shader_workers,
        bool force_context_flush = false);

    std::unique_ptr<ComputePipeline> CreateComputePipeline(const ComputePipelineKey& key,
                                                           const VideoCommon::ShaderInfo* shader);

    std::unique_ptr<ComputePipeline> CreateComputePipeline(ShaderContext::ShaderPools& pools,
                                                           const ComputePipelineKey& key,
                                                           Shader::Environment& env,
                                                           bool force_context_flush = false);

    std::unique_ptr<ShaderWorker> CreateWorkers() const;

    Core::Frontend::EmuWindow& emu_window;
    const Device& device;
    TextureCache& texture_cache;
    BufferCache& buffer_cache;
    ProgramManager& program_manager;
    StateTracker& state_tracker;
    VideoCore::ShaderNotify& shader_notify;
    const bool use_asynchronous_shaders;
    const bool strict_context_required;

    GraphicsPipelineKey graphics_key{};
    GraphicsPipeline* current_pipeline{};

    ShaderContext::ShaderPools main_pools;
    std::unordered_map<GraphicsPipelineKey, std::unique_ptr<GraphicsPipeline>> graphics_cache;
    std::unordered_map<ComputePipelineKey, std::unique_ptr<ComputePipeline>> compute_cache;

    Shader::Profile profile;
    Shader::HostTranslateInfo host_info;

    std::filesystem::path shader_cache_filename;
    std::unique_ptr<ShaderWorker> workers;
};

} // namespace OpenGL

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "core/frontend/emu_window.h"
#include "core/frontend/graphics_context.h"
#include "shader_recompiler/frontend/ir/basic_block.h"
#include "shader_recompiler/frontend/maxwell/control_flow.h"

namespace OpenGL::ShaderContext {
struct ShaderPools {
    void ReleaseContents() {
        flow_block.ReleaseContents();
        block.ReleaseContents();
        inst.ReleaseContents();
    }

    Shader::ObjectPool<Shader::IR::Inst> inst{8192};
    Shader::ObjectPool<Shader::IR::Block> block{32};
    Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block{32};
};

struct Context {
    explicit Context(Core::Frontend::EmuWindow& emu_window)
        : gl_context{emu_window.CreateSharedContext()}, scoped{*gl_context} {}

    std::unique_ptr<Core::Frontend::GraphicsContext> gl_context;
    Core::Frontend::GraphicsContext::Scoped scoped;
    ShaderPools pools;
};

} // namespace OpenGL::ShaderContext


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <glad/glad.h>

#include "video_core/host_shaders/opengl_lmem_warmup_comp.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"

namespace OpenGL {

static constexpr std::array ASSEMBLY_PROGRAM_ENUMS{
    GL_VERTEX_PROGRAM_NV,   GL_TESS_CONTROL_PROGRAM_NV, GL_TESS_EVALUATION_PROGRAM_NV,
    GL_GEOMETRY_PROGRAM_NV, GL_FRAGMENT_PROGRAM_NV,
};

ProgramManager::ProgramManager(const Device& device) {
    glCreateProgramPipelines(1, &pipeline.handle);
    if (device.UseAssemblyShaders()) {
        glEnable(GL_COMPUTE_PROGRAM_NV);
    }
    if (device.HasLmemPerfBug()) {
        lmem_warmup_program =
            CreateProgram(HostShaders::OPENGL_LMEM_WARMUP_COMP, GL_COMPUTE_SHADER);
    }
}

void ProgramManager::BindComputeProgram(GLuint program) {
    glUseProgram(program);
    is_compute_bound = true;
}

void ProgramManager::BindComputeAssemblyProgram(GLuint program) {
    if (current_assembly_compute_program != program) {
        current_assembly_compute_program = program;
        glBindProgramARB(GL_COMPUTE_PROGRAM_NV, program);
    }
    UnbindPipeline();
}

void ProgramManager::BindSourcePrograms(std::span<const OGLProgram, NUM_STAGES> programs) {
    static constexpr std::array<GLenum, 5> stage_enums{
        GL_VERTEX_SHADER_BIT,   GL_TESS_CONTROL_SHADER_BIT, GL_TESS_EVALUATION_SHADER_BIT,
        GL_GEOMETRY_SHADER_BIT, GL_FRAGMENT_SHADER_BIT,
    };
    for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
        if (current_programs[stage] != programs[stage].handle) {
            current_programs[stage] = programs[stage].handle;
            glUseProgramStages(pipeline.handle, stage_enums[stage], programs[stage].handle);
        }
    }
    BindPipeline();
}

void ProgramManager::BindPresentPrograms(GLuint vertex, GLuint fragment) {
    if (current_programs[0] != vertex) {
        current_programs[0] = vertex;
        glUseProgramStages(pipeline.handle, GL_VERTEX_SHADER_BIT, vertex);
    }
    if (current_programs[4] != fragment) {
        current_programs[4] = fragment;
        glUseProgramStages(pipeline.handle, GL_FRAGMENT_SHADER_BIT, fragment);
    }
    glUseProgramStages(
        pipeline.handle,
        GL_TESS_CONTROL_SHADER_BIT | GL_TESS_EVALUATION_SHADER_BIT | GL_GEOMETRY_SHADER_BIT, 0);
    current_programs[1] = 0;
    current_programs[2] = 0;
    current_programs[3] = 0;

    if (current_stage_mask != 0) {
        current_stage_mask = 0;
        for (const GLenum program_type : ASSEMBLY_PROGRAM_ENUMS) {
            glDisable(program_type);
        }
    }
    BindPipeline();
}

void ProgramManager::BindAssemblyPrograms(std::span<const OGLAssemblyProgram, NUM_STAGES> programs,
                                          u32 stage_mask) {
    const u32 changed_mask = current_stage_mask ^ stage_mask;
    current_stage_mask = stage_mask;

    if (changed_mask != 0) {
        for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
            if (((changed_mask >> stage) & 1) != 0) {
                if (((stage_mask >> stage) & 1) != 0) {
                    glEnable(ASSEMBLY_PROGRAM_ENUMS[stage]);
                } else {
                    glDisable(ASSEMBLY_PROGRAM_ENUMS[stage]);
                }
            }
        }
    }
    for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
        if (current_programs[stage] != programs[stage].handle) {
            current_programs[stage] = programs[stage].handle;
            glBindProgramARB(ASSEMBLY_PROGRAM_ENUMS[stage], programs[stage].handle);
        }
    }
    UnbindPipeline();
}

void ProgramManager::RestoreGuestCompute() {}

void ProgramManager::LocalMemoryWarmup() {
    if (lmem_warmup_program.handle != 0) {
        BindComputeProgram(lmem_warmup_program.handle);
        glDispatchCompute(1, 1, 1);
    }
}

void ProgramManager::BindPipeline() {
    if (!is_pipeline_bound) {
        is_pipeline_bound = true;
        glBindProgramPipeline(pipeline.handle);
    }
    UnbindCompute();
}

void ProgramManager::UnbindPipeline() {
    if (is_pipeline_bound) {
        is_pipeline_bound = false;
        glBindProgramPipeline(0);
    }
    UnbindCompute();
}

void ProgramManager::UnbindCompute() {
    if (is_compute_bound) {
        is_compute_bound = false;
        glUseProgram(0);
    }
}
} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <span>

#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"

namespace OpenGL {

class ProgramManager {
    static constexpr size_t NUM_STAGES = 5;

public:
    explicit ProgramManager(const Device& device);

    void BindComputeProgram(GLuint program);

    void BindComputeAssemblyProgram(GLuint program);

    void BindSourcePrograms(std::span<const OGLProgram, NUM_STAGES> programs);

    void BindPresentPrograms(GLuint vertex, GLuint fragment);

    void BindAssemblyPrograms(std::span<const OGLAssemblyProgram, NUM_STAGES> programs,
                              u32 stage_mask);

    void RestoreGuestCompute();

    void LocalMemoryWarmup();

private:
    void BindPipeline();

    void UnbindPipeline();

    void UnbindCompute();

    OGLPipeline pipeline;
    bool is_pipeline_bound{};
    bool is_compute_bound{};

    u32 current_stage_mask = 0;
    std::array<GLuint, NUM_STAGES> current_programs{};
    GLuint current_assembly_compute_program = 0;
    OGLProgram lmem_warmup_program;
};

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <string_view>
#include <vector>
#include <glad/glad.h>

#include "common/logging/log.h"
#include "common/settings.h"
#include "video_core/renderer_opengl/gl_shader_util.h"

namespace OpenGL {

static OGLProgram LinkSeparableProgram(GLuint shader) {
    OGLProgram program;
    program.handle = glCreateProgram();
    glProgramParameteri(program.handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glAttachShader(program.handle, shader);
    glLinkProgram(program.handle);
    glDetachShader(program.handle, shader);
    if (!Settings::values.renderer_debug) {
        return program;
    }
    GLint link_status{};
    glGetProgramiv(program.handle, GL_LINK_STATUS, &link_status);

    GLint log_length{};
    glGetProgramiv(program.handle, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length == 0) {
        return program;
    }
    std::string log(log_length, 0);
    glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
    if (link_status == GL_FALSE) {
        LOG_ERROR(Render_OpenGL, "{}", log);
    } else {
        LOG_WARNING(Render_OpenGL, "{}", log);
    }
    return program;
}

static void LogShader(GLuint shader, std::string_view code = {}) {
    GLint shader_status{};
    glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_status);
    if (shader_status == GL_FALSE) {
        LOG_ERROR(Render_OpenGL, "Failed to build shader");
    }
    GLint log_length{};
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
    if (log_length == 0) {
        return;
    }
    std::string log(log_length, 0);
    glGetShaderInfoLog(shader, log_length, nullptr, log.data());
    if (shader_status == GL_FALSE) {
        LOG_ERROR(Render_OpenGL, "{}", log);
        if (!code.empty()) {
            LOG_INFO(Render_OpenGL, "\n{}", code);
        }
    } else {
        LOG_WARNING(Render_OpenGL, "{}", log);
    }
}

OGLProgram CreateProgram(std::string_view code, GLenum stage) {
    OGLShader shader;
    shader.handle = glCreateShader(stage);

    const GLint length = static_cast<GLint>(code.size());
    const GLchar* const code_ptr = code.data();
    glShaderSource(shader.handle, 1, &code_ptr, &length);
    glCompileShader(shader.handle);
    if (Settings::values.renderer_debug) {
        LogShader(shader.handle, code);
    }
    return LinkSeparableProgram(shader.handle);
}

OGLProgram CreateProgram(std::span<const u32> code, GLenum stage) {
    OGLShader shader;
    shader.handle = glCreateShader(stage);

    glShaderBinary(1, &shader.handle, GL_SHADER_BINARY_FORMAT_SPIR_V_ARB, code.data(),
                   static_cast<GLsizei>(code.size_bytes()));
    glSpecializeShader(shader.handle, "main", 0, nullptr, nullptr);
    if (Settings::values.renderer_debug) {
        LogShader(shader.handle);
    }
    return LinkSeparableProgram(shader.handle);
}

OGLAssemblyProgram CompileProgram(std::string_view code, GLenum target) {
    OGLAssemblyProgram program;
    glGenProgramsARB(1, &program.handle);
    glNamedProgramStringEXT(program.handle, target, GL_PROGRAM_FORMAT_ASCII_ARB,
                            static_cast<GLsizei>(code.size()), code.data());
    if (Settings::values.renderer_debug) {
        const auto err = reinterpret_cast<const char*>(glGetString(GL_PROGRAM_ERROR_STRING_NV));
        if (err && *err) {
            if (std::strstr(err, "error")) {
                LOG_CRITICAL(Render_OpenGL, "\n{}", err);
                LOG_INFO(Render_OpenGL, "\n{}", code);
            } else {
                LOG_WARNING(Render_OpenGL, "\n{}", err);
            }
        }
    }
    return program;
}

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <span>
#include <string_view>

#include <glad/glad.h>

#include "video_core/renderer_opengl/gl_resource_manager.h"

namespace OpenGL {

OGLProgram CreateProgram(std::string_view code, GLenum stage);

OGLProgram CreateProgram(std::span<const u32> code, GLenum stage);

OGLAssemblyProgram CompileProgram(std::string_view code, GLenum target);

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <array>
#include <memory>
#include <span>

#include <glad/glad.h>

#include "common/alignment.h"
#include "common/assert.h"
#include "common/bit_util.h"
#include "common/microprofile.h"
#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"

MICROPROFILE_DEFINE(OpenGL_BufferRequest, "OpenGL", "BufferRequest", MP_RGB(128, 128, 192));

namespace OpenGL {

StagingBufferMap::~StagingBufferMap() {
    if (sync) {
        sync->Create();
    }
}

StagingBuffers::StagingBuffers(GLenum storage_flags_, GLenum map_flags_)
    : storage_flags{storage_flags_}, map_flags{map_flags_} {}

StagingBuffers::~StagingBuffers() = default;

StagingBufferMap StagingBuffers::RequestMap(size_t requested_size, bool insert_fence,
                                            bool deferred) {
    MICROPROFILE_SCOPE(OpenGL_BufferRequest);

    const size_t index = RequestBuffer(requested_size);
    OGLSync* const sync = insert_fence ? &allocs[index].sync : nullptr;
    allocs[index].sync_index = insert_fence ? ++current_sync_index : 0;
    allocs[index].deferred = deferred;
    return StagingBufferMap{
        .mapped_span = std::span(allocs[index].map, requested_size),
        .sync = sync,
        .buffer = allocs[index].buffer.handle,
        .index = index,
    };
}

void StagingBuffers::FreeDeferredStagingBuffer(size_t index) {
    ASSERT(allocs[index].deferred);
    allocs[index].deferred = false;
}

size_t StagingBuffers::RequestBuffer(size_t requested_size) {
    if (const std::optional<size_t> index = FindBuffer(requested_size); index) {
        return *index;
    }
    StagingBufferAlloc alloc;
    alloc.buffer.Create();
    const auto next_pow2_size = Common::NextPow2(requested_size);
    glNamedBufferStorage(alloc.buffer.handle, next_pow2_size, nullptr,
                         storage_flags | GL_MAP_PERSISTENT_BIT);
    alloc.map = static_cast<u8*>(glMapNamedBufferRange(alloc.buffer.handle, 0, next_pow2_size,
                                                       map_flags | GL_MAP_PERSISTENT_BIT));
    alloc.size = next_pow2_size;
    allocs.emplace_back(std::move(alloc));
    return allocs.size() - 1;
}

std::optional<size_t> StagingBuffers::FindBuffer(size_t requested_size) {
    size_t known_unsignaled_index = current_sync_index + 1;
    size_t smallest_buffer = std::numeric_limits<size_t>::max();
    std::optional<size_t> found;
    const size_t num_buffers = allocs.size();
    for (size_t index = 0; index < num_buffers; ++index) {
        StagingBufferAlloc& alloc = allocs[index];
        const size_t buffer_size = alloc.size;
        if (buffer_size < requested_size || buffer_size >= smallest_buffer) {
            continue;
        }
        if (alloc.deferred) {
            continue;
        }
        if (alloc.sync.handle != 0) {
            if (alloc.sync_index >= known_unsignaled_index) {
                // This fence is later than a fence that is known to not be signaled
                continue;
            }
            if (!alloc.sync.IsSignaled()) {
                // Since this fence hasn't been signaled, it's safe to assume all later
                // fences haven't been signaled either
                known_unsignaled_index = std::min(known_unsignaled_index, alloc.sync_index);
                continue;
            }
            alloc.sync.Release();
        }
        smallest_buffer = buffer_size;
        found = index;
    }
    return found;
}

StreamBuffer::StreamBuffer() {
    static constexpr GLenum flags = GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT;
    buffer.Create();
    glObjectLabel(GL_BUFFER, buffer.handle, -1, "Stream Buffer");
    glNamedBufferStorage(buffer.handle, STREAM_BUFFER_SIZE, nullptr, flags);
    mapped_pointer =
        static_cast<u8*>(glMapNamedBufferRange(buffer.handle, 0, STREAM_BUFFER_SIZE, flags));
    for (OGLSync& sync : fences) {
        sync.Create();
    }
}

std::pair<std::span<u8>, size_t> StreamBuffer::Request(size_t size) noexcept {
    ASSERT(size < REGION_SIZE);
    for (size_t region = Region(used_iterator), region_end = Region(iterator); region < region_end;
         ++region) {
        fences[region].Create();
    }
    used_iterator = iterator;

    for (size_t region = Region(free_iterator) + 1,
                region_end = std::min(Region(iterator + size) + 1, NUM_SYNCS);
         region < region_end; ++region) {
        glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
        fences[region].Release();
    }
    if (iterator + size >= free_iterator) {
        free_iterator = iterator + size;
    }
    if (iterator + size > STREAM_BUFFER_SIZE) {
        for (size_t region = Region(used_iterator); region < NUM_SYNCS; ++region) {
            fences[region].Create();
        }
        used_iterator = 0;
        iterator = 0;
        free_iterator = size;

        for (size_t region = 0, region_end = Region(size); region <= region_end; ++region) {
            glClientWaitSync(fences[region].handle, 0, GL_TIMEOUT_IGNORED);
            fences[region].Release();
        }
    }
    const size_t offset = iterator;
    iterator = Common::AlignUp(iterator + size, MAX_ALIGNMENT);
    return {std::span(mapped_pointer + offset, size), offset};
}

StagingBufferMap StagingBufferPool::RequestUploadBuffer(size_t size) {
    return upload_buffers.RequestMap(size, true);
}

StagingBufferMap StagingBufferPool::RequestDownloadBuffer(size_t size, bool deferred) {
    return download_buffers.RequestMap(size, false, deferred);
}

void StagingBufferPool::FreeDeferredStagingBuffer(StagingBufferMap& buffer) {
    download_buffers.FreeDeferredStagingBuffer(buffer.index);
}

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <array>
#include <optional>
#include <span>
#include <utility>
#include <vector>

#include <glad/glad.h>

#include "common/common_types.h"
#include "common/literals.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"

namespace OpenGL {

using namespace Common::Literals;

struct StagingBufferMap {
    ~StagingBufferMap();

    std::span<u8> mapped_span;
    size_t offset = 0;
    OGLSync* sync;
    GLuint buffer;
    size_t index;
};

struct StagingBuffers {
    explicit StagingBuffers(GLenum storage_flags_, GLenum map_flags_);
    ~StagingBuffers();

    StagingBufferMap RequestMap(size_t requested_size, bool insert_fence, bool deferred = false);

    void FreeDeferredStagingBuffer(size_t index);

    size_t RequestBuffer(size_t requested_size);

    std::optional<size_t> FindBuffer(size_t requested_size);

    struct StagingBufferAlloc {
        OGLSync sync;
        OGLBuffer buffer;
        u8* map;
        size_t size;
        size_t sync_index;
        bool deferred;
    };
    std::vector<StagingBufferAlloc> allocs;
    GLenum storage_flags;
    GLenum map_flags;
    size_t current_sync_index = 0;
};

class StreamBuffer {
    static constexpr size_t STREAM_BUFFER_SIZE = 64_MiB;
    static constexpr size_t NUM_SYNCS = 16;
    static constexpr size_t REGION_SIZE = STREAM_BUFFER_SIZE / NUM_SYNCS;
    static constexpr size_t MAX_ALIGNMENT = 256;
    static_assert(STREAM_BUFFER_SIZE % MAX_ALIGNMENT == 0);
    static_assert(STREAM_BUFFER_SIZE % NUM_SYNCS == 0);
    static_assert(REGION_SIZE % MAX_ALIGNMENT == 0);

public:
    explicit StreamBuffer();

    [[nodiscard]] std::pair<std::span<u8>, size_t> Request(size_t size) noexcept;

    [[nodiscard]] GLuint Handle() const noexcept {
        return buffer.handle;
    }

private:
    [[nodiscard]] static size_t Region(size_t offset) noexcept {
        return offset / REGION_SIZE;
    }

    size_t iterator = 0;
    size_t used_iterator = 0;
    size_t free_iterator = 0;
    u8* mapped_pointer = nullptr;
    OGLBuffer buffer;
    std::array<OGLSync, NUM_SYNCS> fences;
};

class StagingBufferPool {
public:
    StagingBufferPool() = default;
    ~StagingBufferPool() = default;

    StagingBufferMap RequestUploadBuffer(size_t size);
    StagingBufferMap RequestDownloadBuffer(size_t size, bool deferred = false);
    void FreeDeferredStagingBuffer(StagingBufferMap& buffer);

private:
    StagingBuffers upload_buffers{GL_MAP_WRITE_BIT, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT};
    StagingBuffers download_buffers{GL_MAP_READ_BIT | GL_CLIENT_STORAGE_BIT, GL_MAP_READ_BIT};
};

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <algorithm>
#include <array>
#include <cstddef>

#include "common/common_types.h"
#include "core/core.h"
#include "video_core/control/channel_state.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"

#define OFF(field_name) MAXWELL3D_REG_INDEX(field_name)
#define NUM(field_name) (sizeof(Maxwell3D::Regs::field_name) / (sizeof(u32)))

namespace OpenGL {

namespace {

using namespace Dirty;
using namespace VideoCommon::Dirty;
using Tegra::Engines::Maxwell3D;
using Regs = Maxwell3D::Regs;
using Tables = Maxwell3D::DirtyState::Tables;
using Table = Maxwell3D::DirtyState::Table;

void SetupDirtyColorMasks(Tables& tables) {
    tables[0][OFF(color_mask_common)] = ColorMaskCommon;
    for (std::size_t rt = 0; rt < Regs::NumRenderTargets; ++rt) {
        const std::size_t offset = OFF(color_mask) + rt * NUM(color_mask[0]);
        FillBlock(tables[0], offset, NUM(color_mask[0]), ColorMask0 + rt);
    }

    FillBlock(tables[1], OFF(color_mask), NUM(color_mask), ColorMasks);
}

void SetupDirtyVertexInstances(Tables& tables) {
    static constexpr std::size_t instance_base_offset = 3;
    for (std::size_t i = 0; i < Regs::NumVertexArrays; ++i) {
        const std::size_t array_offset = OFF(vertex_streams) + i * NUM(vertex_streams[0]);
        const std::size_t instance_array_offset = array_offset + instance_base_offset;
        tables[0][instance_array_offset] = static_cast<u8>(VertexInstance0 + i);
        tables[1][instance_array_offset] = VertexInstances;

        const std::size_t instance_offset = OFF(vertex_stream_instances) + i;
        tables[0][instance_offset] = static_cast<u8>(VertexInstance0 + i);
        tables[1][instance_offset] = VertexInstances;
    }
}

void SetupDirtyVertexFormat(Tables& tables) {
    for (std::size_t i = 0; i < Regs::NumVertexAttributes; ++i) {
        const std::size_t offset = OFF(vertex_attrib_format) + i * NUM(vertex_attrib_format[0]);
        FillBlock(tables[0], offset, NUM(vertex_attrib_format[0]), VertexFormat0 + i);
    }

    FillBlock(tables[1], OFF(vertex_attrib_format), Regs::NumVertexAttributes, VertexFormats);
}

void SetupDirtyViewports(Tables& tables) {
    for (std::size_t i = 0; i < Regs::NumViewports; ++i) {
        const std::size_t transf_offset = OFF(viewport_transform) + i * NUM(viewport_transform[0]);
        const std::size_t viewport_offset = OFF(viewports) + i * NUM(viewports[0]);

        FillBlock(tables[0], transf_offset, NUM(viewport_transform[0]), Viewport0 + i);
        FillBlock(tables[0], viewport_offset, NUM(viewports[0]), Viewport0 + i);
    }

    FillBlock(tables[1], OFF(viewport_transform), NUM(viewport_transform), Viewports);
    FillBlock(tables[1], OFF(viewports), NUM(viewports), Viewports);

    tables[0][OFF(viewport_scale_offset_enabled)] = ViewportTransform;
    tables[1][OFF(viewport_scale_offset_enabled)] = Viewports;
}

void SetupDirtyScissors(Tables& tables) {
    for (std::size_t i = 0; i < Regs::NumViewports; ++i) {
        const std::size_t offset = OFF(scissor_test) + i * NUM(scissor_test[0]);
        FillBlock(tables[0], offset, NUM(scissor_test[0]), Scissor0 + i);
    }
    FillBlock(tables[1], OFF(scissor_test), NUM(scissor_test), Scissors);
}

void SetupDirtyPolygonModes(Tables& tables) {
    tables[0][OFF(polygon_mode_front)] = PolygonModeFront;
    tables[0][OFF(polygon_mode_back)] = PolygonModeBack;

    tables[1][OFF(polygon_mode_front)] = PolygonModes;
    tables[1][OFF(polygon_mode_back)] = PolygonModes;
    tables[0][OFF(fill_via_triangle_mode)] = PolygonModes;
}

void SetupDirtyDepthTest(Tables& tables) {
    auto& table = tables[0];
    table[OFF(depth_test_enable)] = DepthTest;
    table[OFF(depth_write_enabled)] = DepthMask;
    table[OFF(depth_test_func)] = DepthTest;
}

void SetupDirtyStencilTest(Tables& tables) {
    static constexpr std::array offsets = {
        OFF(stencil_enable),          OFF(stencil_front_op.func), OFF(stencil_front_ref),
        OFF(stencil_front_func_mask), OFF(stencil_front_op.fail), OFF(stencil_front_op.zfail),
        OFF(stencil_front_op.zpass),  OFF(stencil_front_mask),    OFF(stencil_two_side_enable),
        OFF(stencil_back_op.func),    OFF(stencil_back_ref),      OFF(stencil_back_func_mask),
        OFF(stencil_back_op.fail),    OFF(stencil_back_op.zfail), OFF(stencil_back_op.zpass),
        OFF(stencil_back_mask)};
    for (const auto offset : offsets) {
        tables[0][offset] = StencilTest;
    }
}

void SetupDirtyAlphaTest(Tables& tables) {
    auto& table = tables[0];
    table[OFF(alpha_test_ref)] = AlphaTest;
    table[OFF(alpha_test_func)] = AlphaTest;
    table[OFF(alpha_test_enabled)] = AlphaTest;
}

void SetupDirtyBlend(Tables& tables) {
    FillBlock(tables[0], OFF(blend_color), NUM(blend_color), BlendColor);

    tables[0][OFF(blend_per_target_enabled)] = BlendIndependentEnabled;

    for (std::size_t i = 0; i < Regs::NumRenderTargets; ++i) {
        const std::size_t offset = OFF(blend_per_target) + i * NUM(blend_per_target[0]);
        FillBlock(tables[0], offset, NUM(blend_per_target[0]), BlendState0 + i);

        tables[0][OFF(blend.enable) + i] = static_cast<u8>(BlendState0 + i);
    }
    FillBlock(tables[1], OFF(blend_per_target), NUM(blend_per_target), BlendStates);
    FillBlock(tables[1], OFF(blend), NUM(blend), BlendStates);
}

void SetupDirtyPrimitiveRestart(Tables& tables) {
    FillBlock(tables[0], OFF(primitive_restart), NUM(primitive_restart), PrimitiveRestart);
}

void SetupDirtyPolygonOffset(Tables& tables) {
    auto& table = tables[0];
    table[OFF(polygon_offset_fill_enable)] = PolygonOffset;
    table[OFF(polygon_offset_line_enable)] = PolygonOffset;
    table[OFF(polygon_offset_point_enable)] = PolygonOffset;
    table[OFF(slope_scale_depth_bias)] = PolygonOffset;
    table[OFF(depth_bias)] = PolygonOffset;
    table[OFF(depth_bias_clamp)] = PolygonOffset;
}

void SetupDirtyMultisampleControl(Tables& tables) {
    FillBlock(tables[0], OFF(anti_alias_alpha_control), NUM(anti_alias_alpha_control),
              MultisampleControl);
}

void SetupDirtyRasterizeEnable(Tables& tables) {
    tables[0][OFF(rasterize_enable)] = RasterizeEnable;
}

void SetupDirtyFramebufferSRGB(Tables& tables) {
    tables[0][OFF(framebuffer_srgb)] = FramebufferSRGB;
}

void SetupDirtyLogicOp(Tables& tables) {
    FillBlock(tables[0], OFF(logic_op), NUM(logic_op), LogicOp);
}

void SetupDirtyFragmentClampColor(Tables& tables) {
    tables[0][OFF(frag_color_clamp)] = FragmentClampColor;
}

void SetupDirtyPointSize(Tables& tables) {
    tables[0][OFF(point_size_attribute)] = PointSize;
    tables[0][OFF(point_size)] = PointSize;
    tables[0][OFF(point_sprite_enable)] = PointSize;
}

void SetupDirtyLineWidth(Tables& tables) {
    tables[0][OFF(line_width_smooth)] = LineWidth;
    tables[0][OFF(line_width_aliased)] = LineWidth;
    tables[0][OFF(line_anti_alias_enable)] = LineWidth;
}

void SetupDirtyClipControl(Tables& tables) {
    auto& table = tables[0];
    table[OFF(window_origin)] = ClipControl;
    table[OFF(depth_mode)] = ClipControl;
}

void SetupDirtyDepthClampEnabled(Tables& tables) {
    tables[0][OFF(viewport_clip_control)] = DepthClampEnabled;
}

void SetupDirtyMisc(Tables& tables) {
    auto& table = tables[0];

    table[OFF(user_clip_enable)] = ClipDistances;

    table[OFF(gl_front_face)] = FrontFace;

    table[OFF(gl_cull_test_enabled)] = CullTest;
    table[OFF(gl_cull_face)] = CullTest;
}

} // Anonymous namespace

void StateTracker::SetupTables(Tegra::Control::ChannelState& channel_state) {
    auto& tables{channel_state.maxwell_3d->dirty.tables};
    SetupDirtyFlags(tables);
    SetupDirtyColorMasks(tables);
    SetupDirtyViewports(tables);
    SetupDirtyScissors(tables);
    SetupDirtyVertexInstances(tables);
    SetupDirtyVertexFormat(tables);
    SetupDirtyPolygonModes(tables);
    SetupDirtyDepthTest(tables);
    SetupDirtyStencilTest(tables);
    SetupDirtyAlphaTest(tables);
    SetupDirtyBlend(tables);
    SetupDirtyPrimitiveRestart(tables);
    SetupDirtyPolygonOffset(tables);
    SetupDirtyMultisampleControl(tables);
    SetupDirtyRasterizeEnable(tables);
    SetupDirtyFramebufferSRGB(tables);
    SetupDirtyLogicOp(tables);
    SetupDirtyFragmentClampColor(tables);
    SetupDirtyPointSize(tables);
    SetupDirtyLineWidth(tables);
    SetupDirtyClipControl(tables);
    SetupDirtyDepthClampEnabled(tables);
    SetupDirtyMisc(tables);
}

void StateTracker::ChangeChannel(Tegra::Control::ChannelState& channel_state) {
    flags = &channel_state.maxwell_3d->dirty.flags;
}

void StateTracker::InvalidateState() {
    flags->set();
}

StateTracker::StateTracker() : flags{&default_flags} {}

} // namespace OpenGL


code:
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <limits>

#include <glad/glad.h>

#include "common/common_types.h"
#include "video_core/dirty_flags.h"
#include "video_core/engines/maxwell_3d.h"

namespace Tegra {
namespace Control {
struct ChannelState;
}
} // namespace Tegra

namespace OpenGL {

namespace Dirty {

enum : u8 {
    First = VideoCommon::Dirty::LastCommonEntry,

    VertexFormats,
    VertexFormat0,
    VertexFormat31 = VertexFormat0 + 31,

    VertexInstances,
    VertexInstance0,
    VertexInstance31 = VertexInstance0 + 31,

    ViewportTransform,
    Viewports,
    Viewport0,
    Viewport15 = Viewport0 + 15,

    Scissors,
    Scissor0,
    Scissor15 = Scissor0 + 15,

    ColorMaskCommon,
    ColorMasks,
    ColorMask0,
    ColorMask7 = ColorMask0 + 7,

    BlendColor,
    BlendIndependentEnabled,
    BlendStates,
    BlendState0,
    BlendState7 = BlendState0 + 7,

    ClipDistances,

    PolygonModes,
    PolygonModeFront,
    PolygonModeBack,

    ColorMask,
    FrontFace,
    CullTest,
    DepthMask,
    DepthTest,
    StencilTest,
    AlphaTest,
    PrimitiveRestart,
    PolygonOffset,
    MultisampleControl,
    RasterizeEnable,
    FramebufferSRGB,
    LogicOp,
    FragmentClampColor,
    PointSize,
    LineWidth,
    ClipControl,
    DepthClampEnabled,

    Last
};
static_assert(Last <= std::numeric_limits<u8>::max());

} // namespace Dirty

class StateTracker {
public:
    explicit StateTracker();

    void BindIndexBuffer(GLuint new_index_buffer) {
        if (index_buffer == new_index_buffer) {
            return;
        }
        index_buffer = new_index_buffer;
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, new_index_buffer);
    }

    void BindFramebuffer(GLuint new_framebuffer) {
        if (framebuffer == new_framebuffer) {
            return;
        }
        framebuffer = new_framebuffer;
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    }

    void ClipControl(GLenum new_origin, GLenum new_depth) {
        if (new_origin == origin && new_depth == depth) {
            return;
        }
        origin = new_origin;
        depth = new_depth;
        glClipControl(origin, depth);
    }

    void SetYNegate(bool new_y_negate) {
        if (new_y_negate == y_negate) {
            return;
        }
        // Y_NEGATE is mapped to gl_FrontMaterial.ambient.a
        y_negate = new_y_negate;
        const std::array ambient{0.0f, 0.0f, 0.0f, y_negate ? -1.0f : 1.0f};
        glMaterialfv(GL_FRONT, GL_AMBIENT, ambient.data());
    }

    void NotifyScreenDrawVertexArray() {
        (*flags)[OpenGL::Dirty::VertexFormats] = true;
        (*flags)[OpenGL::Dirty::VertexFormat0 + 0] = true;
        (*flags)[OpenGL::Dirty::VertexFormat0 + 1] = true;

        (*flags)[VideoCommon::Dirty::VertexBuffers] = true;
        (*flags)[VideoCommon::Dirty::VertexBuffer0] = true;

        (*flags)[OpenGL::Dirty::VertexInstances] = true;
        (*flags)[OpenGL::Dirty::VertexInstance0 + 0] = true;
        (*flags)[OpenGL::Dirty::VertexInstance0 + 1] = true;
    }

    void NotifyPolygonModes() {
        (*flags)[OpenGL::Dirty::PolygonModes] = true;
        (*flags)[OpenGL::Dirty::PolygonModeFront] = true;
        (*flags)[OpenGL::Dirty::PolygonModeBack] = true;
    }

    void NotifyViewport0() {
        (*flags)[OpenGL::Dirty::Viewports] = true;
        (*flags)[OpenGL::Dirty::Viewport0] = true;
    }

    void NotifyScissor0() {
        (*flags)[OpenGL::Dirty::Scissors] = true;
        (*flags)[OpenGL::Dirty::Scissor0] = true;
    }

    void NotifyColorMask(size_t index) {
        (*flags)[OpenGL::Dirty::ColorMasks] = true;
        (*flags)[OpenGL::Dirty::ColorMask0 + index] = true;
    }

    void NotifyBlend0() {
        (*flags)[OpenGL::Dirty::BlendStates] = true;
        (*flags)[OpenGL::Dirty::BlendState0] = true;
    }

    void NotifyFramebuffer() {
        (*flags)[VideoCommon::Dirty::RenderTargets] = true;
    }

    void NotifyFrontFace() {
        (*flags)[OpenGL::Dirty::FrontFace] = true;
    }

    void NotifyCullTest() {
        (*flags)[OpenGL::Dirty::CullTest] = true;
    }

    void NotifyDepthMask() {
        (*flags)[OpenGL::Dirty::DepthMask] = true;
    }

    void NotifyDepthTest() {
        (*flags)[OpenGL::Dirty::DepthTest] = true;
    }

    void NotifyStencilTest() {
        (*flags)[OpenGL::Dirty::StencilTest] = true;
    }

    void NotifyPolygonOffset() {
        (*flags)[OpenGL::Dirty::PolygonOffset] = true;
    }

    void NotifyRasterizeEnable() {
        (*flags)[OpenGL::Dirty::RasterizeEnable] = true;
    }

    void NotifyFramebufferSRGB() {
        (*flags)[OpenGL::Dirty::FramebufferSRGB] = true;
    }

    void NotifyLogicOp() {
        (*flags)[OpenGL::Dirty::LogicOp] = true;
    }

    void NotifyClipControl() {
        (*flags)[OpenGL::Dirty::ClipControl] = true;
    }

    void NotifyAlphaTest() {
        (*flags)[OpenGL::Dirty::AlphaTest] = true;
    }

    void NotifyRange(u8 start, u8 end) {
        for (auto flag = start; flag <= end; flag++) {
            (*flags)[flag] = true;
        }
    }

    void SetupTables(Tegra::Control::ChannelState& channel_state);

    void ChangeChannel(Tegra::Control::ChannelState& channel_state);

    void InvalidateState();

private:
    Tegra::Engines::Maxwell3D::DirtyState::Flags* flags;
    Tegra::Engines::Maxwell3D::DirtyState::Flags default_flags{};

    GLuint framebuffer = 0;
    GLuint index_buffer = 0;
    GLenum origin = GL_LOWER_LEFT;
    GLenum depth = GL_NEGATIVE_ONE_TO_ONE;
    bool y_negate = false;
};

} // namespace OpenGL