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

#include <algorithm>
#include <span>

#include "shader_recompiler/backend/glasm/emit_glasm.h"
#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/maxwell_to_gl.h"

namespace OpenGL {
namespace {
using VideoCore::Surface::PixelFormat;

struct BindlessSSBO {
    GLuint64EXT address;
    GLsizei length;
    GLsizei padding;
};
static_assert(sizeof(BindlessSSBO) == sizeof(GLuint) * 4);

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

[[nodiscard]] GLenum GetTextureBufferFormat(GLenum gl_format) {
    switch (gl_format) {
    case GL_RGBA8_SNORM:
        return GL_RGBA8I;
    case GL_R8_SNORM:
        return GL_R8I;
    case GL_RGBA16_SNORM:
        return GL_RGBA16I;
    case GL_R16_SNORM:
        return GL_R16I;
    case GL_RG16_SNORM:
        return GL_RG16I;
    case GL_RG8_SNORM:
        return GL_RG8I;
    default:
        return gl_format;
    }
}
} // Anonymous namespace

Buffer::Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams null_params)
    : VideoCommon::BufferBase(null_params) {}

Buffer::Buffer(BufferCacheRuntime& runtime, DAddr cpu_addr_, u64 size_bytes_)
    : VideoCommon::BufferBase(cpu_addr_, size_bytes_) {
    buffer.Create();
    if (runtime.device.HasDebuggingToolAttached()) {
        const std::string name = fmt::format("Buffer 0x{:x}", CpuAddr());
        glObjectLabel(GL_BUFFER, buffer.handle, static_cast<GLsizei>(name.size()), name.data());
    }
    glNamedBufferData(buffer.handle, SizeBytes(), nullptr, GL_DYNAMIC_DRAW);
    if (runtime.has_unified_vertex_buffers) {
        glGetNamedBufferParameterui64vNV(buffer.handle, GL_BUFFER_GPU_ADDRESS_NV, &address);
    }
}

void Buffer::ImmediateUpload(size_t offset, std::span<const u8> data) noexcept {
    glNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
                         static_cast<GLsizeiptr>(data.size_bytes()), data.data());
}

void Buffer::ImmediateDownload(size_t offset, std::span<u8> data) noexcept {
    glGetNamedBufferSubData(buffer.handle, static_cast<GLintptr>(offset),
                            static_cast<GLsizeiptr>(data.size_bytes()), data.data());
}

void Buffer::MakeResident(GLenum access) noexcept {
    // Abuse GLenum's order to exit early
    // GL_NONE (default) < GL_READ_ONLY < GL_READ_WRITE
    if (access <= current_residency_access || buffer.handle == 0) {
        return;
    }
    if (std::exchange(current_residency_access, access) != GL_NONE) {
        // If the buffer is already resident, remove its residency before promoting it
        glMakeNamedBufferNonResidentNV(buffer.handle);
    }
    glMakeNamedBufferResidentNV(buffer.handle, access);
}

GLuint Buffer::View(u32 offset, u32 size, PixelFormat format) {
    const auto it{std::ranges::find_if(views, [offset, size, format](const BufferView& view) {
        return offset == view.offset && size == view.size && format == view.format;
    })};
    if (it != views.end()) {
        return it->texture.handle;
    }
    OGLTexture texture;
    texture.Create(GL_TEXTURE_BUFFER);
    const GLenum gl_format{MaxwellToGL::GetFormatTuple(format).internal_format};
    const GLenum texture_format{GetTextureBufferFormat(gl_format)};
    glTextureBufferRange(texture.handle, texture_format, buffer.handle, offset, size);
    views.push_back({
        .offset = offset,
        .size = size,
        .format = format,
        .texture = std::move(texture),
    });
    return views.back().texture.handle;
}

BufferCacheRuntime::BufferCacheRuntime(const Device& device_,
                                       StagingBufferPool& staging_buffer_pool_)
    : device{device_}, staging_buffer_pool{staging_buffer_pool_},
      has_fast_buffer_sub_data{device.HasFastBufferSubData()},
      use_assembly_shaders{device.UseAssemblyShaders()},
      has_unified_vertex_buffers{device.HasVertexBufferUnifiedMemory()},
      stream_buffer{has_fast_buffer_sub_data ? std::nullopt : std::make_optional<StreamBuffer>()} {
    GLint gl_max_attributes;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &gl_max_attributes);
    max_attributes = static_cast<u32>(gl_max_attributes);
    for (auto& stage_uniforms : fast_uniforms) {
        for (OGLBuffer& buffer : stage_uniforms) {
            buffer.Create();
            glNamedBufferData(buffer.handle, VideoCommon::DEFAULT_SKIP_CACHE_SIZE, nullptr,
                              GL_STREAM_DRAW);
        }
    }
    if (use_assembly_shaders) {
        for (auto& stage_uniforms : copy_uniforms) {
            for (OGLBuffer& buffer : stage_uniforms) {
                buffer.Create();
                glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY);
            }
        }
        for (OGLBuffer& buffer : copy_compute_uniforms) {
            buffer.Create();
            glNamedBufferData(buffer.handle, 0x10'000, nullptr, GL_STREAM_COPY);
        }
    }

    device_access_memory = [this]() -> u64 {
        if (device.CanReportMemoryUsage()) {
            return device.GetCurrentDedicatedVideoMemory() + 512_MiB;
        }
        return 2_GiB; // Return minimum requirements
    }();
}

StagingBufferMap BufferCacheRuntime::UploadStagingBuffer(size_t size) {
    return staging_buffer_pool.RequestUploadBuffer(size);
}

StagingBufferMap BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
    return staging_buffer_pool.RequestDownloadBuffer(size, deferred);
}

void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferMap& buffer) {
    staging_buffer_pool.FreeDeferredStagingBuffer(buffer);
}

u64 BufferCacheRuntime::GetDeviceMemoryUsage() const {
    if (device.CanReportMemoryUsage()) {
        return device_access_memory - device.GetCurrentDedicatedVideoMemory();
    }
    return 2_GiB;
}

void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
                                    std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
    if (barrier) {
        PreCopyBarrier();
    }
    for (const VideoCommon::BufferCopy& copy : copies) {
        glCopyNamedBufferSubData(src_buffer, dst_buffer, static_cast<GLintptr>(copy.src_offset),
                                 static_cast<GLintptr>(copy.dst_offset),
                                 static_cast<GLsizeiptr>(copy.size));
    }
    if (barrier) {
        PostCopyBarrier();
    }
}

void BufferCacheRuntime::CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
                                    std::span<const VideoCommon::BufferCopy> copies, bool barrier) {
    CopyBuffer(dst_buffer, src_buffer.Handle(), copies, barrier);
}

void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
                                    std::span<const VideoCommon::BufferCopy> copies, bool barrier,
                                    bool) {
    CopyBuffer(dst_buffer.Handle(), src_buffer, copies, barrier);
}

void BufferCacheRuntime::CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
                                    std::span<const VideoCommon::BufferCopy> copies, bool) {
    CopyBuffer(dst_buffer.Handle(), src_buffer.Handle(), copies, true);
}

void BufferCacheRuntime::PreCopyBarrier() {
    // TODO: finer grained barrier?
    glMemoryBarrier(GL_ALL_BARRIER_BITS);
}

void BufferCacheRuntime::PostCopyBarrier() {
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT | GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
}

void BufferCacheRuntime::Finish() {
    glFinish();
}

void BufferCacheRuntime::ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value) {
    glClearNamedBufferSubData(dest_buffer.Handle(), GL_R32UI, static_cast<GLintptr>(offset),
                              static_cast<GLsizeiptr>(size), GL_RED, GL_UNSIGNED_INT, &value);
}

void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
    if (has_unified_vertex_buffers) {
        buffer.MakeResident(GL_READ_ONLY);
        glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
                               static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
    } else {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
        index_buffer_offset = offset;
    }
}

void BufferCacheRuntime::BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size,
                                          u32 stride) {
    if (index >= max_attributes) {
        return;
    }
    if (has_unified_vertex_buffers) {
        buffer.MakeResident(GL_READ_ONLY);
        glBindVertexBuffer(index, 0, 0, static_cast<GLsizei>(stride));
        glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, index,
                               buffer.HostGpuAddr() + offset, static_cast<GLsizeiptr>(size));
    } else {
        glBindVertexBuffer(index, buffer.Handle(), static_cast<GLintptr>(offset),
                           static_cast<GLsizei>(stride));
    }
}

void BufferCacheRuntime::BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
    // TODO: Should HostBindings provide the correct runtime types to avoid these transforms?
    std::array<GLuint, 32> buffer_handles;
    std::array<GLsizei, 32> buffer_strides;
    std::ranges::transform(bindings.buffers, buffer_handles.begin(),
                           [](const Buffer* const buffer) { return buffer->Handle(); });
    std::ranges::transform(bindings.strides, buffer_strides.begin(),
                           [](u64 stride) { return static_cast<GLsizei>(stride); });
    const u32 count =
        std::min(static_cast<u32>(bindings.buffers.size()), max_attributes - bindings.min_index);
    if (has_unified_vertex_buffers) {
        for (u32 index = 0; index < count; ++index) {
            Buffer& buffer = *bindings.buffers[index];
            buffer.MakeResident(GL_READ_ONLY);
            glBufferAddressRangeNV(GL_VERTEX_ATTRIB_ARRAY_ADDRESS_NV, bindings.min_index + index,
                                   buffer.HostGpuAddr() + bindings.offsets[index],
                                   static_cast<GLsizeiptr>(bindings.sizes[index]));
        }
        static constexpr std::array<size_t, 32> ZEROS{};
        glBindVertexBuffers(bindings.min_index, static_cast<GLsizei>(count),
                            reinterpret_cast<const GLuint*>(ZEROS.data()),
                            reinterpret_cast<const GLintptr*>(ZEROS.data()), buffer_strides.data());
    } else {
        glBindVertexBuffers(bindings.min_index, static_cast<GLsizei>(count), buffer_handles.data(),
                            reinterpret_cast<const GLintptr*>(bindings.offsets.data()),
                            buffer_strides.data());
    }
}

void BufferCacheRuntime::BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer,
                                           u32 offset, u32 size) {
    if (use_assembly_shaders) {
        GLuint handle;
        if (offset != 0) {
            handle = copy_uniforms[stage][binding_index].handle;
            glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size);
        } else {
            handle = buffer.Handle();
        }
        glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0,
                            static_cast<GLsizeiptr>(size));
    } else {
        const GLuint base_binding = graphics_base_uniform_bindings[stage];
        const GLuint binding = base_binding + binding_index;
        glBindBufferRange(GL_UNIFORM_BUFFER, binding, buffer.Handle(),
                          static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
    }
}

void BufferCacheRuntime::BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset,
                                                  u32 size) {
    if (use_assembly_shaders) {
        GLuint handle;
        if (offset != 0) {
            handle = copy_compute_uniforms[binding_index].handle;
            glCopyNamedBufferSubData(buffer.Handle(), handle, offset, 0, size);
        } else {
            handle = buffer.Handle();
        }
        glBindBufferRangeNV(GL_COMPUTE_PROGRAM_PARAMETER_BUFFER_NV, binding_index, handle, 0,
                            static_cast<GLsizeiptr>(size));
    } else {
        glBindBufferRange(GL_UNIFORM_BUFFER, binding_index, buffer.Handle(),
                          static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
    }
}

void BufferCacheRuntime::BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer,
                                           u32 offset, u32 size, bool is_written) {
    if (use_storage_buffers) {
        const GLuint base_binding = graphics_base_storage_bindings[stage];
        const GLuint binding = base_binding + binding_index;
        glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding, buffer.Handle(),
                          static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
    } else {
        const BindlessSSBO ssbo{
            .address = buffer.HostGpuAddr() + offset,
            .length = static_cast<GLsizei>(size),
            .padding = 0,
        };
        buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY);
        glProgramLocalParametersI4uivNV(
            PROGRAM_LUT[stage],
            Shader::Backend::GLASM::PROGRAM_LOCAL_PARAMETER_STORAGE_BUFFER_BASE + binding_index, 1,
            reinterpret_cast<const GLuint*>(&ssbo));
    }
}

