duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

opengl_pipeline.cpp (34043B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR PolyForm-Strict-1.0.0)
      3 
      4 #include "opengl_pipeline.h"
      5 #include "compress_helpers.h"
      6 #include "opengl_device.h"
      7 #include "opengl_stream_buffer.h"
      8 #include "shadergen.h"
      9 
     10 #include "common/assert.h"
     11 #include "common/error.h"
     12 #include "common/file_system.h"
     13 #include "common/hash_combine.h"
     14 #include "common/log.h"
     15 #include "common/path.h"
     16 #include "common/scoped_guard.h"
     17 #include "common/small_string.h"
     18 #include "common/string_util.h"
     19 
     20 #include "fmt/format.h"
     21 
     22 #include <cerrno>
     23 
     24 Log_SetChannel(OpenGLDevice);
     25 
     26 struct PipelineDiskCacheFooter
     27 {
     28   u32 version;
     29   u32 num_programs;
     30   char driver_vendor[128];
     31   char driver_renderer[128];
     32   char driver_version[128];
     33 };
     34 static_assert(sizeof(PipelineDiskCacheFooter) == (sizeof(u32) * 2 + 128 * 3));
     35 
     36 struct PipelineDiskCacheIndexEntry
     37 {
     38   OpenGLPipeline::ProgramCacheKey key;
     39   u32 format;
     40   u32 offset;
     41   u32 uncompressed_size;
     42   u32 compressed_size;
     43 };
     44 static_assert(sizeof(PipelineDiskCacheIndexEntry) == 112); // No padding
     45 
     46 static GLenum GetGLShaderType(GPUShaderStage stage)
     47 {
     48   static constexpr std::array<GLenum, static_cast<u32>(GPUShaderStage::MaxCount)> mapping = {{
     49     GL_VERTEX_SHADER,   // Vertex
     50     GL_FRAGMENT_SHADER, // Fragment
     51     GL_GEOMETRY_SHADER, // Geometry
     52     GL_COMPUTE_SHADER,  // Compute
     53   }};
     54 
     55   return mapping[static_cast<u32>(stage)];
     56 }
     57 
     58 static void FillFooter(PipelineDiskCacheFooter* footer, u32 version)
     59 {
     60   footer->version = version;
     61   footer->num_programs = 0;
     62   StringUtil::Strlcpy(footer->driver_vendor, reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
     63                       std::size(footer->driver_vendor));
     64   StringUtil::Strlcpy(footer->driver_renderer, reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
     65                       std::size(footer->driver_renderer));
     66   StringUtil::Strlcpy(footer->driver_version, reinterpret_cast<const char*>(glGetString(GL_VERSION)),
     67                       std::size(footer->driver_version));
     68 }
     69 
     70 OpenGLShader::OpenGLShader(GPUShaderStage stage, const GPUShaderCache::CacheIndexKey& key, std::string source)
     71   : GPUShader(stage), m_key(key), m_source(std::move(source))
     72 {
     73 }
     74 
     75 OpenGLShader::~OpenGLShader()
     76 {
     77   if (m_id.has_value())
     78     glDeleteShader(m_id.value());
     79 }
     80 
     81 void OpenGLShader::SetDebugName(std::string_view name)
     82 {
     83 #ifdef _DEBUG
     84   if (glObjectLabel)
     85   {
     86     if (m_id.has_value())
     87     {
     88       m_debug_name = {};
     89       glObjectLabel(GL_SHADER, m_id.value(), static_cast<GLsizei>(name.length()),
     90                     static_cast<const GLchar*>(name.data()));
     91     }
     92     else
     93     {
     94       m_debug_name = name;
     95     }
     96   }
     97 #endif
     98 }
     99 
    100 bool OpenGLShader::Compile(Error* error)
    101 {
    102   if (m_compile_tried)
    103   {
    104     if (!m_id.has_value()) [[unlikely]]
    105     {
    106       Error::SetStringView(error, "Shader previously failed to compile.");
    107       return false;
    108     }
    109 
    110     return true;
    111   }
    112 
    113   m_compile_tried = true;
    114 
    115   glGetError();
    116 
    117   GLuint shader = glCreateShader(GetGLShaderType(m_stage));
    118   if (GLenum err = glGetError(); err != GL_NO_ERROR)
    119   {
    120     ERROR_LOG("glCreateShader() failed: {}", err);
    121     OpenGLDevice::SetErrorObject(error, "glCreateShader() failed: ", err);
    122     return false;
    123   }
    124 
    125   const GLchar* string = m_source.data();
    126   const GLint length = static_cast<GLint>(m_source.length());
    127   glShaderSource(shader, 1, &string, &length);
    128   glCompileShader(shader);
    129 
    130   GLint status = GL_FALSE;
    131   glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    132 
    133   GLint info_log_length = 0;
    134   glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &info_log_length);
    135 
    136   if (status == GL_FALSE || info_log_length > 0)
    137   {
    138     std::string info_log;
    139     info_log.resize(info_log_length + 1);
    140     glGetShaderInfoLog(shader, info_log_length, &info_log_length, &info_log[0]);
    141 
    142     if (status == GL_TRUE)
    143     {
    144       ERROR_LOG("Shader compiled with warnings:\n{}", info_log);
    145     }
    146     else
    147     {
    148       ERROR_LOG("Shader failed to compile:\n{}", info_log);
    149       Error::SetStringFmt(error, "Shader failed to compile:\n{}", info_log);
    150       GPUDevice::DumpBadShader(m_source, info_log);
    151       glDeleteShader(shader);
    152       return false;
    153     }
    154   }
    155 
    156   m_id = shader;
    157 
    158 #ifdef _DEBUG
    159   if (glObjectLabel && !m_debug_name.empty())
    160   {
    161     glObjectLabel(GL_SHADER, shader, static_cast<GLsizei>(m_debug_name.length()),
    162                   static_cast<const GLchar*>(m_debug_name.data()));
    163     m_debug_name = {};
    164   }
    165 #endif
    166 
    167   return true;
    168 }
    169 
    170 std::unique_ptr<GPUShader> OpenGLDevice::CreateShaderFromBinary(GPUShaderStage stage, std::span<const u8> data,
    171                                                                 Error* error)
    172 {
    173   // Not supported.. except spir-v maybe? but no point really...
    174   Error::SetStringView(error, "Not supported.");
    175   return {};
    176 }
    177 
    178 std::unique_ptr<GPUShader> OpenGLDevice::CreateShaderFromSource(GPUShaderStage stage, GPUShaderLanguage language,
    179                                                                 std::string_view source, const char* entry_point,
    180                                                                 DynamicHeapArray<u8>* out_binary, Error* error)
    181 {
    182   const GPUShaderLanguage expected_language = IsGLES() ? GPUShaderLanguage::GLSLES : GPUShaderLanguage::GLSL;
    183   if (language != expected_language)
    184   {
    185     return TranspileAndCreateShaderFromSource(
    186       stage, language, source, entry_point, expected_language,
    187       ShaderGen::GetGLSLVersion(IsGLES() ? RenderAPI::OpenGLES : RenderAPI::OpenGL), out_binary, error);
    188   }
    189 
    190   if (std::strcmp(entry_point, "main") != 0)
    191   {
    192     ERROR_LOG("Entry point must be 'main', but got '{}' instead.", entry_point);
    193     Error::SetStringFmt(error, "Entry point must be 'main', but got '{}' instead.", entry_point);
    194     return {};
    195   }
    196 
    197   return std::unique_ptr<GPUShader>(
    198     new OpenGLShader(stage, GPUShaderCache::GetCacheKey(stage, language, source, entry_point), std::string(source)));
    199 }
    200 
    201 //////////////////////////////////////////////////////////////////////////
    202 
    203 bool OpenGLPipeline::VertexArrayCacheKey::operator==(const VertexArrayCacheKey& rhs) const
    204 {
    205   return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
    206 }
    207 
    208 bool OpenGLPipeline::VertexArrayCacheKey::operator!=(const VertexArrayCacheKey& rhs) const
    209 {
    210   return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
    211 }
    212 
    213 size_t OpenGLPipeline::VertexArrayCacheKeyHash::operator()(const VertexArrayCacheKey& k) const
    214 {
    215   std::size_t h = 0;
    216   hash_combine(h, k.num_vertex_attributes, k.vertex_attribute_stride);
    217   for (const VertexAttribute& va : k.vertex_attributes)
    218     hash_combine(h, va.key);
    219   return h;
    220 }
    221 
    222 bool OpenGLPipeline::ProgramCacheKey::operator==(const ProgramCacheKey& rhs) const
    223 {
    224   return (std::memcmp(this, &rhs, sizeof(*this)) == 0);
    225 }
    226 
    227 bool OpenGLPipeline::ProgramCacheKey::operator!=(const ProgramCacheKey& rhs) const
    228 {
    229   return (std::memcmp(this, &rhs, sizeof(*this)) != 0);
    230 }
    231 
    232 size_t OpenGLPipeline::ProgramCacheKeyHash::operator()(const ProgramCacheKey& k) const
    233 {
    234   // TODO: maybe use xxhash here...
    235   std::size_t h = 0;
    236   hash_combine(h, k.va_key.num_vertex_attributes, k.va_key.vertex_attribute_stride);
    237   for (const VertexAttribute& va : k.va_key.vertex_attributes)
    238     hash_combine(h, va.key);
    239   hash_combine(h, k.vs_hash_low, k.vs_hash_high, k.vs_length);
    240   hash_combine(h, k.fs_hash_low, k.fs_hash_high, k.fs_length);
    241   hash_combine(h, k.gs_hash_low, k.gs_hash_high, k.gs_length);
    242   return h;
    243 }
    244 
    245 OpenGLPipeline::ProgramCacheKey OpenGLPipeline::GetProgramCacheKey(const GraphicsConfig& plconfig)
    246 {
    247   Assert(plconfig.input_layout.vertex_attributes.size() <= MAX_VERTEX_ATTRIBUTES);
    248 
    249   const GPUShaderCache::CacheIndexKey& vs_key = static_cast<const OpenGLShader*>(plconfig.vertex_shader)->GetKey();
    250   const GPUShaderCache::CacheIndexKey& fs_key = static_cast<const OpenGLShader*>(plconfig.fragment_shader)->GetKey();
    251   const GPUShaderCache::CacheIndexKey* gs_key =
    252     plconfig.geometry_shader ? &static_cast<const OpenGLShader*>(plconfig.geometry_shader)->GetKey() : nullptr;
    253 
    254   ProgramCacheKey ret;
    255   ret.vs_hash_low = vs_key.source_hash_low;
    256   ret.vs_hash_high = vs_key.source_hash_high;
    257   ret.vs_length = vs_key.source_length;
    258   ret.fs_hash_low = fs_key.source_hash_low;
    259   ret.fs_hash_high = fs_key.source_hash_high;
    260   ret.fs_length = fs_key.source_length;
    261   ret.gs_hash_low = gs_key ? gs_key->source_hash_low : 0;
    262   ret.gs_hash_high = gs_key ? gs_key->source_hash_high : 0;
    263   ret.gs_length = gs_key ? gs_key->source_length : 0;
    264 
    265   std::memset(ret.va_key.vertex_attributes, 0, sizeof(ret.va_key.vertex_attributes));
    266   ret.va_key.vertex_attribute_stride = 0;
    267   ret.va_key.num_vertex_attributes = static_cast<u32>(plconfig.input_layout.vertex_attributes.size());
    268 
    269   if (ret.va_key.num_vertex_attributes > 0)
    270   {
    271     std::memcpy(ret.va_key.vertex_attributes, plconfig.input_layout.vertex_attributes.data(),
    272                 sizeof(VertexAttribute) * ret.va_key.num_vertex_attributes);
    273     ret.va_key.vertex_attribute_stride = plconfig.input_layout.vertex_stride;
    274   }
    275 
    276   return ret;
    277 }
    278 
    279 GLuint OpenGLDevice::LookupProgramCache(const OpenGLPipeline::ProgramCacheKey& key,
    280                                         const GPUPipeline::GraphicsConfig& plconfig, Error* error)
    281 {
    282   auto it = m_program_cache.find(key);
    283   if (it != m_program_cache.end() && it->second.program_id == 0 && it->second.file_uncompressed_size > 0)
    284   {
    285     it->second.program_id = CreateProgramFromPipelineCache(it->second, plconfig);
    286     if (it->second.program_id == 0)
    287     {
    288       ERROR_LOG("Failed to create program from binary.");
    289       m_program_cache.erase(it);
    290       it = m_program_cache.end();
    291       DiscardPipelineCache();
    292     }
    293   }
    294 
    295   if (it != m_program_cache.end())
    296   {
    297     if (it->second.program_id != 0)
    298       it->second.reference_count++;
    299 
    300     return it->second.program_id;
    301   }
    302 
    303   const GLuint program_id = CompileProgram(plconfig, error);
    304   if (program_id == 0)
    305   {
    306     // Compile failed, don't add to map, it just gets confusing.
    307     return 0;
    308   }
    309 
    310   OpenGLPipeline::ProgramCacheItem item;
    311   item.program_id = program_id;
    312   item.reference_count = 1;
    313   item.file_format = 0;
    314   item.file_offset = 0;
    315   item.file_uncompressed_size = 0;
    316   item.file_compressed_size = 0;
    317   if (m_pipeline_disk_cache_file)
    318     AddToPipelineCache(&item);
    319 
    320   m_program_cache.emplace(key, item);
    321   return item.program_id;
    322 }
    323 
    324 GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig, Error* error)
    325 {
    326   OpenGLShader* vertex_shader = static_cast<OpenGLShader*>(plconfig.vertex_shader);
    327   OpenGLShader* fragment_shader = static_cast<OpenGLShader*>(plconfig.fragment_shader);
    328   OpenGLShader* geometry_shader = static_cast<OpenGLShader*>(plconfig.geometry_shader);
    329   if (!vertex_shader || !fragment_shader || !vertex_shader->Compile(error) || !fragment_shader->Compile(error) ||
    330       (geometry_shader && !geometry_shader->Compile(error))) [[unlikely]]
    331   {
    332     return 0;
    333   }
    334 
    335   glGetError();
    336   const GLuint program_id = glCreateProgram();
    337   if (const GLenum err = glGetError(); err != GL_NO_ERROR) [[unlikely]]
    338   {
    339     SetErrorObject(error, "glCreateProgram() failed: ", err);
    340     return 0;
    341   }
    342 
    343   if (m_pipeline_disk_cache_file)
    344     glProgramParameteri(program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
    345 
    346   Assert(plconfig.vertex_shader && plconfig.fragment_shader);
    347   glAttachShader(program_id, vertex_shader->GetGLId());
    348   glAttachShader(program_id, fragment_shader->GetGLId());
    349   if (geometry_shader)
    350     glAttachShader(program_id, geometry_shader->GetGLId());
    351 
    352   if (!ShaderGen::UseGLSLBindingLayout())
    353   {
    354     static constexpr std::array<const char*, static_cast<u8>(GPUPipeline::VertexAttribute::Semantic::MaxCount)>
    355       semantic_vars = {{
    356         "a_pos", // Position
    357         "a_tex", // TexCoord
    358         "a_col", // Color
    359       }};
    360 
    361     for (u32 i = 0; i < static_cast<u32>(plconfig.input_layout.vertex_attributes.size()); i++)
    362     {
    363       const GPUPipeline::VertexAttribute& va = plconfig.input_layout.vertex_attributes[i];
    364       if (va.semantic == GPUPipeline::VertexAttribute::Semantic::Position && va.semantic_index == 0)
    365       {
    366         glBindAttribLocation(program_id, i, "a_pos");
    367       }
    368       else
    369       {
    370         glBindAttribLocation(program_id, i,
    371                              TinyString::from_format("{}{}", semantic_vars[static_cast<u8>(va.semantic.GetValue())],
    372                                                      static_cast<u8>(va.semantic_index)));
    373       }
    374     }
    375 
    376     glBindFragDataLocation(program_id, 0, "o_col0");
    377 
    378     if (m_features.dual_source_blend)
    379     {
    380       if (GLAD_GL_VERSION_3_3 || GLAD_GL_ARB_blend_func_extended)
    381         glBindFragDataLocationIndexed(program_id, 1, 0, "o_col1");
    382       else if (GLAD_GL_EXT_blend_func_extended)
    383         glBindFragDataLocationIndexedEXT(program_id, 1, 0, "o_col1");
    384     }
    385   }
    386 
    387   glLinkProgram(program_id);
    388 
    389   GLint status = GL_FALSE;
    390   glGetProgramiv(program_id, GL_LINK_STATUS, &status);
    391 
    392   GLint info_log_length = 0;
    393   glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_log_length);
    394 
    395   if (status == GL_FALSE || info_log_length > 0) [[unlikely]]
    396   {
    397     std::string info_log;
    398     info_log.resize(info_log_length + 1);
    399     glGetProgramInfoLog(program_id, info_log_length, &info_log_length, &info_log[0]);
    400 
    401     if (status == GL_TRUE)
    402     {
    403       ERROR_LOG("Program linked with warnings:\n{}", info_log);
    404     }
    405     else
    406     {
    407       {
    408         std::stringstream ss;
    409         ss << "########## VERTEX SHADER ##########\n";
    410         ss << vertex_shader->GetSource();
    411         if (geometry_shader)
    412         {
    413           ss << "\n########## GEOMETRY SHADER ##########\n";
    414           ss << geometry_shader->GetSource();
    415         }
    416         ss << "\n########## FRAGMENT SHADER ##########\n";
    417         ss << fragment_shader->GetSource();
    418         ss << "\n#####################################\n";
    419         DumpBadShader(ss.str(), info_log);
    420       }
    421 
    422       ERROR_LOG("Program failed to link:\n{}", info_log);
    423       Error::SetStringFmt(error, "Program failed to link:\n{}", info_log);
    424       glDeleteProgram(program_id);
    425       return 0;
    426     }
    427   }
    428 
    429   PostLinkProgram(plconfig, program_id);
    430 
    431   return program_id;
    432 }
    433 
    434 void OpenGLDevice::PostLinkProgram(const GPUPipeline::GraphicsConfig& plconfig, GLuint program_id)
    435 {
    436   if (!ShaderGen::UseGLSLBindingLayout())
    437   {
    438     GLint location = glGetUniformBlockIndex(program_id, "UBOBlock");
    439     if (location >= 0)
    440       glUniformBlockBinding(program_id, location, 0);
    441 
    442     glUseProgram(program_id);
    443 
    444     // Texture buffer is zero here, so we have to bump it.
    445     const u32 num_textures = std::max<u32>(GetActiveTexturesForLayout(plconfig.layout), 1);
    446     for (u32 i = 0; i < num_textures; i++)
    447     {
    448       location = glGetUniformLocation(program_id, TinyString::from_format("samp{}", i));
    449       if (location >= 0)
    450         glUniform1i(location, i);
    451     }
    452 
    453     glUseProgram(m_last_program);
    454   }
    455 }
    456 
    457 void OpenGLDevice::UnrefProgram(const OpenGLPipeline::ProgramCacheKey& key)
    458 {
    459   auto it = m_program_cache.find(key);
    460   Assert(it != m_program_cache.end() && it->second.program_id != 0 && it->second.reference_count > 0);
    461 
    462   if ((--it->second.reference_count) > 0)
    463     return;
    464 
    465   if (m_last_program == it->second.program_id)
    466   {
    467     m_last_program = 0;
    468     glUseProgram(0);
    469   }
    470 
    471   glDeleteProgram(it->second.program_id);
    472   it->second.program_id = 0;
    473 
    474   // If it's not in the pipeline cache, we need to remove it completely, otherwise we won't recreate it.
    475   if (it->second.file_uncompressed_size == 0)
    476     m_program_cache.erase(it);
    477 }
    478 
    479 OpenGLPipeline::VertexArrayCache::const_iterator
    480 OpenGLDevice::LookupVAOCache(const OpenGLPipeline::VertexArrayCacheKey& key, Error* error)
    481 {
    482   OpenGLPipeline::VertexArrayCache::iterator it = m_vao_cache.find(key);
    483   if (it != m_vao_cache.end())
    484   {
    485     it->second.reference_count++;
    486     return it;
    487   }
    488 
    489   OpenGLPipeline::VertexArrayCacheItem item;
    490   item.vao_id =
    491     CreateVAO(std::span<const GPUPipeline::VertexAttribute>(key.vertex_attributes, key.num_vertex_attributes),
    492               key.vertex_attribute_stride, error);
    493   if (item.vao_id == 0)
    494     return m_vao_cache.cend();
    495 
    496   item.reference_count = 1;
    497   return m_vao_cache.emplace(key, item).first;
    498 }
    499 
    500 GLuint OpenGLDevice::CreateVAO(std::span<const GPUPipeline::VertexAttribute> attributes, u32 stride, Error* error)
    501 {
    502   glGetError();
    503   GLuint vao;
    504   glGenVertexArrays(1, &vao);
    505   if (const GLenum err = glGetError(); err != GL_NO_ERROR)
    506   {
    507     ERROR_LOG("Failed to create vertex array object: {}", err);
    508     SetErrorObject(error, "glGenVertexArrays() failed: ", err);
    509     return 0;
    510   }
    511 
    512   glBindVertexArray(vao);
    513   m_vertex_buffer->Bind();
    514   m_index_buffer->Bind();
    515 
    516   struct VAMapping
    517   {
    518     GLenum type;
    519     GLboolean normalized;
    520     GLboolean integer;
    521   };
    522   static constexpr const std::array<VAMapping, static_cast<u8>(GPUPipeline::VertexAttribute::Type::MaxCount)>
    523     format_mapping = {{
    524       {GL_FLOAT, GL_FALSE, GL_FALSE},         // Float
    525       {GL_UNSIGNED_BYTE, GL_FALSE, GL_TRUE},  // UInt8
    526       {GL_BYTE, GL_FALSE, GL_TRUE},           // SInt8
    527       {GL_UNSIGNED_BYTE, GL_TRUE, GL_FALSE},  // UNorm8
    528       {GL_UNSIGNED_SHORT, GL_FALSE, GL_TRUE}, // UInt16
    529       {GL_SHORT, GL_FALSE, GL_TRUE},          // SInt16
    530       {GL_UNSIGNED_SHORT, GL_TRUE, GL_FALSE}, // UNorm16
    531       {GL_UNSIGNED_INT, GL_FALSE, GL_TRUE},   // UInt32
    532       {GL_INT, GL_FALSE, GL_TRUE},            // SInt32
    533     }};
    534 
    535   for (u32 i = 0; i < static_cast<u32>(attributes.size()); i++)
    536   {
    537     const GPUPipeline::VertexAttribute& va = attributes[i];
    538     const VAMapping& m = format_mapping[static_cast<u8>(va.type.GetValue())];
    539     const void* ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(va.offset.GetValue()));
    540     glEnableVertexAttribArray(i);
    541     if (m.integer)
    542       glVertexAttribIPointer(i, va.components, m.type, stride, ptr);
    543     else
    544       glVertexAttribPointer(i, va.components, m.type, m.normalized, stride, ptr);
    545   }
    546 
    547   if (m_last_vao != m_vao_cache.cend())
    548     glBindVertexArray(m_last_vao->second.vao_id);
    549 
    550   return vao;
    551 }
    552 
    553 void OpenGLDevice::UnrefVAO(const OpenGLPipeline::VertexArrayCacheKey& key)
    554 {
    555   auto it = m_vao_cache.find(key);
    556   Assert(it != m_vao_cache.end() && it->second.reference_count > 0);
    557 
    558   if ((--it->second.reference_count) > 0)
    559     return;
    560 
    561   if (m_last_vao == it)
    562   {
    563     m_last_vao = m_vao_cache.cend();
    564     glBindVertexArray(0);
    565   }
    566 
    567   glDeleteVertexArrays(1, &it->second.vao_id);
    568   m_vao_cache.erase(it);
    569 }
    570 
    571 OpenGLPipeline::OpenGLPipeline(const ProgramCacheKey& key, GLuint program, VertexArrayCache::const_iterator vao,
    572                                const RasterizationState& rs, const DepthState& ds, const BlendState& bs,
    573                                GLenum topology)
    574   : m_key(key), m_vao(vao), m_program(program), m_blend_state(bs), m_rasterization_state(rs), m_depth_state(ds),
    575     m_topology(topology)
    576 {
    577 }
    578 
    579 OpenGLPipeline::~OpenGLPipeline()
    580 {
    581   OpenGLDevice& dev = OpenGLDevice::GetInstance();
    582   dev.UnbindPipeline(this);
    583   dev.UnrefProgram(m_key);
    584   dev.UnrefVAO(m_key.va_key);
    585 }
    586 
    587 void OpenGLPipeline::SetDebugName(std::string_view name)
    588 {
    589 #ifdef _DEBUG
    590   if (glObjectLabel)
    591     glObjectLabel(GL_PROGRAM, m_program, static_cast<u32>(name.length()), name.data());
    592 #endif
    593 }
    594 
    595 std::unique_ptr<GPUPipeline> OpenGLDevice::CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error)
    596 {
    597   const OpenGLPipeline::ProgramCacheKey pkey = OpenGLPipeline::GetProgramCacheKey(config);
    598 
    599   const GLuint program_id = LookupProgramCache(pkey, config, error);
    600   if (program_id == 0)
    601     return {};
    602 
    603   const OpenGLPipeline::VertexArrayCache::const_iterator vao = LookupVAOCache(pkey.va_key, error);
    604   if (vao == m_vao_cache.cend())
    605   {
    606     UnrefProgram(pkey);
    607     return {};
    608   }
    609 
    610   static constexpr std::array<GLenum, static_cast<u32>(GPUPipeline::Primitive::MaxCount)> primitives = {{
    611     GL_POINTS,         // Points
    612     GL_LINES,          // Lines
    613     GL_TRIANGLES,      // Triangles
    614     GL_TRIANGLE_STRIP, // TriangleStrips
    615   }};
    616 
    617   return std::unique_ptr<GPUPipeline>(new OpenGLPipeline(pkey, program_id, vao, config.rasterization, config.depth,
    618                                                          config.blend, primitives[static_cast<u8>(config.primitive)]));
    619 }
    620 
    621 ALWAYS_INLINE_RELEASE void OpenGLDevice::ApplyRasterizationState(GPUPipeline::RasterizationState rs)
    622 {
    623   if (m_last_rasterization_state == rs)
    624     return;
    625 
    626   // Only one thing, no need to check.
    627   if (rs.cull_mode == GPUPipeline::CullMode::None)
    628   {
    629     glDisable(GL_CULL_FACE);
    630   }
    631   else
    632   {
    633     glEnable(GL_CULL_FACE);
    634     glCullFace((rs.cull_mode == GPUPipeline::CullMode::Front) ? GL_FRONT : GL_BACK);
    635   }
    636 
    637   m_last_rasterization_state = rs;
    638 }
    639 
    640 ALWAYS_INLINE_RELEASE void OpenGLDevice::ApplyDepthState(GPUPipeline::DepthState ds)
    641 {
    642   static constexpr std::array<GLenum, static_cast<u32>(GPUPipeline::DepthFunc::MaxCount)> func_mapping = {{
    643     GL_NEVER,   // Never
    644     GL_ALWAYS,  // Always
    645     GL_LESS,    // Less
    646     GL_LEQUAL,  // LessEqual
    647     GL_GREATER, // Greater
    648     GL_GEQUAL,  // GreaterEqual
    649     GL_EQUAL,   // Equal
    650   }};
    651 
    652   if (m_last_depth_state == ds)
    653     return;
    654 
    655   (ds.depth_test != GPUPipeline::DepthFunc::Always || ds.depth_write) ? glEnable(GL_DEPTH_TEST) :
    656                                                                         glDisable(GL_DEPTH_TEST);
    657   glDepthFunc(func_mapping[static_cast<u8>(ds.depth_test.GetValue())]);
    658   if (m_last_depth_state.depth_write != ds.depth_write)
    659     glDepthMask(ds.depth_write);
    660 
    661   m_last_depth_state = ds;
    662 }
    663 
    664 ALWAYS_INLINE_RELEASE void OpenGLDevice::ApplyBlendState(GPUPipeline::BlendState bs)
    665 {
    666   static constexpr std::array<GLenum, static_cast<u32>(GPUPipeline::BlendFunc::MaxCount)> blend_mapping = {{
    667     GL_ZERO,                     // Zero
    668     GL_ONE,                      // One
    669     GL_SRC_COLOR,                // SrcColor
    670     GL_ONE_MINUS_SRC_COLOR,      // InvSrcColor
    671     GL_DST_COLOR,                // DstColor
    672     GL_ONE_MINUS_DST_COLOR,      // InvDstColor
    673     GL_SRC_ALPHA,                // SrcAlpha
    674     GL_ONE_MINUS_SRC_ALPHA,      // InvSrcAlpha
    675     GL_SRC1_ALPHA,               // SrcAlpha1
    676     GL_ONE_MINUS_SRC1_ALPHA,     // InvSrcAlpha1
    677     GL_DST_ALPHA,                // DstAlpha
    678     GL_ONE_MINUS_DST_ALPHA,      // InvDstAlpha
    679     GL_CONSTANT_COLOR,           // ConstantColor
    680     GL_ONE_MINUS_CONSTANT_COLOR, // InvConstantColor
    681   }};
    682 
    683   static constexpr std::array<GLenum, static_cast<u32>(GPUPipeline::BlendOp::MaxCount)> op_mapping = {{
    684     GL_FUNC_ADD,              // Add
    685     GL_FUNC_SUBTRACT,         // Subtract
    686     GL_FUNC_REVERSE_SUBTRACT, // ReverseSubtract
    687     GL_MIN,                   // Min
    688     GL_MAX,                   // Max
    689   }};
    690 
    691   // TODO: driver bugs
    692 
    693   if (bs == m_last_blend_state)
    694     return;
    695 
    696   if (bs.enable != m_last_blend_state.enable)
    697     bs.enable ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
    698 
    699   if (bs.enable)
    700   {
    701     if (bs.blend_factors != m_last_blend_state.blend_factors)
    702     {
    703       glBlendFuncSeparate(blend_mapping[static_cast<u8>(bs.src_blend.GetValue())],
    704                           blend_mapping[static_cast<u8>(bs.dst_blend.GetValue())],
    705                           blend_mapping[static_cast<u8>(bs.src_alpha_blend.GetValue())],
    706                           blend_mapping[static_cast<u8>(bs.dst_alpha_blend.GetValue())]);
    707     }
    708 
    709     if (bs.blend_ops != m_last_blend_state.blend_ops)
    710     {
    711       glBlendEquationSeparate(op_mapping[static_cast<u8>(bs.blend_op.GetValue())],
    712                               op_mapping[static_cast<u8>(bs.alpha_blend_op.GetValue())]);
    713     }
    714 
    715     if (bs.constant != m_last_blend_state.constant)
    716       glBlendColor(bs.GetConstantRed(), bs.GetConstantGreen(), bs.GetConstantBlue(), bs.GetConstantAlpha());
    717   }
    718   else
    719   {
    720     // Keep old values for blend options to potentially avoid calls when re-enabling.
    721     bs.blend_factors.SetValue(m_last_blend_state.blend_factors);
    722     bs.blend_ops.SetValue(m_last_blend_state.blend_ops);
    723     bs.constant.SetValue(m_last_blend_state.constant);
    724   }
    725 
    726   if (bs.write_mask != m_last_blend_state.write_mask)
    727     glColorMask(bs.write_r, bs.write_g, bs.write_b, bs.write_a);
    728 
    729   m_last_blend_state = bs;
    730 }
    731 
    732 void OpenGLDevice::SetPipeline(GPUPipeline* pipeline)
    733 {
    734   if (m_current_pipeline == pipeline)
    735     return;
    736 
    737   OpenGLPipeline* const P = static_cast<OpenGLPipeline*>(pipeline);
    738   m_current_pipeline = P;
    739 
    740   ApplyRasterizationState(P->GetRasterizationState());
    741   ApplyDepthState(P->GetDepthState());
    742   ApplyBlendState(P->GetBlendState());
    743 
    744   if (m_last_vao != P->GetVAO())
    745   {
    746     m_last_vao = P->GetVAO();
    747     glBindVertexArray(m_last_vao->second.vao_id);
    748   }
    749   if (m_last_program != P->GetProgram())
    750   {
    751     m_last_program = P->GetProgram();
    752     glUseProgram(m_last_program);
    753   }
    754 }
    755 
    756 bool OpenGLDevice::OpenPipelineCache(const std::string& filename)
    757 {
    758   DebugAssert(!m_pipeline_disk_cache_file);
    759 
    760   Error error;
    761   m_pipeline_disk_cache_file = FileSystem::OpenCFile(filename.c_str(), "r+b", &error);
    762   m_pipeline_disk_cache_filename = filename;
    763 
    764   if (!m_pipeline_disk_cache_file)
    765   {
    766     // Multiple instances running? Ignore.
    767     if (errno == EACCES)
    768     {
    769       m_pipeline_disk_cache_filename = {};
    770       return true;
    771     }
    772 
    773     // If it doesn't exist, we're going to create it.
    774     if (errno != ENOENT)
    775     {
    776       WARNING_LOG("Failed to open shader cache: {}", error.GetDescription());
    777       m_pipeline_disk_cache_filename = {};
    778       return false;
    779     }
    780 
    781     WARNING_LOG("Disk cache does not exist, creating.");
    782     return DiscardPipelineCache();
    783   }
    784 
    785   // Read footer.
    786   const s64 size = FileSystem::FSize64(m_pipeline_disk_cache_file);
    787   if (size < static_cast<s64>(sizeof(PipelineDiskCacheFooter)) ||
    788       size >= static_cast<s64>(std::numeric_limits<u32>::max()))
    789   {
    790     return DiscardPipelineCache();
    791   }
    792 
    793   PipelineDiskCacheFooter file_footer;
    794   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 ||
    795       std::fread(&file_footer, sizeof(file_footer), 1, m_pipeline_disk_cache_file) != 1)
    796   {
    797     ERROR_LOG("Failed to read disk cache footer.");
    798     return DiscardPipelineCache();
    799   }
    800 
    801   PipelineDiskCacheFooter expected_footer;
    802   FillFooter(&expected_footer, m_shader_cache.GetVersion());
    803 
    804   if (file_footer.version != expected_footer.version ||
    805       std::strncmp(file_footer.driver_vendor, expected_footer.driver_vendor, std::size(file_footer.driver_vendor)) !=
    806         0 ||
    807       std::strncmp(file_footer.driver_renderer, expected_footer.driver_renderer,
    808                    std::size(file_footer.driver_renderer)) != 0 ||
    809       std::strncmp(file_footer.driver_version, expected_footer.driver_version, std::size(file_footer.driver_version)) !=
    810         0)
    811   {
    812     ERROR_LOG("Disk cache does not match expected driver/version.");
    813     return DiscardPipelineCache();
    814   }
    815 
    816   m_pipeline_disk_cache_data_end = static_cast<u32>(size) - sizeof(PipelineDiskCacheFooter) -
    817                                    (sizeof(PipelineDiskCacheIndexEntry) * file_footer.num_programs);
    818   if (m_pipeline_disk_cache_data_end < 0 ||
    819       FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0)
    820   {
    821     ERROR_LOG("Failed to seek to start of index entries.");
    822     return DiscardPipelineCache();
    823   }
    824 
    825   // Read entries.
    826   for (u32 i = 0; i < file_footer.num_programs; i++)
    827   {
    828     PipelineDiskCacheIndexEntry entry;
    829     if (std::fread(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1 ||
    830         (static_cast<s64>(entry.offset) + static_cast<s64>(entry.compressed_size)) >= size)
    831     {
    832       ERROR_LOG("Failed to read disk cache entry.");
    833       return DiscardPipelineCache();
    834     }
    835 
    836     if (m_program_cache.find(entry.key) != m_program_cache.end())
    837     {
    838       ERROR_LOG("Duplicate program in disk cache.");
    839       return DiscardPipelineCache();
    840     }
    841 
    842     OpenGLPipeline::ProgramCacheItem pitem;
    843     pitem.program_id = 0;
    844     pitem.reference_count = 0;
    845     pitem.file_format = entry.format;
    846     pitem.file_offset = entry.offset;
    847     pitem.file_uncompressed_size = entry.uncompressed_size;
    848     pitem.file_compressed_size = entry.compressed_size;
    849     m_program_cache.emplace(entry.key, pitem);
    850   }
    851 
    852   VERBOSE_LOG("Read {} programs from disk cache.", m_program_cache.size());
    853   return true;
    854 }
    855 
    856 bool OpenGLDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data)
    857 {
    858   // Self-managed.
    859   return false;
    860 }
    861 
    862 GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::ProgramCacheItem& it,
    863                                                     const GPUPipeline::GraphicsConfig& plconfig)
    864 {
    865   DynamicHeapArray<u8> compressed_data(it.file_compressed_size);
    866 
    867   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, it.file_offset, SEEK_SET) != 0 ||
    868       std::fread(compressed_data.data(), it.file_compressed_size, 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
    869   {
    870     ERROR_LOG("Failed to read program from disk cache.");
    871     return 0;
    872   }
    873 
    874   Error error;
    875   CompressHelpers::OptionalByteBuffer data = CompressHelpers::DecompressBuffer(
    876     CompressHelpers::CompressType::Zstandard, CompressHelpers::OptionalByteBuffer(std::move(compressed_data)),
    877     it.file_uncompressed_size, &error);
    878   if (!data.has_value())
    879   {
    880     ERROR_LOG("Failed to decompress program from disk cache: {}", error.GetDescription());
    881     return 0;
    882   }
    883 
    884   glGetError();
    885   GLuint prog = glCreateProgram();
    886   if (const GLenum err = glGetError(); err != GL_NO_ERROR) [[unlikely]]
    887   {
    888     ERROR_LOG("Failed to create program object: {}", err);
    889     return 0;
    890   }
    891 
    892   glProgramBinary(prog, it.file_format, data->data(), it.file_uncompressed_size);
    893 
    894   GLint link_status;
    895   glGetProgramiv(prog, GL_LINK_STATUS, &link_status);
    896   if (link_status != GL_TRUE) [[unlikely]]
    897   {
    898     ERROR_LOG("Failed to create GL program from binary: status {}, discarding cache.", link_status);
    899     glDeleteProgram(prog);
    900     return 0;
    901   }
    902 
    903   PostLinkProgram(plconfig, prog);
    904 
    905   return prog;
    906 }
    907 
    908 void OpenGLDevice::AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it)
    909 {
    910   DebugAssert(it->program_id != 0 && it->file_uncompressed_size == 0);
    911   DebugAssert(m_pipeline_disk_cache_file);
    912 
    913   GLint binary_size = 0;
    914   glGetProgramiv(it->program_id, GL_PROGRAM_BINARY_LENGTH, &binary_size);
    915   if (binary_size == 0)
    916   {
    917     WARNING_LOG("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0");
    918     return;
    919   }
    920 
    921   GLenum format = 0;
    922   DynamicHeapArray<u8> uncompressed_data(binary_size);
    923   glGetProgramBinary(it->program_id, binary_size, &binary_size, &format, uncompressed_data.data());
    924   if (binary_size == 0)
    925   {
    926     WARNING_LOG("glGetProgramBinary() failed");
    927     return;
    928   }
    929   else if (static_cast<size_t>(binary_size) != uncompressed_data.size()) [[unlikely]]
    930   {
    931     WARNING_LOG("Size changed from {} to {} after glGetProgramBinary()", uncompressed_data.size(), binary_size);
    932   }
    933 
    934   Error error;
    935   CompressHelpers::OptionalByteBuffer compressed_data =
    936     CompressHelpers::CompressToBuffer(CompressHelpers::CompressType::Zstandard,
    937                                       CompressHelpers::OptionalByteBuffer(std::move(uncompressed_data)), -1, &error);
    938   if (!compressed_data.has_value()) [[unlikely]]
    939   {
    940     ERROR_LOG("Failed to compress program: {}", error.GetDescription());
    941     return;
    942   }
    943 
    944   DEV_LOG("Program binary retrieved and compressed, {} -> {} bytes, format {}", binary_size, compressed_data->size(),
    945           format);
    946 
    947   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0 ||
    948       std::fwrite(compressed_data->data(), compressed_data->size(), 1, m_pipeline_disk_cache_file) != 1)
    949   {
    950     ERROR_LOG("Failed to write binary to disk cache.");
    951   }
    952 
    953   it->file_format = format;
    954   it->file_offset = m_pipeline_disk_cache_data_end;
    955   it->file_uncompressed_size = static_cast<u32>(binary_size);
    956   it->file_compressed_size = static_cast<u32>(compressed_data->size());
    957   m_pipeline_disk_cache_data_end += static_cast<u32>(compressed_data->size());
    958   m_pipeline_disk_cache_changed = true;
    959 }
    960 
    961 bool OpenGLDevice::DiscardPipelineCache()
    962 {
    963   // Remove any other disk cache entries which haven't been loaded.
    964   for (auto it = m_program_cache.begin(); it != m_program_cache.end();)
    965   {
    966     if (it->second.program_id != 0)
    967     {
    968       it->second.file_format = 0;
    969       it->second.file_offset = 0;
    970       it->second.file_uncompressed_size = 0;
    971       it->second.file_compressed_size = 0;
    972       ++it;
    973       continue;
    974     }
    975 
    976     it = m_program_cache.erase(it);
    977   }
    978 
    979   if (m_pipeline_disk_cache_file)
    980     std::fclose(m_pipeline_disk_cache_file);
    981 
    982   Error error;
    983   m_pipeline_disk_cache_data_end = 0;
    984   m_pipeline_disk_cache_file = FileSystem::OpenCFile(m_pipeline_disk_cache_filename.c_str(), "w+b", &error);
    985   if (!m_pipeline_disk_cache_file) [[unlikely]]
    986   {
    987     ERROR_LOG("Failed to reopen pipeline cache: {}", error.GetDescription());
    988     m_pipeline_disk_cache_filename = {};
    989     return false;
    990   }
    991 
    992   return true;
    993 }
    994 
    995 void OpenGLDevice::ClosePipelineCache()
    996 {
    997   const ScopedGuard file_closer = [this]() {
    998     if (m_pipeline_disk_cache_file)
    999     {
   1000       std::fclose(m_pipeline_disk_cache_file);
   1001       m_pipeline_disk_cache_file = nullptr;
   1002     }
   1003   };
   1004 
   1005   if (!m_pipeline_disk_cache_changed)
   1006   {
   1007     VERBOSE_LOG("Not updating pipeline cache because it has not changed.");
   1008     return;
   1009   }
   1010 
   1011   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0) [[unlikely]]
   1012   {
   1013     ERROR_LOG("Failed to seek to data end.");
   1014     return;
   1015   }
   1016 
   1017   u32 count = 0;
   1018 
   1019   for (const auto& it : m_program_cache)
   1020   {
   1021     if (it.second.file_uncompressed_size == 0)
   1022       continue;
   1023 
   1024     PipelineDiskCacheIndexEntry entry;
   1025     std::memcpy(&entry.key, &it.first, sizeof(entry.key));
   1026     entry.format = it.second.file_format;
   1027     entry.offset = it.second.file_offset;
   1028     entry.compressed_size = it.second.file_compressed_size;
   1029     entry.uncompressed_size = it.second.file_uncompressed_size;
   1030 
   1031     if (std::fwrite(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
   1032     {
   1033       ERROR_LOG("Failed to write index entry.");
   1034       return;
   1035     }
   1036 
   1037     count++;
   1038   }
   1039 
   1040   PipelineDiskCacheFooter footer;
   1041   FillFooter(&footer, m_shader_cache.GetVersion());
   1042   footer.num_programs = count;
   1043 
   1044   if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
   1045     ERROR_LOG("Failed to write footer.");
   1046 }