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 }