void BufferCacheRuntime::BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset,
                                                  u32 size, bool is_written) {
    if (use_storage_buffers) {
        if (size != 0) {
            glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, buffer.Handle(),
                              static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
        } else {
            glBindBufferRange(GL_SHADER_STORAGE_BUFFER, binding_index, 0, 0, 0);
        }
    } else {
        const BindlessSSBO ssbo{
            .address = buffer.HostGpuAddr() + offset,
            .length = static_cast<GLsizei>(size),
            .padding = 0,
        };
        buffer.MakeResident(is_written ? GL_READ_WRITE : GL_READ_ONLY);
        glProgramLocalParametersI4uivNV(
            GL_COMPUTE_PROGRAM_NV,
            Shader::Backend::GLASM::PROGRAM_LOCAL_PARAMETER_STORAGE_BUFFER_BASE + binding_index, 1,
            reinterpret_cast<const GLuint*>(&ssbo));
    }
}

void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset,
                                                     u32 size) {
    glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, index, buffer.Handle(),
                      static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
}

void BufferCacheRuntime::BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings) {
    std::array<GLuint, 4> buffer_handles;
    std::ranges::transform(bindings.buffers, buffer_handles.begin(),
                           [](const Buffer* const buffer) { return buffer->Handle(); });
    glBindBuffersRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                       static_cast<GLsizei>(bindings.buffers.size()), buffer_handles.data(),
                       reinterpret_cast<const GLintptr*>(bindings.offsets.data()),
                       reinterpret_cast<const GLsizeiptr*>(bindings.sizes.data()));
}

void BufferCacheRuntime::BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
                                           PixelFormat format) {
    *texture_handles++ = buffer.View(offset, size, format);
}

void BufferCacheRuntime::BindImageBuffer(Buffer& buffer, u32 offset, u32 size, PixelFormat format) {
    *image_handles++ = buffer.View(offset, size, format);
}

void BufferCacheRuntime::BindTransformFeedbackObject(GPUVAddr tfb_object_addr) {
    OGLTransformFeedback& tfb_object = tfb_objects[tfb_object_addr];
    tfb_object.Create();
    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, tfb_object.handle);
}

GLuint BufferCacheRuntime::GetTransformFeedbackObject(GPUVAddr tfb_object_addr) {
    ASSERT(tfb_objects.contains(tfb_object_addr));
    return tfb_objects[tfb_object_addr].handle;
}

} // 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 <unordered_map>

#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_staging_buffer_pool.h"

namespace OpenGL {

class BufferCacheRuntime;

class Buffer : public VideoCommon::BufferBase {
public:
    explicit Buffer(BufferCacheRuntime&, DAddr cpu_addr, u64 size_bytes);
    explicit Buffer(BufferCacheRuntime&, VideoCommon::NullBufferParams);

    void ImmediateUpload(size_t offset, std::span<const u8> data) noexcept;

    void ImmediateDownload(size_t offset, std::span<u8> data) noexcept;

    void MakeResident(GLenum access) noexcept;

    void MarkUsage(u64 offset, u64 size) {}

    [[nodiscard]] GLuint View(u32 offset, u32 size, VideoCore::Surface::PixelFormat format);

    [[nodiscard]] GLuint64EXT HostGpuAddr() const noexcept {
        return address;
    }

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

private:
    struct BufferView {
        u32 offset;
        u32 size;
        VideoCore::Surface::PixelFormat format;
        OGLTexture texture;
    };

    GLuint64EXT address = 0;
    OGLBuffer buffer;
    GLenum current_residency_access = GL_NONE;
    std::vector<BufferView> views;
};

class BufferCacheRuntime {
    friend Buffer;

public:
    static constexpr u8 INVALID_BINDING = std::numeric_limits<u8>::max();

    explicit BufferCacheRuntime(const Device& device_, StagingBufferPool& staging_buffer_pool_);

    [[nodiscard]] StagingBufferMap UploadStagingBuffer(size_t size);

    [[nodiscard]] StagingBufferMap DownloadStagingBuffer(size_t size, bool deferred = false);

    void FreeDeferredStagingBuffer(StagingBufferMap& buffer);

    bool CanReorderUpload(const Buffer&, std::span<const VideoCommon::BufferCopy>) {
        return false;
    }

    void CopyBuffer(GLuint dst_buffer, GLuint src_buffer,
                    std::span<const VideoCommon::BufferCopy> copies, bool barrier);

    void CopyBuffer(GLuint dst_buffer, Buffer& src_buffer,
                    std::span<const VideoCommon::BufferCopy> copies, bool barrier);

    void CopyBuffer(Buffer& dst_buffer, GLuint src_buffer,
                    std::span<const VideoCommon::BufferCopy> copies, bool barrier,
                    bool can_reorder_upload = false);

    void CopyBuffer(Buffer& dst_buffer, Buffer& src_buffer,
                    std::span<const VideoCommon::BufferCopy> copies, bool);

    void PreCopyBarrier();
    void PostCopyBarrier();
    void Finish();

    void TickFrame(Common::SlotVector<Buffer>&) noexcept {}

    void ClearBuffer(Buffer& dest_buffer, u32 offset, size_t size, u32 value);

    void BindIndexBuffer(Buffer& buffer, u32 offset, u32 size);

    void BindVertexBuffer(u32 index, Buffer& buffer, u32 offset, u32 size, u32 stride);

    void BindVertexBuffers(VideoCommon::HostBindings<Buffer>& bindings);

    void BindUniformBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size);

    void BindComputeUniformBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size);

    void BindStorageBuffer(size_t stage, u32 binding_index, Buffer& buffer, u32 offset, u32 size,
                           bool is_written);

    void BindComputeStorageBuffer(u32 binding_index, Buffer& buffer, u32 offset, u32 size,
                                  bool is_written);

    void BindTransformFeedbackBuffer(u32 index, Buffer& buffer, u32 offset, u32 size);

    void BindTransformFeedbackBuffers(VideoCommon::HostBindings<Buffer>& bindings);

    void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
                           VideoCore::Surface::PixelFormat format);

    void BindImageBuffer(Buffer& buffer, u32 offset, u32 size,
                         VideoCore::Surface::PixelFormat format);

    void BindTransformFeedbackObject(GPUVAddr tfb_object_addr);
    GLuint GetTransformFeedbackObject(GPUVAddr tfb_object_addr);

    u64 GetDeviceMemoryUsage() const;

    void BindFastUniformBuffer(size_t stage, u32 binding_index, u32 size) {
        const GLuint handle = fast_uniforms[stage][binding_index].handle;
        const GLsizeiptr gl_size = static_cast<GLsizeiptr>(size);
        if (use_assembly_shaders) {
            glBindBufferRangeNV(PABO_LUT[stage], binding_index, handle, 0, gl_size);
        } else {
            const GLuint base_binding = graphics_base_uniform_bindings[stage];
            const GLuint binding = base_binding + binding_index;
            glBindBufferRange(GL_UNIFORM_BUFFER, binding, handle, 0, gl_size);
        }
    }

    void PushFastUniformBuffer(size_t stage, u32 binding_index, std::span<const u8> data) {
        if (use_assembly_shaders) {
            glProgramBufferParametersIuivNV(
                PABO_LUT[stage], binding_index, 0,
                static_cast<GLsizei>(data.size_bytes() / sizeof(GLuint)),
                reinterpret_cast<const GLuint*>(data.data()));
        } else {
            glNamedBufferSubData(fast_uniforms[stage][binding_index].handle, 0,
                                 static_cast<GLsizeiptr>(data.size_bytes()), data.data());
        }
    }

    std::span<u8> BindMappedUniformBuffer(size_t stage, u32 binding_index, u32 size) noexcept {
        const auto [mapped_span, offset] = stream_buffer->Request(static_cast<size_t>(size));
        const GLuint base_binding = graphics_base_uniform_bindings[stage];
        const GLuint binding = base_binding + binding_index;
        glBindBufferRange(GL_UNIFORM_BUFFER, binding, stream_buffer->Handle(),
                          static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size));
        return mapped_span;
    }

    [[nodiscard]] const GLvoid* IndexOffset() const noexcept {
        return reinterpret_cast<const GLvoid*>(static_cast<uintptr_t>(index_buffer_offset));
    }

    [[nodiscard]] bool HasFastBufferSubData() const noexcept {
        return has_fast_buffer_sub_data;
    }

    [[nodiscard]] bool SupportsNonZeroUniformOffset() const noexcept {
        return !use_assembly_shaders;
    }

    void SetBaseUniformBindings(const std::array<GLuint, 5>& bindings) {
        graphics_base_uniform_bindings = bindings;
    }

    void SetBaseStorageBindings(const std::array<GLuint, 5>& bindings) {
        graphics_base_storage_bindings = bindings;
    }

    void SetImagePointers(GLuint* texture_handles_, GLuint* image_handles_) {
        texture_handles = texture_handles_;
        image_handles = image_handles_;
    }

    void SetEnableStorageBuffers(bool use_storage_buffers_) {
        use_storage_buffers = use_storage_buffers_;
    }

    u64 GetDeviceLocalMemory() const {
        return device_access_memory;
    }

    bool CanReportMemoryUsage() const {
        return device.CanReportMemoryUsage();
    }

    u32 GetStorageBufferAlignment() const {
        return static_cast<u32>(device.GetShaderStorageBufferAlignment());
    }

private:
    static constexpr std::array PABO_LUT{
        GL_VERTEX_PROGRAM_PARAMETER_BUFFER_NV,          GL_TESS_CONTROL_PROGRAM_PARAMETER_BUFFER_NV,
        GL_TESS_EVALUATION_PROGRAM_PARAMETER_BUFFER_NV, GL_GEOMETRY_PROGRAM_PARAMETER_BUFFER_NV,
        GL_FRAGMENT_PROGRAM_PARAMETER_BUFFER_NV,
    };

    const Device& device;
    StagingBufferPool& staging_buffer_pool;

    bool has_fast_buffer_sub_data = false;
    bool use_assembly_shaders = false;
    bool has_unified_vertex_buffers = false;

    bool use_storage_buffers = false;

    u32 max_attributes = 0;

    std::array<GLuint, 5> graphics_base_uniform_bindings{};
    std::array<GLuint, 5> graphics_base_storage_bindings{};
    GLuint* texture_handles = nullptr;
    GLuint* image_handles = nullptr;

    std::optional<StreamBuffer> stream_buffer;

    std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>,
               VideoCommon::NUM_STAGES>
        fast_uniforms;
    std::array<std::array<OGLBuffer, VideoCommon::NUM_GRAPHICS_UNIFORM_BUFFERS>,
               VideoCommon::NUM_STAGES>
        copy_uniforms;
    std::array<OGLBuffer, VideoCommon::NUM_COMPUTE_UNIFORM_BUFFERS> copy_compute_uniforms;

    u32 index_buffer_offset = 0;

    u64 device_access_memory;
    std::unordered_map<GPUVAddr, OGLTransformFeedback> tfb_objects;
};

struct BufferCacheParams {
    using Runtime = OpenGL::BufferCacheRuntime;
    using Buffer = OpenGL::Buffer;
    using Async_Buffer = OpenGL::StagingBufferMap;
    using MemoryTracker = VideoCommon::MemoryTrackerBase<Tegra::MaxwellDeviceMemoryManager>;

    static constexpr bool IS_OPENGL = true;
    static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = true;
    static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT = true;
    static constexpr bool NEEDS_BIND_UNIFORM_INDEX = true;
    static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
    static constexpr bool USE_MEMORY_MAPS = true;
    static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true;

    // TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
    static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
};

using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;

} // namespace OpenGL

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"

namespace VideoCommon {
template class VideoCommon::BufferCache<OpenGL::BufferCacheParams>;
}


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

#include <cstring>

#include "common/cityhash.h"
#include "common/settings.h" // for enum class Settings::ShaderBackend
#include "video_core/renderer_opengl/gl_compute_pipeline.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"

namespace OpenGL {

using Shader::ImageBufferDescriptor;
using Tegra::Texture::TexturePair;
using VideoCommon::ImageId;

constexpr u32 MAX_TEXTURES = 64;
constexpr u32 MAX_IMAGES = 16;

size_t ComputePipelineKey::Hash() const noexcept {
    return static_cast<size_t>(
        Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this));
}

bool ComputePipelineKey::operator==(const ComputePipelineKey& rhs) const noexcept {
    return std::memcmp(this, &rhs, sizeof *this) == 0;
}

ComputePipeline::ComputePipeline(const Device& device, TextureCache& texture_cache_,
                                 BufferCache& buffer_cache_, ProgramManager& program_manager_,
                                 const Shader::Info& info_, std::string code,
                                 std::vector<u32> code_v, bool force_context_flush)
    : texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
      program_manager{program_manager_}, info{info_} {
    switch (device.GetShaderBackend()) {
    case Settings::ShaderBackend::Glsl:
        source_program = CreateProgram(code, GL_COMPUTE_SHADER);
        break;
    case Settings::ShaderBackend::Glasm:
        assembly_program = CompileProgram(code, GL_COMPUTE_PROGRAM_NV);
        break;
    case Settings::ShaderBackend::SpirV:
        source_program = CreateProgram(code_v, GL_COMPUTE_SHADER);
        break;
    }
    std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(),
                uniform_buffer_sizes.begin());

    num_texture_buffers = Shader::NumDescriptors(info.texture_buffer_descriptors);
    num_image_buffers = Shader::NumDescriptors(info.image_buffer_descriptors);

    const u32 num_textures{num_texture_buffers + Shader::NumDescriptors(info.texture_descriptors)};
    ASSERT(num_textures <= MAX_TEXTURES);

    const u32 num_images{num_image_buffers + Shader::NumDescriptors(info.image_descriptors)};
    ASSERT(num_images <= MAX_IMAGES);

    const bool is_glasm{assembly_program.handle != 0};
    const u32 num_storage_buffers{Shader::NumDescriptors(info.storage_buffers_descriptors)};
    use_storage_buffers =
        !is_glasm || num_storage_buffers < device.GetMaxGLASMStorageBufferBlocks();
    writes_global_memory = !use_storage_buffers &&
                           std::ranges::any_of(info.storage_buffers_descriptors,
                                               [](const auto& desc) { return desc.is_written; });
    uses_local_memory = info.uses_local_memory;
    if (force_context_flush) {
        std::scoped_lock lock{built_mutex};
        built_fence.Create();
        // Flush this context to ensure compilation commands and fence are in the GPU pipe.
        glFlush();
        built_condvar.notify_one();
    } else {
        is_built = true;
    }
}

void ComputePipeline::Configure() {
    buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes);
    buffer_cache.UnbindComputeStorageBuffers();
    size_t ssbo_index{};
    for (const auto& desc : info.storage_buffers_descriptors) {
        ASSERT(desc.count == 1);
        buffer_cache.BindComputeStorageBuffer(ssbo_index, desc.cbuf_index, desc.cbuf_offset,
                                              desc.is_written);
        ++ssbo_index;
    }
    texture_cache.SynchronizeComputeDescriptors();

    boost::container::static_vector<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
    boost::container::static_vector<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
    std::array<GLuint, MAX_TEXTURES> gl_samplers;
    std::array<GLuint, MAX_TEXTURES> textures;
    std::array<GLuint, MAX_IMAGES> images;
    GLsizei sampler_binding{};
    GLsizei texture_binding{};
    GLsizei image_binding{};

    const auto& qmd{kepler_compute->launch_description};
    const auto& cbufs{qmd.const_buffer_config};
    const bool via_header_index{qmd.linked_tsc != 0};
    const auto read_handle{[&](const auto& desc, u32 index) {
        ASSERT(((qmd.const_buffer_enable_mask >> desc.cbuf_index) & 1) != 0);
        const u32 index_offset{index << desc.size_shift};
        const u32 offset{desc.cbuf_offset + index_offset};
        const GPUVAddr addr{cbufs[desc.cbuf_index].Address() + offset};
        if constexpr (std::is_same_v<decltype(desc), const Shader::TextureDescriptor&> ||
                      std::is_same_v<decltype(desc), const Shader::TextureBufferDescriptor&>) {
            if (desc.has_secondary) {
                ASSERT(((qmd.const_buffer_enable_mask >> desc.secondary_cbuf_index) & 1) != 0);
                const u32 secondary_offset{desc.secondary_cbuf_offset + index_offset};
                const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].Address() +
                                             secondary_offset};
                const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
                const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
                                  << desc.secondary_shift_left};
                return TexturePair(lhs_raw | rhs_raw, via_header_index);
            }
        }
        return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
    }};
    const auto add_image{[&](const auto& desc, bool blacklist) {
        for (u32 index = 0; index < desc.count; ++index) {
            const auto handle{read_handle(desc, index)};
            views.push_back({
                .index = handle.first,
                .blacklist = blacklist,
                .id = {},
            });
        }
    }};
    for (const auto& desc : info.texture_buffer_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            const auto handle{read_handle(desc, index)};
            views.push_back({handle.first});
        }
    }
    for (const auto& desc : info.image_buffer_descriptors) {
        add_image(desc, false);
    }
    for (const auto& desc : info.texture_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            const auto handle{read_handle(desc, index)};
            views.push_back({handle.first});

            VideoCommon::SamplerId sampler = texture_cache.GetComputeSamplerId(handle.second);
            samplers.push_back(sampler);
        }
    }
    for (const auto& desc : info.image_descriptors) {
        add_image(desc, desc.is_written);
    }
    texture_cache.FillComputeImageViews(std::span(views.data(), views.size()));

    if (!is_built) {
        WaitForBuild();
    }
    if (assembly_program.handle != 0) {
        program_manager.BindComputeAssemblyProgram(assembly_program.handle);
    } else {
        program_manager.BindComputeProgram(source_program.handle);
    }
    buffer_cache.UnbindComputeTextureBuffers();
    size_t texbuf_index{};
    const auto add_buffer{[&](const auto& desc) {
        constexpr bool is_image = std::is_same_v<decltype(desc), const ImageBufferDescriptor&>;
        for (u32 i = 0; i < desc.count; ++i) {
            bool is_written{false};
            if constexpr (is_image) {
                is_written = desc.is_written;
            }
            ImageView& image_view{texture_cache.GetImageView(views[texbuf_index].id)};
            buffer_cache.BindComputeTextureBuffer(texbuf_index, image_view.GpuAddr(),
                                                  image_view.BufferSize(), image_view.format,
                                                  is_written, is_image);
            ++texbuf_index;
        }
    }};
    std::ranges::for_each(info.texture_buffer_descriptors, add_buffer);
    std::ranges::for_each(info.image_buffer_descriptors, add_buffer);

    buffer_cache.UpdateComputeBuffers();

    buffer_cache.runtime.SetEnableStorageBuffers(use_storage_buffers);
    buffer_cache.runtime.SetImagePointers(textures.data(), images.data());
    buffer_cache.BindHostComputeBuffers();

    const VideoCommon::ImageViewInOut* views_it{views.data() + num_texture_buffers +
                                                num_image_buffers};
    const VideoCommon::SamplerId* samplers_it{samplers.data()};
    texture_binding += num_texture_buffers;
    image_binding += num_image_buffers;

    u32 texture_scaling_mask{};

    for (const auto& desc : info.texture_buffer_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            gl_samplers[sampler_binding++] = 0;
        }
    }
    for (const auto& desc : info.texture_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
            textures[texture_binding] = image_view.Handle(desc.type);
            if (texture_cache.IsRescaling(image_view)) {
                texture_scaling_mask |= 1u << texture_binding;
            }
            ++texture_binding;

            const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))};
            const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
                                            !image_view.SupportsAnisotropy()};
            gl_samplers[sampler_binding++] =
                use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle();
        }
    }
    u32 image_scaling_mask{};
    for (const auto& desc : info.image_descriptors) {
        for (u32 index = 0; index < desc.count; ++index) {
            ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
            if (desc.is_written) {
                texture_cache.MarkModification(image_view.image_id);
            }
            images[image_binding] = image_view.StorageView(desc.type, desc.format);
            if (texture_cache.IsRescaling(image_view)) {
                image_scaling_mask |= 1u << image_binding;
            }
            ++image_binding;
        }
    }
    if (info.uses_rescaling_uniform) {
        const f32 float_texture_scaling_mask{Common::BitCast<f32>(texture_scaling_mask)};
        const f32 float_image_scaling_mask{Common::BitCast<f32>(image_scaling_mask)};
        if (assembly_program.handle != 0) {
            glProgramLocalParameter4fARB(GL_COMPUTE_PROGRAM_NV, 0, float_texture_scaling_mask,
                                         float_image_scaling_mask, 0.0f, 0.0f);
        } else {
            glProgramUniform4f(source_program.handle, 0, float_texture_scaling_mask,
                               float_image_scaling_mask, 0.0f, 0.0f);
        }
    }
    if (texture_binding != 0) {
        ASSERT(texture_binding == sampler_binding);
        glBindTextures(0, texture_binding, textures.data());
        glBindSamplers(0, sampler_binding, gl_samplers.data());
    }
    if (image_binding != 0) {
        glBindImageTextures(0, image_binding, images.data());
    }
}

void ComputePipeline::WaitForBuild() {
    if (built_fence.handle == 0) {
        std::unique_lock lock{built_mutex};
        built_condvar.wait(lock, [this] { return built_fence.handle != 0; });
    }
    ASSERT(glClientWaitSync(built_fence.handle, 0, GL_TIMEOUT_IGNORED) != GL_WAIT_FAILED);
    is_built = true;
}

} // namespace OpenGL


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

#pragma once

#include <array>
#include <type_traits>

#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"

namespace Tegra {
class MemoryManager;
}

namespace Tegra::Engines {
class KeplerCompute;
}

namespace Shader {
struct Info;
}

namespace OpenGL {

class Device;
class ProgramManager;

struct ComputePipelineKey {
    u64 unique_hash;
    u32 shared_memory_size;
    std::array<u32, 3> workgroup_size;

    size_t Hash() const noexcept;

    bool operator==(const ComputePipelineKey&) const noexcept;

    bool operator!=(const ComputePipelineKey& rhs) const noexcept {
        return !operator==(rhs);
    }
};
static_assert(std::has_unique_object_representations_v<ComputePipelineKey>);
static_assert(std::is_trivially_copyable_v<ComputePipelineKey>);
static_assert(std::is_trivially_constructible_v<ComputePipelineKey>);

class ComputePipeline {
public:
    explicit ComputePipeline(const Device& device, TextureCache& texture_cache_,
                             BufferCache& buffer_cache_, ProgramManager& program_manager_,
                             const Shader::Info& info_, std::string code, std::vector<u32> code_v,
                             bool force_context_flush = false);

    void Configure();

    [[nodiscard]] bool WritesGlobalMemory() const noexcept {
        return writes_global_memory;
    }

    [[nodiscard]] bool UsesLocalMemory() const noexcept {
        return uses_local_memory;
    }

    void SetEngine(Tegra::Engines::KeplerCompute* kepler_compute_,
                   Tegra::MemoryManager* gpu_memory_) {
        kepler_compute = kepler_compute_;
        gpu_memory = gpu_memory_;
    }

private:
    void WaitForBuild();

    TextureCache& texture_cache;
    BufferCache& buffer_cache;
    Tegra::MemoryManager* gpu_memory;
    Tegra::Engines::KeplerCompute* kepler_compute;
    ProgramManager& program_manager;

    Shader::Info info;
    OGLProgram source_program;
    OGLAssemblyProgram assembly_program;
    VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{};

    u32 num_texture_buffers{};
    u32 num_image_buffers{};

    bool use_storage_buffers{};
    bool writes_global_memory{};
    bool uses_local_memory{};

    std::mutex built_mutex;
    std::condition_variable built_condvar;
    OGLSync built_fence{};
    bool is_built{false};
};

} // namespace OpenGL

namespace std {
template <>
struct hash<OpenGL::ComputePipelineKey> {
    size_t operator()(const OpenGL::ComputePipelineKey& k) const noexcept {
        return k.Hash();
    }
};
} // namespace std


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

#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdlib>
#include <optional>
#include <span>
#include <stdexcept>
#include <vector>

#include <glad/glad.h>

#include "common/literals.h"
#include "common/logging/log.h"
#include "common/polyfill_ranges.h"
#include "common/settings.h"
#include "shader_recompiler/stage.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"

using namespace Common::Literals;

namespace OpenGL {
namespace {
constexpr std::array LIMIT_UBOS = {
    GL_MAX_VERTEX_UNIFORM_BLOCKS,          GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,
    GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_GEOMETRY_UNIFORM_BLOCKS,
    GL_MAX_FRAGMENT_UNIFORM_BLOCKS,        GL_MAX_COMPUTE_UNIFORM_BLOCKS,
};

template <typename T>
T GetInteger(GLenum pname) {
    GLint temporary;
    glGetIntegerv(pname, &temporary);
    return static_cast<T>(temporary);
}

bool TestProgram(const GLchar* glsl) {
    const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)};
    GLint link_status;
    glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
    glDeleteProgram(shader);
    return link_status == GL_TRUE;
}

std::vector<std::string_view> GetExtensions() {
    GLint num_extensions;
    glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
    std::vector<std::string_view> extensions;
    extensions.reserve(num_extensions);
    for (GLint index = 0; index < num_extensions; ++index) {
        extensions.push_back(
            reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, static_cast<GLuint>(index))));
    }
    return extensions;
}

bool HasExtension(std::span<const std::string_view> extensions, std::string_view extension) {
    return std::ranges::find(extensions, extension) != extensions.end();
}

std::array<u32, Shader::MaxStageTypes> BuildMaxUniformBuffers() noexcept {
    std::array<u32, Shader::MaxStageTypes> max;
    std::ranges::transform(LIMIT_UBOS, max.begin(), &GetInteger<u32>);
    return max;
}

bool IsASTCSupported() {
    static constexpr std::array targets{
        GL_TEXTURE_2D,
        GL_TEXTURE_2D_ARRAY,
    };
    static constexpr std::array formats{
        GL_COMPRESSED_RGBA_ASTC_4x4_KHR,           GL_COMPRESSED_RGBA_ASTC_5x4_KHR,
        GL_COMPRESSED_RGBA_ASTC_5x5_KHR,           GL_COMPRESSED_RGBA_ASTC_6x5_KHR,
        GL_COMPRESSED_RGBA_ASTC_6x6_KHR,           GL_COMPRESSED_RGBA_ASTC_8x5_KHR,
        GL_COMPRESSED_RGBA_ASTC_8x6_KHR,           GL_COMPRESSED_RGBA_ASTC_8x8_KHR,
        GL_COMPRESSED_RGBA_ASTC_10x5_KHR,          GL_COMPRESSED_RGBA_ASTC_10x6_KHR,
        GL_COMPRESSED_RGBA_ASTC_10x8_KHR,          GL_COMPRESSED_RGBA_ASTC_10x10_KHR,
        GL_COMPRESSED_RGBA_ASTC_12x10_KHR,         GL_COMPRESSED_RGBA_ASTC_12x12_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR,   GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR,   GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR,   GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR,   GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR,  GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR,  GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR,
        GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR, GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR,
    };
    static constexpr std::array required_support{
        GL_VERTEX_TEXTURE,   GL_TESS_CONTROL_TEXTURE, GL_TESS_EVALUATION_TEXTURE,
        GL_GEOMETRY_TEXTURE, GL_FRAGMENT_TEXTURE,     GL_COMPUTE_TEXTURE,
    };
    for (const GLenum target : targets) {
        for (const GLenum format : formats) {
            for (const GLenum support : required_support) {
                GLint value;
                glGetInternalformativ(target, format, support, 1, &value);
                if (value != GL_FULL_SUPPORT) {
                    return false;
                }
            }
        }
    }
    return true;
}

static bool HasSlowSoftwareAstc(std::string_view vendor_name, std::string_view renderer) {
// ifdef for Unix reduces string comparisons for non-Windows drivers, and Intel
#ifdef YUZU_UNIX
    // Sorted vaguely by how likely a vendor is to appear
    if (vendor_name == "AMD") {
        // RadeonSI
        return true;
    }
    if (vendor_name == "Intel") {
        // Must be inside YUZU_UNIX ifdef as the Windows driver uses the same vendor string
        // iris, crocus
        const bool is_intel_dg = (renderer.find("DG") != std::string_view::npos);
        return is_intel_dg;
    }
    if (vendor_name == "nouveau") {
        return true;
    }
    if (vendor_name == "X.Org") {
        // R600
        return true;
    }
#endif
    if (vendor_name == "Collabora Ltd") {
        // Zink
        return true;
    }
    if (vendor_name == "Microsoft Corporation") {
        // d3d12
        return true;
    }
    if (vendor_name == "Mesa/X.org") {
        // llvmpipe, softpipe, virgl
        return true;
    }
    return false;
}

[[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) {
    const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
    return nsight || HasExtension(extensions, "GL_EXT_debug_tool") ||
           Settings::values.renderer_debug.GetValue();
}
} // Anonymous namespace

Device::Device(Core::Frontend::EmuWindow& emu_window) {
    if (!GLAD_GL_VERSION_4_6) {
        LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
        throw std::runtime_error{"Insufficient version"};
    }
    vendor_name = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
    const std::string_view version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
    const std::string_view renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
    const std::vector extensions = GetExtensions();

    const bool is_nvidia = vendor_name == "NVIDIA Corporation";
    const bool is_amd = vendor_name == "ATI Technologies Inc.";
    const bool is_intel = vendor_name == "Intel";

    const bool has_slow_software_astc =
        !is_nvidia && !is_amd && HasSlowSoftwareAstc(vendor_name, renderer);

#ifdef __unix__
    constexpr bool is_linux = true;
#else
    constexpr bool is_linux = false;
#endif

    bool disable_fast_buffer_sub_data = false;
    if (is_nvidia && version == "4.6.0 NVIDIA 443.24") {
        LOG_WARNING(
            Render_OpenGL,
            "Beta driver 443.24 is known to have issues. There might be performance issues.");
        disable_fast_buffer_sub_data = true;
    }
    max_uniform_buffers = BuildMaxUniformBuffers();
    uniform_buffer_alignment = GetInteger<size_t>(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT);
    shader_storage_alignment = GetInteger<size_t>(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT);
    max_vertex_attributes = GetInteger<u32>(GL_MAX_VERTEX_ATTRIBS);
    max_varyings = GetInteger<u32>(GL_MAX_VARYING_VECTORS);
    max_compute_shared_memory_size = GetInteger<u32>(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE);
    max_glasm_storage_buffer_blocks = GetInteger<u32>(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS);
    has_warp_intrinsics = GLAD_GL_NV_gpu_shader5 && GLAD_GL_NV_shader_thread_group &&
                          GLAD_GL_NV_shader_thread_shuffle;
    has_shader_ballot = GLAD_GL_ARB_shader_ballot;
    has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
    has_image_load_formatted = HasExtension(extensions, "GL_EXT_shader_image_load_formatted");
    has_texture_shadow_lod = HasExtension(extensions, "GL_EXT_texture_shadow_lod");
    has_astc = !has_slow_software_astc && IsASTCSupported();
    has_variable_aoffi = TestVariableAoffi();
    has_component_indexing_bug = false;
    has_precise_bug = TestPreciseBug();
    has_broken_texture_view_formats = (!is_linux && is_intel);
    has_nv_viewport_array2 = GLAD_GL_NV_viewport_array2;
    has_derivative_control = GLAD_GL_ARB_derivative_control;
    has_vertex_buffer_unified_memory = GLAD_GL_NV_vertex_buffer_unified_memory;
    has_debugging_tool_attached = IsDebugToolAttached(extensions);
    has_depth_buffer_float = HasExtension(extensions, "GL_NV_depth_buffer_float");
    has_geometry_shader_passthrough = GLAD_GL_NV_geometry_shader_passthrough;
    has_nv_gpu_shader_5 = GLAD_GL_NV_gpu_shader5;
    has_shader_int64 = HasExtension(extensions, "GL_ARB_gpu_shader_int64");
    has_amd_shader_half_float = GLAD_GL_AMD_gpu_shader_half_float;
    has_sparse_texture_2 = GLAD_GL_ARB_sparse_texture2;
    has_draw_texture = GLAD_GL_NV_draw_texture;
    warp_size_potentially_larger_than_guest = !is_nvidia && !is_intel;
    need_fastmath_off = is_nvidia;
    can_report_memory = GLAD_GL_NVX_gpu_memory_info;

    // At the moment of writing this, only Nvidia's driver optimizes BufferSubData on exclusive
    // uniform buffers as "push constants"
    has_fast_buffer_sub_data = is_nvidia && !disable_fast_buffer_sub_data;

    shader_backend = Settings::values.shader_backend.GetValue();
    use_assembly_shaders = shader_backend == Settings::ShaderBackend::Glasm &&
                           GLAD_GL_NV_gpu_program5 && GLAD_GL_NV_compute_program5 &&
                           GLAD_GL_NV_transform_feedback && GLAD_GL_NV_transform_feedback2;
    if (shader_backend == Settings::ShaderBackend::Glasm && !use_assembly_shaders) {
        LOG_ERROR(Render_OpenGL, "Assembly shaders enabled but not supported");
        shader_backend = Settings::ShaderBackend::Glsl;
    }

    if (shader_backend == Settings::ShaderBackend::Glsl && is_nvidia) {
        const std::string_view driver_version = version.substr(13);
        const int version_major =
            std::atoi(driver_version.substr(0, driver_version.find(".")).data());
        if (version_major >= 495) {
            has_cbuf_ftou_bug = true;
            has_bool_ref_bug = true;
        }
    }
    has_lmem_perf_bug = is_nvidia;

    strict_context_required = emu_window.StrictContextRequired();
    // Blocks Intel OpenGL drivers on Windows from using asynchronous shader compilation.
    // Blocks EGL on Wayland from using asynchronous shader compilation.
    const bool blacklist_async_shaders = (is_intel && !is_linux) || strict_context_required;
    use_asynchronous_shaders =
        Settings::values.use_asynchronous_shaders.GetValue() && !blacklist_async_shaders;
    use_driver_cache = is_nvidia;
    supports_conditional_barriers = !is_intel;

    LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
    LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
    LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
    LOG_INFO(Render_OpenGL, "Renderer_BrokenTextureViewFormats: {}",
             has_broken_texture_view_formats);
    if (Settings::values.use_asynchronous_shaders.GetValue() && !use_asynchronous_shaders) {
        LOG_WARNING(Render_OpenGL, "Asynchronous shader compilation enabled but not supported");
    }
}

std::string Device::GetVendorName() const {
    if (vendor_name == "NVIDIA Corporation") {
        return "NVIDIA";
    }
    if (vendor_name == "ATI Technologies Inc.") {
        return "AMD";
    }
    if (vendor_name == "Intel") {
        // For Mesa, `Intel` is an overloaded vendor string that could mean crocus or iris.
        // Simply return `INTEL` for those as well as the Windows driver.
        return "Intel";
    }
    if (vendor_name == "Intel Open Source Technology Center") {
        return "i965";
    }
    if (vendor_name == "Mesa Project") {
        return "i915";
    }
    if (vendor_name == "Mesa/X.org") {
        // This vendor string is overloaded between llvmpipe, softpipe, and virgl, so just return
        // MESA instead of one of those driver names.
        return "Mesa";
    }
    if (vendor_name == "AMD") {
        return "RadeonSI";
    }
    if (vendor_name == "nouveau") {
        return "Nouveau";
    }
    if (vendor_name == "X.Org") {
        return "R600";
    }
    if (vendor_name == "Collabora Ltd") {
        return "Zink";
    }
    if (vendor_name == "Intel Corporation") {
        return "OpenSWR";
    }
    if (vendor_name == "Microsoft Corporation") {
        return "D3D12";
    }
    if (vendor_name == "NVIDIA") {
        // Mesa's tegra driver reports `NVIDIA`. Only present in this list because the default
        // strategy would have returned `NVIDIA` here for this driver, the same result as the
        // proprietary driver.
        return "Tegra";
    }
    return vendor_name;
}

bool Device::TestVariableAoffi() {
    return TestProgram(R"(#version 430 core
// This is a unit test, please ignore me on apitrace bug reports.
uniform sampler2D tex;
uniform ivec2 variable_offset;
out vec4 output_attribute;
void main() {
    output_attribute = textureOffset(tex, vec2(0), variable_offset);
})");
}

bool Device::TestPreciseBug() {
    return !TestProgram(R"(#version 430 core
in vec3 coords;
out float out_value;
uniform sampler2DShadow tex;
void main() {
    precise float tmp_value = vec4(texture(tex, coords)).x;
    out_value = tmp_value;
})");
}

u64 Device::GetCurrentDedicatedVideoMemory() const {
    GLint cur_avail_mem_kb = 0;
    glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &cur_avail_mem_kb);
    return static_cast<u64>(cur_avail_mem_kb) * 1_KiB;
}

} // namespace OpenGL


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

#pragma once

#include <cstddef>
#include <string>

#include "common/common_types.h"
#include "core/frontend/emu_window.h"
#include "shader_recompiler/stage.h"

namespace Settings {
enum class ShaderBackend : u32;
};

namespace OpenGL {

class Device {
public:
    explicit Device(Core::Frontend::EmuWindow& emu_window);

    [[nodiscard]] std::string GetVendorName() const;

    u64 GetCurrentDedicatedVideoMemory() const;

    u32 GetMaxUniformBuffers(Shader::Stage stage) const noexcept {
        return max_uniform_buffers[static_cast<size_t>(stage)];
    }

    size_t GetUniformBufferAlignment() const {
        return uniform_buffer_alignment;
    }

    size_t GetShaderStorageBufferAlignment() const {
        return shader_storage_alignment;
    }

    u32 GetMaxVertexAttributes() const {
        return max_vertex_attributes;
    }

    u32 GetMaxVaryings() const {
        return max_varyings;
    }

    u32 GetMaxComputeSharedMemorySize() const {
        return max_compute_shared_memory_size;
    }

    u32 GetMaxGLASMStorageBufferBlocks() const {
        return max_glasm_storage_buffer_blocks;
    }

    bool HasWarpIntrinsics() const {
        return has_warp_intrinsics;
    }

    bool HasShaderBallot() const {
        return has_shader_ballot;
    }

    bool HasVertexViewportLayer() const {
        return has_vertex_viewport_layer;
    }

    bool HasImageLoadFormatted() const {
        return has_image_load_formatted;
    }

    bool HasTextureShadowLod() const {
        return has_texture_shadow_lod;
    }

    bool HasVertexBufferUnifiedMemory() const {
        return has_vertex_buffer_unified_memory;
    }

    bool HasASTC() const {
        return has_astc;
    }

    bool HasVariableAoffi() const {
        return has_variable_aoffi;
    }

    bool HasComponentIndexingBug() const {
        return has_component_indexing_bug;
    }

    bool HasPreciseBug() const {
        return has_precise_bug;
    }

    bool HasBrokenTextureViewFormats() const {
        return has_broken_texture_view_formats;
    }

    bool HasFastBufferSubData() const {
        return has_fast_buffer_sub_data;
    }

    bool HasNvViewportArray2() const {
        return has_nv_viewport_array2;
    }

    bool HasDerivativeControl() const {
        return has_derivative_control;
    }

    bool HasDebuggingToolAttached() const {
        return has_debugging_tool_attached;
    }

    bool UseAssemblyShaders() const {
        return use_assembly_shaders;
    }

    bool UseAsynchronousShaders() const {
        return use_asynchronous_shaders;
    }

    bool UseDriverCache() const {
        return use_driver_cache;
    }

    bool HasDepthBufferFloat() const {
        return has_depth_buffer_float;
    }

    bool HasGeometryShaderPassthrough() const {
        return has_geometry_shader_passthrough;
    }

    bool HasNvGpuShader5() const {
        return has_nv_gpu_shader_5;
    }

    bool HasShaderInt64() const {
        return has_shader_int64;
    }

    bool HasAmdShaderHalfFloat() const {
        return has_amd_shader_half_float;
    }

    bool HasSparseTexture2() const {
        return has_sparse_texture_2;
    }

    bool HasDrawTexture() const {
        return has_draw_texture;
    }

    bool IsWarpSizePotentiallyLargerThanGuest() const {
        return warp_size_potentially_larger_than_guest;
    }

    bool NeedsFastmathOff() const {
        return need_fastmath_off;
    }

    bool HasCbufFtouBug() const {
        return has_cbuf_ftou_bug;
    }

    bool HasBoolRefBug() const {
        return has_bool_ref_bug;
    }

    Settings::ShaderBackend GetShaderBackend() const {
        return shader_backend;
    }

    bool IsAmd() const {
        return vendor_name == "ATI Technologies Inc.";
    }

    bool IsIntel() const {
        return vendor_name == "Intel";
    }

    bool CanReportMemoryUsage() const {
        return can_report_memory;
    }

    bool StrictContextRequired() const {
        return strict_context_required;
    }

    bool SupportsConditionalBarriers() const {
        return supports_conditional_barriers;
    }

    bool HasLmemPerfBug() const {
        return has_lmem_perf_bug;
    }

private:
    static bool TestVariableAoffi();
    static bool TestPreciseBug();

    std::array<u32, Shader::MaxStageTypes> max_uniform_buffers{};
    size_t uniform_buffer_alignment{};
    size_t shader_storage_alignment{};
    u32 max_vertex_attributes{};
    u32 max_varyings{};
    u32 max_compute_shared_memory_size{};
    u32 max_glasm_storage_buffer_blocks{};

    Settings::ShaderBackend shader_backend{};

    bool has_warp_intrinsics{};
    bool has_shader_ballot{};
    bool has_vertex_viewport_layer{};
    bool has_image_load_formatted{};
    bool has_texture_shadow_lod{};
    bool has_vertex_buffer_unified_memory{};
    bool has_astc{};
    bool has_variable_aoffi{};
    bool has_component_indexing_bug{};
    bool has_precise_bug{};
    bool has_broken_texture_view_formats{};
    bool has_fast_buffer_sub_data{};
    bool has_nv_viewport_array2{};
    bool has_derivative_control{};
    bool has_debugging_tool_attached{};
    bool use_assembly_shaders{};
    bool use_asynchronous_shaders{};
    bool use_driver_cache{};
    bool has_depth_buffer_float{};
    bool has_geometry_shader_passthrough{};
    bool has_nv_gpu_shader_5{};
    bool has_shader_int64{};
    bool has_amd_shader_half_float{};
    bool has_sparse_texture_2{};
    bool has_draw_texture{};
    bool warp_size_potentially_larger_than_guest{};
    bool need_fastmath_off{};
    bool has_cbuf_ftou_bug{};
    bool has_bool_ref_bug{};
    bool can_report_memory{};
    bool strict_context_required{};
    bool supports_conditional_barriers{};
    bool has_lmem_perf_bug{};

    std::string vendor_name;
};

} // namespace OpenGL


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

#include "common/assert.h"

#include <glad/glad.h>

#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_fence_manager.h"

namespace OpenGL {

GLInnerFence::GLInnerFence(bool is_stubbed_) : FenceBase{is_stubbed_} {}

GLInnerFence::~GLInnerFence() = default;

void GLInnerFence::Queue() {
    if (is_stubbed) {
        return;
    }
    ASSERT(sync_object.handle == 0);
    sync_object.Create();
}

bool GLInnerFence::IsSignaled() const {
    if (is_stubbed) {
        return true;
    }
    ASSERT(sync_object.handle != 0);
    return sync_object.IsSignaled();
}

void GLInnerFence::Wait() {
    if (is_stubbed) {
        return;
    }
    ASSERT(sync_object.handle != 0);
    glClientWaitSync(sync_object.handle, 0, GL_TIMEOUT_IGNORED);
}

FenceManagerOpenGL::FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer_,
                                       Tegra::GPU& gpu_, TextureCache& texture_cache_,
                                       BufferCache& buffer_cache_, QueryCache& query_cache_)
    : GenericFenceManager{rasterizer_, gpu_, texture_cache_, buffer_cache_, query_cache_} {}

Fence FenceManagerOpenGL::CreateFence(bool is_stubbed) {
    return std::make_shared<GLInnerFence>(is_stubbed);
}

void FenceManagerOpenGL::QueueFence(Fence& fence) {
    fence->Queue();
}

bool FenceManagerOpenGL::IsFenceSignaled(Fence& fence) const {
    return fence->IsSignaled();
}

void FenceManagerOpenGL::WaitFence(Fence& fence) {
    fence->Wait();
}

} // namespace OpenGL


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

#pragma once

#include <memory>

#include "common/common_types.h"
#include "video_core/fence_manager.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"

namespace OpenGL {

class GLInnerFence : public VideoCommon::FenceBase {
public:
    explicit GLInnerFence(bool is_stubbed_);
    ~GLInnerFence();

    void Queue();

    bool IsSignaled() const;

    void Wait();

private:
    OGLSync sync_object;
};

using Fence = std::shared_ptr<GLInnerFence>;

struct FenceManagerParams {
    using FenceType = Fence;
    using BufferCacheType = BufferCache;
    using TextureCacheType = TextureCache;
    using QueryCacheType = QueryCache;

    static constexpr bool HAS_ASYNC_CHECK = false;
};

using GenericFenceManager = VideoCommon::FenceManager<FenceManagerParams>;

class FenceManagerOpenGL final : public GenericFenceManager {
public:
    explicit FenceManagerOpenGL(VideoCore::RasterizerInterface& rasterizer, Tegra::GPU& gpu,
                                TextureCache& texture_cache, BufferCache& buffer_cache,
                                QueryCache& query_cache);

protected:
    Fence CreateFence(bool is_stubbed) override;
    void QueueFence(Fence& fence) override;
    bool IsFenceSignaled(Fence& fence) const override;
    void WaitFence(Fence& fence) override;
};

} // namespace OpenGL


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

#include <algorithm>
#include <array>
#include <string>
#include <vector>

#include "common/settings.h" // for enum class Settings::ShaderBackend
#include "common/thread_worker.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/renderer_opengl/gl_graphics_pipeline.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
#include "video_core/renderer_opengl/gl_shader_util.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
#include "video_core/shader_notify.h"
#include "video_core/texture_cache/texture_cache.h"

#if defined(_MSC_VER) && defined(NDEBUG)
#define LAMBDA_FORCEINLINE [[msvc::forceinline]]
#else
#define LAMBDA_FORCEINLINE
#endif

namespace OpenGL {
namespace {
using Shader::ImageBufferDescriptor;
using Shader::ImageDescriptor;
using Shader::NumDescriptors;
using Shader::TextureBufferDescriptor;
using Shader::TextureDescriptor;
using Tegra::Texture::TexturePair;
using VideoCommon::ImageId;

constexpr u32 MAX_TEXTURES = 64;
constexpr u32 MAX_IMAGES = 8;

GLenum Stage(size_t stage_index) {
    switch (stage_index) {
    case 0:
        return GL_VERTEX_SHADER;
    case 1:
        return GL_TESS_CONTROL_SHADER;
    case 2:
        return GL_TESS_EVALUATION_SHADER;
    case 3:
        return GL_GEOMETRY_SHADER;
    case 4:
        return GL_FRAGMENT_SHADER;
    }
    ASSERT_MSG(false, "{}", stage_index);
    return GL_NONE;
}

GLenum AssemblyStage(size_t stage_index) {
    switch (stage_index) {
    case 0:
        return GL_VERTEX_PROGRAM_NV;
    case 1:
        return GL_TESS_CONTROL_PROGRAM_NV;
    case 2:
        return GL_TESS_EVALUATION_PROGRAM_NV;
    case 3:
        return GL_GEOMETRY_PROGRAM_NV;
    case 4:
        return GL_FRAGMENT_PROGRAM_NV;
    }
    ASSERT_MSG(false, "{}", stage_index);
    return GL_NONE;
}

/// Translates hardware transform feedback indices
/// @param location Hardware location
/// @return Pair of ARB_transform_feedback3 token stream first and third arguments
/// @note Read https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_transform_feedback3.txt
std::pair<GLint, GLint> TransformFeedbackEnum(u32 location) {
    const auto index = location / 4;
    if (index >= 8 && index <= 39) {
        return {GL_GENERIC_ATTRIB_NV, index - 8};
    }
    if (index >= 48 && index <= 55) {
        return {GL_TEXTURE_COORD_NV, index - 48};
    }
    switch (index) {
    case 7:
        return {GL_POSITION, 0};
    case 40:
        return {GL_PRIMARY_COLOR_NV, 0};
    case 41:
        return {GL_SECONDARY_COLOR_NV, 0};
    case 42:
        return {GL_BACK_PRIMARY_COLOR_NV, 0};
    case 43:
        return {GL_BACK_SECONDARY_COLOR_NV, 0};
    }
    UNIMPLEMENTED_MSG("index={}", index);
    return {GL_POSITION, 0};
}

template <typename Spec>
bool Passes(const std::array<Shader::Info, 5>& stage_infos, u32 enabled_mask) {
    for (size_t stage = 0; stage < stage_infos.size(); ++stage) {
        if (!Spec::enabled_stages[stage] && ((enabled_mask >> stage) & 1) != 0) {
            return false;
        }
        const auto& info{stage_infos[stage]};
        if constexpr (!Spec::has_storage_buffers) {
            if (!info.storage_buffers_descriptors.empty()) {
                return false;
            }
        }
        if constexpr (!Spec::has_texture_buffers) {
            if (!info.texture_buffer_descriptors.empty()) {
                return false;
            }
        }
        if constexpr (!Spec::has_image_buffers) {
            if (!info.image_buffer_descriptors.empty()) {
                return false;
            }
        }
        if constexpr (!Spec::has_images) {
            if (!info.image_descriptors.empty()) {
                return false;
            }
        }
    }
    return true;
}

using ConfigureFuncPtr = void (*)(GraphicsPipeline*, bool);

template <typename Spec, typename... Specs>
ConfigureFuncPtr FindSpec(const std::array<Shader::Info, 5>& stage_infos, u32 enabled_mask) {
    if constexpr (sizeof...(Specs) > 0) {
        if (!Passes<Spec>(stage_infos, enabled_mask)) {
            return FindSpec<Specs...>(stage_infos, enabled_mask);
        }
    }
    return GraphicsPipeline::MakeConfigureSpecFunc<Spec>();
}

struct SimpleVertexFragmentSpec {
    static constexpr std::array<bool, 5> enabled_stages{true, false, false, false, true};
    static constexpr bool has_storage_buffers = false;
    static constexpr bool has_texture_buffers = false;
    static constexpr bool has_image_buffers = false;
    static constexpr bool has_images = false;
};

struct SimpleVertexSpec {
    static constexpr std::array<bool, 5> enabled_stages{true, false, false, false, false};
    static constexpr bool has_storage_buffers = false;
    static constexpr bool has_texture_buffers = false;
    static constexpr bool has_image_buffers = false;
    static constexpr bool has_images = false;
};

struct DefaultSpec {
    static constexpr std::array<bool, 5> enabled_stages{true, true, true, true, true};
    static constexpr bool has_storage_buffers = true;
    static constexpr bool has_texture_buffers = true;
    static constexpr bool has_image_buffers = true;
    static constexpr bool has_images = true;
};

ConfigureFuncPtr ConfigureFunc(const std::array<Shader::Info, 5>& infos, u32 enabled_mask) {
    return FindSpec<SimpleVertexSpec, SimpleVertexFragmentSpec, DefaultSpec>(infos, enabled_mask);
}
} // Anonymous namespace

GraphicsPipeline::GraphicsPipeline(const Device& device, TextureCache& texture_cache_,
                                   BufferCache& buffer_cache_, ProgramManager& program_manager_,
                                   StateTracker& state_tracker_, ShaderWorker* thread_worker,
                                   VideoCore::ShaderNotify* shader_notify,
                                   std::array<std::string, 5> sources,
                                   std::array<std::vector<u32>, 5> sources_spirv,
                                   const std::array<const Shader::Info*, 5>& infos,
                                   const GraphicsPipelineKey& key_, bool force_context_flush)
    : texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_},
      state_tracker{state_tracker_}, key{key_} {
    if (shader_notify) {
        shader_notify->MarkShaderBuilding();
    }
    u32 num_textures{};
    u32 num_images{};
    u32 num_storage_buffers{};
    for (size_t stage = 0; stage < base_uniform_bindings.size(); ++stage) {
        auto& info{stage_infos[stage]};
        if (infos[stage]) {
            info = *infos[stage];
            enabled_stages_mask |= 1u << stage;
        }
        if (stage < 4) {
            base_uniform_bindings[stage + 1] = base_uniform_bindings[stage];
            base_storage_bindings[stage + 1] = base_storage_bindings[stage];

            base_uniform_bindings[stage + 1] += NumDescriptors(info.constant_buffer_descriptors);
            base_storage_bindings[stage + 1] += NumDescriptors(info.storage_buffers_descriptors);
        }
        enabled_uniform_buffer_masks[stage] = info.constant_buffer_mask;
        std::ranges::copy(info.constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin());

        const u32 num_tex_buffer_bindings{NumDescriptors(info.texture_buffer_descriptors)};
        num_texture_buffers[stage] += num_tex_buffer_bindings;
        num_textures += num_tex_buffer_bindings;

        const u32 num_img_buffers_bindings{NumDescriptors(info.image_buffer_descriptors)};
        num_image_buffers[stage] += num_img_buffers_bindings;
        num_images += num_img_buffers_bindings;

        num_textures += NumDescriptors(info.texture_descriptors);
        num_images += NumDescriptors(info.image_descriptors);
        num_storage_buffers += NumDescriptors(info.storage_buffers_descriptors);

        writes_global_memory |= std::ranges::any_of(
            info.storage_buffers_descriptors, [](const auto& desc) { return desc.is_written; });
        uses_local_memory |= info.uses_local_memory;
    }
    ASSERT(num_textures <= MAX_TEXTURES);
    ASSERT(num_images <= MAX_IMAGES);

    const auto backend = device.GetShaderBackend();
    const bool assembly_shaders{backend == Settings::ShaderBackend::Glasm};
    use_storage_buffers =
        !assembly_shaders || num_storage_buffers <= device.GetMaxGLASMStorageBufferBlocks();
    writes_global_memory &= !use_storage_buffers;
    configure_func = ConfigureFunc(stage_infos, enabled_stages_mask);

    if (key.xfb_enabled && device.UseAssemblyShaders()) {
        GenerateTransformFeedbackState();
    }
    const bool in_parallel = thread_worker != nullptr;
    auto func{[this, sources_ = std::move(sources), sources_spirv_ = std::move(sources_spirv),
               shader_notify, backend, in_parallel,
               force_context_flush](ShaderContext::Context*) mutable {
        for (size_t stage = 0; stage < 5; ++stage) {
            switch (backend) {
            case Settings::ShaderBackend::Glsl:
                if (!sources_[stage].empty()) {
                    source_programs[stage] = CreateProgram(sources_[stage], Stage(stage));
                }
                break;
            case Settings::ShaderBackend::Glasm:
                if (!sources_[stage].empty()) {
                    assembly_programs[stage] =
                        CompileProgram(sources_[stage], AssemblyStage(stage));
                }
                break;
            case Settings::ShaderBackend::SpirV:
                if (!sources_spirv_[stage].empty()) {
                    source_programs[stage] = CreateProgram(sources_spirv_[stage], Stage(stage));
                }
                break;
            }
        }
        if (force_context_flush || in_parallel) {
            std::scoped_lock lock{built_mutex};
            built_fence.Create();
            // Flush this context to ensure compilation commands and fence are in the GPU pipe.
            glFlush();
            built_condvar.notify_one();
        } else {
            is_built = true;
        }
        if (shader_notify) {
            shader_notify->MarkShaderComplete();
        }
    }};
    if (thread_worker) {
        thread_worker->QueueWork(std::move(func));
    } else {
        func(nullptr);
    }
}

template <typename Spec>
void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
    std::array<VideoCommon::ImageViewInOut, MAX_TEXTURES + MAX_IMAGES> views;
    std::array<VideoCommon::SamplerId, MAX_TEXTURES> samplers;
    size_t views_index{};
    size_t samplers_index{};

    texture_cache.SynchronizeGraphicsDescriptors();

    buffer_cache.SetUniformBuffersState(enabled_uniform_buffer_masks, &uniform_buffer_sizes);
    buffer_cache.runtime.SetBaseUniformBindings(base_uniform_bindings);
    buffer_cache.runtime.SetBaseStorageBindings(base_storage_bindings);
    buffer_cache.runtime.SetEnableStorageBuffers(use_storage_buffers);

    const auto& regs{maxwell3d->regs};
    const bool via_header_index{regs.sampler_binding == Maxwell::SamplerBinding::ViaHeaderBinding};
    const auto config_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
        const Shader::Info& info{stage_infos[stage]};
        buffer_cache.UnbindGraphicsStorageBuffers(stage);
        if constexpr (Spec::has_storage_buffers) {
            size_t ssbo_index{};
            for (const auto& desc : info.storage_buffers_descriptors) {
                ASSERT(desc.count == 1);
                buffer_cache.BindGraphicsStorageBuffer(stage, ssbo_index, desc.cbuf_index,
                                                       desc.cbuf_offset, desc.is_written);
                ++ssbo_index;
            }
        }
        const auto& cbufs{maxwell3d->state.shader_stages[stage].const_buffers};
        const auto read_handle{[&](const auto& desc, u32 index) {
            ASSERT(cbufs[desc.cbuf_index].enabled);
            const u32 index_offset{index << desc.size_shift};
            const u32 offset{desc.cbuf_offset + index_offset};
            const GPUVAddr addr{cbufs[desc.cbuf_index].address + offset};
            if constexpr (std::is_same_v<decltype(desc), const TextureDescriptor&> ||
                          std::is_same_v<decltype(desc), const TextureBufferDescriptor&>) {
                if (desc.has_secondary) {
                    ASSERT(cbufs[desc.secondary_cbuf_index].enabled);
                    const u32 second_offset{desc.secondary_cbuf_offset + index_offset};
                    const GPUVAddr separate_addr{cbufs[desc.secondary_cbuf_index].address +
                                                 second_offset};
                    const u32 lhs_raw{gpu_memory->Read<u32>(addr) << desc.shift_left};
                    const u32 rhs_raw{gpu_memory->Read<u32>(separate_addr)
                                      << desc.secondary_shift_left};
                    const u32 raw{lhs_raw | rhs_raw};
                    return TexturePair(raw, via_header_index);
                }
            }
            return TexturePair(gpu_memory->Read<u32>(addr), via_header_index);
        }};
        const auto add_image{[&](const auto& desc, bool blacklist) LAMBDA_FORCEINLINE {
            for (u32 index = 0; index < desc.count; ++index) {
                const auto handle{read_handle(desc, index)};
                views[views_index++] = {
                    .index = handle.first,
                    .blacklist = blacklist,
                    .id = {},
                };
            }
        }};
        if constexpr (Spec::has_texture_buffers) {
            for (const auto& desc : info.texture_buffer_descriptors) {
                for (u32 index = 0; index < desc.count; ++index) {
                    const auto handle{read_handle(desc, index)};
                    views[views_index++] = {handle.first};
                }
            }
        }
        if constexpr (Spec::has_image_buffers) {
            for (const auto& desc : info.image_buffer_descriptors) {
                add_image(desc, false);
            }
        }
        for (const auto& desc : info.texture_descriptors) {
            for (u32 index = 0; index < desc.count; ++index) {
                const auto handle{read_handle(desc, index)};
                views[views_index++] = {handle.first};

                VideoCommon::SamplerId sampler{texture_cache.GetGraphicsSamplerId(handle.second)};
                samplers[samplers_index++] = sampler;
            }
        }
        if constexpr (Spec::has_images) {
            for (const auto& desc : info.image_descriptors) {
                add_image(desc, desc.is_written);
            }
        }
    }};
    if constexpr (Spec::enabled_stages[0]) {
        config_stage(0);
    }
    if constexpr (Spec::enabled_stages[1]) {
        config_stage(1);
    }
    if constexpr (Spec::enabled_stages[2]) {
        config_stage(2);
    }
    if constexpr (Spec::enabled_stages[3]) {
        config_stage(3);
    }
    if constexpr (Spec::enabled_stages[4]) {
        config_stage(4);
    }
    texture_cache.FillGraphicsImageViews<Spec::has_images>(std::span(views.data(), views_index));

    texture_cache.UpdateRenderTargets(false);
    state_tracker.BindFramebuffer(texture_cache.GetFramebuffer()->Handle());

    VideoCommon::ImageViewInOut* texture_buffer_it{views.data()};
    const auto bind_stage_info{[&](size_t stage) LAMBDA_FORCEINLINE {
        size_t index{};
        const auto add_buffer{[&](const auto& desc) {
            constexpr bool is_image = std::is_same_v<decltype(desc), const ImageBufferDescriptor&>;
            for (u32 i = 0; i < desc.count; ++i) {
                bool is_written{false};
                if constexpr (is_image) {
                    is_written = desc.is_written;
                }
                ImageView& image_view{texture_cache.GetImageView(texture_buffer_it->id)};
                buffer_cache.BindGraphicsTextureBuffer(stage, index, image_view.GpuAddr(),
                                                       image_view.BufferSize(), image_view.format,
                                                       is_written, is_image);
                ++index;
                ++texture_buffer_it;
            }
        }};
        const Shader::Info& info{stage_infos[stage]};
        buffer_cache.UnbindGraphicsTextureBuffers(stage);

        if constexpr (Spec::has_texture_buffers) {
            for (const auto& desc : info.texture_buffer_descriptors) {
                add_buffer(desc);
            }
        }
        if constexpr (Spec::has_image_buffers) {
            for (const auto& desc : info.image_buffer_descriptors) {
                add_buffer(desc);
            }
        }
        texture_buffer_it += Shader::NumDescriptors(info.texture_descriptors);
        if constexpr (Spec::has_images) {
            texture_buffer_it += Shader::NumDescriptors(info.image_descriptors);
        }
    }};
    if constexpr (Spec::enabled_stages[0]) {
        bind_stage_info(0);
    }
    if constexpr (Spec::enabled_stages[1]) {
        bind_stage_info(1);
    }
    if constexpr (Spec::enabled_stages[2]) {
        bind_stage_info(2);
    }
    if constexpr (Spec::enabled_stages[3]) {
        bind_stage_info(3);
    }
    if constexpr (Spec::enabled_stages[4]) {
        bind_stage_info(4);
    }
    buffer_cache.UpdateGraphicsBuffers(is_indexed);
    buffer_cache.BindHostGeometryBuffers(is_indexed);

    if (!IsBuilt()) {
        WaitForBuild();
    }
    const bool use_assembly{assembly_programs[0].handle != 0};
    if (use_assembly) {
        program_manager.BindAssemblyPrograms(assembly_programs, enabled_stages_mask);
    } else {
        program_manager.BindSourcePrograms(source_programs);
    }
    const VideoCommon::ImageViewInOut* views_it{views.data()};
    const VideoCommon::SamplerId* samplers_it{samplers.data()};
    GLsizei texture_binding = 0;
    GLsizei image_binding = 0;
    GLsizei sampler_binding{};
    std::array<GLuint, MAX_TEXTURES> textures;
    std::array<GLuint, MAX_IMAGES> images;
    std::array<GLuint, MAX_TEXTURES> gl_samplers;
    const auto prepare_stage{[&](size_t stage) {
        buffer_cache.runtime.SetImagePointers(&textures[texture_binding], &images[image_binding]);
        buffer_cache.BindHostStageBuffers(stage);

        texture_binding += num_texture_buffers[stage];
        image_binding += num_image_buffers[stage];

        views_it += num_texture_buffers[stage];
        views_it += num_image_buffers[stage];

        u32 texture_scaling_mask{};
        u32 image_scaling_mask{};
        u32 stage_texture_binding{};
        u32 stage_image_binding{};

        const auto& info{stage_infos[stage]};
        if constexpr (Spec::has_texture_buffers) {
            for (const auto& desc : info.texture_buffer_descriptors) {
                for (u32 index = 0; index < desc.count; ++index) {
                    gl_samplers[sampler_binding++] = 0;
                }
            }
        }
        for (const auto& desc : info.texture_descriptors) {
            for (u32 index = 0; index < desc.count; ++index) {
                ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
                textures[texture_binding] = image_view.Handle(desc.type);
                if (texture_cache.IsRescaling(image_view)) {
                    texture_scaling_mask |= 1u << stage_texture_binding;
                }
                ++texture_binding;
                ++stage_texture_binding;

                const Sampler& sampler{texture_cache.GetSampler(*(samplers_it++))};
                const bool use_fallback_sampler{sampler.HasAddedAnisotropy() &&
                                                !image_view.SupportsAnisotropy()};
                gl_samplers[sampler_binding++] =
                    use_fallback_sampler ? sampler.HandleWithDefaultAnisotropy() : sampler.Handle();
            }
        }
        for (const auto& desc : info.image_descriptors) {
            for (u32 index = 0; index < desc.count; ++index) {
                ImageView& image_view{texture_cache.GetImageView((views_it++)->id)};
                if (desc.is_written) {
                    texture_cache.MarkModification(image_view.image_id);
                }
                images[image_binding] = image_view.StorageView(desc.type, desc.format);
                if (texture_cache.IsRescaling(image_view)) {
                    image_scaling_mask |= 1u << stage_image_binding;
                }
                ++image_binding;
                ++stage_image_binding;
            }
        }
        if (info.uses_rescaling_uniform) {
            const f32 float_texture_scaling_mask{Common::BitCast<f32>(texture_scaling_mask)};
            const f32 float_image_scaling_mask{Common::BitCast<f32>(image_scaling_mask)};
            const bool is_rescaling{texture_cache.IsRescaling()};
            const f32 config_down_factor{Settings::values.resolution_info.down_factor};
            const f32 down_factor{is_rescaling ? config_down_factor : 1.0f};
            if (use_assembly) {
                glProgramLocalParameter4fARB(AssemblyStage(stage), 0, float_texture_scaling_mask,
                                             float_image_scaling_mask, down_factor, 0.0f);
            } else {
                glProgramUniform4f(source_programs[stage].handle, 0, float_texture_scaling_mask,
                                   float_image_scaling_mask, down_factor, 0.0f);
            }
        }
        if (info.uses_render_area) {
            const auto render_area_width(static_cast<GLfloat>(regs.surface_clip.width));
            const auto render_area_height(static_cast<GLfloat>(regs.surface_clip.height));
            if (use_assembly) {
                glProgramLocalParameter4fARB(AssemblyStage(stage), 1, render_area_width,
                                             render_area_height, 0.0f, 0.0f);
            } else {
                glProgramUniform4f(source_programs[stage].handle, 1, render_area_width,
                                   render_area_height, 0.0f, 0.0f);
            }
        }
    }};
    if constexpr (Spec::enabled_stages[0]) {
        prepare_stage(0);
    }
    if constexpr (Spec::enabled_stages[1]) {
        prepare_stage(1);
    }
    if constexpr (Spec::enabled_stages[2]) {
        prepare_stage(2);
    }
    if constexpr (Spec::enabled_stages[3]) {
        prepare_stage(3);
    }
    if constexpr (Spec::enabled_stages[4]) {
        prepare_stage(4);
    }
    if (texture_binding != 0) {
        ASSERT(texture_binding == sampler_binding);
        glBindTextures(0, texture_binding, textures.data());
        glBindSamplers(0, sampler_binding, gl_samplers.data());
    }
    if (image_binding != 0) {
        glBindImageTextures(0, image_binding, images.data());
    }
}

void GraphicsPipeline::ConfigureTransformFeedbackImpl() const {
    const GLenum buffer_mode =
        num_xfb_buffers_active == 1 ? GL_INTERLEAVED_ATTRIBS : GL_SEPARATE_ATTRIBS;
    glTransformFeedbackAttribsNV(num_xfb_attribs, xfb_attribs.data(), buffer_mode);
}

void GraphicsPipeline::GenerateTransformFeedbackState() {
    // TODO(Rodrigo): Inject SKIP_COMPONENTS*_NV when required. An unimplemented message will signal
    // when this is required.
    GLint* cursor{xfb_attribs.data()};

    num_xfb_buffers_active = 0;
    for (size_t feedback = 0; feedback < Maxwell::NumTransformFeedbackBuffers; ++feedback) {
        const auto& layout = key.xfb_state.layouts[feedback];
        UNIMPLEMENTED_IF_MSG(layout.stride != layout.varying_count * 4, "Stride padding");
        if (layout.varying_count == 0) {
            continue;
        }
        num_xfb_buffers_active++;

        const auto& locations = key.xfb_state.varyings[feedback];
        std::optional<u32> current_index;
        for (u32 offset = 0; offset < layout.varying_count; ++offset) {
            const auto get_attribute = [&locations](u32 index) -> u32 {
                switch (index % 4) {
                case 0:
                    return locations[index / 4].attribute0.Value();
                case 1:
                    return locations[index / 4].attribute1.Value();
                case 2:
                    return locations[index / 4].attribute2.Value();
                case 3:
                    return locations[index / 4].attribute3.Value();
                }
                UNREACHABLE();
                return 0;
            };

            const auto attribute{get_attribute(offset)};
            const auto index = attribute / 4U;

            if (current_index == index) {
                // Increase number of components of the previous attachment
                ++cursor[-2];
                continue;
            }
            current_index = index;

            std::tie(cursor[0], cursor[2]) = TransformFeedbackEnum(attribute);
            cursor[1] = 1;
            cursor += XFB_ENTRY_STRIDE;
        }
    }
    num_xfb_attribs = static_cast<GLsizei>((cursor - xfb_attribs.data()) / XFB_ENTRY_STRIDE);
}

void GraphicsPipeline::WaitForBuild() {
    if (built_fence.handle == 0) {
        std::unique_lock lock{built_mutex};
        built_condvar.wait(lock, [this] { return built_fence.handle != 0; });
    }
    ASSERT(glClientWaitSync(built_fence.handle, 0, GL_TIMEOUT_IGNORED) != GL_WAIT_FAILED);
    is_built = true;
}

bool GraphicsPipeline::IsBuilt() noexcept {
    if (is_built) {
        return true;
    }
    if (built_fence.handle == 0) {
        return false;
    }
    is_built = built_fence.IsSignaled();
    return is_built;
}

} // namespace OpenGL


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

#pragma once

#include <array>
#include <cstring>
#include <type_traits>
#include <utility>

#include "common/bit_field.h"
#include "common/cityhash.h"
#include "common/common_types.h"
#include "shader_recompiler/shader_info.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_buffer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_texture_cache.h"
#include "video_core/transform_feedback.h"

namespace OpenGL {

namespace ShaderContext {
struct Context;
}

class Device;
class ProgramManager;

using Maxwell = Tegra::Engines::Maxwell3D::Regs;
using ShaderWorker = Common::StatefulThreadWorker<ShaderContext::Context>;

struct GraphicsPipelineKey {
    std::array<u64, 6> unique_hashes;
    union {
        u32 raw;
        BitField<0, 1, u32> xfb_enabled;
        BitField<1, 1, u32> early_z;
        BitField<2, 4, Maxwell::PrimitiveTopology> gs_input_topology;
        BitField<6, 2, Maxwell::Tessellation::DomainType> tessellation_primitive;
        BitField<8, 2, Maxwell::Tessellation::Spacing> tessellation_spacing;
        BitField<10, 1, u32> tessellation_clockwise;
        BitField<11, 3, Tegra::Engines::Maxwell3D::EngineHint> app_stage;
    };
    std::array<u32, 3> padding;
    VideoCommon::TransformFeedbackState xfb_state;

    size_t Hash() const noexcept {
        return static_cast<size_t>(Common::CityHash64(reinterpret_cast<const char*>(this), Size()));
    }

    bool operator==(const GraphicsPipelineKey& rhs) const noexcept {
        return std::memcmp(this, &rhs, Size()) == 0;
    }

    bool operator!=(const GraphicsPipelineKey& rhs) const noexcept {
        return !operator==(rhs);
    }

    [[nodiscard]] size_t Size() const noexcept {
        if (xfb_enabled != 0) {
            return sizeof(GraphicsPipelineKey);
        } else {
            return offsetof(GraphicsPipelineKey, padding);
        }
    }
};
static_assert(std::has_unique_object_representations_v<GraphicsPipelineKey>);
static_assert(std::is_trivially_copyable_v<GraphicsPipelineKey>);
static_assert(std::is_trivially_constructible_v<GraphicsPipelineKey>);

class GraphicsPipeline {
public:
    explicit GraphicsPipeline(const Device& device, TextureCache& texture_cache_,
                              BufferCache& buffer_cache_, ProgramManager& program_manager_,
                              StateTracker& state_tracker_, ShaderWorker* thread_worker,
                              VideoCore::ShaderNotify* shader_notify,
                              std::array<std::string, 5> sources,
                              std::array<std::vector<u32>, 5> sources_spirv,
                              const std::array<const Shader::Info*, 5>& infos,
                              const GraphicsPipelineKey& key_, bool force_context_flush = false);

    void Configure(bool is_indexed) {
        configure_func(this, is_indexed);
    }

    void ConfigureTransformFeedback() const {
        if (num_xfb_attribs != 0) {
            ConfigureTransformFeedbackImpl();
        }
    }

    [[nodiscard]] const GraphicsPipelineKey& Key() const noexcept {
        return key;
    }

    [[nodiscard]] bool WritesGlobalMemory() const noexcept {
        return writes_global_memory;
    }

    [[nodiscard]] bool UsesLocalMemory() const noexcept {
        return uses_local_memory;
    }

    [[nodiscard]] bool IsBuilt() noexcept;

    template <typename Spec>
    static auto MakeConfigureSpecFunc() {
        return [](GraphicsPipeline* pipeline, bool is_indexed) {
            pipeline->ConfigureImpl<Spec>(is_indexed);
        };
    }

    void SetEngine(Tegra::Engines::Maxwell3D* maxwell3d_, Tegra::MemoryManager* gpu_memory_) {
        maxwell3d = maxwell3d_;
        gpu_memory = gpu_memory_;
    }

private:
    template <typename Spec>
    void ConfigureImpl(bool is_indexed);

    void ConfigureTransformFeedbackImpl() const;

    void GenerateTransformFeedbackState();

    void WaitForBuild();

    TextureCache& texture_cache;
    BufferCache& buffer_cache;
    Tegra::MemoryManager* gpu_memory;
    Tegra::Engines::Maxwell3D* maxwell3d;
    ProgramManager& program_manager;
    StateTracker& state_tracker;
    const GraphicsPipelineKey key;

    void (*configure_func)(GraphicsPipeline*, bool){};

    std::array<OGLProgram, 5> source_programs;
    std::array<OGLAssemblyProgram, 5> assembly_programs;
    u32 enabled_stages_mask{};

    std::array<Shader::Info, 5> stage_infos{};
    std::array<u32, 5> enabled_uniform_buffer_masks{};
    VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
    std::array<u32, 5> base_uniform_bindings{};
    std::array<u32, 5> base_storage_bindings{};
    std::array<u32, 5> num_texture_buffers{};
    std::array<u32, 5> num_image_buffers{};

    bool use_storage_buffers{};
    bool writes_global_memory{};
    bool uses_local_memory{};

    static constexpr std::size_t XFB_ENTRY_STRIDE = 3;
    GLsizei num_xfb_attribs{};
    u32 num_xfb_buffers_active{};
    std::array<GLint, 128 * XFB_ENTRY_STRIDE * Maxwell::NumTransformFeedbackBuffers> xfb_attribs{};

    std::mutex built_mutex;
    std::condition_variable built_condvar;
    OGLSync built_fence{};
    bool is_built{false};
};

} // namespace OpenGL

namespace std {
template <>
struct hash<OpenGL::GraphicsPipelineKey> {
    size_t operator()(const OpenGL::GraphicsPipelineKey& k) const noexcept {
        return k.Hash();
    }
};
} // namespace std


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

#include <algorithm>
#include <memory>
#include <utility>
#include <vector>

#include <glad/glad.h>

#include "core/core.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_opengl/gl_query_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"

namespace OpenGL {

namespace {

constexpr GLenum GetTarget(VideoCore::QueryType type) {
    switch (type) {
    case VideoCore::QueryType::SamplesPassed:
        return GL_SAMPLES_PASSED;
    case VideoCore::QueryType::PrimitivesGenerated:
        return GL_PRIMITIVES_GENERATED;
    case VideoCore::QueryType::TfbPrimitivesWritten:
        return GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN;
    default:
        break;
    }
    UNIMPLEMENTED_MSG("Query type {}", type);
    return 0;
}

} // Anonymous namespace

QueryCache::QueryCache(RasterizerOpenGL& rasterizer_,
                       Tegra::MaxwellDeviceMemoryManager& device_memory_)
    : QueryCacheLegacy(rasterizer_, device_memory_), gl_rasterizer{rasterizer_} {
    EnableCounters();
}

QueryCache::~QueryCache() = default;

OGLQuery QueryCache::AllocateQuery(VideoCore::QueryType type) {
    auto& reserve = query_pools[static_cast<std::size_t>(type)];
    OGLQuery query;
    if (reserve.empty()) {
        query.Create(GetTarget(type));
        return query;
    }

    query = std::move(reserve.back());
    reserve.pop_back();
    return query;
}

void QueryCache::Reserve(VideoCore::QueryType type, OGLQuery&& query) {
    query_pools[static_cast<std::size_t>(type)].push_back(std::move(query));
}

bool QueryCache::AnyCommandQueued() const noexcept {
    return gl_rasterizer.AnyCommandQueued();
}

HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> dependency_,
                         VideoCore::QueryType type_)
    : HostCounterBase{std::move(dependency_)}, cache{cache_}, type{type_}, query{
                                                                               cache.AllocateQuery(
                                                                                type)} {
    glBeginQuery(GetTarget(type), query.handle);
}

HostCounter::~HostCounter() {
    cache.Reserve(type, std::move(query));
}

void HostCounter::EndQuery() {
    if (!cache.AnyCommandQueued()) {
        // There are chances a query waited on without commands (glDraw, glClear, glDispatch). Not
        // having any of these causes a lock. glFlush is considered a command, so we can safely wait
        // for this. Insert to the OpenGL command stream a flush.
        glFlush();
    }
    glEndQuery(GetTarget(type));
}

u64 HostCounter::BlockingQuery([[maybe_unused]] bool async) const {
    GLint64 value;
    glGetQueryObjecti64v(query.handle, GL_QUERY_RESULT, &value);
    return static_cast<u64>(value);
}

CachedQuery::CachedQuery(QueryCache& cache_, VideoCore::QueryType type_, VAddr cpu_addr_,
                         u8* host_ptr_)
    : CachedQueryBase{cpu_addr_, host_ptr_}, cache{&cache_}, type{type_} {}

CachedQuery::~CachedQuery() = default;

CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept
    : CachedQueryBase(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}

CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
    cache = rhs.cache;
    type = rhs.type;
    CachedQueryBase<HostCounter>::operator=(std::move(rhs));
    return *this;
}

u64 CachedQuery::Flush([[maybe_unused]] bool async) {
    // Waiting for a query while another query of the same target is enabled locks Nvidia's driver.
    // To avoid this disable and re-enable keeping the dependency stream.
    // But we only have to do this if we have pending waits to be done.
    auto& stream = cache->Stream(type);
    const bool slice_counter = WaitPending() && stream.IsEnabled();
    if (slice_counter) {
        stream.Disable();
    }

    auto result = VideoCommon::CachedQueryBase<HostCounter>::Flush();

    if (slice_counter) {
        stream.Enable();
    }

    return result;
}

} // namespace OpenGL